From 1d6597b40773b688ca317e1ab1ffe133b370afb1 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Tue, 17 Nov 2015 23:13:30 +0100 Subject: [PATCH 001/144] [enh] initial structure --- .nojekyll | 0 docs/_themes/searx_theme/layout.html | 20 + docs/_themes/searx_theme/relations.html | 15 + docs/_themes/searx_theme/static/style.css_t | 441 ++++++++++++++++++++ docs/_themes/searx_theme/theme.conf | 7 + docs/conf.py | 358 ++++++++++++++++ docs/dev/contribution_guide.rst | 71 ++++ docs/dev/install/installation.rst | 277 ++++++++++++ docs/dev/plugins.rst | 45 ++ docs/dev/search_api.rst | 24 ++ docs/dev/translation.rst | 74 ++++ docs/index.rst | 39 ++ docs/static/img/searx_logo_small.png | Bin 0 -> 6472 bytes docs/user/search_syntax.rst | 33 ++ 14 files changed, 1404 insertions(+) create mode 100644 .nojekyll create mode 100644 docs/_themes/searx_theme/layout.html create mode 100644 docs/_themes/searx_theme/relations.html create mode 100644 docs/_themes/searx_theme/static/style.css_t create mode 100644 docs/_themes/searx_theme/theme.conf create mode 100644 docs/conf.py create mode 100644 docs/dev/contribution_guide.rst create mode 100644 docs/dev/install/installation.rst create mode 100644 docs/dev/plugins.rst create mode 100644 docs/dev/search_api.rst create mode 100644 docs/dev/translation.rst create mode 100644 docs/index.rst create mode 100644 docs/static/img/searx_logo_small.png create mode 100644 docs/user/search_syntax.rst diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/_themes/searx_theme/layout.html b/docs/_themes/searx_theme/layout.html new file mode 100644 index 00000000..edf70c1a --- /dev/null +++ b/docs/_themes/searx_theme/layout.html @@ -0,0 +1,20 @@ +{%- extends "basic/layout.html" %} +{%- block extrahead %} + {{ super() }} + {% if theme_touch_icon %} + + {% endif %} + + +{% endblock %} +{%- block relbar2 %}{% endblock %} +{%- block relbar1 %}{% endblock %} +{%- block sidebarsearch %}{% endblock %} +{%- block sidebarsourcelink %}{% endblock %} +{%- block sidebartoc %}{% endblock %} +{%- block footer %} + +{%- endblock %} diff --git a/docs/_themes/searx_theme/relations.html b/docs/_themes/searx_theme/relations.html new file mode 100644 index 00000000..13cfa019 --- /dev/null +++ b/docs/_themes/searx_theme/relations.html @@ -0,0 +1,15 @@ + diff --git a/docs/_themes/searx_theme/static/style.css_t b/docs/_themes/searx_theme/static/style.css_t new file mode 100644 index 00000000..4a266671 --- /dev/null +++ b/docs/_themes/searx_theme/static/style.css_t @@ -0,0 +1,441 @@ +/* + * flasky.css_t + * ~~~~~~~~~~~~ + * + * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. + * :license: Flask Design License, see LICENSE for details. + */ + +{% set page_width = '940px' %} +{% set sidebar_width = '220px' %} + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; + font-size: 17px; + background-color: white; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + width: {{ page_width }}; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 {{ sidebar_width }}; +} + +div.sphinxsidebar { + width: {{ sidebar_width }}; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +img.floatingflask { + padding: 0 0 10px 10px; + float: right; +} + +div.footer { + width: {{ page_width }}; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 0 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 -20px; + text-align: center; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: 'Garamond', 'Georgia', serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar input { + border: 1px solid #ccc; + font-family: 'Georgia', serif; + font-size: 1em; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Garamond', 'Georgia', serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #ddd; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #eaeaea; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + background: #fafafa; + margin: 20px -30px; + padding: 10px 30px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +div.admonition tt.xref, div.admonition a tt { + border-bottom: 1px solid #fafafa; +} + +dd div.admonition { + margin-left: -60px; + padding-left: 60px; +} + +div.admonition p.admonition-title { + font-family: 'Garamond', 'Georgia', serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: white; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt { + font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +img.screenshot { +} + +tt.descname, tt.descclassname { + font-size: 0.95em; +} + +tt.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #eee; + -webkit-box-shadow: 2px 2px 4px #eee; + box-shadow: 2px 2px 4px #eee; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #eee; + -webkit-box-shadow: 2px 2px 4px #eee; + box-shadow: 2px 2px 4px #eee; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #eee; + background: #fdfdfd; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.footnote td.label { + width: 0px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #eee; + padding: 7px 30px; + margin: 15px -30px; + line-height: 1.3em; +} + +dl pre, blockquote pre, li pre { + margin-left: -60px; + padding-left: 60px; +} + +dl dl pre { + margin-left: -90px; + padding-left: 90px; +} + +tt { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid white; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt { + background: #EEE; +} + + +@media screen and (max-width: 600px) { + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + div.sphinxsidebar { + display: none; + } + +} + +div.sidebar_container, div.sidebar_container h1 { +} + +div.sidebar_container h1 { + padding: 0; + margin: 0; + font-size: 350%; + line-height: 100%; +} + +div.sidebar_container ul li { + padding: 2px 8px; + font-size: 0.9em; +} diff --git a/docs/_themes/searx_theme/theme.conf b/docs/_themes/searx_theme/theme.conf new file mode 100644 index 00000000..38f9aaa9 --- /dev/null +++ b/docs/_themes/searx_theme/theme.conf @@ -0,0 +1,7 @@ +[theme] +inherit = basic +stylesheet = style.css +pygments_style = flask_theme_support.FlaskyStyle + +[options] +touch_icon = diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..30fe9031 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- +# +# searx documentation build configuration file, created by +# sphinx-quickstart on Tue Nov 17 17:12:13 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'searx' +copyright = u'2015, Adam Tauber' +author = u'Adam Tauber' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.8.0' +# The full version, including alpha/beta/rc tags. +release = '0.8.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +sys.path.append(os.path.abspath('_themes')) +html_theme_path = ['_themes'] +html_theme = 'searx_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'searxdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'searx.tex', u'searx Documentation', + u'Adam Tauber', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'searx', u'searx Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'searx', u'searx Documentation', + author, 'searx', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The basename for the epub file. It defaults to the project name. +#epub_basename = project + +# The HTML theme for the epub output. Since the default themes are not optimized +# for small screen space, using the same theme for HTML and epub output is +# usually not wise. This defaults to 'epub', a theme designed to save visual +# space. +#epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or 'en' if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +#epub_tocscope = 'default' + +# Fix unsupported image types using the Pillow. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True diff --git a/docs/dev/contribution_guide.rst b/docs/dev/contribution_guide.rst new file mode 100644 index 00000000..5685549e --- /dev/null +++ b/docs/dev/contribution_guide.rst @@ -0,0 +1,71 @@ +How to contribute +----------------- + +Prime directives: Privacy, Hackability +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Searx has 2 prime directives, privacy-by-design and hackability. The +hackability comes in at least 3 levels: + +- support for search engines +- plugins for altering search behaviour +- hacking searx itself. + +Happy hacking. Observe the lack of "world domination" among the +directives, searx has no intentions for wide mass-adoption, rounded +corners, etc. The prime directive: "privacy" - deserves a seperate +chapter, as it's quite uncommon unfortunately, here it goes: + +Privacy-by-design +^^^^^^^^^^^^^^^^^ + +Searx is a privacy-respecting, hackable meta-search engine. It was born +out of the need for a privacy-respecing search facility that can be +expanded easily to maximise both its search and it's privacy protecting +capabilities. + +Consequences of Privacy-by-design are that some widely used features +work differently or not by default or at all. If some feature reduces +the privacy perserving aspects of searx, it should by default be +switched of, if implemented at all. There is enough search engines +already out there providing such features. = Since privacy-preservation +is a prime goal, if some feature does reduce the protection of searx and +is implemented, care should be taken to educate the user about the +consequences of choosing to enable this. Further features which +implement widely known features in a manner that protects privacy but +thus deviate from the users expectations should also be explained to the +user. Also if you think that something works weird with searx, maybe +it's because of the tool you use is designed in a way to interfere with +privacy respect, submiting a bugreport to the vendor of the tool that +misbehaves might be a good feedback for the vendor to reconsider his +disrespect towards his customers (e.g. GET vs POST requests in various +browsers). + +Remember the other prime directive of searx is to be hackable, so if the +above privacy concerns do not fancy you, simply fork it. + +Code +~~~~ + +Code modifications are accepted in pull requests, don't forget to add +yourself to the AUTHORS file. + +Python code follows all the pep8 standards except maximum line width +which is 120 char. + +Please be sure that the submitted code doesn't break existing tests and +follows coding conventions. + +If new functionality implemented, tests are highly appreciated. + +Translation +~~~~~~~~~~~ + +Translation currently happens on +`transifex `__. Please do not +update translation files in the repo. + +Documentation +~~~~~~~~~~~~~ + +The main place of the documentation is this wiki, updates are welcome. diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst new file mode 100644 index 00000000..2ce6a6b5 --- /dev/null +++ b/docs/dev/install/installation.rst @@ -0,0 +1,277 @@ +Installation +============ + +Step by step installation for Debian / Ubuntu with virtualenv. + +Source: https://about.okhin.fr/posts/Searx/ with some additions + +How to: `Setup searx in a couple of hours with a free SSL +certificate `__ + +Basic installation +------------------ + +For Ubuntu, be sure to have enable universe repository. + +Install packages : + +.. code:: sh + + sudo apt-get install git build-essential libxslt-dev python-dev python-virtualenv python-pybabel zlib1g-dev libffi-dev libssl-dev + +Install searx : + +.. code:: sh + + cd /usr/local + sudo git clone https://github.com/asciimoo/searx.git + sudo useradd searx -d /usr/local/searx + sudo chown searx:searx -R /usr/local/searx + +Install dependencies in a virtualenv : + +.. code:: sh + + sudo -u searx -i + cd /usr/local/searx + virtualenv searx-ve + . ./searx-ve/bin/activate + pip install -r requirements.txt + python setup.py install + +Configuration +------------- + +.. code:: sh + + sed -i -e "s/ultrasecretkey/`openssl rand -hex 16`/g" searx/settings.yml + +Edit searx/settings.yml if necessary. + +Check +----- + +Start searx : + +.. code:: sh + + python searx/webapp.py + +Go to http://localhost:8888 + +If everything works fine, disable the debug option in settings.yml : + +.. code:: sh + + sed -i -e "s/debug : True/debug : False/g" searx/settings.yml + +At this point searx is not demonized ; uwsgi allows this. + +You can exit the virtualenv and the searx user bash (enter exit command +twice). + +uwsgi +----- + +Install packages : + +.. code:: sh + + sudo apt-get install uwsgi uwsgi-plugin-python + +Create the configuration file /etc/uwsgi/apps-available/searx.ini with +this content : + +:: + + [uwsgi] + # Who will run the code + uid = searx + gid = searx + + # disable logging for privacy + disable-logging = true + + # Number of workers (usually CPU count) + workers = 4 + + # The right granted on the created socket + chmod-socket = 666 + + # Plugin to use and interpretor config + single-interpreter = true + master = true + plugin = python + + # Module to import + module = searx.webapp + + # Virtualenv and python path + virtualenv = /usr/local/searx/searx-ve/ + pythonpath = /usr/local/searx/ + chdir = /usr/local/searx/searx/ + +Activate the uwsgi application and restart : + +.. code:: sh + + cd /etc/uwsgi/apps-enabled + ln -s ../apps-available/searx.ini + /etc/init.d/uwsgi restart + +Web server +---------- + +with nginx +^^^^^^^^^^ + +If nginx is not installed (uwsgi will not work with the package +nginx-light) : + +.. code:: sh + + sudo apt-get install nginx + +Hosted at / +""""""""""" + +Create the configuration file /etc/nginx/sites-available/searx with this +content : + +.. code:: nginx + + server { + listen 80; + server_name searx.example.com; + root /usr/local/searx; + + location / { + include uwsgi_params; + uwsgi_pass unix:/run/uwsgi/app/searx/socket; + } + } + +Restart service : + +.. code:: sh + + sudo service nginx restart + sudo service uwsgi restart + +from subdirectory URL (/searx) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Add this configuration in the server config file +/etc/nginx/sites-available/default : + +.. code:: nginx + + location = /searx { rewrite ^ /searx/; } + location /searx { + try_files $uri @searx; + } + location @searx { + uwsgi_param SCRIPT_NAME /searx; + include uwsgi_params; + uwsgi_modifier1 30; + uwsgi_pass unix:/run/uwsgi/app/searx/socket; + } + +Enable base\_url in searx/settings.yml + +:: + + base_url : http://your.domain.tld/searx/ + +Restart service : + +.. code:: sh + + sudo service nginx restart + sudo service uwsgi restart + +disable logs +~~~~~~~~~~~~ + +for better privacy you can disable nginx logs about searx. + +how to proceed : below ``uwsgi_pass`` in +/etc/nginx/sites-available/default add + +:: + + access_log /dev/null; + error_log /dev/null; + +Restart service : + +.. code:: sh + + sudo service nginx restart + +with apache +----------- + +Add wsgi mod : + +.. code:: sh + + sudo apt-get install libapache2-mod-uwsgi + sudo a2enmod uwsgi + +Add this configuration in the file /etc/apache2/apache2.conf : + +.. code:: apache + + + Options FollowSymLinks Indexes + SetHandler uwsgi-handler + uWSGISocket /run/uwsgi/app/searx/socket + + +Note that if your instance of searx is not at the root, you should +change ```` by the location of your instance, like +````. + +Restart Apache : + +.. code:: sh + + sudo /etc/init.d/apache2 restart + +disable logs +------------ + +For better privacy you can disable Apache logs. + +WARNING : not tested + +WARNING : you can only disable logs for the whole (virtual) server not +for a specific path. + +Go back to /etc/apache2/apache2.conf and above ```` add : + +.. code:: apache + + CustomLog /dev/null combined + +Restart Apache : + +.. code:: sh + + sudo /etc/init.d/apache2 restart + +How to update +------------- + +.. code:: sh + + cd /usr/local/searx + sudo -u searx -i + . ./searx-ve/bin/activate + git stash + git pull origin master + git stash apply + pip install --upgrade -r requirements.txt + sudo service uwsgi restart + diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst new file mode 100644 index 00000000..c51fdf0e --- /dev/null +++ b/docs/dev/plugins.rst @@ -0,0 +1,45 @@ +Plugins +------- + +Plugins can extend/replace functionality of various components inside +searx. + +example\_plugin.py +~~~~~~~~~~~~~~~~~~ + +.. code:: python + + name = 'Example plugin' + description = 'This plugin extends the suggestions with the word "example"' + default_on = False # disable by default + + js_dependencies = tuple() # optional, list of static js files + css_dependencies = tuple() # optional, list of static css files + + + # attach callback to the post search hook + # request: flask request object + # ctx: the whole local context of the post search hook + def post_search(request, ctx): + ctx['search'].suggestions.add('example') + return True + +Currently implemented plugin entry points (a.k.a hooks) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Pre search hook (``pre_search``) +- Post search hook (``post_search``) +- Result hook (``on_result``) (is called if a new result is added (see + https\_rewrite plugin)) + +Feel free to add more hooks to the code if it is required by a plugin. + +TODO +~~~~ + +- Better documentation +- More hooks +- search hook (is called while searx is requesting results (for + example: things like math-solver), the different hooks are running + parallel) + diff --git a/docs/dev/search_api.rst b/docs/dev/search_api.rst new file mode 100644 index 00000000..cd86fd71 --- /dev/null +++ b/docs/dev/search_api.rst @@ -0,0 +1,24 @@ +Search API +========== + +Search API endpoints: ``/``, ``/search`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Endpoints have equivalent functionality. + +Parameters +^^^^^^^^^^ + ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ +| Name | Description | | ++==================+====================================================================================================+=============================+ +| ``q`` | The search query, see :doc:`/user/search_syntax` | required | ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ +| ``categories`` | Comma separated list, specifies the active search categories | optional | ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ +| ``engines`` | Comma separated list, specifies the active search engines | optional | ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ +| ``pageno`` | Search page number | optional (default: ``1``) | ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ + +Both ``GET`` and ``POST`` methods are supported. diff --git a/docs/dev/translation.rst b/docs/dev/translation.rst new file mode 100644 index 00000000..0fc02a7d --- /dev/null +++ b/docs/dev/translation.rst @@ -0,0 +1,74 @@ +Translation +=========== + +run these commands in the root directory of searx + +Add new language +~~~~~~~~~~~~~~~~ + +``pybabel init -i messages.pot -d searx/translations -l it`` + +Update .po files +~~~~~~~~~~~~~~~~ + +``./utils/update-translations.sh`` + +You may have errors here. In that case, edit the +``update-translations.sh`` script to change ``pybabel`` to +``pybabel-python2`` + +After this step, you can modify the .po files. + +Compile translations +~~~~~~~~~~~~~~~~~~~~ + +``pybabel compile -d searx/translations`` + +Transifex stuff +~~~~~~~~~~~~~~~ + +Init Project +^^^^^^^^^^^^ + +.. code:: shell + + tx set --auto-local -r searx.messagespo 'searx/translations//LC_MESSAGES/messages.po' \ + --source-lang en --type PO --source-file messages.pot --execute + +http://docs.transifex.com/developer/client/set + +*TODO: mapping between transifex and searx* + +Get translations +^^^^^^^^^^^^^^^^ + +.. code:: shell + + tx pull -a + +http://docs.transifex.com/developer/client/pull + +Upload source File +^^^^^^^^^^^^^^^^^^ + +:: + + tx push -s + +Upload all Translation +^^^^^^^^^^^^^^^^^^^^^^ + +:: + + tx push -s -t + +upload specifc Translation (only for admins) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + tx push -t -l tr + +http://docs.transifex.com/developer/client/push + +*TODO: upload empty files? (new translations)* diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..abb62062 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,39 @@ +Privacy-respecting free metasearch engine +========================================= + +Searx is a free software internet metasearch engine which aggregates results from other search engines, while not storing information about its users. Searx does not track or profile its users, nor does it share its users' data with third parties. Additionally, searx can be used over Tor for online anonymity. + + +Features +-------- + + - Self hosted + - Does not track its users + - Does not share its users data with third parties + - Does not use cookies by default + - Does not profile its users + - Does not collect its users data + - Offers secure, encrypted connections (HTTPS/SSL) + - Hosted by organisations, such as La Quadrature du Net, which promote digital rights + + +Further reading +--------------- + +.. toctree:: + :maxdepth: 1 + + user/search_syntax + + +Developer documentation +----------------------- + +.. toctree:: + :maxdepth: 1 + + dev/contribution_guide + dev/install/installation + dev/search_api + dev/plugins + dev/translation diff --git a/docs/static/img/searx_logo_small.png b/docs/static/img/searx_logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..083932686b19d396dbbb6312cb46d73adbf48aa3 GIT binary patch literal 6472 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 i;3Z7A&!_Qqvi=_*y?ARXUj97*0000`_ +search in IT category **and** duckduckgo **and** wikipedia for ``qwer``). + +See the `/preferences page `_ for the +list of engines, categories and languages. + +Examples +~~~~~~~~ + +Search in wikipedia for ``qwer``: +`!wp qwer `__ or +`!wikipedia qwer `_ + +Image search: +`!images Cthulhu `_ + +Custom language in wikipedia: +`:hu !wp hackerspace `_ From 11d6d80d8831459161ac08ad0587ab8775ffd3e3 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Sat, 28 Nov 2015 19:26:45 +0100 Subject: [PATCH 003/144] [doc] engines overview added --- docs/dev/engine_overview.rst | 314 +++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 315 insertions(+) create mode 100644 docs/dev/engine_overview.rst diff --git a/docs/dev/engine_overview.rst b/docs/dev/engine_overview.rst new file mode 100644 index 00000000..7f170e9d --- /dev/null +++ b/docs/dev/engine_overview.rst @@ -0,0 +1,314 @@ +Engine overview +=============== + + +searx is a `metasearch-engine `__, +so it is using different search engines to provide better results. + +Because there is no general search-api which can be used for every +search-engine, there must be build an adapter between searx and the +external search-engine. This adapters are stored in the folder +`*searx/engines* `__, +and this site is build to make an general documentation about this +engines + + +.. contents:: + :depth: 3 + +general engine configuration +---------------------------- + +It is required to tell searx what results can the engine provide. The +arguments can be inserted in the engine file, or in the settings file +(normally ``settings.yml``). The arguments in the settings file override +the one in the engine file. + +Really, it is for most options no difference if there are contained in +the engine-file or in the settings. But there is a standard where to +place specific arguments by default. + + +engine-file +~~~~~~~~~~~ + ++---------------------+-----------+-----------------------------------------+ +| argument | type | information | ++=====================+===========+=========================================+ +| categories | list | pages, in which the engine is working | ++---------------------+-----------+-----------------------------------------+ +| paging | boolean | support multible pages | ++---------------------+-----------+-----------------------------------------+ +| language\_support | boolean | support language choosing | ++---------------------+-----------+-----------------------------------------+ + +settings.yml +~~~~~~~~~~~~ + ++------------+----------+-----------------------------------------------+ +| argument | type | information | ++============+==========+===============================================+ +| name | string | name of search-engine | ++------------+----------+-----------------------------------------------+ +| engine | string | name of searx-engine (filename without .py) | ++------------+----------+-----------------------------------------------+ +| shortcut | string | shortcut of search-engine | ++------------+----------+-----------------------------------------------+ +| timeout | string | specific timeout for search-engine | ++------------+----------+-----------------------------------------------+ + +overrides +~~~~~~~~~ + +There are some options, with have default values in the engine, but are +often overwritten by the settings. If the option is assigned in the +engine-file with ``None`` it has to be redefined in the settings, +otherwise searx is not starting with that engine. + +The naming of that overrides can be wathever you want. But we recommend +the using of already used overrides if possible: + ++-----------------------+----------+--------------------------------------------------------------+ +| argument | type | information | ++=======================+==========+==============================================================+ +| base\_url | string | base-url, can be overwrite to use same engine on other url | ++-----------------------+----------+--------------------------------------------------------------+ +| number\_of\_results | int | maximum number of results per request | ++-----------------------+----------+--------------------------------------------------------------+ +| language | string | ISO code of language and country like en\_US | ++-----------------------+----------+--------------------------------------------------------------+ +| api\_key | string | api-key if required by engine | ++-----------------------+----------+--------------------------------------------------------------+ + +example-code +~~~~~~~~~~~~ + +.. code:: python + + # engine dependent config + categories = ['general'] + paging = True + language_support = True + +doing request +------------- + +To perform a search you have to specific at least a url on which the +request is performing + +passed arguments +~~~~~~~~~~~~~~~~ + +This arguments can be used to calculate the search-query. Furthermore, +some of that parameters are filled with default values which can be +changed for special purpose. + ++----------------------+------------+------------------------------------------------------------------------+ +| argument | type | default-value, informations | ++======================+============+========================================================================+ +| url | string | ``''`` | ++----------------------+------------+------------------------------------------------------------------------+ +| method | string | ``'GET'`` | ++----------------------+------------+------------------------------------------------------------------------+ +| headers | set | ``{}`` | ++----------------------+------------+------------------------------------------------------------------------+ +| data | set | ``{}`` | ++----------------------+------------+------------------------------------------------------------------------+ +| cookies | set | ``{}`` | ++----------------------+------------+------------------------------------------------------------------------+ +| verify | boolean | ``True`` | ++----------------------+------------+------------------------------------------------------------------------+ +| headers.User-Agent | string | a random User-Agent | ++----------------------+------------+------------------------------------------------------------------------+ +| category | string | current category, like ``'general'`` | ++----------------------+------------+------------------------------------------------------------------------+ +| started | datetime | current date-time | ++----------------------+------------+------------------------------------------------------------------------+ +| pageno | int | current pagenumber | ++----------------------+------------+------------------------------------------------------------------------+ +| language | string | specific language code like ``'en_US'``, or ``'all'`` if unspecified | ++----------------------+------------+------------------------------------------------------------------------+ + +parsed arguments +~~~~~~~~~~~~~~~~ + +The function ``def request(query, params):`` is always returning the +``params`` variable back. Inside searx, the following paramters can be +used to specific a search-request: + ++------------+-----------+----------------------------------------------------------+ +| argument | type | information | ++============+===========+==========================================================+ +| url | string | requested url | ++------------+-----------+----------------------------------------------------------+ +| method | string | HTTP request methode | ++------------+-----------+----------------------------------------------------------+ +| headers | set | HTTP header informations | ++------------+-----------+----------------------------------------------------------+ +| data | set | HTTP data informations (parsed if ``method != 'GET'``) | ++------------+-----------+----------------------------------------------------------+ +| cookies | set | HTTP cookies | ++------------+-----------+----------------------------------------------------------+ +| verify | boolean | Performing SSL-Validity check | ++------------+-----------+----------------------------------------------------------+ + +example-code +~~~~~~~~~~~~ + +.. code:: python + + # search-url + base_url = 'https://example.com/' + search_string = 'search?{query}&page={page}' + + # do search-request + def request(query, params): + search_path = search_string.format( + query=urlencode({'q': query}), + page=params['pageno']) + + params['url'] = base_url + search_path + + return params + +returning results +----------------- + +Searx has the possiblity to return results in different media-types. +Currently the following media-types are supported: + +- default +- images +- videos +- torrent +- map + +to set another media-type as default, you must set the parameter +``template`` to the required type. + +default +~~~~~~~ + ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++====================+====================================================================================================================================+ +| url | string, which is representing the url of the result | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, which is representing the title of the result | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| content | string, which is giving a general result-text | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, represent when the result is published | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ + +images +~~~~~~ + +to use this template, the parameter + ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++====================+===========================================================================================================================================================+ +| template | is set to ``images.html`` | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| url | string, which is representing the url to the result site | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, which is representing the title of the result *(partly implemented)* | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| content | *(partly implemented)* | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, represent when the result is published *(partly implemented)* | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| img\_src | string, which is representing the url to the result image | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| thumbnail\_src | string, which is representing the url to a small-preview image | ++--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ + +videos +~~~~~~ + ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++====================+====================================================================================================================================+ +| template | is set to ``videos.html`` | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| url | string, which is representing the url of the result | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, which is representing the title of the result | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| content | *(not implemented yet)* | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, represent when the result is published | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| thumbnail | string, which is representing the url to a small-preview image | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------+ + +torrent +~~~~~~~ + ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| template | is set to ```torrent.html``` | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| url | string, which is representing the url of the result | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, which is representing the title of the result | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| content | string, which is giving a general result-text | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | [datetime.datetime](https://docs.python.org/2/library/datetime.html#datetime-objects), represent when the result is published _(not implemented yet)_ | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| seed | int, number of seeder | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| leech | int, number of leecher | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| filesize | int, size of file in bytes | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| files | int, number of files | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| magnetlink | string, which is the [magnetlink](https://en.wikipedia.org/wiki/Magnet_URI_scheme) of the result | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| torrentfile | string, which is the torrentfile of the result | ++------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +map +~~~ + ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++=========================+====================================================================================================================================+ +| url | string, which is representing the url of the result | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, which is representing the title of the result | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| content | string, which is giving a general result-text | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, represent when the result is published | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| latitude | latitude of result (in decimal format) | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| longitude | longitude of result (in decimal format) | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| boundingbox | boundingbox of result (array of 4. values ``[lat-min, lat-max, lon-min, lon-max]``) | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| geojson | geojson of result (http://geojson.org) | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| osm.type | type of osm-object (if OSM-Result) | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| osm.id | id of osm-object (if OSM-Result) | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| address.name | name of object | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| address.road | street adress of object | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| address.house\_number | house number of object | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| address.locality | city, place of object | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| address.postcode | postcode of object | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ +| address.country | country of object | ++-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ + diff --git a/docs/index.rst b/docs/index.rst index abb62062..09dd1798 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,6 +34,7 @@ Developer documentation dev/contribution_guide dev/install/installation + dev/engine_overview dev/search_api dev/plugins dev/translation From ae69a58089cc195d211cd195c79cd60bb49a474f Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Tue, 1 Dec 2015 15:32:06 +0100 Subject: [PATCH 004/144] [doc] grammar & format fixes in engine overview --- docs/dev/engine_overview.rst | 175 +++++++++++++++++------------------ 1 file changed, 86 insertions(+), 89 deletions(-) diff --git a/docs/dev/engine_overview.rst b/docs/dev/engine_overview.rst index 7f170e9d..05a54b64 100644 --- a/docs/dev/engine_overview.rst +++ b/docs/dev/engine_overview.rst @@ -3,14 +3,13 @@ Engine overview searx is a `metasearch-engine `__, -so it is using different search engines to provide better results. +so it uses different search engines to provide better results. -Because there is no general search-api which can be used for every -search-engine, there must be build an adapter between searx and the -external search-engine. This adapters are stored in the folder -`*searx/engines* `__, -and this site is build to make an general documentation about this -engines +Because there is no general search API which could be used for every +search engine, an adapter has to be built between searx and the +external search engines. Adapters are stored under the folder +`searx/engines +`__. .. contents:: @@ -19,17 +18,16 @@ engines general engine configuration ---------------------------- -It is required to tell searx what results can the engine provide. The -arguments can be inserted in the engine file, or in the settings file +It is required to tell searx the type of results the engine provides. The +arguments can be set in the engine file or in the settings file (normally ``settings.yml``). The arguments in the settings file override -the one in the engine file. +the ones in the engine file. -Really, it is for most options no difference if there are contained in -the engine-file or in the settings. But there is a standard where to -place specific arguments by default. +It does not matter if an options is stored in the engine file or in the +settings. However, the standard way is the following: -engine-file +engine file ~~~~~~~~~~~ +---------------------+-----------+-----------------------------------------+ @@ -60,27 +58,27 @@ settings.yml overrides ~~~~~~~~~ -There are some options, with have default values in the engine, but are -often overwritten by the settings. If the option is assigned in the -engine-file with ``None`` it has to be redefined in the settings, -otherwise searx is not starting with that engine. +A few of the options have default values in the engine, but are +often overwritten by the settings. If ``None`` is assigned to an option +in the engine file, it has to be redefined in the settings, +otherwise searx will not start with that engine. -The naming of that overrides can be wathever you want. But we recommend -the using of already used overrides if possible: +The naming of that overrides is arbitrary. But the recommended +overrides are the following: -+-----------------------+----------+--------------------------------------------------------------+ -| argument | type | information | -+=======================+==========+==============================================================+ -| base\_url | string | base-url, can be overwrite to use same engine on other url | -+-----------------------+----------+--------------------------------------------------------------+ -| number\_of\_results | int | maximum number of results per request | -+-----------------------+----------+--------------------------------------------------------------+ -| language | string | ISO code of language and country like en\_US | -+-----------------------+----------+--------------------------------------------------------------+ -| api\_key | string | api-key if required by engine | -+-----------------------+----------+--------------------------------------------------------------+ ++-----------------------+----------+----------------------------------------------------------------+ +| argument | type | information | ++=======================+==========+================================================================+ +| base\_url | string | base-url, can be overwritten to use same engine on other URL | ++-----------------------+----------+----------------------------------------------------------------+ +| number\_of\_results | int | maximum number of results per request | ++-----------------------+----------+----------------------------------------------------------------+ +| language | string | ISO code of language and country like en\_US | ++-----------------------+----------+----------------------------------------------------------------+ +| api\_key | string | api-key if required by engine | ++-----------------------+----------+----------------------------------------------------------------+ -example-code +example code ~~~~~~~~~~~~ .. code:: python @@ -90,21 +88,20 @@ example-code paging = True language_support = True -doing request -------------- +making a request +---------------- -To perform a search you have to specific at least a url on which the -request is performing +To perform a search an URL have to be specified. In addition to +specifying an URL, arguments can be passed to the query. passed arguments ~~~~~~~~~~~~~~~~ -This arguments can be used to calculate the search-query. Furthermore, -some of that parameters are filled with default values which can be -changed for special purpose. +These arguments can be used to construct the search query. Furthermore, +parameters with default value can be redefined for special purposes. +----------------------+------------+------------------------------------------------------------------------+ -| argument | type | default-value, informations | +| argument | type | default-value, information | +======================+============+========================================================================+ | url | string | ``''`` | +----------------------+------------+------------------------------------------------------------------------+ @@ -132,27 +129,27 @@ changed for special purpose. parsed arguments ~~~~~~~~~~~~~~~~ -The function ``def request(query, params):`` is always returning the -``params`` variable back. Inside searx, the following paramters can be -used to specific a search-request: +The function ``def request(query, params):`` always returns the +``params`` variable. Inside searx, the following paramters can be +used to specify a search request: -+------------+-----------+----------------------------------------------------------+ -| argument | type | information | -+============+===========+==========================================================+ -| url | string | requested url | -+------------+-----------+----------------------------------------------------------+ -| method | string | HTTP request methode | -+------------+-----------+----------------------------------------------------------+ -| headers | set | HTTP header informations | -+------------+-----------+----------------------------------------------------------+ -| data | set | HTTP data informations (parsed if ``method != 'GET'``) | -+------------+-----------+----------------------------------------------------------+ -| cookies | set | HTTP cookies | -+------------+-----------+----------------------------------------------------------+ -| verify | boolean | Performing SSL-Validity check | -+------------+-----------+----------------------------------------------------------+ ++------------+-----------+---------------------------------------------------------+ +| argument | type | information | ++============+===========+=========================================================+ +| url | string | requested url | ++------------+-----------+---------------------------------------------------------+ +| method | string | HTTP request method | ++------------+-----------+---------------------------------------------------------+ +| headers | set | HTTP header information | ++------------+-----------+---------------------------------------------------------+ +| data | set | HTTP data information (parsed if ``method != 'GET'``) | ++------------+-----------+---------------------------------------------------------+ +| cookies | set | HTTP cookies | ++------------+-----------+---------------------------------------------------------+ +| verify | boolean | Performing SSL-Validity check | ++------------+-----------+---------------------------------------------------------+ -example-code +example code ~~~~~~~~~~~~ .. code:: python @@ -171,10 +168,10 @@ example-code return params -returning results ------------------ +returned results +---------------- -Searx has the possiblity to return results in different media-types. +Searx is able to return results of different media-types. Currently the following media-types are supported: - default @@ -183,8 +180,8 @@ Currently the following media-types are supported: - torrent - map -to set another media-type as default, you must set the parameter -``template`` to the required type. +To set another media-type as default, the parameter +``template`` must be set to the desired type. default ~~~~~~~ @@ -246,31 +243,31 @@ videos torrent ~~~~~~~ -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| result-parameter | information | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| template | is set to ```torrent.html``` | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| url | string, which is representing the url of the result | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| title | string, which is representing the title of the result | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| content | string, which is giving a general result-text | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| publishedDate | [datetime.datetime](https://docs.python.org/2/library/datetime.html#datetime-objects), represent when the result is published _(not implemented yet)_ | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| seed | int, number of seeder | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| leech | int, number of leecher | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| filesize | int, size of file in bytes | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| files | int, number of files | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| magnetlink | string, which is the [magnetlink](https://en.wikipedia.org/wiki/Magnet_URI_scheme) of the result | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ -| torrentfile | string, which is the torrentfile of the result | -+------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| template | is set to ```torrent.html``` | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| url | string, which is representing the url of the result | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, which is representing the title of the result | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| content | string, which is giving a general result-text | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, represent when the result is published *(not implemented yet)* | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| seed | int, number of seeder | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| leech | int, number of leecher | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| filesize | int, size of file in bytes | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| files | int, number of files | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| magnetlink | string, which is the `magnetlink `__ of the result | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ +| torrentfile | string, which is the torrentfile of the result | ++------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ map From 240802003ee98e93e084788665445d81a9623c7d Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Thu, 10 Dec 2015 21:38:39 +0100 Subject: [PATCH 005/144] [doc] fix small mistakes in engine overview --- docs/dev/engine_overview.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dev/engine_overview.rst b/docs/dev/engine_overview.rst index 05a54b64..186cc0f9 100644 --- a/docs/dev/engine_overview.rst +++ b/docs/dev/engine_overview.rst @@ -23,7 +23,7 @@ arguments can be set in the engine file or in the settings file (normally ``settings.yml``). The arguments in the settings file override the ones in the engine file. -It does not matter if an options is stored in the engine file or in the +It does not matter if an option is stored in the engine file or in the settings. However, the standard way is the following: @@ -63,7 +63,7 @@ often overwritten by the settings. If ``None`` is assigned to an option in the engine file, it has to be redefined in the settings, otherwise searx will not start with that engine. -The naming of that overrides is arbitrary. But the recommended +The naming of overrides is arbitrary. But the recommended overrides are the following: +-----------------------+----------+----------------------------------------------------------------+ From 3b068141928c17b6bb7b343d6e926fc3c443a022 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Thu, 10 Dec 2015 21:40:22 +0100 Subject: [PATCH 006/144] [doc] rewrite contribution guide --- docs/dev/contribution_guide.rst | 105 ++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/docs/dev/contribution_guide.rst b/docs/dev/contribution_guide.rst index 5685549e..73f4ea33 100644 --- a/docs/dev/contribution_guide.rst +++ b/docs/dev/contribution_guide.rst @@ -4,68 +4,93 @@ How to contribute Prime directives: Privacy, Hackability ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Searx has 2 prime directives, privacy-by-design and hackability. The -hackability comes in at least 3 levels: +Searx has two prime directives, privacy-by-design and hackability. The +hackability comes in three levels: -- support for search engines -- plugins for altering search behaviour -- hacking searx itself. +- support of search engines +- plugins to alter search behaviour +- hacking searx itself -Happy hacking. Observe the lack of "world domination" among the -directives, searx has no intentions for wide mass-adoption, rounded -corners, etc. The prime directive: "privacy" - deserves a seperate -chapter, as it's quite uncommon unfortunately, here it goes: +Note the lack of "world domination" among the directives. +Searx has no intention of wide mass-adoption, rounded +corners, etc. The prime directive "privacy" deserves a separate +chapter, as it's quite uncommon unfortunately. Privacy-by-design ^^^^^^^^^^^^^^^^^ -Searx is a privacy-respecting, hackable meta-search engine. It was born -out of the need for a privacy-respecing search facility that can be -expanded easily to maximise both its search and it's privacy protecting -capabilities. +Searx was born out of the need for a privacy-respecting search tool +which can be extended easily to maximize both its search and its +privacy protecting capabilities. -Consequences of Privacy-by-design are that some widely used features -work differently or not by default or at all. If some feature reduces -the privacy perserving aspects of searx, it should by default be -switched of, if implemented at all. There is enough search engines -already out there providing such features. = Since privacy-preservation -is a prime goal, if some feature does reduce the protection of searx and -is implemented, care should be taken to educate the user about the -consequences of choosing to enable this. Further features which -implement widely known features in a manner that protects privacy but -thus deviate from the users expectations should also be explained to the -user. Also if you think that something works weird with searx, maybe -it's because of the tool you use is designed in a way to interfere with -privacy respect, submiting a bugreport to the vendor of the tool that -misbehaves might be a good feedback for the vendor to reconsider his -disrespect towards his customers (e.g. GET vs POST requests in various -browsers). +A few widely used features work differently or turned off by default or not implemented +at all as a consequence of privacy-by-design. + +If a feature reduces the privacy preserving aspects of searx, it +should be switched off by default or should not implemented at all. +There are plenty of search engines already providing such features. +If a feature reduces the protection of searx, users must be +informed about the effect of choosing to enable it. Features +that protect privacy but differ from the expectations of the +user should also be explained. + +Also, if you think that something works weird with searx, +it's might be because of the tool you use is designed in a way to interfere with +the privacy respect. Submitting a bugreport to the vendor of the tool that +misbehaves might be a good feedback to reconsider the disrespect to +its customers (e.g. GET vs POST requests in various browsers). Remember the other prime directive of searx is to be hackable, so if the above privacy concerns do not fancy you, simply fork it. +Happy hacking. + Code ~~~~ -Code modifications are accepted in pull requests, don't forget to add -yourself to the AUTHORS file. +In order to submit a patch, please follow the steps below: -Python code follows all the pep8 standards except maximum line width -which is 120 char. +- Follow coding conventions. -Please be sure that the submitted code doesn't break existing tests and -follows coding conventions. + - PEP8 standards apply, except the convention of line length + + - Maximum line length is 120 characters + +- Check if your code breaks existing tests. If so, update the tests or fix your code. + +- If your code can be unit-tested, add unit tests. + +- Add yourself to the AUTHORS file. + +- Create a pull request. -If new functionality implemented, tests are highly appreciated. Translation ~~~~~~~~~~~ -Translation currently happens on -`transifex `__. Please do not -update translation files in the repo. +Translation currently takes place on +`transifex `__. + +**Please, do not update translation files in the repo.** Documentation ~~~~~~~~~~~~~ -The main place of the documentation is this wiki, updates are welcome. +The documentation is built using Sphinx. So in order to be able to generate the required +files, you have to install it on your system. (It can be installed easily using pip.) + +1. Checkout the gh-pages branch. + +2. Edit the rst file you wish to update. Or create a new rst file and place it under the appropriate folder. + +3. Build the documentation using Sphinx. + +4. Add the updated and created files of these extension: + + - .rst + + - .html + + - .txt + +6. Create a pull request. From 08d26609a5cffa1c70d6aa22cfa4455e1feb2002 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Sun, 10 Jan 2016 20:10:53 +0100 Subject: [PATCH 007/144] update & fix install guide after install refactor --- docs/dev/install/installation.rst | 81 ++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index 2ce6a6b5..e15ff8b2 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -1,25 +1,28 @@ Installation ============ -Step by step installation for Debian / Ubuntu with virtualenv. +Step by step installation for Debian/Ubuntu with virtualenv. Source: https://about.okhin.fr/posts/Searx/ with some additions How to: `Setup searx in a couple of hours with a free SSL certificate `__ +.. contents:: + :depth: 3 + Basic installation ------------------ For Ubuntu, be sure to have enable universe repository. -Install packages : +Install packages: .. code:: sh sudo apt-get install git build-essential libxslt-dev python-dev python-virtualenv python-pybabel zlib1g-dev libffi-dev libssl-dev -Install searx : +Install searx: .. code:: sh @@ -28,7 +31,7 @@ Install searx : sudo useradd searx -d /usr/local/searx sudo chown searx:searx -R /usr/local/searx -Install dependencies in a virtualenv : +Install dependencies in a virtualenv: .. code:: sh @@ -36,8 +39,7 @@ Install dependencies in a virtualenv : cd /usr/local/searx virtualenv searx-ve . ./searx-ve/bin/activate - pip install -r requirements.txt - python setup.py install + ./manage.sh update_packages Configuration ------------- @@ -51,7 +53,7 @@ Edit searx/settings.yml if necessary. Check ----- -Start searx : +Start searx: .. code:: sh @@ -59,7 +61,7 @@ Start searx : Go to http://localhost:8888 -If everything works fine, disable the debug option in settings.yml : +If everything works fine, disable the debug option in settings.yml: .. code:: sh @@ -73,14 +75,14 @@ twice). uwsgi ----- -Install packages : +Install packages: .. code:: sh sudo apt-get install uwsgi uwsgi-plugin-python Create the configuration file /etc/uwsgi/apps-available/searx.ini with -this content : +this content: :: @@ -111,7 +113,7 @@ this content : pythonpath = /usr/local/searx/ chdir = /usr/local/searx/searx/ -Activate the uwsgi application and restart : +Activate the uwsgi application and restart: .. code:: sh @@ -126,7 +128,7 @@ with nginx ^^^^^^^^^^ If nginx is not installed (uwsgi will not work with the package -nginx-light) : +nginx-light): .. code:: sh @@ -136,7 +138,7 @@ Hosted at / """"""""""" Create the configuration file /etc/nginx/sites-available/searx with this -content : +content: .. code:: nginx @@ -151,7 +153,7 @@ content : } } -Restart service : +Restart service: .. code:: sh @@ -159,10 +161,10 @@ Restart service : sudo service uwsgi restart from subdirectory URL (/searx) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"""""""""""""""""""""""""""""" Add this configuration in the server config file -/etc/nginx/sites-available/default : +/etc/nginx/sites-available/default: .. code:: nginx @@ -183,7 +185,7 @@ Enable base\_url in searx/settings.yml base_url : http://your.domain.tld/searx/ -Restart service : +Restart service: .. code:: sh @@ -195,7 +197,7 @@ disable logs for better privacy you can disable nginx logs about searx. -how to proceed : below ``uwsgi_pass`` in +how to proceed: below ``uwsgi_pass`` in /etc/nginx/sites-available/default add :: @@ -203,23 +205,23 @@ how to proceed : below ``uwsgi_pass`` in access_log /dev/null; error_log /dev/null; -Restart service : +Restart service: .. code:: sh sudo service nginx restart with apache ------------ +^^^^^^^^^^^ -Add wsgi mod : +Add wsgi mod: .. code:: sh sudo apt-get install libapache2-mod-uwsgi sudo a2enmod uwsgi -Add this configuration in the file /etc/apache2/apache2.conf : +Add this configuration in the file /etc/apache2/apache2.conf: .. code:: apache @@ -233,29 +235,29 @@ Note that if your instance of searx is not at the root, you should change ```` by the location of your instance, like ````. -Restart Apache : +Restart Apache: .. code:: sh sudo /etc/init.d/apache2 restart disable logs ------------- +"""""""""""" For better privacy you can disable Apache logs. -WARNING : not tested +WARNING: not tested -WARNING : you can only disable logs for the whole (virtual) server not +WARNING: you can only disable logs for the whole (virtual) server not for a specific path. -Go back to /etc/apache2/apache2.conf and above ```` add : +Go back to /etc/apache2/apache2.conf and above ```` add: .. code:: apache CustomLog /dev/null combined -Restart Apache : +Restart Apache: .. code:: sh @@ -272,6 +274,27 @@ How to update git stash git pull origin master git stash apply - pip install --upgrade -r requirements.txt + ./manage.sh update_packages sudo service uwsgi restart +Docker +------ + +Make sure you have installed Docker. For instance, you can deploy searx like this: + +.. code:: sh + + docker pull wonderfall/searx + docker run -d --name searx -p $PORT:8888 wonderfall/searx + +Go to http://localhost:$PORT. + +See https://hub.docker.com/r/wonderfall/searx/ for more informations. + +It's also possible to build searx from the embedded Dockerfile. + +.. code:: sh + + git clone https://github.com/asciimoo/searx.git + cd searx + docker build -t whatever/searx . From fb62d01194bc6049a0dc1e5ffbc3ca2f9536f26c Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Thu, 21 Jan 2016 16:25:40 +0100 Subject: [PATCH 008/144] [enh] improve translation documentation --- docs/dev/translation.rst | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/dev/translation.rst b/docs/dev/translation.rst index 0fc02a7d..c070c36e 100644 --- a/docs/dev/translation.rst +++ b/docs/dev/translation.rst @@ -6,23 +6,29 @@ run these commands in the root directory of searx Add new language ~~~~~~~~~~~~~~~~ -``pybabel init -i messages.pot -d searx/translations -l it`` +.. code:: shell + + pybabel init -i messages.pot -d searx/translations -l it Update .po files ~~~~~~~~~~~~~~~~ -``./utils/update-translations.sh`` +.. code:: shell + + ./utils/update-translations.sh You may have errors here. In that case, edit the ``update-translations.sh`` script to change ``pybabel`` to -``pybabel-python2`` +``pybabel-python2 or pybabel2`` After this step, you can modify the .po files. Compile translations ~~~~~~~~~~~~~~~~~~~~ -``pybabel compile -d searx/translations`` +.. code:: shell + + pybabel compile -d searx/translations Transifex stuff ~~~~~~~~~~~~~~~ @@ -32,12 +38,14 @@ Init Project .. code:: shell + tx init # Transifex instance: https://www.transifex.com/asciimoo/searx/ + tx set --auto-local -r searx.messagespo 'searx/translations//LC_MESSAGES/messages.po' \ --source-lang en --type PO --source-file messages.pot --execute -http://docs.transifex.com/developer/client/set +http://docs.transifex.com/client/init/ -*TODO: mapping between transifex and searx* +http://docs.transifex.com/client/set/ Get translations ^^^^^^^^^^^^^^^^ @@ -46,29 +54,27 @@ Get translations tx pull -a -http://docs.transifex.com/developer/client/pull +http://docs.transifex.com/client/pull Upload source File ^^^^^^^^^^^^^^^^^^ -:: +.. code:: shell tx push -s Upload all Translation ^^^^^^^^^^^^^^^^^^^^^^ -:: +.. code:: shell tx push -s -t -upload specifc Translation (only for admins) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +upload specifc Translation +~~~~~~~~~~~~~~~~~~~~~~~~~~ -:: +.. code:: shell tx push -t -l tr -http://docs.transifex.com/developer/client/push - -*TODO: upload empty files? (new translations)* +http://docs.transifex.com/client/push From cf9bd14a7d8a34c08af5f7a6370525c559d2ed96 Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Thu, 21 Jan 2016 16:30:42 +0100 Subject: [PATCH 009/144] [fix] little typo --- docs/dev/translation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/translation.rst b/docs/dev/translation.rst index c070c36e..bc32eda9 100644 --- a/docs/dev/translation.rst +++ b/docs/dev/translation.rst @@ -19,7 +19,7 @@ Update .po files You may have errors here. In that case, edit the ``update-translations.sh`` script to change ``pybabel`` to -``pybabel-python2 or pybabel2`` +``pybabel-python2`` or ``pybabel2`` After this step, you can modify the .po files. From 016ab5c1e87fa86b5e618616e1dd9d49d880c506 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Mon, 1 Feb 2016 21:28:13 +0100 Subject: [PATCH 010/144] add development quickstart --- docs/dev/quickstart.rst | 90 +++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 91 insertions(+) create mode 100644 docs/dev/quickstart.rst diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst new file mode 100644 index 00000000..edca8af5 --- /dev/null +++ b/docs/dev/quickstart.rst @@ -0,0 +1,90 @@ +Development Quickstart +---------------------- + +This quickstart guide gets your environment set up with searx. Furthermore, it gives a +short introduction to the new manage.sh script. + +How to setup your development environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First, clone the source code of searx to the desired folder. In this case the source +is cloned to ~/myprojects/searx. Then create and activate the searx-ve +virtualenv and install the required packages using manage.sh. + +.. code:: sh + + cd ~/myprojects + git clone https://github.com/asciimoo/searx.git + cd searx + virtualenv searx-ve + . ./searx-ve/bin/activate + ./manage.sh update_dev_packages + + +How to run tests +~~~~~~~~~~~~~~~~ + +Tests can be run using the manage.sh script. + +Following tests and checks are available: + +- Unit tests + +- Selenium tests + +- PEP8 validation + +- Unit test coverage check + +For example unit tests are run with the command below: + +.. code:: sh + + ./manage.sh unit_tests + +For further test options, please consult the help of the manage.sh script. + + +How to compile styles and javascript +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +How to build styles +^^^^^^^^^^^^^^^^^^^ + +Less is required to build the styles of searx. Less can be installed using either NodeJS or Apt. + +.. code:: sh + + sudo apt-get install nodejs + sudo npm install -g less + + +OR + +.. code:: sh + + sudo apt-get install node-less + +After satisfying the requirements styles can be build using manage.sh + +.. code:: sh + + ./manage.sh styles + + +How to build the source of the oscar theme +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Grunt must be installed in order to build the javascript sources. It depends on NodeJS, so first +Node has to be installed. + +.. code:: sh + + sudo apt-get install nodejs + sudo npm install -g grunt-cli + +After installing grunt, the files can be built using the following command: + +.. code:: sh + + ./manage.sh build_grunt diff --git a/docs/index.rst b/docs/index.rst index 09dd1798..d6e21894 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,7 @@ Developer documentation .. toctree:: :maxdepth: 1 + dev/quickstart dev/contribution_guide dev/install/installation dev/engine_overview From 9c56154cfe121c6b89f48dd6b9af4e5e029d8a6e Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Thu, 21 Apr 2016 13:15:04 +0200 Subject: [PATCH 011/144] add dev tips to quickstart --- docs/dev/contribution_guide.rst | 2 ++ docs/dev/quickstart.rst | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/dev/contribution_guide.rst b/docs/dev/contribution_guide.rst index 73f4ea33..65e58729 100644 --- a/docs/dev/contribution_guide.rst +++ b/docs/dev/contribution_guide.rst @@ -64,6 +64,8 @@ In order to submit a patch, please follow the steps below: - Create a pull request. +For more help on getting started with searx development, see :ref:`devquickstart`. + Translation ~~~~~~~~~~~ diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst index edca8af5..92ef6238 100644 --- a/docs/dev/quickstart.rst +++ b/docs/dev/quickstart.rst @@ -1,3 +1,5 @@ +.. _devquickstart: + Development Quickstart ---------------------- @@ -88,3 +90,19 @@ After installing grunt, the files can be built using the following command: .. code:: sh ./manage.sh build_grunt + + + +Tips for debugging/development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Turn on debug logging + Whether you are working on a new engine or trying to eliminate a bug, it is always a good idea + to turn on debug logging. When debug logging is enabled a stack trace appears, + instead of the cryptic ``Internal Server Error`` message. It can be turned on by setting + ``debug: False`` to ``debug: True`` in settings.yml. + +2. Run ``./manage.sh tests`` before creating a PR. + Failing build on Travis is common because of PEP8 checks. So a new commit must be created + containing these format fixes. This phase can be skipped if ``./manage.sh tests`` is run + locally before creating a PR. From 2430baa1fd6e4ecba4bc72a8e71c49a38b424216 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Thu, 21 Apr 2016 13:16:18 +0200 Subject: [PATCH 012/144] update install docs on reverse proxy --- docs/dev/install/installation.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index e15ff8b2..d69f30e8 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -171,6 +171,8 @@ Add this configuration in the server config file location = /searx { rewrite ^ /searx/; } location /searx { try_files $uri @searx; + proxy_pass http://localhost:9999/; + proxy_set_header X-Script-Name /searx; } location @searx { uwsgi_param SCRIPT_NAME /searx; @@ -179,6 +181,7 @@ Add this configuration in the server config file uwsgi_pass unix:/run/uwsgi/app/searx/socket; } + Enable base\_url in searx/settings.yml :: From b40d7a2b36014f8db825c06350ed84db7eed67d5 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Thu, 21 Apr 2016 14:30:17 +0200 Subject: [PATCH 013/144] add reverse proxy configuration --- docs/dev/install/installation.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index d69f30e8..cbbd25f5 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -171,8 +171,6 @@ Add this configuration in the server config file location = /searx { rewrite ^ /searx/; } location /searx { try_files $uri @searx; - proxy_pass http://localhost:9999/; - proxy_set_header X-Script-Name /searx; } location @searx { uwsgi_param SCRIPT_NAME /searx; @@ -182,6 +180,23 @@ Add this configuration in the server config file } +OR + +using reverse proxy +(Please, note that reverse proxy advised to be used in case of single-user or low-traffic instances.) + +.. code:: nginx + + location /searx { + proxy_pass http://127.0.0.1:8888; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Script-Name /searx; + proxy_buffering off; + } + + Enable base\_url in searx/settings.yml :: From ffcceed5170209bbc2fe54322973d72c9c4fff78 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Sat, 9 Jul 2016 21:46:20 +0200 Subject: [PATCH 014/144] version bump to 0.9.0 && year to 2016 --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 30fe9031..b1392bab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ master_doc = 'index' # General information about the project. project = u'searx' -copyright = u'2015, Adam Tauber' +copyright = u'2015-2016, Adam Tauber' author = u'Adam Tauber' # The version info for the project you're documenting, acts as replacement for @@ -57,9 +57,9 @@ author = u'Adam Tauber' # built documents. # # The short X.Y version. -version = '0.8.0' +version = '0.9.0' # The full version, including alpha/beta/rc tags. -release = '0.8.0' +release = '0.9.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 2ad8715b3282557d39da21721487ff85683b1219 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Sat, 9 Jul 2016 22:31:21 +0200 Subject: [PATCH 016/144] better API docs && more features in list --- docs/admin/api.rst | 78 +++++++++++++++++++++++++++++++++++++++++ docs/dev/plugins.rst | 34 +++++++++--------- docs/dev/search_api.rst | 19 ++++++---- docs/index.rst | 21 +++++++---- 4 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 docs/admin/api.rst diff --git a/docs/admin/api.rst b/docs/admin/api.rst new file mode 100644 index 00000000..94508a28 --- /dev/null +++ b/docs/admin/api.rst @@ -0,0 +1,78 @@ +.. _adminapi: + +Administration API +------------------ + +Get configuration data +~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: sh + + GET /config + +Sample response +``````````````` + +.. code:: sh + + { + "autocomplete": "", + "categories": [ + "map", + "it", + "images", + ], + "default_locale": "", + "default_theme": "oscar", + "engines": [ + { + "categories": [ + "map" + ], + "enabled": true, + "name": "openstreetmap", + "shortcut": "osm" + }, + { + "categories": [ + "it" + ], + "enabled": true, + "name": "arch linux wiki", + "shortcut": "al" + }, + { + "categories": [ + "images" + ], + "enabled": true, + "name": "google images", + "shortcut": "goi" + }, + { + "categories": [ + "it" + ], + "enabled": false, + "name": "bitbucket", + "shortcut": "bb" + }, + ], + "instance_name": "searx", + "locales": { + "de": "Deutsch (German)", + "en": "English", + "eo": "Esperanto (Esperanto)", + }, + "plugins": [ + { + "enabled": true, + "name": "HTTPS rewrite" + }, + { + "enabled": false, + "name": "Vim-like hotkeys" + } + ], + "safe_search": 0 + } diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index c51fdf0e..0f52af0d 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -1,11 +1,11 @@ Plugins ------- -Plugins can extend/replace functionality of various components inside +Plugins can extend or replace functionality of various components of searx. -example\_plugin.py -~~~~~~~~~~~~~~~~~~ +Example plugin +~~~~~~~~~~~~~~ .. code:: python @@ -24,22 +24,22 @@ example\_plugin.py ctx['search'].suggestions.add('example') return True -Currently implemented plugin entry points (a.k.a hooks) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Plugin entry points +~~~~~~~~~~~~~~~~~~~ -- Pre search hook (``pre_search``) -- Post search hook (``post_search``) -- Result hook (``on_result``) (is called if a new result is added (see - https\_rewrite plugin)) +Entry points (hooks) define when a plugin runs. Right now only three hooks are implemented. So feel free to implement a hook if it fits the behaviour of your plugin. -Feel free to add more hooks to the code if it is required by a plugin. +Pre search hook +``````````````` -TODO -~~~~ +Runs BEFORE the search request. Function to implement: ``pre_search`` -- Better documentation -- More hooks -- search hook (is called while searx is requesting results (for - example: things like math-solver), the different hooks are running - parallel) +Post search hook +```````````````` +Runs AFTER the search request. Function to implement: ``post_search`` + +Result hook +``````````` + +Runs when a new result is added to the result list. Function to implement: ``on_result`` diff --git a/docs/dev/search_api.rst b/docs/dev/search_api.rst index cd86fd71..59d3c825 100644 --- a/docs/dev/search_api.rst +++ b/docs/dev/search_api.rst @@ -1,13 +1,20 @@ Search API ========== -Search API endpoints: ``/``, ``/search`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The search supports both ``GET`` and ``POST``. However, using ``GET`` the parameters of the request remain hidden. So it is advised to use ``GET`` for querying. -Endpoints have equivalent functionality. +Furthermore, two enpoints ``/`` and ``/search`` are available for querying. + +.. code:: sh + + GET / + +.. code:: sh + + GET /search Parameters -^^^^^^^^^^ +`````````` +------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ | Name | Description | | @@ -18,7 +25,7 @@ Parameters +------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ | ``engines`` | Comma separated list, specifies the active search engines | optional | +------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ +| ``lang`` | Code of the language | optional (default: ``all``) | ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ | ``pageno`` | Search page number | optional (default: ``1``) | +------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ - -Both ``GET`` and ``POST`` methods are supported. diff --git a/docs/index.rst b/docs/index.rst index d6e21894..c24eff4d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ -Privacy-respecting free metasearch engine -========================================= +Welcome to searx +================ -Searx is a free software internet metasearch engine which aggregates results from other search engines, while not storing information about its users. Searx does not track or profile its users, nor does it share its users' data with third parties. Additionally, searx can be used over Tor for online anonymity. +Searx is a free software internet metasearch engine which aggregates results from more than 70 engines, while not storing information about its users. Searx does not track or profile its users, nor does it share its users' data with third parties. Additionally, searx can be used over Tor for online anonymity. Features @@ -15,16 +15,26 @@ Features - Does not collect its users data - Offers secure, encrypted connections (HTTPS/SSL) - Hosted by organisations, such as La Quadrature du Net, which promote digital rights + - About 70 supported search engines + - Easy intergration with any search engine -Further reading ---------------- +User documentation +------------------ .. toctree:: :maxdepth: 1 user/search_syntax +Administrator documentation +--------------------------- + +.. toctree:: + :maxdepth: 1 + + dev/install/installation + admin/api Developer documentation ----------------------- @@ -34,7 +44,6 @@ Developer documentation dev/quickstart dev/contribution_guide - dev/install/installation dev/engine_overview dev/search_api dev/plugins From 521f398709ea481fdf55f1e4c111f063f91fa8c0 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Sat, 9 Jul 2016 23:01:00 +0200 Subject: [PATCH 018/144] disable breaking "searx" into multiple lines --- docs/_themes/searx_theme/static/style.css_t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_themes/searx_theme/static/style.css_t b/docs/_themes/searx_theme/static/style.css_t index 4a266671..0a312b76 100644 --- a/docs/_themes/searx_theme/static/style.css_t +++ b/docs/_themes/searx_theme/static/style.css_t @@ -38,6 +38,8 @@ div.bodywrapper { div.sphinxsidebar { width: {{ sidebar_width }}; + word-wrap: normal !important; + overflow-wrap: normal !important; } hr { From 9e135f9fab3894cc86264cb7552252ef0cf10581 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Fri, 15 Jul 2016 18:41:05 +0200 Subject: [PATCH 019/144] simplify index page --- docs/dev/install/installation.rst | 2 ++ docs/index.rst | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index cbbd25f5..b641a628 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -1,3 +1,5 @@ +.. _installation: + Installation ============ diff --git a/docs/index.rst b/docs/index.rst index c24eff4d..46960c0f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,22 +1,25 @@ Welcome to searx ================ -Searx is a free software internet metasearch engine which aggregates results from more than 70 engines, while not storing information about its users. Searx does not track or profile its users, nor does it share its users' data with third parties. Additionally, searx can be used over Tor for online anonymity. +Search without being tracked. +Searx is a free internet metasearch engine which aggregates results from more than 70 search services. Users are neither tracked nor profiled. Additionally, searx can be used over Tor for online anonymity. + +Get started with searx by using one of the `public instances`_. If you don't trust anyone, you can set up your own, see :ref:`installation`. + +.. _public instances: https://github.com/asciimoo/searx/wiki/Searx-instances Features -------- - Self hosted - - Does not track its users - - Does not share its users data with third parties - - Does not use cookies by default - - Does not profile its users - - Does not collect its users data - - Offers secure, encrypted connections (HTTPS/SSL) - - Hosted by organisations, such as La Quadrature du Net, which promote digital rights + - No user tracking + - No user profiling - About 70 supported search engines - - Easy intergration with any search engine + - Easy intergration with any search engines + - Cookies are not used by default + - Secure, encrypted connections (HTTPS/SSL) + - Hosted by organisations, such as La Quadrature du Net, which promote digital rights User documentation From 6d99db7af33f82c00dbb9d11e17a9c6a907591cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Tue, 6 Sep 2016 18:13:20 +0200 Subject: [PATCH 021/144] add new search parameter time_range --- docs/dev/search_api.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/dev/search_api.rst b/docs/dev/search_api.rst index 59d3c825..c1bc6a13 100644 --- a/docs/dev/search_api.rst +++ b/docs/dev/search_api.rst @@ -1,7 +1,7 @@ Search API ========== -The search supports both ``GET`` and ``POST``. However, using ``GET`` the parameters of the request remain hidden. So it is advised to use ``GET`` for querying. +The search supports both ``GET`` and ``POST``. Furthermore, two enpoints ``/`` and ``/search`` are available for querying. @@ -29,3 +29,5 @@ Parameters +------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ | ``pageno`` | Search page number | optional (default: ``1``) | +------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ +| ``time_range`` | Time range of search | optional | ++------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ From 013139b036e31043e25991112a34588011650243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Tue, 6 Sep 2016 18:16:29 +0200 Subject: [PATCH 023/144] add embedded search to docs --- docs/admin/api.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/admin/api.rst b/docs/admin/api.rst index 94508a28..8d616224 100644 --- a/docs/admin/api.rst +++ b/docs/admin/api.rst @@ -76,3 +76,19 @@ Sample response ], "safe_search": 0 } + +Embed search bar +---------------- + +The search bar can be embedded into websites. Just paste the example into the HTML of the site. +URL of the searx instance and values are customizable. + +.. code:: html + +
+ + + + + +
From daf736f1dda06507b63520d37e1160de44ba6636 Mon Sep 17 00:00:00 2001 From: Noemi Vanyi Date: Sat, 10 Sep 2016 15:12:11 +0200 Subject: [PATCH 025/144] add possible values of time_range param --- docs/dev/search_api.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/dev/search_api.rst b/docs/dev/search_api.rst index c1bc6a13..03bb6f3f 100644 --- a/docs/dev/search_api.rst +++ b/docs/dev/search_api.rst @@ -16,18 +16,18 @@ Furthermore, two enpoints ``/`` and ``/search`` are available for querying. Parameters `````````` -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ -| Name | Description | | -+==================+====================================================================================================+=============================+ -| ``q`` | The search query, see :doc:`/user/search_syntax` | required | -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ -| ``categories`` | Comma separated list, specifies the active search categories | optional | -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ -| ``engines`` | Comma separated list, specifies the active search engines | optional | -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ -| ``lang`` | Code of the language | optional (default: ``all``) | -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ -| ``pageno`` | Search page number | optional (default: ``1``) | -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ -| ``time_range`` | Time range of search | optional | -+------------------+----------------------------------------------------------------------------------------------------+-----------------------------+ ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| Name | Description | | ++==================+====================================================================================================+==================================================+ +| ``q`` | The search query, see :doc:`/user/search_syntax` | required | ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| ``categories`` | Comma separated list, specifies the active search categories | optional | ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| ``engines`` | Comma separated list, specifies the active search engines | optional | ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| ``lang`` | Code of the language | optional (default: ``all``) | ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| ``pageno`` | Search page number | optional (default: ``1``) | ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| ``time_range`` | Time range of search | optional (possible: ``day``, ``month``, ``year``)| ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ From dca4d276a45cc85f97db326636b4b671a3263225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sun, 30 Oct 2016 01:01:22 +0200 Subject: [PATCH 027/144] add sample config of filtron --- docs/admin/filtron.rst | 114 +++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 115 insertions(+) create mode 100644 docs/admin/filtron.rst diff --git a/docs/admin/filtron.rst b/docs/admin/filtron.rst new file mode 100644 index 00000000..c422cb0a --- /dev/null +++ b/docs/admin/filtron.rst @@ -0,0 +1,114 @@ +How to protect an instance +========================== + +Searx depens on external search services. To avoid the abuse of these services it is advised to limit the number of requests processed by searx. + +An application firewall, ``filtron`` solves exactly this problem. Information on how to install it can be found at the `project page of filtron `__. + +Sample configuration of filtron +------------------------------- + +An example configuration can be find below. This configuration limits the access of + + * scripts or applications (roboagent limit) + + * webcrawlers (botlimit) + + * IPs which send too many requests (IP limit) + + * too many json, csv, etc. requests (rss/json limit) + + * the same UserAgent of if too many requests (useragent limit) + + +.. code:: json + + [ + { + "name": "search request", + "filters": ["Param:q", "Path=^(/|/search)$"], + "interval": , + "limit": , + "subrules": [ + { + "name": "roboagent limit", + "interval": , + "limit": , + "filters": ["Header:User-Agent=(curl|cURL|Wget|python-requests|Scrapy|FeedFetcher|Go-http-client)"], + "actions": [ + {"name": "block", + "params": {"message": "Rate limit exceeded"}} + ] + }, + { + "name": "botlimit", + "limit": 0, + "stop": true, + "filters": ["Header:User-Agent=(Googlebot|bingbot|Baiduspider|yacybot|YandexMobileBot|YandexBot|Yahoo! Slurp|MJ12bot|AhrefsBot|archive.org_bot|msnbot|MJ12bot|SeznamBot|linkdexbot|Netvibes|SMTBot|zgrab|James BOT)"], + "actions": [ + {"name": "block", + "params": {"message": "Rate limit exceeded"}} + ] + }, + { + "name": "IP limit", + "interval": , + "limit": , + "stop": true, + "aggregations": ["Header:X-Forwarded-For"], + "actions": [ + {"name": "block", + "params": {"message": "Rate limit exceeded"}} + ] + }, + { + "name": "rss/json limit", + "interval": , + "limit": , + "stop": true, + "filters": ["Param:format=(csv|json|rss)"], + "actions": [ + {"name": "block", + "params": {"message": "Rate limit exceeded"}} + ] + }, + { + "name": "useragent limit", + "interval": , + "limit": , + "aggregations": ["Header:User-Agent"], + "actions": [ + {"name": "block", + "params": {"message": "Rate limit exceeded"}} + ] + } + ] + } + ] + + + +Route request through filtron +----------------------------- + +Filtron can be started using the following command: + +.. code:: bash + + $ filtron -rules rules.json + +It listens on 127.0.0.1:4004 and forwards filtered requests to 127.0.0.1:8888 by default. + +Use it along with ``nginx`` with the following example configuration. + +.. code:: bash + + location / { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + proxy_pass http://127.0.0.1:4004/; + } + +Requests are coming from port 4004 going through filtron and then forwarded to port 8888 where a searx is being run. diff --git a/docs/index.rst b/docs/index.rst index 46960c0f..18e4b6d0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -38,6 +38,7 @@ Administrator documentation dev/install/installation admin/api + admin/filtron Developer documentation ----------------------- From c2cf46092e7e805587fde191ea9c620ee2bcb158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Fri, 4 Nov 2016 18:38:07 +0100 Subject: [PATCH 029/144] docs: fix typos based on #653 --- docs/dev/engine_overview.rst | 226 ++++++++++++++++++----------------- docs/dev/plugins.rst | 2 +- docs/index.rst | 2 +- 3 files changed, 116 insertions(+), 114 deletions(-) diff --git a/docs/dev/engine_overview.rst b/docs/dev/engine_overview.rst index 186cc0f9..da7ee271 100644 --- a/docs/dev/engine_overview.rst +++ b/docs/dev/engine_overview.rst @@ -30,15 +30,17 @@ settings. However, the standard way is the following: engine file ~~~~~~~~~~~ -+---------------------+-----------+-----------------------------------------+ -| argument | type | information | -+=====================+===========+=========================================+ -| categories | list | pages, in which the engine is working | -+---------------------+-----------+-----------------------------------------+ -| paging | boolean | support multible pages | -+---------------------+-----------+-----------------------------------------+ -| language\_support | boolean | support language choosing | -+---------------------+-----------+-----------------------------------------+ ++----------------------+-----------+-----------------------------------------+ +| argument | type | information | ++======================+===========+=========================================+ +| categories | list | pages, in which the engine is working | ++----------------------+-----------+-----------------------------------------+ +| paging | boolean | support multible pages | ++----------------------+-----------+-----------------------------------------+ +| language\_support | boolean | support language choosing | ++----------------------+-----------+-----------------------------------------+ +| time\_range\_support | boolean | support search time range | ++----------------------+-----------+-----------------------------------------+ settings.yml ~~~~~~~~~~~~ @@ -186,126 +188,126 @@ To set another media-type as default, the parameter default ~~~~~~~ -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| result-parameter | information | -+====================+====================================================================================================================================+ -| url | string, which is representing the url of the result | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| title | string, which is representing the title of the result | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| content | string, which is giving a general result-text | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| publishedDate | `datetime.datetime `__, represent when the result is published | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ ++--------------------+---------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++====================+===============================================================================================================+ +| url | string, url of the result | ++--------------------+---------------------------------------------------------------------------------------------------------------+ +| title | string, title of the result | ++--------------------+---------------------------------------------------------------------------------------------------------------+ +| content | string, general result-text | ++--------------------+---------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, time of publish | ++--------------------+---------------------------------------------------------------------------------------------------------------+ images ~~~~~~ to use this template, the parameter -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| result-parameter | information | -+====================+===========================================================================================================================================================+ -| template | is set to ``images.html`` | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| url | string, which is representing the url to the result site | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| title | string, which is representing the title of the result *(partly implemented)* | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| content | *(partly implemented)* | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| publishedDate | `datetime.datetime `__, represent when the result is published *(partly implemented)* | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| img\_src | string, which is representing the url to the result image | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| thumbnail\_src | string, which is representing the url to a small-preview image | -+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++====================+=======================================================================================================================================+ +| template | is set to ``images.html`` | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| url | string, url to the result site | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, title of the result *(partly implemented)* | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| content | *(partly implemented)* | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, time of publish *(partly implemented)* | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| img\_src | string, url to the result image | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| thumbnail\_src | string, url to a small-preview image | ++--------------------+---------------------------------------------------------------------------------------------------------------------------------------+ videos ~~~~~~ -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| result-parameter | information | -+====================+====================================================================================================================================+ -| template | is set to ``videos.html`` | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| url | string, which is representing the url of the result | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| title | string, which is representing the title of the result | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| content | *(not implemented yet)* | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| publishedDate | `datetime.datetime `__, represent when the result is published | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| thumbnail | string, which is representing the url to a small-preview image | -+--------------------+------------------------------------------------------------------------------------------------------------------------------------+ ++--------------------+--------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++====================+==============================================================================================================+ +| template | is set to ``videos.html`` | ++--------------------+--------------------------------------------------------------------------------------------------------------+ +| url | string, url of the result | ++--------------------+--------------------------------------------------------------------------------------------------------------+ +| title | string, title of the result | ++--------------------+--------------------------------------------------------------------------------------------------------------+ +| content | *(not implemented yet)* | ++--------------------+--------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, time of publish | ++--------------------+--------------------------------------------------------------------------------------------------------------+ +| thumbnail | string, url to a small-preview image | ++--------------------+--------------------------------------------------------------------------------------------------------------+ torrent ~~~~~~~ -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| result-parameter | information | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| template | is set to ```torrent.html``` | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| url | string, which is representing the url of the result | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| title | string, which is representing the title of the result | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| content | string, which is giving a general result-text | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| publishedDate | `datetime.datetime `__, represent when the result is published *(not implemented yet)* | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| seed | int, number of seeder | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| leech | int, number of leecher | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| filesize | int, size of file in bytes | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| files | int, number of files | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| magnetlink | string, which is the `magnetlink `__ of the result | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -| torrentfile | string, which is the torrentfile of the result | -+------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| template | is set to ``torrent.html`` | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| url | string, url of the result | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| title | string, title of the result | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| content | string, general result-text | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, time of publish *(not implemented yet)* | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| seed | int, number of seeder | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| leech | int, number of leecher | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| filesize | int, size of file in bytes | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| files | int, number of files | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| magnetlink | string, `magnetlink `__ of the result | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ +| torrentfile | string, torrentfile of the result | ++------------------+---------------------------------------------------------------------------------------------------------------------------------------+ map ~~~ -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| result-parameter | information | -+=========================+====================================================================================================================================+ -| url | string, which is representing the url of the result | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| title | string, which is representing the title of the result | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| content | string, which is giving a general result-text | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| publishedDate | `datetime.datetime `__, represent when the result is published | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| latitude | latitude of result (in decimal format) | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| longitude | longitude of result (in decimal format) | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| boundingbox | boundingbox of result (array of 4. values ``[lat-min, lat-max, lon-min, lon-max]``) | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| geojson | geojson of result (http://geojson.org) | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| osm.type | type of osm-object (if OSM-Result) | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| osm.id | id of osm-object (if OSM-Result) | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| address.name | name of object | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| address.road | street adress of object | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| address.house\_number | house number of object | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| address.locality | city, place of object | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| address.postcode | postcode of object | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ -| address.country | country of object | -+-------------------------+------------------------------------------------------------------------------------------------------------------------------------+ ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| result-parameter | information | ++=========================+==============================================================================================================+ +| url | string, url of the result | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| title | string, title of the result | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| content | string, general result-text | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| publishedDate | `datetime.datetime `__, time of publish | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| latitude | latitude of result (in decimal format) | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| longitude | longitude of result (in decimal format) | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| boundingbox | boundingbox of result (array of 4. values ``[lat-min, lat-max, lon-min, lon-max]``) | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| geojson | geojson of result (http://geojson.org) | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| osm.type | type of osm-object (if OSM-Result) | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| osm.id | id of osm-object (if OSM-Result) | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| address.name | name of object | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| address.road | street name of object | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| address.house\_number | house number of object | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| address.locality | city, place of object | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| address.postcode | postcode of object | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| address.country | country of object | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index 0f52af0d..1d77f861 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -11,7 +11,7 @@ Example plugin name = 'Example plugin' description = 'This plugin extends the suggestions with the word "example"' - default_on = False # disable by default + default_on = False # disabled by default js_dependencies = tuple() # optional, list of static js files css_dependencies = tuple() # optional, list of static css files diff --git a/docs/index.rst b/docs/index.rst index 18e4b6d0..81b9fe33 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ Features - No user tracking - No user profiling - About 70 supported search engines - - Easy intergration with any search engines + - Easy integration with any search engine - Cookies are not used by default - Secure, encrypted connections (HTTPS/SSL) - Hosted by organisations, such as La Quadrature du Net, which promote digital rights From ee18ad780ea5c069c98e33f395dcd972c8a0bd09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Fri, 4 Nov 2016 21:53:11 +0100 Subject: [PATCH 031/144] conf: bump searx version to 0.10.0 --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b1392bab..3ede3445 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ author = u'Adam Tauber' # built documents. # # The short X.Y version. -version = '0.9.0' +version = '0.10.0' # The full version, including alpha/beta/rc tags. -release = '0.9.0' +release = '0.10.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 31ea4280bfab409a2518f95af26da0de32f4645e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sat, 5 Nov 2016 11:48:49 +0100 Subject: [PATCH 034/144] "why use a personal instance?" page --- docs/index.rst | 1 + docs/user/own-instance.rst | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docs/user/own-instance.rst diff --git a/docs/index.rst b/docs/index.rst index 81b9fe33..da5a886d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,6 +29,7 @@ User documentation :maxdepth: 1 user/search_syntax + admin/own-instance Administrator documentation --------------------------- diff --git a/docs/user/own-instance.rst b/docs/user/own-instance.rst new file mode 100644 index 00000000..56f9b011 --- /dev/null +++ b/docs/user/own-instance.rst @@ -0,0 +1,43 @@ +Why use a private instance? +=========================== + +"Is it worth to run my own instance?" is a common question among searx users. Before answering this question, see what options a searx user has. + +Public instances are open to everyone who has access to its URL. Usually, these are operated by unknown parties (from the users' point of view). Private instances can be used by a select group of people. It is for example a searx of group of friends or a company which can be accessed through VPN. Also it can be single user one which runs on the user's laptop. + +To gain more insight on how these instances work let's dive into how searx protects its users. + +How does searx protect privacy? +------------------------------- + +Searx protects the privacy of its users in multiple ways regardless of the type of the instance (private, public). Removal of private data from search requests comes in three forms: + + 1. removal of private data from requests going to search services + 2. not forwarding anything from a third party services through search services (e.g. advertisement) + 3. removal of private data from requests going to the result pages + +Removing private data means not forwarding IP addresses, not sending cookies to external search engines and generating a random browser profile for every request. Thus, it does not matter if a public or private instance handles the request, because it is anonymized in both cases. + +Searx does not serve adds or tracking content unlike most search services. So private data is not forwarded to third parties who might monetize it. Besides protecting users from search services, both referring page and search query are hidden from visited result pages. + +What are the consequences of using public instances? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If someone uses a public instance, he/she has to trust the administrator of that instance. +This means that the user of the public instance does not know whether his/her requests are logged, aggregated and sent or sold to a third party. + +Also, public instances without proper protection are more vulnerable to abusing the search service, In this case the external service in exchange returns CAPTCHAs or bans the IP of the instance. Thus, search requests return less results. + +I see. What about private instances? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If users run their own instances, everything is in their control: the source code, logging settings and private data. Unknown instance administrators do not have to be trusted. + +Furthermore, as the default settings of their instance is editable, there is no need to use cookies to tailor searx to their needs. So preferences will not be reset to defaults when clearing browser cookies. As settings are stored on their computer, it will not be accessible to others as long as their computer is not compromised. + +Conclusion +---------- + +Always use an instance which is operated by people you trust. The privacy features of searx are available to users no matter what kind of instance they use. + +If someone is on the go or just wants to try searx for the first time public instances are the best choices. Additionally, public instance are making a world a better place, because those who cannot or do not want to run an instance, have access to a privacy respecting search service. From f55eea083f9100e4275208c34347088be251c83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sat, 5 Nov 2016 11:49:07 +0100 Subject: [PATCH 035/144] generated files of parent --- docs/index.rst | 2 +- docs/user/own-instance.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index da5a886d..abd57f08 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,7 @@ User documentation :maxdepth: 1 user/search_syntax - admin/own-instance + user/own-instance Administrator documentation --------------------------- diff --git a/docs/user/own-instance.rst b/docs/user/own-instance.rst index 56f9b011..3f280228 100644 --- a/docs/user/own-instance.rst +++ b/docs/user/own-instance.rst @@ -16,7 +16,7 @@ Searx protects the privacy of its users in multiple ways regardless of the type 2. not forwarding anything from a third party services through search services (e.g. advertisement) 3. removal of private data from requests going to the result pages -Removing private data means not forwarding IP addresses, not sending cookies to external search engines and generating a random browser profile for every request. Thus, it does not matter if a public or private instance handles the request, because it is anonymized in both cases. +Removing private data means not forwarding not sending cookies to external search engines and generating a random browser profile for every request. Thus, it does not matter if a public or private instance handles the request, because it is anonymized in both cases. IP addresses will be the IP of the insance. But searx can be ocnfigured to use proxy or Tor. `Result proxy `__ is supported, too. Searx does not serve adds or tracking content unlike most search services. So private data is not forwarded to third parties who might monetize it. Besides protecting users from search services, both referring page and search query are hidden from visited result pages. From 50f25193c17707157c905f455b1d3a27c3edc001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sat, 5 Nov 2016 16:22:29 +0100 Subject: [PATCH 036/144] engine overview fix torrent table --- docs/dev/engine_overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/engine_overview.rst b/docs/dev/engine_overview.rst index da7ee271..d57ca5b3 100644 --- a/docs/dev/engine_overview.rst +++ b/docs/dev/engine_overview.rst @@ -247,7 +247,7 @@ torrent +------------------+---------------------------------------------------------------------------------------------------------------------------------------+ | result-parameter | information | -+------------------+---------------------------------------------------------------------------------------------------------------------------------------+ ++==================+=======================================================================================================================================+ | template | is set to ``torrent.html`` | +------------------+---------------------------------------------------------------------------------------------------------------------------------------+ | url | string, url of the result | From d0d2d18a4d1d47b5809c9129f2b891ab6f80e42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sat, 5 Nov 2016 16:26:53 +0100 Subject: [PATCH 038/144] fix typos in own instance page --- docs/user/own-instance.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/own-instance.rst b/docs/user/own-instance.rst index 3f280228..71d9f11a 100644 --- a/docs/user/own-instance.rst +++ b/docs/user/own-instance.rst @@ -16,7 +16,7 @@ Searx protects the privacy of its users in multiple ways regardless of the type 2. not forwarding anything from a third party services through search services (e.g. advertisement) 3. removal of private data from requests going to the result pages -Removing private data means not forwarding not sending cookies to external search engines and generating a random browser profile for every request. Thus, it does not matter if a public or private instance handles the request, because it is anonymized in both cases. IP addresses will be the IP of the insance. But searx can be ocnfigured to use proxy or Tor. `Result proxy `__ is supported, too. +Removing private data means not sending cookies to external search engines and generating a random browser profile for every request. Thus, it does not matter if a public or private instance handles the request, because it is anonymized in both cases. IP addresses will be the IP of the instance. But searx can be configured to use proxy or Tor. `Result proxy `__ is supported, too. Searx does not serve adds or tracking content unlike most search services. So private data is not forwarded to third parties who might monetize it. Besides protecting users from search services, both referring page and search query are hidden from visited result pages. From 6ecd8e3bd72df43d903800ee05e33185efe22ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sun, 20 Nov 2016 21:12:52 +0100 Subject: [PATCH 040/144] installation: nginx mod to sites-enabled --- docs/dev/install/installation.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index b641a628..b02718fe 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -3,20 +3,13 @@ Installation ============ -Step by step installation for Debian/Ubuntu with virtualenv. - -Source: https://about.okhin.fr/posts/Searx/ with some additions - -How to: `Setup searx in a couple of hours with a free SSL -certificate `__ - .. contents:: :depth: 3 Basic installation ------------------ -For Ubuntu, be sure to have enable universe repository. +Step by step installation for Debian/Ubuntu with virtualenv. For Ubuntu, be sure to have enable universe repository. Install packages: @@ -166,7 +159,7 @@ from subdirectory URL (/searx) """""""""""""""""""""""""""""" Add this configuration in the server config file -/etc/nginx/sites-available/default: +/etc/nginx/sites-enabled/default: .. code:: nginx @@ -318,3 +311,11 @@ It's also possible to build searx from the embedded Dockerfile. git clone https://github.com/asciimoo/searx.git cd searx docker build -t whatever/searx . + +References +========== + + * https://about.okhin.fr/posts/Searx/ with some additions + + * How to: `Setup searx in a couple of hours with a free SSL certificate `__ + From e43e5af207580bab7aeb3b8d866a27b282babab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sat, 18 Mar 2017 23:55:20 +0100 Subject: [PATCH 042/144] bump searx version to 0.11.0 --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3ede3445..01249957 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ author = u'Adam Tauber' # built documents. # # The short X.Y version. -version = '0.10.0' +version = '0.11.0' # The full version, including alpha/beta/rc tags. -release = '0.10.0' +release = '0.11.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 1bd51c6de2f31ad15e5689ee7ff80f2283baa817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sat, 18 Mar 2017 23:57:19 +0100 Subject: [PATCH 044/144] rewrite Translation page --- docs/dev/translation.rst | 87 ++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 53 deletions(-) diff --git a/docs/dev/translation.rst b/docs/dev/translation.rst index bc32eda9..ab4dd98c 100644 --- a/docs/dev/translation.rst +++ b/docs/dev/translation.rst @@ -1,80 +1,61 @@ Translation =========== -run these commands in the root directory of searx +Requirements +------------ -Add new language -~~~~~~~~~~~~~~~~ + * Transifex account -.. code:: shell + * Installed CLI tool of Transifex - pybabel init -i messages.pot -d searx/translations -l it +Init Transifex project +---------------------- -Update .po files -~~~~~~~~~~~~~~~~ - -.. code:: shell - - ./utils/update-translations.sh - -You may have errors here. In that case, edit the -``update-translations.sh`` script to change ``pybabel`` to -``pybabel-python2`` or ``pybabel2`` - -After this step, you can modify the .po files. - -Compile translations -~~~~~~~~~~~~~~~~~~~~ - -.. code:: shell - - pybabel compile -d searx/translations - -Transifex stuff -~~~~~~~~~~~~~~~ - -Init Project -^^^^^^^^^^^^ +After installing ``transifex`` using pip, run the following command to initialize the project. .. code:: shell tx init # Transifex instance: https://www.transifex.com/asciimoo/searx/ + +After ``$HOME/.transifexrc`` is created, get a Transifex API key and insert it into the configuration file. + +Create a configuration file for ``tx`` named ``$HOME/.tx/config``. + +.. code:: shell + + [main] + host = https://www.transifex.com + [searx.messagespo] + file_filter = searx/translations//LC_MESSAGES/messages.po + source_file = messages.pot + source_lang = en + type = PO + + +Then run ``tx set``: + +.. code:: shell + tx set --auto-local -r searx.messagespo 'searx/translations//LC_MESSAGES/messages.po' \ --source-lang en --type PO --source-file messages.pot --execute -http://docs.transifex.com/client/init/ -http://docs.transifex.com/client/set/ +Update translations +------------------- -Get translations -^^^^^^^^^^^^^^^^ +To retrieve the latest translations, pull it from Transifex. .. code:: shell tx pull -a -http://docs.transifex.com/client/pull - -Upload source File -^^^^^^^^^^^^^^^^^^ +Then check the new languages. If strings translated are not enough, delete those folders, because +those should not be compiled. Call the command below to compile the ``.po`` files. .. code:: shell - tx push -s + pybabel compile -d searx/translations -Upload all Translation -^^^^^^^^^^^^^^^^^^^^^^ -.. code:: shell - - tx push -s -t - -upload specifc Translation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: shell - - tx push -t -l tr - -http://docs.transifex.com/client/push +After the compilation is finished commit the ``.po`` and ``.mo`` files and create a PR. From b8f5c75374a491d883036a9532bb98d02284c8a9 Mon Sep 17 00:00:00 2001 From: James Coleman Date: Wed, 19 Apr 2017 15:29:32 -0500 Subject: [PATCH 046/144] Corrected spelling of ads in user/own-instance.html --- docs/user/own-instance.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/own-instance.rst b/docs/user/own-instance.rst index 71d9f11a..555f82cc 100644 --- a/docs/user/own-instance.rst +++ b/docs/user/own-instance.rst @@ -18,7 +18,7 @@ Searx protects the privacy of its users in multiple ways regardless of the type Removing private data means not sending cookies to external search engines and generating a random browser profile for every request. Thus, it does not matter if a public or private instance handles the request, because it is anonymized in both cases. IP addresses will be the IP of the instance. But searx can be configured to use proxy or Tor. `Result proxy `__ is supported, too. -Searx does not serve adds or tracking content unlike most search services. So private data is not forwarded to third parties who might monetize it. Besides protecting users from search services, both referring page and search query are hidden from visited result pages. +Searx does not serve ads or tracking content unlike most search services. So private data is not forwarded to third parties who might monetize it. Besides protecting users from search services, both referring page and search query are hidden from visited result pages. What are the consequences of using public instances? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 93e6258463d67a33c956788a5552627a9798fb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Fri, 12 May 2017 22:51:48 +0200 Subject: [PATCH 047/144] add first blog entry on python3 support --- docs/_themes/searx_theme/relations.html | 1 + docs/blog/blog.rst | 7 +++ docs/blog/python3.rst | 54 ++++++++++++++++++++++++ docs/blog/searxpy3.png | Bin 0 -> 30947 bytes 4 files changed, 62 insertions(+) create mode 100644 docs/blog/blog.rst create mode 100644 docs/blog/python3.rst create mode 100644 docs/blog/searxpy3.png diff --git a/docs/_themes/searx_theme/relations.html b/docs/_themes/searx_theme/relations.html index 13cfa019..b7539e5a 100644 --- a/docs/_themes/searx_theme/relations.html +++ b/docs/_themes/searx_theme/relations.html @@ -3,6 +3,7 @@ diff --git a/docs/blog/blog.rst b/docs/blog/blog.rst new file mode 100644 index 00000000..aad448c6 --- /dev/null +++ b/docs/blog/blog.rst @@ -0,0 +1,7 @@ +Blog +==== + +.. toctree:: + :maxdepth: 1 + + python3 diff --git a/docs/blog/python3.rst b/docs/blog/python3.rst new file mode 100644 index 00000000..f9a05890 --- /dev/null +++ b/docs/blog/python3.rst @@ -0,0 +1,54 @@ +Introducing Python3 support +=========================== + +As most operation systems are coming with Python3 installed by default. So it is time for searx to support Python3. But don't worry support of Python2.7 won't be dropped. + +.. image:: hurray.png + :scale: 50 % + :alt: hurray + :align: center + + +How to run searx using Python3 +------------------------------ + +Please make sure that you run at least Python3.5. + +To run searx, first a Python3 virtualenv should be created. After entering the virtualenv, +dependencies must be installed. Then run searx with python3 instead of the usual python command. + +.. code:: sh + + virtualenv -p python3 venv3 + source venv3/bin/activate + pip3 install -r requirements.txt + python3 searx/webapp.py + + +If you want to run searx using Python2.7, you don't have to do anything differently as before. + +Fun facts +--------- + +- 115 files were changed when implementing the support for both Python versions. + +- All of the dependencies was compatible except for the robotframework used for browser tests. Thus, these tests were migrated to splinter. So from now on both versions are being tested on Travis and can be tested locally. + +If you found bugs... +-------------------- + +...please open an issue on `GitHub`_. Make sure that you mention your Python version in your issue, +so we can investigate it properly. + +.. _GitHub: https://github.com/asciimoo/searx/issues + +Acknowledgement +--------------- + +This development was sponsored by `NLnet Foundation`_. + +.. _NLnet Foundation: https://nlnet.nl/ + + +| Happy hacking. +| kvch // 2017.05.13 22:57 diff --git a/docs/blog/searxpy3.png b/docs/blog/searxpy3.png new file mode 100644 index 0000000000000000000000000000000000000000..8eeaeec55b6b633fb1ad02234371c5341acbdbc2 GIT binary patch literal 30947 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 literal 0 HcmV?d00001 From b92631bf8c36a8b7fa1fada219428af8e2abb566 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 12 Jun 2017 12:34:30 +0200 Subject: [PATCH 048/144] various mods: uwsgi setup and version update ++ py3 img src fix ++ new sphinx version - closes #939 --- docs/blog/python3.rst | 2 +- docs/conf.py | 6 +++--- docs/dev/install/installation.rst | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/blog/python3.rst b/docs/blog/python3.rst index f9a05890..eb14a49e 100644 --- a/docs/blog/python3.rst +++ b/docs/blog/python3.rst @@ -3,7 +3,7 @@ Introducing Python3 support As most operation systems are coming with Python3 installed by default. So it is time for searx to support Python3. But don't worry support of Python2.7 won't be dropped. -.. image:: hurray.png +.. image:: searxpy3.png :scale: 50 % :alt: hurray :align: center diff --git a/docs/conf.py b/docs/conf.py index 01249957..52aad663 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ master_doc = 'index' # General information about the project. project = u'searx' -copyright = u'2015-2016, Adam Tauber' +copyright = u'2015-2017, Adam Tauber' author = u'Adam Tauber' # The version info for the project you're documenting, acts as replacement for @@ -57,9 +57,9 @@ author = u'Adam Tauber' # built documents. # # The short X.Y version. -version = '0.11.0' +version = '0.12.0' # The full version, including alpha/beta/rc tags. -release = '0.11.0' +release = '0.12.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index b02718fe..e1ae3f2a 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -99,6 +99,8 @@ this content: single-interpreter = true master = true plugin = python + lazy-apps = true + enable-threads = true # Module to import module = searx.webapp From 74d8ed284dfb5363df4280350a8e29125f67a10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Sun, 9 Jul 2017 22:02:29 +0200 Subject: [PATCH 050/144] add format param to search api --- docs/dev/search_api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/dev/search_api.rst b/docs/dev/search_api.rst index 03bb6f3f..331bc4eb 100644 --- a/docs/dev/search_api.rst +++ b/docs/dev/search_api.rst @@ -31,3 +31,5 @@ Parameters +------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ | ``time_range`` | Time range of search | optional (possible: ``day``, ``month``, ``year``)| +------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +| ``format`` | Output format of results | optional (possible: ``json``, ``csv``, ``rss``) | ++------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ From 6cc9f5694979956fd54a695bff6d454ca17f721d Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Tue, 8 Aug 2017 15:19:02 +0200 Subject: [PATCH 052/144] [enh] add morty documentation --- docs/admin/morty.rst | 21 +++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 22 insertions(+) create mode 100644 docs/admin/morty.rst diff --git a/docs/admin/morty.rst b/docs/admin/morty.rst new file mode 100644 index 00000000..49e62bba --- /dev/null +++ b/docs/admin/morty.rst @@ -0,0 +1,21 @@ +How to setup result proxy +========================= + +By default searx can only act as an image proxy for result images, +but it is possible to proxify all the result URLs with an external service, +`morty `__. + +To use this feature, morty has to be installed and activated in searx's ``settings.yml``. + +Add the following snippet to your ``settings.yml`` and restart searx: + + +.. code:: yaml + + result_proxy: + url : http://127.0.0.1:3000/ + key : your_morty_proxy_key + +``url`` is the address of the running morty service + +``key`` is an optional argument, see `morty's README `__ for more information. diff --git a/docs/index.rst b/docs/index.rst index abd57f08..7854fdb0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ Administrator documentation dev/install/installation admin/api admin/filtron + admin/morty Developer documentation ----------------------- From 979dd3d96c91c0430adca7528cda18cc7e547211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Tue, 22 Aug 2017 21:30:00 +0200 Subject: [PATCH 053/144] blog: add post about searx-admin --- docs/blog/admin.rst | 42 ++++++++++++++++++++++++++++++ docs/blog/blog.rst | 1 + docs/blog/searx-admin-engines.png | Bin 0 -> 50840 bytes 3 files changed, 43 insertions(+) create mode 100644 docs/blog/admin.rst create mode 100644 docs/blog/searx-admin-engines.png diff --git a/docs/blog/admin.rst b/docs/blog/admin.rst new file mode 100644 index 00000000..18d5ed33 --- /dev/null +++ b/docs/blog/admin.rst @@ -0,0 +1,42 @@ +Searx admin interface: manage your instance from your browser +============================================================= + +One of the biggest advantages of searx is being extremely customizable. But at first it can be daunting to newcomers. +A barrier of taking advantage of this feature is our ugly settings file which is sometimes hard to understand and edit. + +To make self-hosting searx more accessible a new tool is introduced, called ``searx-admin``. +It is a web application which is capable of managing your instance and manipulating its settings via a web UI. +It aims to replace editing of ``settings.yml`` for less experienced administrators or people +who prefer graphical admin interfaces. + +.. figure:: searx-admin-engines.png + :scale: 50 % + :alt: Screenshot of engine list + :align: center + :figclass: align-center + + Configuration page of engines + + +Since ``searx-admin`` acts as a supervisor for searx, we have decided to implement it +as a standalone tool instead of part of searx. Another reason for making it a standalone +tool is that the codebase and dependencies of searx should not grow because of a fully optional feature, +which does not affect existing instances. + + +Installation +------------ + +Installation guide can be found in the repository of searx-admin: +https://github.com/kvch/searx-admin#installation--usage + +Acknowledgements +---------------- + +This development was sponsored by `NLnet Foundation`_. + +.. _NLnet Foundation: https://nlnet.nl/ + + +| Happy hacking. +| kvch // 2017.08.22 21:25 diff --git a/docs/blog/blog.rst b/docs/blog/blog.rst index aad448c6..77553d5c 100644 --- a/docs/blog/blog.rst +++ b/docs/blog/blog.rst @@ -5,3 +5,4 @@ Blog :maxdepth: 1 python3 + admin diff --git a/docs/blog/searx-admin-engines.png b/docs/blog/searx-admin-engines.png new file mode 100644 index 0000000000000000000000000000000000000000..610bacdf7b5e107d4329893b323e91a53bac40b7 GIT binary patch literal 50840 zcmce;XIN8R*DZ{4D+mf$5Jie8MX45g3pXlAm)=1@>Am-$f}n^f2#7Ql0U?b5ImaAhth{)jDu0TKk&29r?3BX& zyBcI<$F#`Ej@D2fg?B`4D^9@+xtpwl79}MmZd`2){-txjr{}KeWa;i@=K7dS)7rz` z{jsaX)6NrQWS7Yl?*6UiJ@|Xr$N$_kzIxTlQzT)3D3Q)p=ID{5<8PE?mhT+XDknP@ zV~|a6C$*r?XNxWDO|_*(J;f?V zCw|JDdNIBpxH^H6SpR+dc86tU{%E{a^4mci?%0jX)|f$T2%n#bx<+KkB~lyLY^$28fDMc`MZ~v^glcC!ndX>tUboSnu7HI z<=uvA6~Z(P@dtPD-oMWHIDimCkUo8LVDWI4@M`_=RCZ#qfhuXZ$jI&!4sU*W_wWO_ z>EXLUQ>=%tZw|;0U!#zRFEVR~_x-=WXt>m;%b5|r@nN-Q*I#7LLRN&9^LKy0YRk9| zUr>6}CUM0nP^@>fFEex+WouxH$E~&|iboMw1cih+*x6ygo?sT=)Og#lIhqM6KivGb044GENxUjp6uXSJYosH z>CZF%>+1}%f9;7OFoC~cQqV*lAmb2DBjqI(6~he;4LDrc=g(JocouO^RmSDstHdZa z(<3^l%@%>G?Ck7wvac@OH2)ENLTXn_PEJlmrH%OAhD$ZEvAOxmE``ht43cwQV?6Px z#Fjp9FDq~MgZul$GSSYC5>p3RQ%)iVgJBKa`J|X;P!$-UrS(oy9fb}2Q$yjmy?pA# ziFi>L^D6wZl9Cb+4^Kr!#U)0@U%!6&zCR$YaH=NmM9-af@zCJ@a{6@gk-+v@M%+5p zrBgZSJb4e&&Yhl7cP?zQFWhR=+1{i&2s1V9r&D7Un6(VpA6TmwPF0{BUs;@(aDNgg zg~jkaFI*o*D{`D#^V;6_$jt85)h>F!^qS&GWp2CbY;E>{d1mJAf{ShV8|y?|3-#iq zL04+KvzpD#JPCfD2m2;11qFqDA_W75Qk@;xro8plwDgfq^EpZyS0{U6;YFN)zjCR! zMKcrAxo6}Y zjoqbYJAR6>z;U>AHrbD#gCh;WMZA9fx@;Ic4g6#zqP@M{(ZRvPv+~TWN4j$S5B@&H zsf$y4&A0oC%f#NrF5>-@hK!j-0D2chy|7~=vF~)y2Z0;I@Em$%0fy6cY6GT75 z+itFQ!E|RE{u*)HzaUTXSf!#yZSt4-+)f|Y0N1+Rv^mO=Md;R#qTxrecFqs3PejH# z4hQ}i>5yj!fh@CET)?o|eUD)#>BLvN#UJv>L>&svNX|82LzWN6T} z@xzn`$C+E=(E9BGnkM?5$=N%o`E{w1)uyzc9AspjsN)o+l6dFV7N^Jawgw7>vefSR zU^){MlcepsmL#P=_ zOG*Z$W8S{~gtd+1)i+0(b$z}kCMKq!px|3E(%0wJo+!q~#`Zf@cjPSX>+9S$fx9`% z(M{aJ#nXG88LY3HB-Kazdqz+#4mLtk1X;!W%2c#)d57+pyi13J1IGtnkS6~PAEVBSqDW)nkVw**u6^Bl|3V3oBB{%Q2 zKMA2baXk&^nfPvf>AgCEcKCMQ$6{%*JGdfTnT2U3o%#I1Is=i_+t>6#5UywS*{lj} zl7y7twdy5-1dZwRIk{hXYqW@#x26(e+$*MH%T_$~QUr}eNe=0fjY;DS-%3R(8)dei zJtv+~wiDw!Ok{TU=Ch zXMdl8o<1!jgH_CR>Qg9-tE)I-LRM3=dl6k_qOCm*$HIcag9py=_~9v8CHu(nN5{we z9PGHTvZkmc2%lhsupCSm7#Ij~v%kn`biD(yGCRu^e2tH9xWGnJS@~6HsJw_>azX;G zzhBgCMwx;lR3VxZk^tP#U6HW3xH#%F%bcTw(m_FxynX8y4YT+oxEQrAJ;^O|UuA5h{UX_$I%IHp#4iHBmh?yD5Q3Rs6u`lT;O+2u&vdTzH zQ-~_CXn4(b=eZtlEX-$iE-u9GRP>!tn#a23Kjdh(eJ6*R3@Z!VOqQ2|ppQJzmTsihV|zR@`07OXbzdAG2hR>Ajz+G3UdJ``vL=?wZww zX+9wl(1NR1r6O5S^SY*{-eG;Q9aZ~#H0&|TEFz7jlb@YR=1MNfW7gUqU-e?WcEFjZ zmu!B^&@;fTzI)hZS|v3FdAn<^VunEn)hMnXP?&x5N^4n1G*NXI8QP>_(4}S2^)6AJ z&^V}Y^{ODsk+EVr{Hzo@r_EYA(Lk6@ym~+ZJLuUYQoix+W`!h^yqq;*BXSDkRj?Lic%+?fb>s@T-=_b*q?$oHJ5fk=_>wK4)hrO(7~X?He-*kVe!d3ILKN}m80ekM6 zn$ZFA9n$;CC~SLz$jVaDXyANWTp)3O)R6bQPms*Mwz$Va58#M{oyo|mgS8d{fe;@b zfA8MCDk6Sm`Z`1?_#0ZnfUg1?v@Fn@Wnycz(Prf^iu?j_Wg)XfowrFKeim!bn>__}DUc zdIU-A#`Kv?3Isp6PJfBz67f%mTuuq!4_r%LU1#3->gw4B+hDr<^kdS5@S72Bqe}8g zJgBfQ*PAU`?yy3Mrph3hYO0waJa1y}a$53nOhXH;uA`=q9=-KBxFwXIG)u2V-iH+jGUH zBNACv0RfoJ=XLp^5wCWZ7xyPeqr(+DJl5a7sbMi;dA(0d@AZM8V!k>iBVy_lm&@wn z{9wmhZ1EI6E$-**fL+r7!-t48W!%9Qh0PLktiI+Q*=JJxnZiiZz6J5x$Bzh7POaA` zDX>BX$=4n4cjP8YR+e?Cy|&bXo9ylu(;Y@d8;9G(ne9wiH%|!dPE~?JA5$X!^ocRg z)jo#kf>>|8?YH$?K}AK%W8q!mS3AKEc@_=rAB0+jvxnnM1LqeON{& zJb6+y;=9O|;veeV}`U3cZ=nyu(KmESwVEjBhb zO5A3fA7`OZk<1dpj~d?ql)7V*R$W~k!=p>sL?f59aBI^o({+#%BQDEF4279af!)seBG+ALHhjyyB#L=@jl2(2gVm83=<_;0X|HdX3T?BBdvfe%QE%^4US zv}9piAgq_ki;8iV(Fj{pJ)4s z{*AhQE#_AlT6HDVbhMuMXxMAEfAqjy(IR?vDt<6==)jiU3?lsHW_ zt!~6;KRY@!Wj6ccsg%=u31!Mpo)2CzUmTnIDjB6Sf)qBYB#d4sMym1admHa$UuQFH zE-TwX@b)ae=C~R{7sXyKl_X2yS6RkUE9})={P$_Ik*#TBsTkfUdKGEYnmI|q#f+kp z{qk6MMAS)dhpuDddwux9k)ji9{sBhZD8n#SGO{rw!}jhbLjuPM1xmJAB7S!d$%ZaOlykHkiBw z0+Cug@SwQMI#I+aImfG7sL#j^CKgOS!!=WNb++L5;vQK6v`^>etZ@NU!du%CjkQeu zYV!t_sMA;F`b~JwQDF?)k_Wif!apn{fBzYiiF3vAyVGkTm{wHcI}kH^xhk z_x$4fw2XGn*Gf+1lYJi^4fH~=x%5Z{=7x+k{%0xoD%KC}|<1JXeY}?y9S+tEfbjQgTY>-10nDx9}_T zIh51>a-MBwywGwk%_+{w3NMXdl_+XG1AwsIfWTlcJ8^S6qluH%Gb3<)DDM_2_K+#*JYvz&9| zV?t4=$r%Qj-DD%t*8V5A*#qncOmKPVx% zw!7}`?irufI*8@#2;{iS{?<}nUS4i)?qoxxX@PZD=chXWcZnMr2#*m1Y)H{|;087N9L_+ zr`PAsiJV{y^dMTxV}^ZL%YL`^HLN}nQkU3@m2@S}wI&(_w1f~}`j<1~aBb+Sg!Oua z8|Ujye617ux+w0QcR@@3af#^w;;7iY$)z5ph*Xpfv(Qirf0EmRld0*)Dx4;*s-JhQ zNZ~%#1H-p_Z*%SOQh2gMmoTJh6NKNKRhipThAG1DYi?m$0zY}c+zOgkL*g3CfkSrQ z@Q}*Q`R}R*aW)AJku>Gy^!qjS@7Qm8{1_W{IVNGz(U=c~Z^gz>kE_pZRT+%NzVs*B zd`hEN@t8Gu0a5qkwy4B)Q{0VRuD844(oBEUK0TP4nSs#yzKvn$)WXsdU$C>Y^PjCP z9qt&#G}nXeV-ytodwb0t)zK6cmn6Oaq!15gh3tBj-`}W#s@Z(r#;N1r;Gn<1AF?rU zL(790RQG%Tq%Rp6hP<(M=!&h%6+?_|4BZtdQkIWhHeHXCAmT1W)H^<3u2mbHzdf5} zXPC09{i-bCnIwm0K}T@jhbapov5ZTvmSP`M-vM?o#M|9?F;s0S>|K*sms@# z$tF{UWGU6WLPOk+Z);4lXXr*=n7JOQKR2COX=YNjxNd6XnM*KQ?9lmHT$)JSlRLS7 z@nU9^^ziW26XN2$tSjOeXPup>-P1HQRBXY~scTyt>fM|)gpSvm_;p&$r#uzP(^D(G z0XIv6{Biyr7_avglFUo5L%5L<;dq2d-?|7#Nm=GnMwuuo@Y~I+Kgt6F5;bpCEh;`x zPZL1*s3kcEx?j*`h^eZt8|{1i^F|7`NUr0(;8gx_iH_VJ@Rf+Doh6Zjgd~L5B0bNsh#PPO?@UVQcSwm73OU9dbGa?brkmn4 zLg*l_dYr0{98JKxLF&%ii1mfVt&Ig(TMH#bB5E zNJW<@gaqQwpAFWOBrSta+s>M-3f=ead2bb=SJa621Lu-otdtjjTsA^IQyia|`ZoK} zOiopMo3&N+li%#K4UE^T3LCSjl6qkv9TyC0M}=8ino#p8$gde4J;!&RD<|EUv-hzO zsY_R3;tdwncDCW-;mP{Ito2gi<`bI%ZP~Dk(NC?EsR$z}b8pY=(u6&xz7oe5R-i)k zR~tzt4;dDj0fM6=R>Xs`LO@8V%)7la2d75GW~s_@%WaKrmv-OYXA-r-$q<`(RqzN| zkO{`cF1a2zKV9^F4>>d1&_s;lP;zy3h4K$IF){JtEZ5%dE)-N*Sy?+fJ0CuLNN}pu zul$B=;VUYK(hOZi*n{jV?LOBD%tyd#9RqM$Ji58>7FV~nJiWX+nwz1R)4F^2Za@J3 z)16SbWiTBUi`7A45@xpo_U1F7j#HCd4Ngt9c=&MaSDF$K9hFehQF{{5nVHvlc~71^ z$#m<>dYY0#6sO9E0{HaZyLV$_54pH9fY4eVDp~J964n8>3o)vws=A&s&z6dY~3P5T)DMKVo)Tw%~&DHp0`|{hgP5 zC`Z+|F8jQC=3hEGMTK5^zge%8S~xYmuHY!VH{{$Wk`#*)d2c`6C*>l_{iCJZI0t)C zv3WB|(KAb*ay7Ilzp6i=7potm+izWFR%4$x$ROcdyjDHl?3j*2S0AG;7DYvMqs~U^ zgd8uMKA&h*}g2Bh%c7rJ#++p6b<(3`^~FOXH7*4^$o7EClLy z?Tqk3{Fqq-l%2tE38RC@ng&&at-*LXnM~eQG?Eb)?*9 zor2Z>vKkQeMZ=!BsitU$7uz3%?1UW#H=wX<(tLjM;#%wNjsyG(GBPsK$==={_SUES(Zqs+g6Wnx zrKke$)rrEw!hrp)0hX1bM@?rcOj6#ytx>%dgzdp#dyGHk7mPe?mQ?Q(y3|o{sa9Zd zin#YmF8p3%b5#K<$>&%^#V{x2gf=JRqVbM~s7P&!JqZDQy0sOxj*Sj98Xij8mIs5Z zUXH9T?lTI1LQ*65OZoMiY4*K6Y?0YX8|@VHfMIjviC68o#J--Rh*nhO`H*|SNC;*; z3!@mq@0q$hO<(ro%hpyUt4|rgjpE{B;9r3fPVE9lw~@9Yzi)8RKtn?V=)m#u)2B~& zd4%o5c?Spn&uVS}zU3V!GgiTK`2)~D5pc5HvXeezH{0J zGe}R%qQRYXPLV=EgYwgxFFXZ(bxu{|ijFoJ)I0dmO}A%#xlGH`nU3}gJHE(y`4swt zv7q%i4(^zZIeauQPJFRrJ_-!53|CqS8({_2vTok7?$MVDJuTRF(IraM}og~?E^ zj&9HH?VAi%W3`2HcvwX5uhE|TI>jpWN$dX90iK;`T4LXmo%zYQK{!wEXU$ZUTBYPY zlGgLk>6hpz7c1jp#=H#yjk3XakG{6@+|PE5LV}Jq=TY41SFbiUx7^Rj3JMk` ziEAdn3@-m3Uk_5)1#c~gRYoDeFc&S0Do8*rV8n3_-Xb0UM|vDt6XO5TMKT@tmzI_a zjvharqhqM4X>lu_@zSLTxm0$A0?<^+f}Ze6Nwp0OOv}z!vMWSI12dPOk4B?k#{Q6* zn3{ToF#s^t^a#W$9G#qs)Smtxy>*3=^d1>GlP=*&%LZY8r5j=r2%iQ;j?7JnYJQS- z{jho=CGN!h)}ho|O(y?O&Gnzp_Ww+%{lB@WT3hMfS+a_SpD)h3xVQio0vW{?Q~L4a zLx3`C*^uO*fV9I5!h(I=5({b&P8!?BOX^0kmeDSSZx?W2CH}i>kWqn~820Ehb}C=3 zH*~7t=n!`vtA-3Lz5hFZVPOHNQNQ_LX`mG#M5#&F-zPi?{8Cb~8^U0smv7ky*-pf9 zXa|kNFG5~^YjrWA{?sMmRtUQ*BNfD*2E^9#upP!=)PKEg+3Ik)G$A^xn8e-ZfXSkj z+IYjRARBh#ET?jkxOTD!NDnzW`+sU_QCi$=!E&i3z~F8#;b7L3#ot@^!&&{y3lL2b z#$lNE;-aF?n3|e8IXU6pK{#p>P*hS{8l_^pa^(tyDO+3H()fG-KA9m+0~9nfMBSrX zO#(3QLg=>Kru6|s4!3?*ATOa@999X!0CwIzTFK+ zOA9JNUy!7QgWUfS882j~1Jwsm)gWi{oo|WdGgM8H<{KpU;JjD z3TJnLO?Pg|%m;cNoz_IL^(Pc8bAjhc*Bi0WtB`G+iEcVaPrnH;$kNiXw6qj7!L1iu z$(`1MYeN8e0ynX`8EPrLwwSkX-_|}qm2Zq&u{^w-)ZhCa9-FT&-X{DBMP%#c$x^DD zn6R+2R+-fvH#~wdU_MJax;OKOTMxYm(BrBLg4UhTmB4-6Xy8Vd{NI27ZDV5tZPzQb zzsN`OB8)945}<-n&9Jkzb%on+Z^yB^_Fe*;glfk6=B9S0=;#xk+4+nVlTvpZOUrbu zt#Og#@E3*XxoXmaB3-_?u;95gi0$p2nVd}RTf_|x&UB=(($ixvwS3p(#T7ajmU-qB z6^V?B=YHu*m5*eR`rt_#5a$?9;{Nsu$YT}b&o60l$6#&SnwwunM1W`yD`h2|ID@}aBbJdvD*w4yTI^AL`1l*p}Xf56%~W&AQ=MhTBt@(Pp_@3%X97}FiMDVxQ6w$HCsD7A;z{pQ*G^Jf1U*g2Zs(yJNdiow4zu_M|(SAqgxFK1h5kn*$-$ucyJkM zyS+RN6a%!%AZH5{^;lAMI$Y+(D(ZY0^!Bv0dz7c)L4hG*W@ZNDFo)q%?86(4tfN&( zq?y^LnulQ#5dcVM7D)k|%;?nKcO3DQ$PnG7OZyL^o;a>K3`rYxwN-nIesijF?WJX6x(i>T+4B*j9|vGB!?% zk1w%oV~miq5C++0#0Z)`>dwr>L=sxqg5%=);%{3`^^uwY%WFF}C@2V)K#GJHu-OG) zzgk0>XliQm8gh}>)`oVFH?((?UkGb z3{^scAqYo`ip|De!}N4?mxIH?!*9nc7g9Mys(E>N#mQ5>o1UFjiV}36dr)alx_>n` zv*Y*yH6^99m)B^Smy?|xN-_;NVG!pfB_%~g_0lw$5rIaOW_fxvOrkJHFDpcupuG<1 zkn^(O$brb+boH3$85R~3U0q!m7gF&IDKY@x?Wm!Qp!P4XJq#p;^kCs~Q` zT_W+y03M<_RT!oHi|NcEt3a9F)Yy1~F@ihB7HCRWofXhfY`Qs}oL08Bwjj}f%>V7% z4cvR=Edc>EFe^Z-wBT@tmHC&&#XmS5CRJx_6u7GPomZjyf(2Jt2otFbtULr0JOJqr z9~c#)j*^pWXRUKxSLTd{^}!u;wTvGet~Cs-@H4f~C=uZ|VXu0y z>5zIqY6=*E@$oK?Sb#qV_Yr>FG2HCzKeu`b9$ftV{P5Aiz^9^ZTap`!Ooo{8`9x4l z?c+nA6@nftV28*5-goscocBLqf&X_G{nH-y-)vzr$aLroPqi)P7iBfsRfRJyWS42^ zD99$D-9alf>sv(f3-?2JlbP0DkdwItiIUM#K<0mvvgPGlqt3dM$vVjaZn!3Zf@*9u zWIvBgkjXGXS93=WOfB<3J)oZ5fAYkoJdR!A6UehKlbPoK+kFz+ECHqIplr@Q47wSw zY?-g0sb%u}jT#eK82Qg5L86_=8-s|>@ZG99bNHB>-fAG|$o({pcx6u9J;}pwgRgx; zcm?1+roeP$7xGQhqtAZ_JyDb%5U50+75I-1IeR1>{iesu%*-r!?8XBy7H&+vWc|nI zK`1dA7$I`RdXXOQn!XGfdDMV?A!}tgsmEcYu1~QRE29yH0AH45^ju z#Oi7_g3lXT=a`kr|9qD$@~GfZc=Pvvym>fN{s+h2Kka>FO-QG&IXMoFj)f`af{b9s z3H)hxrp?j~WFjX?JxpQY7GS1>BZnF6KB0^x8G#{dP%SenYp~D(7x^A_DWcv1R|sc% zYNmht_U+TB@u(~n1%-i94?9Okex%Gl<||P;T?gePp9>-=TG1t2&tceDuuCDI3 zWh)3s-2XT+h@ggb>T9(YoSB{$5)lDa^zOZTVxppr-5%n`3JNdi0Au}VXpkqxp0)LL zFm6EFGcq>5bLY;|qK$(CMvs>&$0+%`}#yK zU;fCosTB3-#sx-3Ms6OSXHTDI>)3(l;o*Ui{X}`<#0e^@?|h|SzCbG>rrx`Iw_jDp zJ%^Rlmmm|Y8u|cwonu&J`S>xWpa85OK+qT`CnqK0Atv|s_37&9fQXv*@uQ4tJ6I0_ z#;Tt{zK)GOzwfKe*}Xhm1|^Y^C6R#l@$vDkkQNpezJ6VSQe9gcJ2GNoXvnjDu%dt$ zPs)V`Lq^KYRi(^>lndl>eZ3rj)zt>p7S_|UKN}h#la&E5hLi$wtk8v%450<5c3-xBYL_)g*E4Mi)KpaP*h(}1*|c9Ue15*Z2E4KGNWOo+1;HFJ z01CVC@+B2y;|JHc509bY;e-z#piNVe02R~sTTLm=S_*_1BkIiOee1_H7IibVwuju* zfQHx9)HE?kR?Ub;@%^{toCTh+WdI{aVwuOx2U6-xw@>gm9HFDIrVbKy*0y(KHQim= z&_du0BjoaZHy2u4TazTbsMzWwSrHC{g3oFm3WIwj+F=^z7r?c-WW) zs}`G*BB1cAZo#UmR!vDqr?)*x0-UjB{=2RQ3$GhPe{&!TXf6NWi&=WGY zwhQ3aBY}SorP$7Imudd^L8RVDuKc})uK3?4l%nBJ4XiL(85bC5QC>t_}Wm~6e+(#I`f}l zr}g#qFNrvAY;C~`J4H=>N!-jK>*E8MIbJy(YARz} zJmhB3(!i#%wzUN-i3G^o+Ti-|moGm^crC-DDnGR49;ao2N-swv)7JujgiK267h?a# zjrb7lurfU>FGJDOv=6Uz$wl!T)U^6WANCp12ie98vuL*6jb;XcKWKMlvD^EK>3KUu+DjR zKp-GUuF(^y!AxE(EALz4HEL%{#rD(qcCZH9^J8HOwbsef8{wu06Tni$Ifao!!U`iN zzr7n*Qe50PIqvcK!8I`Hy4U!d1Zs^$-pkPeQ>d{g{@*AE6CO?~cb(7(vavI-H)JVM zbm)NV>})zlO1KR??dh18n&X#gV7?h0p&}d)3gi(I+I%gsOi4{med0t37!~rre}|~u zi#8?pRS~e2dM+-j(Q49SNWuY3DE)j({9a#`w9CZZzCmBT>`>~ZFPWM1+hC%CS`UC8 zP)87(qGMz0MZyT)Ul1uS+#&TzqMfW0G8!7c_BI#6)pvH+!P9dK{`OiLOzT@jLecq} zQHcBU<%hq%C_uqcXpjSQ3N*Vx5ZuX+9))m0{GH$7$tb2r`S#5lXCi*PABngVz^9&T=abQG=hj?5=fq_1kA zVp?kIWV%X1Fdange0p5IRU`yIqt-3iOs&A3@z5>I<~KdH^QAuPmM|Z@h9B>1LDu*u%J<1kDPcfuZ{K2OO8bKQQMe8jbdZwo^rGz#xQ))TPXpFmYotpsws^8TIRs+Wtm74G0RpWbHL^Dq1bqs6>MjR;?Mzs`KOD zkOHMlgZ=m5h4q zStZoPAU`6w`S=3Ho>1_fZ#kps~!~1@Z)x#o{mKQbyKX;L}(xTjA&7Ns#jMR8@U#f=1`&1}>M(o};SMRFkTs z9GUZ)-C=$=5krr7vA>r)Bl-uH8r0!1Ab`JgbahAFyH#YvGLn54=DJeLJePifqbF%O zLS%$a7JRj-<>f{MFotzKKXoaX4qd+X(gcn0T28EaxHAjp)(6){0=A8DSH1m;Bcz7D zGMIRe!z_l{wyC|ncX1OS2gH#)jm!gBGXWcO>RYlfEkDk>JY4L}fqHD9#LWt_A+Rst zjRgu;2NeKSxo>6ICCB2wDNi#na3P*3QNE3d!S?j92j56&1JqH-w_N>%TsG{IQ~6uw zc+u(@WDpNAz-O9NFUf{s?D_`PB>lFsB{^f4zLXu@_JUOg>uXENwA4KlACt>+esNq@ zB59robVUf=Rk>Hzc6Ry1k3*4;(Az}2`XI>$YaG<>)LzjN}b|FXm>Maj!K&1biK!xF`+QvpDuZbO@$14ctu&hzDzo zjo=5U=Tey80_>hsV0z=_&-N2Yw%-|5pb#GJH>j8mhq`omd093L0CBS$u#GA{xpWgE zRl5qoIm zfst4?3@lgWvvt0eovMZQ16NGnMwcBMl-%smjADmsyF5Mp`O3M(NiG_rUo-OZ@{t#A z{@Cgj_Fc{?8nLZf0~>OTfJLL`c>xpunbwOqP8?9*hIz?O7PKOY?W~|a+s9oDp@T4k z%E`e)@@TKT&jcx5k_H%<{^YM5(X8!{aSeXoDKQ3oqpauPF?OgZE5Ac8QM1`w9AW3= zY!*Yx$dF0gav58w*(sH&($v=<7#v)fpSSV5oYn@T2juPTT@2QikDL39EO;4nH1nFG zxs;-`!2LZm^m}HeH`-hUypj6Wy1To(Yt4XOy*>c*pbL;nPtUqcf?@2w8q?ehU`%jT zF=!F)b5y%lz8UkvuonT2HM;xBDQr+WHqCXQv`B*1-) zLRUfzw!1`G4L7K*y;C_^Gl7800kHBw7pFtUVrCA2+|^P5-8hksK=ld0&5ZkG0;_s2f(?LmJ{sR$k#mckFY(QGQ7BZ1@MDl;K2@{@mH@- z?p_aqyPuH-WWJr>AsrqWnR4r~qN=K@j7)X#iL>@G`B%>ZPjF=8Nn_)Ew!8ZJ@dNhD zJ;XL)0}KfP4|jB+VS)hhr#T9XC><=dA+o2>pNDePPeWOHnDC5x$v2Tj*j^7PxE~?Z zKY58p$|~yWM}j4PnlA>z**Nd(;FX)d;GSN=cGC5J=A=3 zLjtxpK_e?F0)Two>V_x<2z@D|kz$2N*se;leS>y^0<>{<#ZM!9mU`(CU<%nV)s!=` zz*Gr7Y9zGb5mLWN$1eR*fnCk2MTs(z~j(`k7D&1ji$jgW3RKL5f zImHzXG~H>k7vw*mu->HE@IUQ}BtTQ)`2+=#mB`CHdNc)vA(V2{q163*2mc!7Gas3)hOac`Cbi zPfti!O9`B6d`mKCF;wl`11}!zf~t?WCSN(4f zYZkC5P(B=QTo85|Nd$iJ+_?>K0Q_;7cqv}|MAR8u{{%L}5LS@HeOM{mKd1#xvPzeK z`SJ+j7Zed2tE-u4G!e>{hKNfH7cUOfI{a>u18Wjkdbc5);XrW-1b_)`*G|*m0P10| zK!I{~>>2HM`TsQ_?-Zve>cWd0aGZpyjw*YIOr#;LGmnP~C^R%wl9Yy>>1`;Ezl3Q4 zgj#w!CzR{A;(5=9VIUk>r00$}F-slj=k#c=Xmp)^!0qMs#F7#&WcW17FrKY)~<=F9W z;8m@!uLtHe_hzVQXRYWrmq|IcpX9WXm#Z)y4H$u|ky7be+!6tv;H{7igIz_)C?D#yu%=6KP2~+W{t>3!~o#VA@RT(M!#u>;$eGNZ9 z2`iZ!H=s(w7%YLz0i=f(H$BxoQ;+WN%qzg$n4nofjhe=Sy8Rk7AlUrY)s^?K<|(AR zs`YjWm|l$#FSbdzT!0_AD5#JT2;^MnC$McszJ9%2x&*@n^Tq?v6xj2@U}7!!0k{>Y zT~>pPj82)!zZT<;${XzTPV4Fj5}ZR`qfAnxo-VPg=v{zBGGNQ zLZGmF=n_K;Vp_fk;H~cxrVr|P7N4oN7fGbt&01kRWDnUeFl6lih++p7!Gp1+2*t|l zsX)EN%}Y9d!`9l#MW_J-u+~xRZ7GPVZnqM41%ndT>BmkMYQo%{q~kflfQ}+pnQ1QV z-nXk3)mK47P5trX2WbOMTH18Q_DFtGD`6(V32K<}v9WLN^S_SdKLDl`^~nrMfp>Sx_DtbDx04x~G_ts? z#5Bk8Q=E>XUp|QEJVov+7SDaIU5X|oQkf7!2dgx2Yrtxn%{iD31O|w!B+m~lUq7d| z{YRh35PJ|aF67btJhF8fXw=KQD*{bb*MEZxf;@!oK0fB)b95O5RaeUhh0HF~R(nfnSGW^RdCx(=i6e6Iq_sX1VY8}8) z!gHw$XMn=rgP8f_hb${AE0BCj#))0lLqHnCVjr8C+0rD=@RyxFya_u|w;lFvgX}H> zm;yGM^28|S5BP&ua|u^8>bscp>C>lS|346QVX>6ID%Z`?8TG_Dzkds(pMgy`0!VbVQsXhn~Jgj0e>HbX&D`s?` zfNk}5W@ctU>Zz;0t$BEjm$!iLGh}aYQS%r)eW?F0F8~iJGqXuJ4}-y)=jr9>l&ryY zKHPO`6kd0QVJ=-ZzRvtDg0vw6yaGO-RWp+9Jy8@H#g)^BESN zJ9n;;10GxvU^@uxu-R9ja_5S$u#RevK(R3cs=-5&jz2wi z*uQV{`Hn;n4-a6~jQz&^e0;9QGy|gyt}p69=fMISz_2fXYkm0=CY(5MV^Z5glS@ku z!U+h%jzx)bKtO?IwJWdH3$G zW5=4dD`0;zY>R2^2KL3@-=CD2P6JnnwJoDrV+EbNisYbZHp>2yTmS0fRf@L%ffNVJ zN$AOopS!H5vLF@?;|qvVgd^AHQ z(BZ}fCMKreh~>Gt{#vTC@AS7XoCFoS*8_UM$T-;Bo8Gs$lyd!{>C&;HMe|L_X~2hA zSy}@4(4@QxcpeIbrd0NRgYWxCixppQF!*)F_>VN(VqAM!|EU9lKDN7J4Am5*b6^+% z_kT}GPEKA1qzdNyQB^0fWt8bmjE@8R^4JeIG{jp4x-qya3+lc@*h$lvpO{#C_H5iZ z`$u?)5?gRWLIT#dVmj7v${%Mf*b95q508P!slNVx?;2lwoJXGThHnM@;zF@AOiF67 zVSoi>dSwOSzvH~mSSk>NTDajvMD|5($$Ke8O_?*TK}PzgjCTaxQHqxk6@{&$v!K9$ zrNC9yQIHYVnrf1`kk_&iT!7keCD`A%0B>N0s!uVCYh`pd-C4eL@_Aw+laC|F|Gy>_MTj<%2jwvL3UD$X!E{(%4HGv>)eClM z0YwPqBH&pPl_qRwZ=ao&1tKSaxxU4K!8WrZoWy%G3J{7w4gf+ISdQ86kPb*V@cem_ zA!Itv6iB92=JMRFnbd2F5XwLqd+^}F-MhiMT;RMZ92h$admG@_6hOI#oDGUus)24m z7X3XJ@g&c(VOvc7&!34EZ%6ReZjpo{iL6_+eKYieQ}n`=Y{xh9PlgpK7mNB2eWe2s zjHlgxvl1x7c{C}4IbLk-Wlr*?(_&qBi>BQ$ z9?@is;1nbs1#Jk7Pc0&~>jEpQK=9YBtUaLi&}GY|;1GuR51SZ4HyRn{1)bKkDlo|) z9?}hsW&e@rCU&Q;Sq|7n0WOq-?P>BsKjWn~c@(ygg>NXS(}aA(d%1R9GIAP+shX)! zb8AL@kTjg37lt0#4;h@NA4d|G6yl7|U8&+|WHWWnV+~@uOhM)v#6;#Rj`yk90vS{@ zU3map-GdlApgq7!1q^r@_T-a%@z%1)Ma^?{)2;!<8~x3jbND(1_$P~4qWk$KTJ>1G}OW(OJF1`n3@ECchs*R00&Evjv zF?wpfh29YxAWY|(s2jcf%>8n`n$6BL@R>lqaY?0~cAZpJL|jH}F=8R{H^qP4yvVcO z$qrd>1w>zj+*MZ59sXH8{4#5n0h1zyL!fwunoCS<(T?aZC6w?vS|u75gt*r0RjUJX zTIj6|4cVMdf%}n8DfvBzGVZ}k^TTDk+S;eHCNzvLhAxPgg?UN0!<<;QJ6eLK|LY%D zeB7aM?!^>bD?@&@MZPCCAsw-c+}s$$OG!z_iXIVC0j4=E(@~QF3E`ys!?->;AQ$C< zKDzt6r~D~)Kr5+dsV2^xk`M{-8k%;^OJKcB(Rk$W3A{v-%^Ik`z)YH%yxx@kDyx33 zPTO!rvh7%maI+LXi((H>okIpOeIy=%Q>~I_G5!)?vScamWoAmWN#U_vOUrMQ9pP@J z%y6P-&;+Z@_FDIJKo3z|YDKVE;Ln|ZEY3IN?$>2jX~%|6EKT>lxM_N=;_0c3qL1I` zQvJSImBaO}&~9kc1Z#Ckw}^0OLoDN`)3SdVs;=jjyWAwej|2!Z?!lCqbAz^YklHfa z)A@u=?%4`1ol|f}P-Lp5VblD}f8I!j;RYjSQck`=qR>uIw(+#VRY69!yDQ&ftH}ba z;?A6(5Vw-ST_GzIVQ; zW^~WHyXqM5+5B_Kk>(pB$W3}=)ZLh70X zg$TLmCP9zS`KBo&DB)gR9g(8l?2+;G#f;;p7SQ0fkxT6Ym(`|kNkYn7mj9d>!`AQP zwkNx6F|a~vMjqV)=SkMWM?zu4@MQbnJBptr%A6`7 zj8;`uH9h*Zxas;W&TsGH+7Z%1|5wYH8~oUUP0(>yD@bvi@#OOw^4}-&RPS|_8WChE zlT=Yy?!ds~h$^yfWaIF`8WTd!_dlPHdpDaY+9hc`GTQ&rC-*P9+Sv6CNgoVLlB(5>3n1+zs%Df^zM+$4Y2<3~ zD|u$RJQ?>)YIf(y6xlz*YUg@55x}7D5m9u5-#zuA+Cruu+S(o^5HcDqh(pLI1a+~~ zZ0-GjKg#M5jf3uEGPD;~hM$$f@WO<7A9r?`Z@P1N6Zv0zlSnaH;eXldPYW_Di<|b( z`V`CX#hL#cE@u;(UDq#hcPg2>3|Hp2nZ_>8|Cf&m5!=iM>+iacz&va`LUxWw*50Si z3N42bC*w0d1p6>S4_{$k9KOOyqyrY);VVRo!&g{Tqyv`e;p_kJMGTzx)JYql!SS=U z2FY>yuC}(4qGF<|)wYpU*|3R@PQ4$*&5YH%aV^>!8WXN1-lInF=xixPL7Ty;LxzJ zxauTTy?unt>RB>BS?MQmOM_zx4?oO-op;a5O3%n3*%!!k zZ+kLj5YwNKrbneKmHUAN6nW$S$J?97^_aK+-zAhmni<4sk)0?)`!*RFgR)g5WsoeX zv}&(Q#=c8P%h;k+5|UQCNQWqv7-Hvk+d8fgD)$c4J7s%PXGOO0Qn^}zYqKFJ}slY)#2U!ebj%u zPu~bT9u}77-tZnE30sX@7KnL7ZX#Z?Q>RXS`t%9Jo8s`$(q()5I zR`NpRq##dfLc)5i*RAtNRT8P#QfGx={l#zeV>T7<9MmaoWF*Dy$9weMXZYjLq1Tb1 z7ZzHy>MT3+R2$SE^HSizRPbR~tC=%qOkQ!_HuwAj$G7m60)O@P?k=<%X?6EUY~Q}! z@v`yUxyw^LT+Gc+!YwH&T`#Dn8-U6+*a_0xf!Q+V;>A|*+@X_~t-5AT#lK|nVm7K% zHsJYcC0?TgK6|`9G3>$b}wKfrN=lvds(RzvdTk0KgRo;^E!`0!iZ zbHmg792eupojrTWB2i`Z=rb`fHS~cFX-Xn`R%UX|dhqs-9?fTM=j;;r6>6^Hi>)Ixl0N3ehRLi)p3v}c^nr)XmkT#Zz&2 zJ;JMQB4fCgo1MHd3zqqumU?@5p3?AXtAD9l$L_Cm4seq#aJ|Sc6MyEALSF8pEK^k}G_U!;E%@8|aIoglPRXRl_ zYeXzAYou#Q9uzZ=e%au{5|Pz#3^*X_aNhZWQ`eB$bLY+5v|&Sjs&_ji#Z_bqZXy>W zZ?1VD>7J2GIgZ-kVI_+?GSc<)FABf7y1Jr@#&)uM&z_(za{OR#Acv=$&ZSHmR zSoS)_2@?*Ax9^kJ^8u>m`W?+)*A5qNz3pXCYn*vBX)HCqC$raa166}NY^b!W&QaZN z)g3!^>eP`V)1wyH7^gyy9k!Zdo>Lgq)VrrHdR_RZNce-H-O4GA&q|IbBV@5TIU!Z|L1Nb0ofW}?DVQ26uW#fvEsjeOYi=kM#N z<~@JTk)=`doq~Dodpa?3aZgJdfhXtTlwaYi@ZtH%_SRMb?3DQahab>;y$h|eT=<}> z-?uXI#Jz4LBQ>4&_4NuWlMxeaM}|%4%_g2WgPrUq(EDBJUE1!*cU>k8uGwz$>$Y8N zs9rpUmS{Z$+!iN~6|1FK_^<{$X@b~}1Q!s{fuLW=So?A0e19jRX=2P`EyH}f65D<9 zAdzY2*(2?0CxTX~25&A&^)`@T^J6z8rt9IF3iBh_#@2nio+)xs|qd zcBvmDqu3>177w&XS_y4^)i(bM$H(J!@u0HUhZ6Etw6(q1O(l1%A%{{}#AF2tLVTPK z2M&Ae$ z3Q7RS0%t^^!$D2r&5`~GLeeahf}XCQ5uEL4b)fyX_=?cvUfXjkuNG9BM$H^I?tSCB z#rU%*d~xcBeZ1!n_q;gkVAMHS`(9Z9>I)zZ}aaeq)l6(>+6v-d)K zMuExF&~UK3WR-URhry%dV~ajt%d?8G>dc)o{c(hW8ifjXy2kfdP*D4qyZBv9Osw3? zhqy}|N%r_{*|Jx_L6n2<(Js;y*A^ty5)3Z)`Tvz7NANEi7Vp6Q2uOj5GBb0S8C zUAuP4CIBF_1cbfly}y{a#}qXb4o8hYEnB&=sH3%kaLi_&X?DPsNv_~5=a&4ndeyhx zyzwOgf~izKfluEY(>#s4E;!85SHQIC3VKABD)f-b^Egie2s&Wj0F|A1ph0=!M0a)` z@cd>@U!Shl3Q**9KT*w){UQ6)hkrkOxCU50creOmel~mmzMp39wiBgE>q7@Mw6xch zuDn><`%aIXiJ-pe)XxmxBo=0tlCuqQL}uS@tOEbzZeD?ZAtYgSJ7w`^y|C0$emWBh}pN*A^S}6u=%vb;<$9 z*-{!Sb3=^AtCii^9?r%`cwK6$?wB!SR8_A>JznkLlxP8mQ2Tq!yG4UB>r9?BY4=i5 z4Tl=1@Hywd;|+#?(Q{JFc5}86p?U~9c`_1{+PVgwHOfPjr$u!kJ}d!`mKs@V)8iVa)aS@U!qU?8?4|T24^9%%NFULg(*Miyr6wy= zJetl0S!%9(G)x1nq6L(5ZE}MVfIHgi$VikKR75W}UgMF+R%fvD@A4KSP|udjJ%XOt zE(Hh)Xy%qE9dLJPku?-&~dq{<8x6-QvA zdi9Ft>u-&=&-L6SaZqgKVX+X|l0ovig&43})yI#YbLOd_!a5QYv!7 z*xG$D?e4gPWAMGRX8G2Vb)K<-5bJjFB^F9~Lfz0>9@NmkuMv;oEb$YwBu}Y`3mrl5 ztyRbMe0@*|HINMG7Vf-xMYJe@!KIC_yT1(>qbsIg*&7E=v&PyIy~&da>HVHijGJ5nRhwntwaCmE^ zG@G7JIgzwUtaaq1Nx!9j-d~VpRvhFb_R!zyl$hn)cki}KnMbQmo_vX(+VQluLBuw* zqWq~F><5ZEhWB?$Z>jS}T1-aB1sLI+9Ag4%4;+9-g&-z#Pj<$7sS)Z=7TG=Nqwn_P z@Zl2!F2uyZXu29N9bBGktS8GE0Ygg#O%yT_15mKa#`hbD5-N4p%zgH(a0yyC&d}#i zo=6QF_966x53%k#5%gj`J;y0lblJNO9*s`wFTq@j68?Jw3)g&WFbmhP>v+e(PdJ`A%+u5LRUH6s)a`rs?Q1xDHi5qg-tXwdUAVyEHVcEul*yAj z;Glh5l1lE~y(^(W<4~05nAG&~#XOT26slPxKEJMR2G8mtNER<$T4rauVnvmm)siJ= zA|ngW2>)i44&8Hlg2B$05mTU9PQmcHS(ILtEssWKY)`HsKCC#+V!%^=EsHJ zzbnX_8Ftx~TDn`c4IC-{c#EX-Z=SI?<9~PTS<#P6OjJ?!|zdQWlW?hLEsn*S%ad0y#`IP)a{MeYVzaf=qgT}n6WON zw!v89jk6|;M4RjrN!KF@YTW|-K`}!$0Aa#I31@f$Nv=I+>rLZ&7HaLM%&jx4Zj^{P zhR<^hU-H`X-@BHHr1R9&EUj&1;uC*O4ZGt`9ZqgR94B$BucWh|RhNIw1s-PD)!!B@ z=mbB&!tk-G3eUYw_Q`Wk*Yn7+*c$0U(H&yvg>;;Oh6(Z>>kMjo3^0!BGJ+fl1V#ui z+FbAwkHOSoXxQZxScP{fRRm!ewKwFJdxpG1K)!7%iaI3J%|&h{`L?Q*Lv;0$ra5a> zu4!~HI%8N(rV)n>-uCDV7jXL@EF1_!l?EHD7@Pde>E9oFLu?6e@NUJxGJUgh{RrpJ zky=CeTLpbd=O~#EtGo_iZ~kc?XAcN$L@v3|GI*6kWrUBezr?~~zH+#YL!t$EsI4lE zZH8>hN|z`-cPsgb+zkl@VCC9!wKLRZ&>VahoIMnyiJV-Gj|NG?;o3m#TjL&RS2tUwAxi=Jmw4q&$HXR`?9g$bHT?!&qHF!a+-~~h%fibxXj@YfpCFs;< zdI%1ojJ3P>??1NVmCL23j_p#rPg5s9bU8L&@p{C&zzt6CLskw~u5KDpAZfIMFMcG` zQsn*6->6*Q=2~kcaI@BkaW?h?e+CP&aaFAP)FP_t;G7_%u7)FIO~1BEt?JhUtvowj z+Fs;N==!;?q-am~^qAA9SJP?=2PBUp(+McF)vYs+l4TIDqMy+DEa&{3<9;%$qC_Wj z<@{Qg^LuxCpD`3CIRDtpX#BuCsN$VUiYj_p4fm5N9RfJ*7rO7Q{Ef?hm)Xucs!E)r z0x9z?zo+c{aO>4;|Nf3ywML_Lth7W_bu7P6Hx>IY%-64Z%VWJK)wupC1`f6FF|nw^0m|Ymx$P8M>_;>1KsoSI&uVm96Z6W~{RqQw(-u!82NP zl$C*kvY1Z5G8+Zt5oGItAUE;TqG9eoV8HzO^UKckKPZ;`jtW>y@>J8?FeQ=4$~`!1 zWaLRSyr)iiv6pfj($O_n=2q|?$zKrCG3Y9VTj{P z(Jc@gY?)z|ayUh1)^(iibl#%PqNQo!F$ zOfVu6t~Zyu+sCIXjfi1T%(Z|sGiS~u>9Q!%DZ(}ljL5CnJp$m1BLnA(y4RP>r&9Si z=*EOQ0$eIotz=5b?EMPp+EP8EBPfv&;F@Yz#6kcS*QA7d$D?VfxNhn&WWb-gRM<_0 z<+RmXfpyM4P#;FaKnIW6j%yx~u92?Xo$hY?Rg-Bp*c!2^M+zAvubw?~q+28$ z21tN=Sjc^L^ung{S0QF%LhXBOhn&X+I#+|8$bO^0R$UwHCz4#C$>qPXGiUW!o(b8plJc8k}A3xFv4X-`5lZ@T#J&uB8Mc z3&26!s0zj#{_+dl{dWrZ5eD|%W*RkR!UQRUzbhg(?0=L$XhgHT8&c7BMaLG7p1Auk z*!ug_u?xTXrp5D5?WG3zmh-#>-rpr z?YYR0_yEegTwrDvEyBgkpPBTW`7#Gky5VEe{_wae>0h;3iw6sMD9p}Hn1F$OX9aHt zG6&vgB*nzoA1p|WLG;k3CHi%PDBb(+U%vVWhM+W@I*dbOtoS?b4e+8FN9g}>(n_Iy z6R%88)>a5e@~?1E5-cx0=j(DR1Kh3np)`Ctwm$W}VWFgO7e8wG_U-u7+MB8pz>^gM zXc^EZAD^~nYcPaoLO=G6U_U3}Y?aIE$xYrwf)REbJ`-Xye98xe3=hG$MOY71L9MN7 zaK0^e)9bcEW={_s*EU;vATBQ6lb~=3XnggpG-d* z7)Y~7KUh%#i+t65vze^H`N+uA6CjoC5>`aNglSUxBqYWDb)Jony*PTNsw#XCR#;-5 zTU^|hnoOBEF|I(!qYbqw!2}P6!dQi^#lycea(WU5<>%*zA!C2ttU)^wh%jkG(MjN5 z0S@&gDKozwRNoyskZ1ShbyaL`Z0I=o&slm;?zFbI4;nT#ys42NK%jl>E^Z*eugjV< zPmdox>TLM(!K$e@?5q2K#h=yH)GNO7hyTSJvV{IG_{0DCo6`GOcCcJSwA~N4^!yM% zxfLDo6`r6v5p{QgUXb+{Yxn%sP0u>t*VpY!1fDUthuHK}ZY=tJ>{1gQ@E*2AfK7;s z_3J;-DUw7cb=xjMUdQ9=pMNS$m@q*@gKQBQE!Rsuhz_n2Z55TpCNC(uGc$j~x=Fc> zFnqtwgaD+dlNA-G2WPXH0bn?;=7+L7N{s?So-yO7abMxx_`U6(zj&}U&kJ;r$>75U4rL(5-DfKN#IN7-S zDR6>1eR?&`t*F@J4;Cj~QX0YM?5yw+;H+7M|NijtvCb^Dw(=6>5Ss)n7cg7M z^<*pRr=WE6!)Xo{*cLaj*^B7+T53z|szog`Xv!BD)2vRS$U#7y>LMR8tJS z0P+Lc#;}iIm)~rDd>INz$SyOF95*kblX=`xr6NXu`3d4C~GelRC}cOprG|q z`FdCWIL||;6&rSX0vHU>I;)a8$8j|eKR+AhZvFZT^qY!`=xV63NbCie>(QCr*yfIo z(RAt3-o1M_))Wb5>bEcCtI}np z_=GP1{-b>M~8kHDdf%t=mI{OQrvM*h^FNM!K5AH_4L>dI%5O-Z!3(NZ+1#?ueMM zO|x0w({uRwtep~25!m;?I4k#RCP zYf>8x`{XY-^%Xr@pT2#G<0VgPN!QysUtiLqRD;jj`1uzp1AIE$ZjB6hQq@GB&!WK! zy|?K&q}snG{>~@RP5ymeaboJ%;wHUMsMugy8sMZ%XY{k^W^RSn$*IQ@a6Ma7VKAEDQ^Xg`iXNy+x1LMCwu<&!P_SbjVW))sxSM;rT65f*E zA-S&w&y#!T>w0d=f9YJD%}zB8I@RAIPx^zqre}@PU!qAsaGm5&EggD(p)($}tCaoY z7CdRl@`)AS+m_y{dp|3^^-tP5#61cPTuI8#!uT1|16h z$72v`)tqFZ6m7a`#!r<$hVMY$rxY|VBKMlw-!G-#a_dExfBdp;#oce6HJdz|uZZd# zB-slgpnTn5Pi(l>nJ>b8bkofvTGY(5V(Q0 z>gkf8opMUC2I>}iIz~l`&gOL=UNnBE@nm2%MF!mll8HKasH}+nhz%c#>bjJr=iL3h zTSQZ*QP9m`Q@0*uIm+Xbj{E96qIJRZ?xm!4bpNPk{cKv9(8qVzXDP=nkN<;o0->t+ zDtqW3cEE~)Ncz#Q-Gw%{D}RWTN-!F1uyc@V|7}LvIp2@{y4v+~dNhsceWpLlI@4}x zH6=j!GR^2=QWNh>hI>BI(Lo*|xVDTU&L}?Q3OM%>o_P^{8$%C;fJt|&qP83EEV1~l zBz%SMJY;_!sgZ}ogb(0Y`l$4jZ+QD_YGztE8F~2Pc_Q>ThOwe(r}`u zgav{XufelIElX61G(RNn^aNQwH$n{f0JUV85(EOWt^%(nq?KAIwOBFODW0bHrEy0= zNtY@tC&7o^r&`-n>r>P2gCszTJ3%#a5TWNfjtTj<-*#90uw|WLTUhG+%3qFe=VrYQ zb}}@)&$|Hd#K}vuuLZ0}kwmUDlK5XuOn~En+^n3R&gPtffPactkg!p$7cYpC7v8_1 zsCl`^2bxTHLu557+~tThS8F&ImhmIp@`u+lg1x6`#Ff0)j|n(J8dtZ|G8)AlphQV zkrdU~$m)2^P*O6|%_=D=kyXYuYyKd13#YTePKHsO$X=%q;Q7>GC+}EOW`FZBbr4Ut zEP@sHy^k0XuT7BQm0M6h0a6^z)E=SpCMzrF3BeXI)Z5#JXq9((Te-^zBN)T`X<#%+ zLV`WKz7A>2#kUo&LZXaX_jtU>>TW7Insy<{Dd*uQj&?p3h{OsTNeu_f3crrbt|zkw z!9xEg@sz3xD7;1Sq>A-!AykZC21d!mcWSI1mXH?_qH2UQN(3eS@Aexh55cJr$>+=ghURpptK22VRDw}wML=I7@RXfK*7{&=5D zRcf10fof~X^qlUDD!rDlFXi$dfEjnoZ7mg@y@)W6i}m$n6yJpR=BWi@c(|!sh?y@g zEF{;mk*b?IgbMl-X*eJogS0>EQ{F|K6jlX(Jp6Ff%4REFUQj==%Y*C)`LHiG;?1ba zy0<~z z>w*P=-5t=fqXKS#9cmXab<0SyRrXTgfrgeX<=(ccuAi1E2)0z6fAT&ZMhc8L#?I~vtSbLQUsJD^)n4-oS4Bc5|hi8^fMo=PqE zN7x??oFAO_rS(-#SwGGQXLoWyveJ}SM43ry_k@vvRJr@S;^{0)T0&?0rKHFaa6{Dj zmqkYrB`uYxL!07hRGSxDvUhm5%b>&PULURtHTxI60^7HIfK<)&Os@^^+YM5@SRszm zH?$`|*-Y0+OEGFiV-MgcQeV;*W+0WbhCU)mjJGZU^(GK-Ke(+#v;zIe7LqcAvL>%2?i)qP|CzlD9 zjW+dsAnVp~%4eK0hgW!(Q~I2UxNDg{!d#D{3Uvtu@C^hyo{*oI%Ib#rm2_xv5ISoi z!LZ-B@jFRLG>pw{M+#?6z8HCu{Iviwmj7iI4+clF44n5! zW&q)V^9DP~>d0^S#wV_$tm3}q{y5A zVf;vq9-V+;$z3z(_z-+u^~w99t6yHbtKCqfXuphXQP88hGL1NP@{1SeM&u$T@SF)t z1Aj;71QEA~If>^^B>(zpqV&gmmCo#P^Ch<6p}(4E=#yRTr@i>`p2GOz_p6ix9%f{? zV^mM=YTMM3;X<>_aY0QGnr+9Yb(@TyT#eki=cGV@18+D;$VwZozaczresFS8J z$)jSj%C8hu*KA6;Nw{Ty1w#4`WP4d$kj4Wrw3BX35= z++Tma@##vIrlovIMQwX#*p(AT5uR}4f^?X}JlZO)iw}Xsbt}6(!s`y`w*{fvC#r9o z=~$l~FMd;1+$i|K$|>FMj6HeywwrA|GA5=Nr3AJ`&j$+a#osoM&63XPmChbbDLJxLbSGRcm zjJ9V$T#tgbd$5O4V5tM8KfkyH2E)T6HX*yH)v?5t*|1f5*&$j9M7lBk&WjqUphLCv z*zx1)RMi}ub%82Qdl%U*vpVaV%o&sQoxaN~}@@=WtG8Own zF&5e-c&3z*fG<6C=za>6>!N|a!Ro7fe{cn+;)lVHparecSZVSCG|1iP(6$z;{5Lbj z6YpqEo3^M%q50g2+Wt*Uc#~2H$crzN(j|}pm9?kko$~@_jK0fqbSH&ifRvzlE%HGk zZLpI8y7xD|(gLZjx=(o;FZGs+lln1-%AI*66Wi042#1K4ohd;cFHFo9oSc#2J!O=_ z-LXLy^`Ow-DUe{{k*WJj!Q+arSKy0=mls7C=n0^k!A_)hS!=p#Iq|~szuoQ~G6w;# z&E)M$Qj0osI9ut}kx7^c`mJkuVUWO~7#RNy5BIy3wxb*b;You|-Q6Dzgl*^z_q%3S zm`~e9nTkST_-TKIF4CyvG#u~h#9h9;u=s^x@qqg+>vWZ#@9}EGho}8udPHt3C$-Jm zwH&b@j(WZ%@wF_)>nvEE5Jj`-D5PGd{STI~1KXvjfI@#1{OPPtqy3jcbIWJ?z(kXV zHjVfXQ8A;C_K=fl2NX-T#G`E$9+=|f{3@+nmROlus8XxTzqkrF4my=qe2$A6>z*wX zz@_cAX+nGeM3e8jZ&ih?qrRMe94e@s+dd{L@yGts0x&rwT ztVUFCK^Bhv-suJU&bgtX?Xj1m4w@1nK~hn%Ny{tkB`rk+f`SBOdL@@V;2+3-#1sXX4FusJq)c|AvYR#uKc>lY8s>$zf8N=fy9Ts4sJg-xhU z>xT5Vyy5U?vZ_7FH6&+DtgRX`c<>k@{_MRFCPR9n3xKYH#I{?P$p%sl`hx2~LV**Z z0gXFkNE2J$p@b~Sfdd1Rniw{yxp?5wln2m_^eQ)@6^&9EoDJ#Vhn`chOo zlB&6_@4$MC-5tYk^<+@p4+?GhcHt25SGA8P(vPtVfuc?S>U?G_>HNE7$0_B`RJUuw zPM+zah4yyh9$%}r6WR*mU{VLg1V2e$tAud15mIRLch_IO)Bw92*%H~&U2=Wk9~GgV zk8xJ&Ebef#=y1BF7aj~(T0mh;q7(^AZyeOm&p)AJS9qf*V6kk<=As9I!a0BtO|Rqg zEF2L8D8NEdE=`S{bFH|ft1Dv(8>Zo>rlXk&o%*{$kFJwR1Mm^`=a<_# z&ZO+`ilE?0y<{Bm-ZwL_C2lz}U%Q7WV(-F&v1IAewW`4^C_OF?vWx3|=!ex?D1QqY zPZD4BT6;q)<EElj6m$R?%WK#M_*8IYC|b?c zcO_yQzW2&EUs0itEE$BJ^edt*pvKW=IOkgjWq6M2B@6s@^9;&*k~bRT9>@j5Rs$y# z<`m0_J_YqM()FnAF&0D!9q$t=cV!_;A~llnJOd&XCDyM)k@ zrAwc4(6(Cm)7O6a>z`O?)l}%#gxgK()33(HLRK4|Xv(LT(JLY%KU#Iw2LZvz#XpI! z5N2Uv^x*FTqy(Q#a*bpUpDcjL;GZ}5z@Q2{3TML#vVowVdX5;jAHMpz(BvK&>d{Qj zR2QLeMC&8*I-u93EFpa8?Od6h~{CSfg z;Ms)8VM9(NW!L4)1lnTNAWbbzCoP-tGDN8+K6qu^J3)d_s;taXH`G|goptI zO7hdf$M3DH9@spf^&5A9y9|k7?Hmsz@q^3{)I~<#oa6C_*WW4!KI*BUJIT^Hhl=~p z(*65+0WN)P4tyLP}wFE%M=dq_1{N)GW@YrF1}2fg64&viqL^713v+uKnmg*w`#6@ZtW zG8$h*3*xM$FZraJV$07-?NNV)vS~hGXc1~*Q1J3@&Ha%B&DpYLS_tM~XRh><)}F7_ zEX%spbu~7XaMHqUDaz{4{==laa%wTb1+ehBo;UMtkL_q}eUpZ@>xzS1>hE48kL zrKX&?(fuu0Kq&YY?C9XQ%=L_Uv9bF3R7lPb4#41dP*_RQ8Hk=*Qg4|mx#L}9DDy$` zE;e&T8`&wIe7!jA7mr(2-OXE?4k5p~YHp>rF~w3|T6)e7uA8jzS=D)RN>52PIGzQI z(%Yw1!-2V5S4|yjSTD}Tt2>DJnHdlPReXvrbP22i7 zekM#R8~^?~{I}l-ircq*bi+psY{fenn}?K>W*sop*Vk7*45V}gVWn})%C?pi3m=TC zlbikO!QZZH;le77_{78dbbVt>3oaR&_kIHgXqRI#433PXpy?+e@wuUa^c>l}E&qOt zD^;J-NApb{DW@k8frFc}Ci%d=eVgseOihR!JBwmq#*C$s4IU|>zyI4I&tp{NmMm@X za5S4?N13b#Cod-#=r-cj>e^c6oRTGj&#@bVs*&>xqly&TQ9g}*uCtfkKVQBq$-a8D zxDVAMYG05aSZDVvD;(@9C!p+Ty$A`-Sj(Vk6xk8E(;jg*zOpdF(oZ@-g2m02mr2I0 z43|OYO`l&}KgUN*f>D-Vq1q3+Ex+b${r82UMQm7=9Ms-kl-EwIKc9EeVE0d9?Vf(Y z?YnjVu_%RIMDM<5tN#ACuzDsYEx*>9`%PFFa_(Huj4{Cv-m_c(7hU)NLLz*P310r6fA;*y}Z8U=VsKI_VQ1PK2-J!A7LK1dN$|0P-qZT5V_IA zqmA}++NyLB?su3*#Wx)z&ql?kU0G!2DM`~CKYl#WE)OB~!quxwRk2De zm^ZHgYs;E7XQXbwad_BV9}DcopARwEsuRcgK6>=Kqp_nf+;QY9DrT*V2PhKa*{V>h z5(tUl0f|Qm`i!e~-TY>#v~kpa9)c6VZGNMs~x7Xi3nQbS~< z*Cc2=i4rU!?G&nMU2RohYnn2Wx_#4!pKZ?#>-q(WFc1gDgW(xOY>bj4euK1KhjddR z{{^wrvdPDcbSUM(-olwbM0-DG%rB`2;duIri$_Ja!ErHen|V)KA&=1!MC6`0etbdz z8O3+Zx?@wc4jhO~ZPDur(aD;BJ!9?YLu1yC{$2WZ9z581(+T|(7$SAsB{bMTJX@>1ZTT;e5vahDRLj)K~QKz zMr3AW5bzz5u{JX&M-Ra)m>v=v{O{_F!$`4I4p{gHL3Lf+{Nm9h%?-!nX&RRF; z{Ft#52dRIO|8M|2Hwou3LOVwFk*kUwkhI#xQJwYuN65?j<}P5=IlDbXi2cn`*jT~WLVwI~lreKXz(;5~rmLuU zoC>28*lIY~U_MeOFx;ZxiA_cliXmqF)rHvDob2ps#(Jpgi+pL>@=EWCJczF@T|Ed?Fmf@b z_Xttc5qg~&PAlbeCgrbHNi4Ec5HwQvRxf(lXs{D+5Ee-H{V%f~J^GX?dIc*Y>2k1Z z%jeQ!-qDFk^DL{VkR~wBdn=Bsr|SnBoYM8yR8Y7jj5wng5@6(Dk|ib$f*v7Rl}v6T zj^sT*#UXZuzX3WS=%U<3^tu17CF0t@8=e>*pMQ!$c>`FM&wKNsX2~)v;>Z}(PD#8v zerO=kF}|fLt8?_8)RNS|y(2C=9v4FJcwXZ9^Q$wWQ;I^AyVLzTdLtFXe_ zk#z+XOJBx;yVRabNh$IiT)xHAGs!J@o=%49gbBZDoR533%Xhi+RYWM^+F%=K^Ozxa z_$>YpS?#S;kq~j@ju}Hr!3D8x(UKFoBVTkUthn7iO<#7{0GE+;#N>JdSQ)F?zJh#X zZZvUn6Z@o>-*)Tw!3`OnfEp$S#R~Zh7yvQN6dNLH-e70fe2Ho#n3J=j@U~oz7NkS7 zh4`-$i1@l122EMG=M1o}HF)J&s(jXCCkd!I6)y@`78u;IFwdIjf%Sf>nvw zjrly3PL9h~F|IDhWRK{_^i>zck}E}AN;w)gy}X22l^GqP^%Hnf%DUmDo{?f`#?TFe<$F1&ZRTEbQrtRwFETy2TP|q4BOmluhc7X3*!W~;s{k4XG0`}Ic zmI{}&sCMyN;KRxXTM$5VU_y{*vO*Ecaml}o5eeFZjcLz0KmR|OC`Dzv&yZ)+O^6!2-hdqGBTvexHpr1BL>$mK>!lE6TpKG6s`f2LR9vpL# z42yN~=rgy>m+Gr8dqc(b)5B?fyj$blJ!`~ua~`L*jw=`SWPB`B9{Y%kTRn5X3}rOe zs_1^a8aadDG!0~A&7V8Bt}@|4@P_uiTetARnZ(e+e3QUq$H=WA!tM9kV3DY$ktmfRiYsjohYhHIqYpi+nx&?z4it|HU{4_d$A3lA$bm787 z0LPe^W7e%&Ve$zDE4df0Ny_hLV7^thxKVoIisLpc!uOH}4G7hI@<55+aOFx_`F=fH z=dkp6aj7oRFPc2HSkfO5)liGr7xKrP6duwl@6BoDNK&%6@j1Hd%QF6K&YTD3Crc@% z!0kE-`}aJLevjW56&F7i7a2CjCA@Yer4Tq$7NwW4>#Y*Wk^3zB*3?(ucd5DgP4qWF z(gN*$Zt=B*1RHDX6vq4rFz4+1nc3MXRA`8IQ_m$5hUm!v7Up3o=k+GmRUkB>%pX^~ zDlS>4rNjM5cYD@{mq@o-yir2`i+IdWS0<;S_o3-ZY<|5dP+{DWO1<2hnL=D7_Rnt*$+7``p3$tyC@s5_j73S{d|^Fnl6!S=UBQZ zFeU|NRyv1L2oTX3_2N5)$6k4E=@s8?JMrEv ztzCAWsMstHKnmijrqB_#maU$)9zy7+jpqeO|Zrk{LEE94( z_doq~6Xgq35lL@y5heNgtpxA$R3a}$w$upz2|7b<_3xUtC2KNrsVP$etu+|WX{)NZ zgiv4{jj1&^tAr=;V*k>lNm|kuHJo9*H6o;ofr|3( z$Xw>-*$2~FQfm7tczABmN$vfy?**~WM9GI&I-ql~93)!A;P?WpCw*Ua#tg*JoO9gU z`}giKLQ)WPVVg!-XRwpI3)@Y~Me2k7GLk!Nn$gZPfn34j9 zX~R;rIjJ_B$cdjgcW$OwGXt27gud%^!kH!uC9_$x?hq2fQ@ULW0SOFRWo0D@H6Q*V zDjuFok|HuP5?Mxps$dGjxE~HFUU@)~`LL@uO645_(bkfHwamc40EHO`GO4#M4Q1^0G|1-BF%;CCSpx^A5Gb5mDNH2B z?w@Zv)PO)_M0s0ZlD0j2d2WAU$RLPCd19FlZD~$(R`Gdxa!BSbhK1oiY~A_=*;Qaz z9w#ncc~GN@^0oecnhrBx`ZJ{i`|0V^(74F&v6zq*tdm1^5pnkH1kZ^iWZ1ZQ+plbC z{^7!iaLLB*5-}u;nQBG0swDJG5My5WJ>|a6b@pz&3_)DPbg7inEZ<02FerW1#SxQx zg{SS>yO+A0^as}7^o57dQXqLVl?mfC?gC71tC+gT$Ew(CYEPX)g@RS9egI6}wF3UT zx_ErHhun&2)7m$5(8sK;txidenlRzQf!qT-cM3T#0z=EGFQFjNE>7>~Uuz4tU7OrX z*z9VPC7ET%g`!I^25NxCU@}0n&r50p`RY#^Yi}mRczX7GK5(IUTX(H!txZ~URi#E) z+JpVcHn9(a`f+wOO5?C&m?zjo$D2XEABHm(glWFcSjSCGO>2r%+J$i*81<#zLXdFR zbeAyl^N_VW5k;7WC7{VDn-+`{bPlN4BbLvbwI*DksLwUF;Lm zvZamNk3L#Z6HK=u)U}dvAwy+lb=YFpk}e8sLSK*pSbh8LgvJ)PC{F zlZEAzR_|tyvr4Wiw(R9XtsT|UqJKa;p?6K(nX|D*_Cm-{i~`k-0mZ1{=cl*B@2#^B zFIjYPsX*=n{(^!;$q3DGB4*0*m^LO&BBTb>vEq)8jICja>cptE+w0Y_5=P`wmMQ>&N?qXX9=Fgz}Go0wY&K6V1>{F>r|Mof3Y!0n`>n8g=4QAoE` zImEe0<6!yB;Kp+$k&o#n=HgVa{3WIjZE~i@>TO-Sb}cXnrw)S4r%&sI;p_kWsD1{C zfB52UmbyKChr!N0zDa*~27z(7jo#*O6d7RjNf;JE=SQj-oL!Q<*XN&!C}%(tu?dBV zP9~E~05>>d&^>I5TlUG-(-7;Mr6sxW7Z#nNGx)no7qA766`!`s2-l>Q5xLxY;==aa zQ(?!-v-F%oi<+Nd%1p%o=6W$3`~jg1==UOIY?6p8+4CqAi|v72K?neQ73%)}1negu z_w(mV%Ts^m^y0`1(;c4__TVoqfVW3UQ2IGn(S=MX)|^T?C4}Us_3{N662=kwcD$1( zDg~V-nqnT+bafvIUv;XQgjtm;o zcY0vU*lGSz#M6gZ#&UQ9(&jg|uPpj#oEUo`s~p2VZK;M_?Fa|W5M0Kp!6Y=$8y7Y> zas<%6n?~`cCKs1d$)GS~$Kf=`YQ=9dSGr=i<>#75@87d$NzI;u{QM2^`RGE)aX_U_ zNR2RaEa%!a${ZdFP9{ROyqnXSF0?mpmpVL#NpKSyE}gpcrS=9(yn9{#qK*5hh@bms zb&X~9Q0QZ=ZcH7z>hC?v;)RR;^5baQBzKI)dy|;^ySx_p`t%>HY)XcbHw(h2t*=TC zWSqhNi663}HrVBKrdQ9ahIa3ugC`Kl~4+yOW!|$a7KQe`3Sl$Pcq1^ra|ahe0bim)EIc0emN%GHV)OXbQuk8p zTW`R3v|Xg^4QKAK1Z@yDcM@z>I>zg@&ZTEU0V>34V8K+>m?xaKv}QE=vtK*Ax~8I7 z<<1=*L-bA|^9PF)-k97ds-ch=I%EjMJwYy;Q2-_v(K{q?D13Gm3-Dbv^(25${YYhH zd#VZ{YO*D>o^(_ zT(V#7+q+lbylBy0QvywFA4ul=_rxxoFajgz(M(J0#tsg286ag8(Lgs(=L~{& zSt8U3LpJaeo0tsY1|qkogvQ3#ZhMx=0A#4Ql`H#hh+8Hw&Rwy-ol!-0EnO?0<>$L| zMLcT~0n?Tbw?-;^=@akSMI@ZJ@2BY3zf08Y2!kmwC^A`R`7y?h3gb6(M z!Vv{5L#~T)e6uGUZKmxuCl!c>8_Yh^B#LDAivvK^R-z^#K~va7QBvaUw6;DK7+CJ0 zepHo^6@l{JE=4mTx#JAev)R-Pc9Nh1-rf4>xKTu~hST|q2LS@N$?W)sZRypnVk;*r{%tEa2-FM%qOrIVY-A=Bg%*>5P zjYlw=g|l?h0d`@Oy3C)DC7Y(`e01~_LNA>*j3UW1KEKdTOcT2Po91b?WZrm72mbH0waX~3v%H%9O_%`2NnQZm!(benMQ?Zomk-}7 z=z^$=MIj9FLMv-xwIzvcPEZbu#LElWLMYY;$*7ME;Ar4_3Z1fhFk7SjkNUEun|sBG z&@qTHMY@DXQ;i0WbNqF+E1+PBcS}Eoy@u~(-3sFn2P3BgQzbcK)vaFVI$0D9mRMl@ zh1iq8BVya7V0kGX9W_IE5J|se!L^eUlhPiEeKx5mGum6{n6j1JuwgfRyStd=+t}!= za2GpF7m=70E3a*@(~v_Xd{!0Zi66%kU(nk^6h*4iv0i=uz(<;5$xt1l5hyvMUX;90 zSbJsiyu)RViF8LqCW8msp34Y(hHwb*PbSCGjV)Ptp?>Rs;!mQFOG%g-xi@JZ?*62 zeyaip4XCeG|GLTYh;gD0zUH@GWI(<0fBfO)vFY2+i4J&dulXNeYQn6YO*2kXf-gU| zd}O-*s4Qr$cnX`S7t(rpL7FU@3u5UbW5qfHj;Rg!qPjZG#u^9A*SZ8O9oS`Z)}=50 zQEdfG=Vlr-mHhnq^Y!)p49`?GmVrESzEn7D#R&6a8{p%g+cZWIM*85!jvd_7p53PQW z6A^n?P@R9%w{COCw+;4!aH30X?j==NDn0B*iQP9G@wq6sfPwDP8?AN%k1z#T{srKy^uBpW>P1cGc4kk7}<>ync zOm?beqM2~cTbBk|Gt74G;aA;*2JG#KU3?Lfjd}FAR($KX_4GgSw>>jP%wzN7=lhA? zFL#7G+GluY96$U?_m*qEr7IqFA0C4hFZx)tZMw+W7aP(4RD=_>#rIGBusZFBzNoCT z%2)e~hy`A0u9g@CO0)o9zt1@v(nU(Pe0`npC8GUBOS2?{nujq zU#+fxeDSce`;YGKZu4EeAm-rmp&}xy7CyNmAtJIZ>#0gzLBjUjDc9w`x0|b7FZSo^ zKUut@`*pfSl(K2`g@L2!_PeGF#UBb&#bWYT{o8tNIT1MXyNLFz2em%qEFW1P;;nMa zzpQ&l^6!H`q&*695C!`sIbCyx*jnfj{(sZPE`beGN1Yg>c_awgG&_-bG~Pm>sf zX3&yy+%0~(&EpQn+@54CGi5Gk0w?rSxQm)4RcqpPT|b^7cU9<^2#BD)q&VDy?q2{* zm1E=L$A|M+Qe6S^5 zkWgwsz3$eWO`A5QU`zzkuf zo$j5^X|0M^fBIcD&X#Lg`X^`Z>WQmXU5JVTrvoNkHI?`T*1=+LwU*k$!U{pB=AaJ6 zh)_}O_(SjlSAOWc!%WK_Hb8R6omEqD=J_U4(KEKJj@O)&P4ieMPIM z##nrJ!aTGS=JLT!=W>NeWel+BU4b0`7=J>bS8m=kmv~cKYoAaM_(re=#QHQv?MXI_ zdI8J$oIF2;U)cHZjcJA$?4%I3L6aT24#y(wl*w)r+Uz-Vc1W$JPN!7F&}8~kAtu9w zLdQM1m5;Ks2TDq+o|w<&HQ0$?5o^!q&!1D3cho3<#$E9a^?8go0EXSBO-HPkOT?{z zdYYnn?(i<0X`SSOTEaLNRQr6q7!e@FMSh%-@n%oH{E;-H(Wb@j%l9`6=k&(?rcs@e zn^=m&9`z7A+b11JsPHLm`1C2*ezDx;3TS(PdLIUV{&x5(3UHKK7$~V9cte!4?rPlD zBpN_aR|?#f78ZrfvjXqK_fk;p+T9qT*Yv5eMtI0xd(V3S3d#{rGR7a6{-R!ZfGw6# zeQdnufvjKJIE{!~h!uGa$F+>T2W2a$X8P{)^)rNcQ@kQ)uhQHV!W&V_Hwi2Q-doeC zYnLxO5RJ+2_1g1uk|T~CIpT!u6)eCCasJJlZ76GKxM^*L8#;E1xI9<_b0`C(R#U(8 z{p41hIkSdHmyOrj=w9%3o^yN0w6AE3#Cn{}K(ouQZ5$F-<{#3*N{ty6z$j1VZ=TDo zOs3Eejc!PO>@yhXclIP%DThb;=U+~+xXX|HxLsd{FX0LPl z`Dg!UaFC(oT&0yyEJ%up;(-_*){*yKW8g2{(<}E=B7ixJ9K- zlM8nE*tP?VJfM-~9@b#r5r(=*-OkCWMSxJ*lG>u9@C&!X?H$;?9i zzV96Z7*!kV_^~6#Bbytgs?|~7=~j0!qCKnlLC_U>%4hOnhO-6 z94L+ebjTd2nr_!3+XU5I{O&u2(Vr_Hg#|BWln40JqWwb zu&|{iqv=Ku*|gudf%g4{cOf{_6q%=_@P+S0C1)RvVZEfPZ7 z*m5gTE~U*X$#vzjq=;4*X|(CSZQC?MR7Rl)Gf_e=)4gSOv8hCw>E@cuxEpiZ=Raw` zbJ}yhZ#$h+X3YD}d;k5O=lMNP%wR~FpTyy9g0JfHl{TP|$;n#&T>E`GI)H#(K+{1e zo<^JqCd=M6oPI9j+mcfNC2^^dVF^2r#X_|N5==Z#u$g;imBF!l4}mp;Fo@0H3+WR& z#zzqdsH^a23)~ill!zMRnr)*%Oy_50yug6(@4x*v63VtRH0;2jTm`)Q7+J)D5VGXw z@Z+!Ma@~k#2n?99)vN*Sw-8sc^Ycl2|FCWI;I?f)>TJVJ4=OrH4|ZXmI-zZxRJ~e< zLdlqmp0r~Jw4nEbI=i}>s}jnGqk)cGy;^z5PepdG(TxfiS-@aUR_S0IPYy_7!1!Nj}h4J~c3@H?HF4r4D?3x~=H%Roj7SPBf9 z!NnoodImqJ5FTCKc%%(AMMpyqpnDg96hocS2~Er^-0spXF>fU!^YHEMR^YIz%pTjB zPSioi>u|WMD?+djNZO4U=53zkmc%z`&g$9GL|bJGVHKJ7!iu1Ex&}1_NLnKA!gMCw z<0WCtgto=yGP03C%E)sK{ja1u%w7uv=ENhUgd;5|EZm0Y$>$oT6ICYPn$#H`8gcfwh&2AjmY)tSH4~oBSY!f-xGEAd@J&TSsF}Q zQL6rl`y`nVo~H!Zh14-Evoi#vr8D=~fVP6J4^KQIL!j&8_(A{)DGo>+Vjl)zuxjMh z=XTeyVI6gzK%z`ORZ+pC9qxB10#y))l(|07Y_DiI>Vyeb(drz!^L_C~m?Bj*ZHLej zC?e)nplO5=FC#Uz3_E@$^0;xB-|5JN69qj`C_sPd@8JO-vTslo;+edi`Ys{z$piQD zyqug7OpZZZ2xET`Mu@NHrHK0{k)#t$c|u1Vq6jC&<)=AgMpwfMS{9&s02W}!pm#)| z{Gb-!6J)`yFghMd9k?E`1)w3$*zAhIq?QR6%0JAJ{)uJN^QHwwC(p`mgUhhsN*nr= zTag6$s88WA|E|#HNcIc))H`y*q5AlUF&a4H*kavNx(POVB-SO&Y#?Od-1g5Dm;-C+ zNc$CByhg^x#J(MIJX|wQcN4V=&sY)*1CEVYOq!oV4@W8eL|ML+O2SX@EW^p#Lu|$| z{tAo=jS`P4Pw-A=!cYTP^SFk2_yB1jo#6%Cja&=l+Q{4O<=&p2dP(&W#V9a*Z*1>G z-L~l0B_x)ZAQfz&ZNv_8YYFUoZ}_t4bU4J>&$j;X{vxQR!R6hmCks!EI+5l?Rzhyx zihuq$iZXDwE^s7;DwT|Ng(`-H%Y$UC{L^x=7xy8`7BqzvXLr{r4v!u^nZz)%G%#2k z4)TG#kvPt&VErYeQ(ep&5PspNpcoSO9Unt{{?+O~`f~q8ZOHOT>q!3m6Vp+=JoYZ> z2l3~lj<}xxgZKEGl&`pbPU&EhOy=NM&}awF4wm(tq0pZSG;7zBkKwf!msiQ2>uhgt z`fRC;Yy}+CJ<>Nr&_L@IntFUk^^O2X~fM+Q>2k_$OmqE{=$$Jj$yZg+Cl#eP42 zex{$e>-kozaw*wj94O-gY-sOOb}&^pSQq)kTf<`$?)(yAO}CX7X(W@gjeti5{Z-fH zPSr(f4zx5M&dYRptj0{c9b#g6h_~wl_UtON7X`RC$|u-pNH(1$UjZkfm-~jpFQ;4| zr#w$@!|)$VEDm+qRx>38{lrP`ySVZ0grUN0*PJA^TLynvy)`twACMfvaJ^^o;grky znf`4hOTb^fZiCQDGAAHK)1hzdy(*>Ml}lpJW;f=uJtm#ki(`D7o1~`R#=1#)mpv4u zy69)uhP9UueSufaT=0SQy;D~Vr9m&6}Aa>J(|RKU>@SwoP18Q+!5@_ukg&36K&(y)aW1Hoe6g`TW--fr2vj zx$e~scR2Qb7=MC7cGP%g|JWWHFiYPwh-E{SUdcmObw8YSe)*iD9oNsVTpA{4V+>O$%)-07CuAC=ZfMF9CsxUM6^{r%|klUpB+fA-bzmo=A_ zTx*>d{l{=eM@kc)8YZ~@w0pzZXLW195_Sj#0>BtZ*AlY3MPXCP0?A?4>F1C% zPNV7oWiU2)`U=1QI{u5hOas&N{3K&@t<)K%scH)1T7FoboB$$lqWO|1h+xhtq*N~Z$Pr0LCT|72&nhw4;e z3-1Ie?iAg!XS=CeS^o5Q9J5~y?ArouX4u38dYj* zfOj%ONmjY*p}<=9lkO`2vt}7`_Y~GU;T*7U-8+or_w)vRB@;2p;iDs0b#(8adAdUA>IgIgk)M8n-1;^ zB2vLDECOp24x*tXK`}rTh+PG*LIfxP`50ce<>0YF9CCW;BvAoX;U2fIplKwwKZw@E zr>Tl_yB;6`(7wG;Nq|7}PM)l7GlEV=Ma%McK3~p18w1%NjFljnio3UB{QI@Oa7%6l z9t1Hdsx93l*X%T#U41ABAZ^sq(lSnhUKsXLh)sI0fQ%SSIV7Z~q+q@q9xsqF*g)vl zKr$bZ%Z*!ZQDn(8jO39_9~I3xNs!VNK=2H2pEYaXJ_fQ3+HbU3s39B{@*=E%YTc7sxxOvQtC zMhV3bG8v3PlSeTH z1MwOI&S{t=)a*qC4vm-wbsBIYtS$oKiqPR;kQ}tHsRa`hDJjjs9oF-BRht`4YsulK zn3@eqHU%DR&y(u^4h6zkwO1YN3Jh`eYV;skMOOaG>i=h`##IPRkc8XNhfs#1XI(p; zsO^xp1h)d14#7Q_gNX&?r@7G3q?Q3NhKxkJJq+r{boB#j@7Ze4;ZTqeo!J*Jw!yyx z_Vchc+FBw;)8jc~z>*2g1oYm>pvTPt%qU!#u`Uc)PF{%Zi1r4;s5F0Qaw4$p!^e*& zyg9iNXQd^E32JL=i31Y?vQD)xp{5_+YLQCrHaKB1reSzwqw)gi7U6SKw|X47^Kvti z_72$|zO4Q=#TZPRNE}s-nWu{AvZT1h$N1Gy6*I2`U@8PxwT~2($(478Fbv-y!WKH{-I<3*N?z&8NO#b zWkhadV$xl;?**-`CD0s-4-dliH@CEOYxZ>Jl!ziXZw3?%UR(@n1KxTZAhysr{=Mc- zCF$eE?Ow&$jY!@g{CQ^Pa!n6~)@un67TyXMclYZ}XhUXAbtv^^y`MRG9;-posc|1f zu@0T+t5-p~Nq>YsD+(byA?COggN7j)7R#78Ux3<@iCMeWy7hw|dBT**__$1nJ7%u({Qi<7$}%OUWmzW~=3 B5@7%U literal 0 HcmV?d00001 From 07ef4a3e4afc390ac08fe2ac4b6b2e1195604cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Mon, 23 Oct 2017 16:29:43 +0200 Subject: [PATCH 055/144] update search api with params and description on syntax --- docs/dev/search_api.rst | 215 +++++++++++++++++++++++++++++++++++----- 1 file changed, 192 insertions(+), 23 deletions(-) diff --git a/docs/dev/search_api.rst b/docs/dev/search_api.rst index 331bc4eb..ea3b0501 100644 --- a/docs/dev/search_api.rst +++ b/docs/dev/search_api.rst @@ -5,31 +5,200 @@ The search supports both ``GET`` and ``POST``. Furthermore, two enpoints ``/`` and ``/search`` are available for querying. -.. code:: sh - GET / +``GET /`` -.. code:: sh - - GET /search +``GET /search`` Parameters -`````````` +~~~~~~~~~~ -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| Name | Description | | -+==================+====================================================================================================+==================================================+ -| ``q`` | The search query, see :doc:`/user/search_syntax` | required | -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| ``categories`` | Comma separated list, specifies the active search categories | optional | -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| ``engines`` | Comma separated list, specifies the active search engines | optional | -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| ``lang`` | Code of the language | optional (default: ``all``) | -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| ``pageno`` | Search page number | optional (default: ``1``) | -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| ``time_range`` | Time range of search | optional (possible: ``day``, ``month``, ``year``)| -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ -| ``format`` | Output format of results | optional (possible: ``json``, ``csv``, ``rss``) | -+------------------+----------------------------------------------------------------------------------------------------+--------------------------------------------------+ +.. code:: sh + + q + +The search query. This string is passed to external search services. +Thus, searx supports syntax of each search service. For example, ``site:github.com searx`` is a valid +query for Google. However, if simply the query above is passed to any search engine which does not filter its +results based on this syntax, you might not get the results you wanted. + + +See more at :doc:`/user/search_syntax` + +Required. + +.. code:: sh + + categories + +Comma separated list, specifies the active search categories + +Optional. + +.. code:: sh + + engines + +Comma separated list, specifies the active search engines. + +Optional. + +.. code:: sh + + lang + +Code of the language. + +Optional. + +Default: ``all`` + +.. code:: sh + + pageno + +Search page number. + +Optional. + +Default: ``1`` + +.. code:: sh + + time_range + +Time range of search for engines which support it. See if an engine supports time range search in the preferences page of an instance. + +Optional. + +Possible: ``day``, ``month``, ``year`` + +.. code:: sh + + format + +Output format of results. + +Optional. + +Possible: ``json``, ``csv``, ``rss`` + +.. code:: sh + + results_on_new_tab + +Open search results on new tab. + +Optional. + +Default: ``0`` + +Possible: ``0``, ``1`` + +.. code:: sh + + image_proxy + +Proxy image results through searx. + +Optional. + +Default: ``False`` + +Possible: ``True``, ``False`` + +.. code:: sh + + autocomplete + +Service which completes words as you type. + +Optional. + +Default: empty + +Possible: ``google``, ``dbpedia``, ``duckduckgo``, ``startpage``, ``wikipedia`` + +.. code:: sh + + safesearch + +Filter search results of engines which support safe search. See if an engine supports safe search in the preferences page of an instance. + +Optional. + +Default: ``None`` + +Possible: ``0``, ``1``, ``None`` + +.. code:: sh + + theme + +Theme of instance. + +Optional. + +Default: ``oscar`` + +Possible: ``oscar``, ``simple``, ``legacy``, ``pix-art``, ``courgette`` + +Please note, available themes depend on an instance. It is possible that an instance administrator deleted, created or renamed themes on his/her instance. See the available options in the preferences page of the instance. + +.. code:: sh + + oscar-style + +Style of Oscar theme. It is only parsed if the theme of an instance is ``oscar``. + +Optional. + +Default: ``logicodev`` + +Possible: ``pointhi``, ``logicodev`` + +Please note, available styles depend on an instance. It is possible that an instance administrator deleted, created or renamed styles on his/her instance. See the available options in the preferences page of the instance. + +.. code:: sh + + enabled_plugins + +List of enabled plugins. + +Optional. + +Default: ``HTTPS_rewrite``, ``Self_Informations``, ``Search_on_category_select``, ``Tracker_URL_remover`` + +Possible: ``DOAI_rewrite``, ``HTTPS_rewrite``, ``Infinite_scroll``, ``Vim-like_hotkeys``, ``Self_Informations``, ``Tracker_URL_remover``, ``Search_on_category_select`` + +.. code:: sh + + disabled_plugins + +List of disabled plugins. + +Optional. + +Default: ``DOAI_rewrite``, ``Infinite_scroll``, ``Vim-like_hotkeys`` + +Possible: ``DOAI_rewrite``, ``HTTPS_rewrite``, ``Infinite_scroll``, ``Vim-like_hotkeys``, ``Self_Informations``, ``Tracker_URL_remover``, ``Search_on_category_select`` + +.. code:: sh + + enabled_engines + +List of enabled engines. + +Optional. + +Possible: all engines + +.. code:: sh + + disabled_engines + +List of disabled engines. + +Optional. + +Possible: all engines From 2134c7c6939ce4f20fbb6ffca330ad7af0ff673a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Wed, 17 Jan 2018 22:05:10 +0100 Subject: [PATCH 056/144] change python-pybabel to python-babel in package requirements Closes #1147 --- docs/dev/install/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index e1ae3f2a..8b6bb69b 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -15,7 +15,7 @@ Install packages: .. code:: sh - sudo apt-get install git build-essential libxslt-dev python-dev python-virtualenv python-pybabel zlib1g-dev libffi-dev libssl-dev + sudo apt-get install git build-essential libxslt-dev python-dev python-virtualenv python-babel zlib1g-dev libffi-dev libssl-dev Install searx: From 20e85275cbea356827bd71fc8136245d37b0f209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Tue, 23 Jan 2018 20:43:22 +0100 Subject: [PATCH 057/144] remove Gratipay and Flattr from sidebar --- docs/_themes/searx_theme/relations.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/_themes/searx_theme/relations.html b/docs/_themes/searx_theme/relations.html index b7539e5a..5fb65cdd 100644 --- a/docs/_themes/searx_theme/relations.html +++ b/docs/_themes/searx_theme/relations.html @@ -10,7 +10,5 @@
From 94af140318338ab6e7b064b3b26b5fb45523d17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bourrel?= Date: Thu, 30 Aug 2018 11:14:03 +0200 Subject: [PATCH 058/144] Update quickstart.rst Rename `build_grunt` to `grunt_build` as in manage.sh --- docs/dev/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst index 92ef6238..e7d4baf4 100644 --- a/docs/dev/quickstart.rst +++ b/docs/dev/quickstart.rst @@ -89,7 +89,7 @@ After installing grunt, the files can be built using the following command: .. code:: sh - ./manage.sh build_grunt + ./manage.sh grunt_build From 10ebe8cec9d926ca1c7c19336bf60c1ee88d9300 Mon Sep 17 00:00:00 2001 From: tmikaeld Date: Sun, 21 Oct 2018 11:36:11 +0200 Subject: [PATCH 060/144] Added missing symlink instruction --- docs/dev/install/installation.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index 8b6bb69b..6c3f773a 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -150,6 +150,10 @@ content: } } +Create a symlink to sites-enabled: + +sudo ln -s /etc/nginx/sites-available/searx /etc/nginx/sites-enabled/searx + Restart service: .. code:: sh From 77494166d2c61dcc5e62447e88e6d3bb4e5254c2 Mon Sep 17 00:00:00 2001 From: tmikaeld Date: Sun, 21 Oct 2018 11:42:00 +0200 Subject: [PATCH 061/144] Formatting... --- docs/dev/install/installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/dev/install/installation.rst b/docs/dev/install/installation.rst index 6c3f773a..50b33a32 100644 --- a/docs/dev/install/installation.rst +++ b/docs/dev/install/installation.rst @@ -152,7 +152,9 @@ content: Create a symlink to sites-enabled: -sudo ln -s /etc/nginx/sites-available/searx /etc/nginx/sites-enabled/searx +.. code:: sh + + sudo ln -s /etc/nginx/sites-available/searx /etc/nginx/sites-enabled/searx Restart service: From e75af78d7aacdd545825ebb2aee902276e9d55a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natal=20Ng=C3=A9tal?= Date: Wed, 5 Dec 2018 18:52:36 +0100 Subject: [PATCH 062/144] [Scripts] Force upgrade requirements. Force the upgrade of requirements with pip. At the moment, if the version in the requirement file is changed, there will be no effect, because the dependencies is already present. --- manage.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manage.sh b/manage.sh index a352ccc6..20d2cba3 100755 --- a/manage.sh +++ b/manage.sh @@ -18,12 +18,12 @@ ACTION="$1" update_packages() { pip install --upgrade pip pip install --upgrade setuptools - pip install -r "$BASE_DIR/requirements.txt" + pip install -Ur "$BASE_DIR/requirements.txt" } update_dev_packages() { update_packages - pip install -r "$BASE_DIR/requirements-dev.txt" + pip install -Ur "$BASE_DIR/requirements-dev.txt" } install_geckodriver() { From 906320d271d4c8e8aa72b240457ee343edcd4312 Mon Sep 17 00:00:00 2001 From: Robby O'Connor Date: Sun, 18 Aug 2019 18:16:12 -0400 Subject: [PATCH 063/144] add --no-cache to package installs Add --no-cache to apk upgrade and apk add calls. --- Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index fb4f2cb6..33535837 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,8 +25,8 @@ RUN addgroup -g ${SEARX_GID} searx && \ COPY requirements.txt ./requirements.txt -RUN apk -U upgrade \ - && apk add -t build-dependencies \ +RUN apk upgrade --no-cache \ + && apk add --no-cache -t build-dependencies \ build-base \ py3-setuptools \ python3-dev \ @@ -36,7 +36,7 @@ RUN apk -U upgrade \ openssl-dev \ tar \ git \ - && apk add \ + && apk add --no-cache \ ca-certificates \ su-exec \ python3 \ @@ -48,8 +48,7 @@ RUN apk -U upgrade \ uwsgi-python3 \ && pip3 install --upgrade pip \ && pip3 install --no-cache -r requirements.txt \ - && apk del build-dependencies \ - && rm -f /var/cache/apk/* + && apk del build-dependencies COPY --chown=searx:searx . . From dae5bcf253235c7551d6120b881bc07ae3b7abcc Mon Sep 17 00:00:00 2001 From: Robert O'Connor Date: Sun, 18 Aug 2019 18:26:40 -0400 Subject: [PATCH 064/144] some other minor tweaks to move stuff very unlikely to change to the top in efforts to delay cache invalidation --- Dockerfile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 33535837..c0e6cbc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,16 @@ FROM alpine:3.10 +ENTRYPOINT ["/sbin/tini","--","/usr/local/searx/dockerfiles/docker-entrypoint.sh"] +EXPOSE 8080 +VOLUME /etc/searx +VOLUME /var/log/uwsgi +RUN addgroup -g ${SEARX_GID} searx && \ + adduser -u ${SEARX_UID} -D -h /usr/local/searx -s /bin/sh -G searx searx -ARG VERSION_GITCOMMIT=unknow -ARG SEARX_GIT_VERSION=unknow +ARG VERSION_GITCOMMIT=unknown +ARG SEARX_GIT_VERSION=unknown -ARG SEARX_GID=1000 -ARG SEARX_UID=1000 +ARG SEARX_GID=977 +ARG SEARX_UID=977 ARG TIMESTAMP_SETTINGS=0 ARG TIMESTAMP_UWSGI=0 @@ -14,14 +20,9 @@ ARG LABEL_VCS_URL= ENV BASE_URL= \ MORTY_KEY= \ MORTY_URL= -EXPOSE 8080 -VOLUME /etc/searx -VOLUME /var/log/uwsgi WORKDIR /usr/local/searx -RUN addgroup -g ${SEARX_GID} searx && \ - adduser -u ${SEARX_UID} -D -h /usr/local/searx -s /bin/sh -G searx searx COPY requirements.txt ./requirements.txt @@ -59,7 +60,6 @@ RUN su searx -c "/usr/bin/python3 -m compileall -q searx"; \ echo "VERSION_STRING = VERSION_STRING + \"-$VERSION_GITCOMMIT\"" >> /usr/local/searx/searx/version.py; \ fi -ENTRYPOINT ["/sbin/tini","--","/usr/local/searx/dockerfiles/docker-entrypoint.sh"] # Keep this argument at the end since it change each time ARG LABEL_DATE= From 86d1a4931fcdef88312b330bffa908afe5a70abf Mon Sep 17 00:00:00 2001 From: Robert O'Connor Date: Sun, 18 Aug 2019 18:29:02 -0400 Subject: [PATCH 065/144] .. --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c0e6cbc8..f336d4b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,6 @@ ENTRYPOINT ["/sbin/tini","--","/usr/local/searx/dockerfiles/docker-entrypoint.sh EXPOSE 8080 VOLUME /etc/searx VOLUME /var/log/uwsgi -RUN addgroup -g ${SEARX_GID} searx && \ - adduser -u ${SEARX_UID} -D -h /usr/local/searx -s /bin/sh -G searx searx ARG VERSION_GITCOMMIT=unknown ARG SEARX_GIT_VERSION=unknown @@ -12,6 +10,9 @@ ARG SEARX_GIT_VERSION=unknown ARG SEARX_GID=977 ARG SEARX_UID=977 +RUN addgroup -g ${SEARX_GID} searx && \ + adduser -u ${SEARX_UID} -D -h /usr/local/searx -s /bin/sh -G searx searx + ARG TIMESTAMP_SETTINGS=0 ARG TIMESTAMP_UWSGI=0 ARG LABEL_VCS_REF= From 07d02502411ef5097d3394e0615010461ec32878 Mon Sep 17 00:00:00 2001 From: Venca24 Date: Tue, 20 Aug 2019 16:10:25 +0200 Subject: [PATCH 066/144] [fix] mojeek engine --- searx/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/settings.yml b/searx/settings.yml index cf2b13e0..b162839e 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -743,7 +743,7 @@ engines: title_xpath : ./h2 content_xpath : ./p[@class="s"] suggestion_xpath : /html/body//div[@class="top-info"]/p[@class="top-info spell"]/a - first_page_num : 1 + first_page_num : 0 page_size : 10 disabled : True From bb4d223770bc8cb333dc2c5ca13e8430b18051dd Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Mon, 26 Aug 2019 21:54:01 -0700 Subject: [PATCH 067/144] [fix] google images --- searx/engines/google_images.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/searx/engines/google_images.py b/searx/engines/google_images.py index d9a49e9c..63691311 100644 --- a/searx/engines/google_images.py +++ b/searx/engines/google_images.py @@ -70,11 +70,21 @@ def response(resp): try: metadata = loads(result) - img_format = "{0} {1}x{2}".format(metadata['ity'], str(metadata['ow']), str(metadata['oh'])) - source = "{0} ({1})".format(metadata['st'], metadata['isu']) + + img_format = metadata.get('ity', '') + img_width = metadata.get('ow', '') + img_height = metadata.get('oh', '') + if img_width and img_height: + img_format += " {0}x{1}".format(img_width, img_height) + + source = metadata.get('st', '') + source_url = metadata.get('isu', '') + if source_url: + source += " ({0})".format(source_url) + results.append({'url': metadata['ru'], 'title': metadata['pt'], - 'content': metadata['s'], + 'content': metadata.get('s', ''), 'source': source, 'img_format': img_format, 'thumbnail_src': metadata['tu'], From 4d17d453bf99641797dcbfa1f1a35dd5b21dcab4 Mon Sep 17 00:00:00 2001 From: "mathieu.brunot" Date: Mon, 14 Oct 2019 01:36:11 +0200 Subject: [PATCH 068/144] :wheelchair: Add aria labels for oscar buttons Signed-off-by: mathieu.brunot --- searx/templates/oscar/search.html | 4 ++-- searx/templates/oscar/search_full.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/searx/templates/oscar/search.html b/searx/templates/oscar/search.html index 59ee4688..bf9a9af5 100644 --- a/searx/templates/oscar/search.html +++ b/searx/templates/oscar/search.html @@ -3,9 +3,9 @@
- + - +
diff --git a/searx/templates/oscar/search_full.html b/searx/templates/oscar/search_full.html index 6fdae402..fd8a9a39 100644 --- a/searx/templates/oscar/search_full.html +++ b/searx/templates/oscar/search_full.html @@ -6,9 +6,9 @@ {% else %}
{% endif %} - + - +
From 2485d41bdd59fb820241241769ea9ad833ea470c Mon Sep 17 00:00:00 2001 From: "mathieu.brunot" Date: Mon, 14 Oct 2019 02:02:41 +0200 Subject: [PATCH 069/144] :whale: Config for instance name and autocomplete Signed-off-by: mathieu.brunot --- Dockerfile | 4 +++- dockerfiles/docker-entrypoint.sh | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fb4f2cb6..1815ddc5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,9 @@ ARG TIMESTAMP_UWSGI=0 ARG LABEL_VCS_REF= ARG LABEL_VCS_URL= -ENV BASE_URL= \ +ENV INSTANCE_NAME=searx \ + AUTOCOMPLETE= \ + BASE_URL= \ MORTY_KEY= \ MORTY_URL= EXPOSE 8080 diff --git a/dockerfiles/docker-entrypoint.sh b/dockerfiles/docker-entrypoint.sh index c731e0a8..8b4c3486 100755 --- a/dockerfiles/docker-entrypoint.sh +++ b/dockerfiles/docker-entrypoint.sh @@ -29,6 +29,8 @@ do printf " -f Always update on the configuration files (existing files are renamed with the .old suffix)\n" printf " Without this option, new configuration files are copied with the .new suffix\n" printf "\nEnvironment variables:\n\n" + printf " INSTANCE_NAME settings.yml : general.instance_name\n" + printf " AUTOCOMPLETE settings.yml : search.autocomplete\n" printf " BASE_URL settings.yml : server.base_url\n" printf " MORTY_URL settings.yml : result_proxy.url\n" printf " MORTY_KEY settings.yml : result_proxy.key\n" @@ -53,6 +55,8 @@ patch_searx_settings() { # update settings.yml sed -i -e "s|base_url : False|base_url : ${BASE_URL}|g" \ + -e "s/instance_name : \"searx\"/instance_name : \"${INSTANCE_NAME}\"/g" \ + -e "s/autocomplete : \"\"/autocomplete : \"${AUTOCOMPLETE}\"/g" \ -e "s/ultrasecretkey/$(openssl rand -hex 32)/g" \ "${CONF}" From 94ea9d6622cf7784ca45a84ff804cd697b765e39 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 13:52:15 +0200 Subject: [PATCH 070/144] [fix] duckduckgo paging - closes #1677 --- searx/engines/duckduckgo.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/searx/engines/duckduckgo.py b/searx/engines/duckduckgo.py index fb8f523a..4c93380f 100644 --- a/searx/engines/duckduckgo.py +++ b/searx/engines/duckduckgo.py @@ -65,21 +65,36 @@ def get_region_code(lang, lang_list=[]): def request(query, params): - if params['time_range'] and params['time_range'] not in time_range_dict: + if params['time_range'] not in (None, 'None', '') and params['time_range'] not in time_range_dict: return params offset = (params['pageno'] - 1) * 30 region_code = get_region_code(params['language'], supported_languages) - if region_code: - params['url'] = url.format( - query=urlencode({'q': query, 'kl': region_code}), offset=offset, dc_param=offset) + params['url'] = 'https://duckduckgo.com/html/' + if params['pageno'] > 1: + params['method'] = 'POST' + params['data']['q'] = query + params['data']['s'] = offset + params['data']['dc'] = 30 + params['data']['nextParams'] = '' + params['data']['v'] = 'l' + params['data']['o'] = 'json' + params['data']['api'] = '/d.js' + if params['time_range'] in time_range_dict: + params['data']['df'] = time_range_dict[params['time_range']] + if region_code: + params['data']['kl'] = region_code else: - params['url'] = url.format( - query=urlencode({'q': query}), offset=offset, dc_param=offset) + if region_code: + params['url'] = url.format( + query=urlencode({'q': query, 'kl': region_code}), offset=offset, dc_param=offset) + else: + params['url'] = url.format( + query=urlencode({'q': query}), offset=offset, dc_param=offset) - if params['time_range'] in time_range_dict: - params['url'] += time_range_url.format(range=time_range_dict[params['time_range']]) + if params['time_range'] in time_range_dict: + params['url'] += time_range_url.format(range=time_range_dict[params['time_range']]) return params @@ -91,7 +106,9 @@ def response(resp): doc = fromstring(resp.text) # parse results - for r in doc.xpath(result_xpath): + for i,r in enumerate(doc.xpath(result_xpath)): + if i >= 30: + break try: res_url = r.xpath(url_xpath)[-1] except: From 3d0c22c58d011579277868838bbb2255eaf174af Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 14:18:02 +0200 Subject: [PATCH 071/144] [fix] normalize time range parameter --- searx/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/query.py b/searx/query.py index 382aed87..c4002bd3 100644 --- a/searx/query.py +++ b/searx/query.py @@ -184,7 +184,7 @@ class SearchQuery(object): self.lang = lang self.safesearch = safesearch self.pageno = pageno - self.time_range = time_range + self.time_range = None if time_range in ('', 'None', None) else time_range self.timeout_limit = timeout_limit def __str__(self): From 77a70fe54165547b0f8f5e1a2cf7f2b9b706805d Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 14:18:41 +0200 Subject: [PATCH 072/144] [fix] update startpage engine - closes #1601 --- searx/engines/startpage.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/searx/engines/startpage.py b/searx/engines/startpage.py index 6638f3d8..868b964f 100644 --- a/searx/engines/startpage.py +++ b/searx/engines/startpage.py @@ -15,6 +15,7 @@ from dateutil import parser from datetime import datetime, timedelta import re from searx.engines.xpath import extract_text +from searx.languages import language_codes # engine dependent config categories = ['general'] @@ -22,7 +23,7 @@ categories = ['general'] # (probably the parameter qid), require # storing of qid's between mulitble search-calls -# paging = False +paging = True language_support = True # search-url @@ -32,23 +33,33 @@ search_url = base_url + 'do/search' # specific xpath variables # ads xpath //div[@id="results"]/div[@id="sponsored"]//div[@class="result"] # not ads: div[@class="result"] are the direct childs of div[@id="results"] -results_xpath = '//li[contains(@class, "search-result") and contains(@class, "search-item")]' -link_xpath = './/h3/a' -content_xpath = './p[@class="search-item__body"]' +results_xpath = '//div[@class="w-gl__result"]' +link_xpath = './/a[@class="w-gl__result-title"]' +content_xpath = './/p[@class="w-gl__description"]' # do search-request def request(query, params): - offset = (params['pageno'] - 1) * 10 params['url'] = search_url params['method'] = 'POST' - params['data'] = {'query': query, - 'startat': offset} + params['data'] = { + 'query': query, + 'page': params['pageno'], + 'cat': 'web', + 'cmd': 'process_search', + 'engine0': 'v1all', + } + # set language if specified if params['language'] != 'all': - params['data']['with_language'] = ('lang_' + params['language'].split('-')[0]) + language = 'english' + for lc, _, _, lang in language_codes: + if lc == params['language']: + language = lang + params['data']['language'] = language + params['data']['lui'] = language return params From 07a0a50e0df8aa1576a9688602e3a30024534d5c Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 14:25:49 +0200 Subject: [PATCH 073/144] [fix] update voat xpaths - closes #1581 --- searx/settings.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/searx/settings.yml b/searx/settings.yml index cf2b13e0..7a93bbff 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -703,9 +703,9 @@ engines: shortcut: vo categories: social media search_url : https://searchvoat.co/?t={query} - url_xpath : //div[@class="entry"]/p/a[contains(@class, "title")]/@href - title_xpath : //div[@class="entry"]/p/a[contains(@class, "title")] - content_xpath : //div[@class="entry"]/p/span[@class="domain"]/a/text() + url_xpath : //div[@class="entry"]//p[@class="title"]/a/@href + title_xpath : //div[@class="entry"]//p[@class="title"]/a/text() + content_xpath : //div[@class="entry"]//span[@class="domain"]/a/text() timeout : 10.0 disabled : True From 28c75c187f8ec9971e09cf002a2ea7028f515473 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 14:58:20 +0200 Subject: [PATCH 074/144] [fix] update query params sanitization - closes #722 --- searx/plugins/tracker_url_remover.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/searx/plugins/tracker_url_remover.py b/searx/plugins/tracker_url_remover.py index 630c8a63..a1ed0d05 100644 --- a/searx/plugins/tracker_url_remover.py +++ b/searx/plugins/tracker_url_remover.py @@ -17,10 +17,10 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. from flask_babel import gettext import re -from searx.url_utils import urlunparse +from searx.url_utils import urlunparse, parse_qsl, urlencode -regexes = {re.compile(r'utm_[^&]+&?'), - re.compile(r'(wkey|wemail)[^&]+&?'), +regexes = {re.compile(r'utm_[^&]+'), + re.compile(r'(wkey|wemail)[^&]*'), re.compile(r'&$')} name = gettext('Tracker URL remover') @@ -34,12 +34,18 @@ def on_result(request, search, result): if query == "": return True + parsed_query = parse_qsl(query) - for reg in regexes: - query = reg.sub('', query) + changed = False + for i,(param_name,_) in enumerate(list(parsed_query)): + for reg in regexes: + if reg.match(param_name): + parsed_query.pop(i) + changed = True + break - if query != result['parsed_url'].query: - result['parsed_url'] = result['parsed_url']._replace(query=query) - result['url'] = urlunparse(result['parsed_url']) + if changed: + result['parsed_url'] = result['parsed_url']._replace(query=urlencode(parsed_query)) + result['url'] = urlunparse(result['parsed_url']) return True From 27ed31c7a21c4468bc86aaf220e30315e366c425 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 15:00:54 +0200 Subject: [PATCH 075/144] [fix] add message to SearxParameterException - fixes #1722 --- searx/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/searx/exceptions.py b/searx/exceptions.py index c605ddca..0175acfa 100644 --- a/searx/exceptions.py +++ b/searx/exceptions.py @@ -28,5 +28,6 @@ class SearxParameterException(SearxException): else: message = 'Invalid value "' + value + '" for parameter ' + name super(SearxParameterException, self).__init__(message) + self.message = message self.parameter_name = name self.parameter_value = value From e74bdf84296a4173dd625e25b699196e87db8ac8 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 15:09:25 +0200 Subject: [PATCH 076/144] [fix] engine test --- tests/unit/engines/test_startpage.py | 123 ++++++--------------------- 1 file changed, 25 insertions(+), 98 deletions(-) diff --git a/tests/unit/engines/test_startpage.py b/tests/unit/engines/test_startpage.py index a4704ce2..ac445473 100644 --- a/tests/unit/engines/test_startpage.py +++ b/tests/unit/engines/test_startpage.py @@ -18,12 +18,9 @@ class TestStartpageEngine(SearxTestCase): self.assertIn('data', params) self.assertIn('query', params['data']) self.assertIn(query, params['data']['query']) - self.assertIn('with_language', params['data']) - self.assertIn('lang_fr', params['data']['with_language']) dicto['language'] = 'all' params = startpage.request(query, dicto) - self.assertNotIn('with_language', params['data']) def test_response(self): self.assertRaises(AttributeError, startpage.response, None) @@ -35,33 +32,32 @@ class TestStartpageEngine(SearxTestCase): self.assertEqual(startpage.response(response), []) html = """ -
  • -

    - - This should be the title +

    -

    - This should be the content. -

    -

    - www.speedtest.net/fr/ - - - - - Navigation avec Ixquick Proxy - - - - - Mis en surbrillance - -

    -
  • - """ + +

    This should be the content.

    +
    + """ # noqa response = mock.Mock(text=html.encode('utf-8')) results = startpage.response(response) self.assertEqual(type(results), list) @@ -69,72 +65,3 @@ class TestStartpageEngine(SearxTestCase): self.assertEqual(results[0]['title'], 'This should be the title') self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/') self.assertEqual(results[0]['content'], 'This should be the content.') - - html = """ -
  • -

    - - This should be the title - - -

    -

    - This should be the content. -

    -

    - www.speedtest.net/fr/ - - - - - Navigation avec Ixquick Proxy - - - - - Mis en surbrillance - -

    -
  • -
  • -

    - -

    -

    - This should be the content. -

    -

    - www.speedtest.net/fr/ - -

    -
  • -
  • -

    - - This should be the title - - -

    -

    - www.speedtest.net/fr/ - - - - - Navigation avec Ixquick Proxy - - - - - Mis en surbrillance - -

    -
  • - """ - response = mock.Mock(text=html.encode('utf-8')) - results = startpage.response(response) - self.assertEqual(type(results), list) - self.assertEqual(len(results), 1) - self.assertEqual(results[0]['content'], '') From ed1c1bdb047f9fe93026739b9dc074393e5c1d46 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Oct 2019 15:09:39 +0200 Subject: [PATCH 077/144] [fix] pep8 --- searx/engines/duckduckgo.py | 2 +- searx/engines/startpage.py | 1 - searx/plugins/tracker_url_remover.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/searx/engines/duckduckgo.py b/searx/engines/duckduckgo.py index 4c93380f..e77ef012 100644 --- a/searx/engines/duckduckgo.py +++ b/searx/engines/duckduckgo.py @@ -106,7 +106,7 @@ def response(resp): doc = fromstring(resp.text) # parse results - for i,r in enumerate(doc.xpath(result_xpath)): + for i, r in enumerate(doc.xpath(result_xpath)): if i >= 30: break try: diff --git a/searx/engines/startpage.py b/searx/engines/startpage.py index 868b964f..0f0ec6e1 100644 --- a/searx/engines/startpage.py +++ b/searx/engines/startpage.py @@ -51,7 +51,6 @@ def request(query, params): 'engine0': 'v1all', } - # set language if specified if params['language'] != 'all': language = 'english' diff --git a/searx/plugins/tracker_url_remover.py b/searx/plugins/tracker_url_remover.py index a1ed0d05..8cc063bb 100644 --- a/searx/plugins/tracker_url_remover.py +++ b/searx/plugins/tracker_url_remover.py @@ -37,7 +37,7 @@ def on_result(request, search, result): parsed_query = parse_qsl(query) changed = False - for i,(param_name,_) in enumerate(list(parsed_query)): + for i, (param_name, _) in enumerate(list(parsed_query)): for reg in regexes: if reg.match(param_name): parsed_query.pop(i) From c98a2df36d04e32d677309a32eabd3fbfa926111 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 13:12:17 +0200 Subject: [PATCH 078/144] [fix] enable paging support for arxiv engine --- searx/engines/arxiv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/searx/engines/arxiv.py b/searx/engines/arxiv.py index 18286189..e3c871d1 100644 --- a/searx/engines/arxiv.py +++ b/searx/engines/arxiv.py @@ -17,6 +17,7 @@ from searx.url_utils import urlencode categories = ['science'] +paging = True base_url = 'http://export.arxiv.org/api/query?search_query=all:'\ + '{query}&start={offset}&max_results={number_of_results}' From 6ca162237842c43ab055cd963f838374663d132f Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 13:27:05 +0200 Subject: [PATCH 079/144] [fix] update 1x engine --- searx/engines/www1x.py | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/searx/engines/www1x.py b/searx/engines/www1x.py index 50880324..f1154b16 100644 --- a/searx/engines/www1x.py +++ b/searx/engines/www1x.py @@ -11,8 +11,8 @@ """ from lxml import html -import re from searx.url_utils import urlencode, urljoin +from searx.engines.xpath import extract_text # engine dependent config categories = ['images'] @@ -34,41 +34,18 @@ def request(query, params): def response(resp): results = [] - # get links from result-text - regex = re.compile('(|', '"/>') - - dom = html.fromstring(cur_element) - link = dom.xpath('//a')[0] + link = res.xpath('//a')[0] url = urljoin(base_url, link.attrib.get('href')) - title = link.attrib.get('title', '') + title = extract_text(link) - thumbnail_src = urljoin(base_url, link.xpath('.//img')[0].attrib['src']) + thumbnail_src = urljoin(base_url, res.xpath('.//img')[0].attrib['src']) # TODO: get image with higher resolution img_src = thumbnail_src - # check if url is showing to a photo - if '/photo/' not in url: - continue - # append result results.append({'url': url, 'title': title, From 7177c9e12f238aeac66e450ee94c849c927dd407 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 14:42:31 +0200 Subject: [PATCH 080/144] [fix] update devianart engine --- searx/engines/deviantart.py | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/searx/engines/deviantart.py b/searx/engines/deviantart.py index bb85c6dc..97aa1e1b 100644 --- a/searx/engines/deviantart.py +++ b/searx/engines/deviantart.py @@ -24,7 +24,7 @@ time_range_support = True # search-url base_url = 'https://www.deviantart.com/' -search_url = base_url + 'browse/all/?offset={offset}&{query}' +search_url = base_url + 'search?page={page}&{query}' time_range_url = '&order={range}' time_range_dict = {'day': 11, @@ -37,9 +37,8 @@ def request(query, params): if params['time_range'] and params['time_range'] not in time_range_dict: return params - offset = (params['pageno'] - 1) * 24 - params['url'] = search_url.format(offset=offset, + params['url'] = search_url.format(page=params['pageno'], query=urlencode({'q': query})) if params['time_range'] in time_range_dict: params['url'] += time_range_url.format(range=time_range_dict[params['time_range']]) @@ -57,28 +56,28 @@ def response(resp): dom = html.fromstring(resp.text) - regex = re.compile(r'\/200H\/') # parse results - for result in dom.xpath('.//span[@class="thumb wide"]'): - link = result.xpath('.//a[@class="torpedo-thumb-link"]')[0] - url = link.attrib.get('href') - title = extract_text(result.xpath('.//span[@class="title"]')) - thumbnail_src = link.xpath('.//img')[0].attrib.get('src') - img_src = regex.sub('/', thumbnail_src) + for row in dom.xpath('//div[contains(@data-hook, "content_row")]'): + for result in row.xpath('./div'): + link = result.xpath('.//a[@data-hook="deviation_link"]')[0] + url = link.attrib.get('href') + title = link.attrib.get('title') + thumbnail_src = result.xpath('.//img')[0].attrib.get('src') + img_src = thumbnail_src - # http to https, remove domain sharding - thumbnail_src = re.sub(r"https?://(th|fc)\d+.", "https://th01.", thumbnail_src) - thumbnail_src = re.sub(r"http://", "https://", thumbnail_src) + # http to https, remove domain sharding + thumbnail_src = re.sub(r"https?://(th|fc)\d+.", "https://th01.", thumbnail_src) + thumbnail_src = re.sub(r"http://", "https://", thumbnail_src) - url = re.sub(r"http://(.*)\.deviantart\.com/", "https://\\1.deviantart.com/", url) + url = re.sub(r"http://(.*)\.deviantart\.com/", "https://\\1.deviantart.com/", url) - # append result - results.append({'url': url, - 'title': title, - 'img_src': img_src, - 'thumbnail_src': thumbnail_src, - 'template': 'images.html'}) + # append result + results.append({'url': url, + 'title': title, + 'img_src': img_src, + 'thumbnail_src': thumbnail_src, + 'template': 'images.html'}) # return results return results From 72459b246be8dbf7f237b48677b0dfecb60d43ef Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 14:52:57 +0200 Subject: [PATCH 081/144] [fix] convert bytes type to string in language detection (fixes dictzone) --- searx/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/searx/utils.py b/searx/utils.py index d88bc989..eb5da2fa 100644 --- a/searx/utils.py +++ b/searx/utils.py @@ -308,14 +308,15 @@ def int_or_zero(num): def is_valid_lang(lang): is_abbr = (len(lang) == 2) + lang = lang.lower().decode('utf-8') if is_abbr: for l in language_codes: - if l[0][:2] == lang.lower(): + if l[0][:2] == lang: return (True, l[0][:2], l[3].lower()) return False else: for l in language_codes: - if l[1].lower() == lang.lower(): + if l[1].lower() == lang or l[3].lower() == lang: return (True, l[0][:2], l[3].lower()) return False From 1057e42cfdc1e9ea4f49bc0da0af41de742db703 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 15:11:27 +0200 Subject: [PATCH 082/144] [fix] update digg engine --- searx/engines/digg.py | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/searx/engines/digg.py b/searx/engines/digg.py index 4369ccb8..073410eb 100644 --- a/searx/engines/digg.py +++ b/searx/engines/digg.py @@ -15,7 +15,8 @@ import string from dateutil import parser from json import loads from lxml import html -from searx.url_utils import quote_plus +from searx.url_utils import urlencode +from datetime import datetime # engine dependent config categories = ['news', 'social media'] @@ -23,7 +24,7 @@ paging = True # search-url base_url = 'https://digg.com/' -search_url = base_url + 'api/search/{query}.json?position={position}&format=html' +search_url = base_url + 'api/search/?{query}&from={position}&size=20&format=html' # specific xpath variables results_xpath = '//article' @@ -38,9 +39,9 @@ digg_cookie_chars = string.ascii_uppercase + string.ascii_lowercase +\ # do search-request def request(query, params): - offset = (params['pageno'] - 1) * 10 + offset = (params['pageno'] - 1) * 20 params['url'] = search_url.format(position=offset, - query=quote_plus(query)) + query=urlencode({'q': query})) params['cookies']['frontend.auid'] = ''.join(random.choice( digg_cookie_chars) for _ in range(22)) return params @@ -52,30 +53,17 @@ def response(resp): search_result = loads(resp.text) - if 'html' not in search_result or 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 = ''.join(result.xpath(content_xpath)) - pubdate = result.xpath(pubdate_xpath)[0].attrib.get('datetime') - publishedDate = parser.parse(pubdate) - - # http to https - thumbnail = thumbnail.replace("http://static.digg.com", "https://static.digg.com") + for result in search_result['mapped']: + published = datetime.strptime(result['created']['ISO'], "%Y-%m-%d %H:%M:%S") # append result - results.append({'url': url, - 'title': title, - 'content': content, + results.append({'url': result['url'], + 'title': result['title'], + 'content': result['excerpt'], 'template': 'videos.html', - 'publishedDate': publishedDate, - 'thumbnail': thumbnail}) + 'publishedDate': published, + 'thumbnail': result['images']['thumbImage']}) # return results return results From bbe4442a863b92767c71812acbe2ab72afaaeb54 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 15:27:37 +0200 Subject: [PATCH 083/144] [fix] update gigablast engine --- searx/engines/gigablast.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/searx/engines/gigablast.py b/searx/engines/gigablast.py index a6aa5d71..6b040223 100644 --- a/searx/engines/gigablast.py +++ b/searx/engines/gigablast.py @@ -35,8 +35,8 @@ search_string = 'search?{query}'\ '&ff={safesearch}'\ '&rxiec={rxieu}'\ '&ulse={ulse}'\ - '&rand={rxikd}' # current unix timestamp - + '&rand={rxikd}'\ + '&dbez={dbez}' # specific xpath variables results_xpath = '//response//result' url_xpath = './/url' @@ -70,7 +70,8 @@ def request(query, params): rxieu=random.randint(1000000000, 9999999999), ulse=random.randint(100000000, 999999999), lang=language, - safesearch=safesearch) + safesearch=safesearch, + dbez=random.randint(100000000, 999999999)) params['url'] = base_url + search_path From 7d8fd4b95eb6082d54cf1c8128dce580bf346893 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 15:32:03 +0200 Subject: [PATCH 084/144] [fix] pep8 --- searx/engines/deviantart.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/searx/engines/deviantart.py b/searx/engines/deviantart.py index 97aa1e1b..a0e27e62 100644 --- a/searx/engines/deviantart.py +++ b/searx/engines/deviantart.py @@ -37,7 +37,6 @@ def request(query, params): if params['time_range'] and params['time_range'] not in time_range_dict: return params - params['url'] = search_url.format(page=params['pageno'], query=urlencode({'q': query})) if params['time_range'] in time_range_dict: @@ -56,7 +55,6 @@ def response(resp): dom = html.fromstring(resp.text) - # parse results for row in dom.xpath('//div[contains(@data-hook, "content_row")]'): for result in row.xpath('./div'): From 3c425f09c14bbc297346271dac5b30af7a294b9e Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 15:32:21 +0200 Subject: [PATCH 085/144] [fix] remove useless engine tests --- tests/unit/engines/test_deviantart.py | 71 ---------------------- tests/unit/engines/test_digg.py | 85 --------------------------- tests/unit/engines/test_www1x.py | 43 -------------- 3 files changed, 199 deletions(-) diff --git a/tests/unit/engines/test_deviantart.py b/tests/unit/engines/test_deviantart.py index bd2cf182..a3115103 100644 --- a/tests/unit/engines/test_deviantart.py +++ b/tests/unit/engines/test_deviantart.py @@ -22,74 +22,3 @@ class TestDeviantartEngine(SearxTestCase): dicto['time_range'] = 'year' params = deviantart.request(query, dicto) self.assertEqual({}, params['url']) - - def test_response(self): - self.assertRaises(AttributeError, deviantart.response, None) - self.assertRaises(AttributeError, deviantart.response, []) - self.assertRaises(AttributeError, deviantart.response, '') - self.assertRaises(AttributeError, deviantart.response, '[]') - - response = mock.Mock(text='') - self.assertEqual(deviantart.response(response), []) - - response = mock.Mock(status_code=302) - self.assertEqual(deviantart.response(response), []) - - html = """ -
    - - - - - Title of image -
    - """ - response = mock.Mock(text=html) - results = deviantart.response(response) - self.assertEqual(type(results), list) - self.assertEqual(len(results), 1) - self.assertEqual(results[0]['title'], 'Title of image') - self.assertEqual(results[0]['url'], 'https://url.of.image') - self.assertNotIn('content', results[0]) - self.assertEqual(results[0]['thumbnail_src'], 'https://url.of.thumbnail') - - html = """ - - - - - - Test - - - - - - - Title of image - - - - 5 years ago - - in Animation - - - - More Like This - - - """ - response = mock.Mock(text=html) - results = deviantart.response(response) - self.assertEqual(type(results), list) - self.assertEqual(len(results), 0) diff --git a/tests/unit/engines/test_digg.py b/tests/unit/engines/test_digg.py index 6e7c9cc9..8bc4c67c 100644 --- a/tests/unit/engines/test_digg.py +++ b/tests/unit/engines/test_digg.py @@ -14,88 +14,3 @@ class TestDiggEngine(SearxTestCase): self.assertIn('url', params) self.assertIn(query, params['url']) self.assertIn('digg.com', params['url']) - - def test_response(self): - self.assertRaises(AttributeError, digg.response, None) - self.assertRaises(AttributeError, digg.response, []) - self.assertRaises(AttributeError, digg.response, '') - self.assertRaises(AttributeError, digg.response, '[]') - - response = mock.Mock(text='{}') - self.assertEqual(digg.response(response), []) - - response = mock.Mock(text='{"data": []}') - self.assertEqual(digg.response(response), []) - - json = """ - { - "status": "ok", - "num": 10, - "next_position": 20, - "html": "" - } - """ - json = json.replace('\r\n', '').replace('\n', '').replace('\r', '') - response = mock.Mock(text=json) - results = digg.response(response) - self.assertEqual(type(results), list) - self.assertEqual(len(results), 1) - self.assertEqual(results[0]['title'], 'Title of article') - self.assertEqual(results[0]['url'], 'http://url.of.link') - self.assertEqual(results[0]['thumbnail'], 'http://url.of.image.jpeg') - self.assertEqual(results[0]['content'], '') - - json = """ - { - "status": "error", - "num": 10, - "next_position": 20 - } - """ - response = mock.Mock(text=json) - results = digg.response(response) - self.assertEqual(type(results), list) - self.assertEqual(len(results), 0) diff --git a/tests/unit/engines/test_www1x.py b/tests/unit/engines/test_www1x.py index 9df8de6b..40f5200f 100644 --- a/tests/unit/engines/test_www1x.py +++ b/tests/unit/engines/test_www1x.py @@ -12,46 +12,3 @@ class TestWww1xEngine(SearxTestCase): self.assertTrue('url' in params) self.assertTrue(query in params['url']) self.assertTrue('1x.com' in params['url']) - - def test_response(self): - self.assertRaises(AttributeError, www1x.response, None) - self.assertRaises(AttributeError, www1x.response, []) - self.assertRaises(AttributeError, www1x.response, '') - self.assertRaises(AttributeError, www1x.response, '[]') - - response = mock.Mock(text='') - self.assertEqual(www1x.response(response), []) - html = """ - - - - - - - ] - > - - -
    Photos
    - - - - ]]>
    - """ - response = mock.Mock(text=html) - results = www1x.response(response) - self.assertEqual(type(results), list) - self.assertEqual(len(results), 1) - self.assertEqual(results[0]['url'], 'https://1x.com/photo/123456') - self.assertEqual(results[0]['thumbnail_src'], 'https://1x.com/images/user/testimage-123456.jpg') - self.assertEqual(results[0]['content'], '') - self.assertEqual(results[0]['template'], 'images.html') From 3c34e0c3a7d8adc8d5e1d4a38a8321383713be09 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Oct 2019 15:34:29 +0200 Subject: [PATCH 086/144] [mod] remove cloudflare protected digbt from engines list --- searx/settings.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/searx/settings.yml b/searx/settings.yml index 7a93bbff..835fbe5f 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -161,11 +161,12 @@ engines: weight : 2 disabled : True - - name : digbt - engine : digbt - shortcut : dbt - timeout : 6.0 - disabled : True +# cloudflare protected +# - name : digbt +# engine : digbt +# shortcut : dbt +# timeout : 6.0 +# disabled : True - name : digg engine : digg From a6f20caf32af463b57a026ee7cb7ed6317db6b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Mon, 23 Sep 2019 17:14:32 +0200 Subject: [PATCH 087/144] add initial support for offline engines && command engine --- searx/engines/__init__.py | 28 ++++--- searx/engines/soundcloud.py | 2 +- searx/engines/wolframalpha_noapi.py | 2 +- searx/plugins/https_rewrite.py | 3 + searx/plugins/oa_doi_rewrite.py | 3 + searx/plugins/tracker_url_remover.py | 3 + searx/results.py | 74 +++++++++++-------- searx/search.py | 74 +++++++++++++++---- searx/static/themes/courgette/css/style.css | 2 +- searx/static/themes/courgette/less/style.less | 4 + searx/static/themes/legacy/css/style.css | 2 +- searx/static/themes/legacy/less/style.less | 4 + .../courgette/result_templates/key-value.html | 13 ++++ .../legacy/result_templates/key-value.html | 13 ++++ searx/templates/oscar/macros.html | 10 ++- .../oscar/result_templates/key-value.html | 19 +++++ .../simple/result_templates/key-value.html | 11 +++ searx/utils.py | 15 ++++ searx/webapp.py | 7 +- 19 files changed, 228 insertions(+), 61 deletions(-) create mode 100644 searx/templates/courgette/result_templates/key-value.html create mode 100644 searx/templates/legacy/result_templates/key-value.html create mode 100644 searx/templates/oscar/result_templates/key-value.html create mode 100644 searx/templates/simple/result_templates/key-value.html diff --git a/searx/engines/__init__.py b/searx/engines/__init__.py index a10b1ccd..2585711a 100644 --- a/searx/engines/__init__.py +++ b/searx/engines/__init__.py @@ -27,7 +27,7 @@ from json import loads from requests import get from searx import settings from searx import logger -from searx.utils import load_module, match_language +from searx.utils import load_module, match_language, get_engine_from_settings logger = logger.getChild('engines') @@ -53,7 +53,8 @@ engine_default_args = {'paging': False, 'disabled': False, 'suspend_end_time': 0, 'continuous_errors': 0, - 'time_range_support': False} + 'time_range_support': False, + 'offline': False} def load_engine(engine_data): @@ -128,14 +129,17 @@ def load_engine(engine_data): engine.stats = { 'result_count': 0, 'search_count': 0, - 'page_load_time': 0, - 'page_load_count': 0, 'engine_time': 0, 'engine_time_count': 0, 'score_count': 0, 'errors': 0 } + if not engine.offline: + engine.stats['page_load_time'] = 0 + engine.stats['page_load_count'] = 0 + + for category_name in engine.categories: categories.setdefault(category_name, []).append(engine) @@ -173,11 +177,6 @@ def get_engines_stats(): results_num = \ engine.stats['result_count'] / float(engine.stats['search_count']) - if engine.stats['page_load_count'] != 0: - load_times = engine.stats['page_load_time'] / float(engine.stats['page_load_count']) # noqa - else: - load_times = 0 - if engine.stats['engine_time_count'] != 0: this_engine_time = engine.stats['engine_time'] / float(engine.stats['engine_time_count']) # noqa else: @@ -189,14 +188,19 @@ def get_engines_stats(): else: score = score_per_result = 0.0 - max_pageload = max(load_times, max_pageload) + if not engine.offline: + load_times = 0 + if engine.stats['page_load_count'] != 0: + load_times = engine.stats['page_load_time'] / float(engine.stats['page_load_count']) # noqa + max_pageload = max(load_times, max_pageload) + pageloads.append({'avg': load_times, 'name': engine.name}) + max_engine_times = max(this_engine_time, max_engine_times) max_results = max(results_num, max_results) max_score = max(score, max_score) max_score_per_result = max(score_per_result, max_score_per_result) max_errors = max(max_errors, engine.stats['errors']) - pageloads.append({'avg': load_times, 'name': engine.name}) engine_times.append({'avg': this_engine_time, 'name': engine.name}) results.append({'avg': results_num, 'name': engine.name}) scores.append({'avg': score, 'name': engine.name}) @@ -255,7 +259,7 @@ def initialize_engines(engine_list): load_engines(engine_list) def engine_init(engine_name, init_fn): - init_fn() + init_fn(get_engine_from_settings(engine_name)) logger.debug('%s engine: Initialized', engine_name) for engine_name, engine in engines.items(): diff --git a/searx/engines/soundcloud.py b/searx/engines/soundcloud.py index 3ba9a7f3..87099854 100644 --- a/searx/engines/soundcloud.py +++ b/searx/engines/soundcloud.py @@ -66,7 +66,7 @@ def get_client_id(): return "" -def init(): +def init(engine_settings=None): global guest_client_id # api-key guest_client_id = get_client_id() diff --git a/searx/engines/wolframalpha_noapi.py b/searx/engines/wolframalpha_noapi.py index 2cbbc5ad..387c9fa1 100644 --- a/searx/engines/wolframalpha_noapi.py +++ b/searx/engines/wolframalpha_noapi.py @@ -55,7 +55,7 @@ def obtain_token(): return token -def init(): +def init(engine_settings=None): obtain_token() diff --git a/searx/plugins/https_rewrite.py b/searx/plugins/https_rewrite.py index 3d986770..82556017 100644 --- a/searx/plugins/https_rewrite.py +++ b/searx/plugins/https_rewrite.py @@ -225,6 +225,9 @@ def https_url_rewrite(result): def on_result(request, search, result): + if 'parsed_url' not in result: + return True + if result['parsed_url'].scheme == 'http': https_url_rewrite(result) return True diff --git a/searx/plugins/oa_doi_rewrite.py b/searx/plugins/oa_doi_rewrite.py index d4942498..be80beb2 100644 --- a/searx/plugins/oa_doi_rewrite.py +++ b/searx/plugins/oa_doi_rewrite.py @@ -35,6 +35,9 @@ def get_doi_resolver(args, preference_doi_resolver): def on_result(request, search, result): + if 'parsed_url' not in result: + return True + doi = extract_doi(result['parsed_url']) if doi and len(doi) < 50: for suffix in ('/', '.pdf', '/full', '/meta', '/abstract'): diff --git a/searx/plugins/tracker_url_remover.py b/searx/plugins/tracker_url_remover.py index 8cc063bb..9e18867b 100644 --- a/searx/plugins/tracker_url_remover.py +++ b/searx/plugins/tracker_url_remover.py @@ -30,6 +30,9 @@ preference_section = 'privacy' def on_result(request, search, result): + if 'parsed_url' not in result: + return True + query = result['parsed_url'].query if query == "": diff --git a/searx/results.py b/searx/results.py index be74a836..a127024c 100644 --- a/searx/results.py +++ b/searx/results.py @@ -197,6 +197,13 @@ class ResultContainer(object): self.infoboxes.append(infobox) def _merge_result(self, result, position): + if 'url' in result: + self.__merge_url_result(result, position) + return + + self.__merge_result_no_url(result, position) + + def __merge_url_result(self, result, position): result['parsed_url'] = urlparse(result['url']) # if the result has no scheme, use http as default @@ -210,51 +217,60 @@ class ResultContainer(object): if result.get('content'): result['content'] = WHITESPACE_REGEX.sub(' ', result['content']) - # check for duplicates - duplicated = False + duplicated = self.__find_duplicated_http_result(result) + if duplicated: + self.__merge_duplicated_http_result(duplicated, result, position) + return + + # if there is no duplicate found, append result + result['positions'] = [position] + with RLock(): + self._merged_results.append(result) + + def __find_duplicated_http_result(self, result): result_template = result.get('template') for merged_result in self._merged_results: + if 'parsed_url' not in merged_result: + continue if compare_urls(result['parsed_url'], merged_result['parsed_url'])\ and result_template == merged_result.get('template'): if result_template != 'images.html': # not an image, same template, same url : it's a duplicate - duplicated = merged_result - break + return merged_result else: # it's an image # it's a duplicate if the parsed_url, template and img_src are differents if result.get('img_src', '') == merged_result.get('img_src', ''): - duplicated = merged_result - break + return merged_result + return None - # merge duplicates together - if duplicated: - # using content with more text - if result_content_len(result.get('content', '')) >\ - result_content_len(duplicated.get('content', '')): - duplicated['content'] = result['content'] + def __merge_duplicated_http_result(self, duplicated, result, position): + # using content with more text + if result_content_len(result.get('content', '')) >\ + result_content_len(duplicated.get('content', '')): + duplicated['content'] = result['content'] - # merge all result's parameters not found in duplicate - for key in result.keys(): - if not duplicated.get(key): - duplicated[key] = result.get(key) + # merge all result's parameters not found in duplicate + for key in result.keys(): + if not duplicated.get(key): + duplicated[key] = result.get(key) - # add the new position - duplicated['positions'].append(position) + # add the new position + duplicated['positions'].append(position) - # add engine to list of result-engines - duplicated['engines'].add(result['engine']) + # add engine to list of result-engines + duplicated['engines'].add(result['engine']) - # using https if possible - if duplicated['parsed_url'].scheme != 'https' and result['parsed_url'].scheme == 'https': - duplicated['url'] = result['parsed_url'].geturl() - duplicated['parsed_url'] = result['parsed_url'] + # using https if possible + if duplicated['parsed_url'].scheme != 'https' and result['parsed_url'].scheme == 'https': + duplicated['url'] = result['parsed_url'].geturl() + duplicated['parsed_url'] = result['parsed_url'] - # if there is no duplicate found, append result - else: - result['positions'] = [position] - with RLock(): - self._merged_results.append(result) + def __merge_result_no_url(self, result, position): + result['engines'] = set([result['engine']]) + result['positions'] = [position] + with RLock(): + self._merged_results.append(result) def order_results(self): for result in self._merged_results: diff --git a/searx/search.py b/searx/search.py index 9c7142c7..45f54bb0 100644 --- a/searx/search.py +++ b/searx/search.py @@ -77,7 +77,7 @@ def send_http_request(engine, request_params): return req(request_params['url'], **request_args) -def search_one_request(engine, query, request_params): +def search_one_http_request(engine, query, request_params): # update request parameters dependent on # search-engine (contained in engines folder) engine.request(query, request_params) @@ -97,7 +97,53 @@ def search_one_request(engine, query, request_params): return engine.response(response) +def search_one_offline_request(engine, query, request_params): + return engine.search(query, request_params) + + def search_one_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit): + if engines[engine_name].offline: + return search_one_offline_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit) + return search_one_http_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit) + + +def search_one_offline_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit): + engine = engines[engine_name] + + try: + search_results = search_one_offline_request(engine, query, request_params) + + if search_results: + result_container.extend(engine_name, search_results) + + engine_time = time() - start_time + result_container.add_timing(engine_name, engine_time, engine_time) + with threading.RLock(): + engine.stats['engine_time'] += engine_time + engine.stats['engine_time_count'] += 1 + + except ValueError as e: + record_offline_engine_stats_on_error(engine, result_container, start_time) + logger.exception('engine {0} : invalid input : {1}'.format(engine_name, e)) + except Exception as e: + record_offline_engine_stats_on_error(engine, result_container, start_time) + + result_container.add_unresponsive_engine(( + engine_name, + u'{0}: {1}'.format(gettext('unexpected crash'), e), + )) + logger.exception('engine {0} : exception : {1}'.format(engine_name, e)) + + +def record_offline_engine_stats_on_error(engine, result_container, start_time): + engine_time = time() - start_time + result_container.add_timing(engine.name, engine_time, engine_time) + + with threading.RLock(): + engine.stats['errors'] += 1 + + +def search_one_http_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit): # set timeout for all HTTP requests requests_lib.set_timeout_for_thread(timeout_limit, start_time=start_time) # reset the HTTP total time @@ -111,7 +157,7 @@ def search_one_request_safe(engine_name, query, request_params, result_container try: # send requests and parse the results - search_results = search_one_request(engine, query, request_params) + search_results = search_one_http_request(engine, query, request_params) # check if the engine accepted the request if search_results is not None: @@ -427,20 +473,22 @@ class Search(object): continue # set default request parameters - request_params = default_request_params() - request_params['headers']['User-Agent'] = user_agent + request_params = {} + if not engine.offline: + request_params = default_request_params() + request_params['headers']['User-Agent'] = user_agent + + if hasattr(engine, 'language') and engine.language: + request_params['language'] = engine.language + else: + request_params['language'] = search_query.lang + + request_params['safesearch'] = search_query.safesearch + request_params['time_range'] = search_query.time_range + request_params['category'] = selected_engine['category'] request_params['pageno'] = search_query.pageno - if hasattr(engine, 'language') and engine.language: - request_params['language'] = engine.language - else: - request_params['language'] = search_query.lang - - # 0 = None, 1 = Moderate, 2 = Strict - request_params['safesearch'] = search_query.safesearch - request_params['time_range'] = search_query.time_range - # append request to list requests.append((selected_engine['name'], search_query.query, request_params)) diff --git a/searx/static/themes/courgette/css/style.css b/searx/static/themes/courgette/css/style.css index ad5d233f..508c4b60 100644 --- a/searx/static/themes/courgette/css/style.css +++ b/searx/static/themes/courgette/css/style.css @@ -1 +1 @@ -a,h2{color:#666}.center,html{position:relative}#categories_container>div,.top_margin a{display:inline-block}#categories,.center{text-align:center}#categories .hidden,.cache_link,.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]{-webkit-appearance:textfield}h2{text-transform:uppercase}body{font-family:sans-serif;line-height:1.5;margin:0;background:#EEE}html{min-height:100%}.title h1{font-size:7em;color:#3498DB;margin:-20px auto 0;line-height:100px;padding-bottom:20px}.center{max-width:70em;background:rgba(255,255,255,.6);padding:2em;margin:7% auto 0}.center.search{position:static;width:auto;background:0 0;margin:auto;padding-top:1.8em}@media screen and (min-width:1001px){.center:after{content:"";z-index:-1;background:url(../img/bg-body-index.jpg) no-repeat;background-size:cover;width:100%;height:100%;top:0;left:0;position:fixed}.center.search:after{content:none}}.autocompleter-choices{position:absolute;margin:0;padding:0;background:#FFF}.autocompleter-choices li{padding:.5em 1em}.autocompleter-choices li:hover{background:#3498DB;color:#FFF;cursor:pointer}.top_margin{position:absolute;bottom:-3.5em;width:100%;left:0}.top_margin a{margin-right:1em;color:#FFF;text-decoration:none}.top_margin a:focus,.top_margin a:hover{text-decoration:underline}@media screen and (max-width:1000px){.center{background:0 0}.top_margin a{color:#333}}.checkbox_container{margin-top:1.5em}.checkbox_container label{padding:.5em 1em;color:#333;cursor:pointer;font-size:.9em}.checkbox_container input[type=checkbox]:checked+label,.checkbox_container label:hover{background:#3498DB;color:#FFF}.checkbox_container input[type=checkbox]{position:absolute;top:-9999px}#categories .hidden{display:none;position:absolute;bottom:1em;left:0;text-align:center;width:100%;font-size:.9em;color:#333}#categories:hover .hidden,.right a{display:block}@media screen and (max-width:900px){#categories_container{letter-spacing:-5px}#categories_container>div{letter-spacing:normal;margin-top:1em}.checkbox_container{margin:0}.checkbox_container label{display:block;background:#CCC;padding:1em;border:1px solid #FFF}.top_margin{position:static}#categories .hidden{position:static;display:block}}@media screen and (max-width:900px) and (min-width:501px){#categories_container>div{width:31%;margin-left:2.333%}#categories_container>div:nth-child(3n+1){margin-left:0}}@media screen and (max-width:500px){#categories_container>div{width:48%;margin-left:2%;font-size:.9em}#categories_container>div:nth-child(2n+1){margin-left:0}.title h1{background:url(../img/searx-mobile.png) no-repeat;width:200px;height:39px}}#search_wrapper{position:relative}.q{padding:.5em 3em .5em 1em;width:100%;font-size:1.5em;border:0;color:#666}.cache_link,.result p{font-size:.9em}#search_submit{position:absolute;top:0;right:0;border:0;background:url(../img/search-icon.png) center center/65% auto no-repeat #3498DB;text-indent:-9999px;width:5em;height:100%;cursor:pointer}#sidebar,.right{position:fixed;width:15em;right:0;text-align:right}#search_submit:focus,#search_submit:hover{background-color:#0665A2}#sidebar{background:#3498DB;top:0;height:100%;padding:1.5em}.right{bottom:1.5em;z-index:1;padding:0 1.5em}.right a{color:#FFF;text-decoration:none}#sidebar form,#suggestions form,.row fieldset{display:inline-block}.right a:focus,.right a:hover{text-decoration:underline}#preferences{background:url(../img/preference-icon.png) right center/12% auto no-repeat;padding-right:1.8em}#search_url input{border:0;padding:.5em}#sidebar>div{margin-bottom:1em;color:#FFF}#sidebar input[type=submit]{background:#CCC;border:0;padding:.5em 1em;cursor:pointer;margin-top:.5em}#sidebar input[type=submit]:focus,#sidebar input[type=submit]:hover{color:#FFF;background-color:#0665A2}#results{padding:0 17em 0 2em}.result .content{margin:0;color:#666}.result .url{margin-top:0;color:#FF6530}.result .favicon{float:left;position:relative;top:.5em;margin-right:.5em}.definition_result{background:#CCC;padding:1em}.definition_result .result_title,.definition_result p{margin:0}.result_title{margin-bottom:0;font-weight:400}.result_title a{color:#3498DB;text-decoration:none}#answers,#suggestions span{color:#666}.result_title a:focus,.result_title a:hover{text-decoration:underline}.cache_link{color:#666}.search.center{padding-right:17em}#answers{border:2px solid #3498DB;padding:20px;text-align:center;max-width:70em;margin:0 auto 20px}#suggestions{margin-bottom:1em}#suggestions form{vertical-align:top;margin-bottom:.5em}#suggestions input[type=submit]{color:#333;padding:.5em 1em;border:0;background:#CCC;cursor:pointer}#suggestions input[type=submit]:focus,#suggestions input[type=submit]:hover{background:#3498DB;color:#FFF}#pagination{margin:1.5em 0 2em}#pagination form+form{float:right;margin-top:-2em}input[type=submit]{display:inline-block;background:#3498DB;color:#FFF;border:0;padding:.6em 1em;cursor:pointer}input[type=submit]:focus,input[type=submit]:hover{background:#0665A2}.row{max-width:60em;margin:auto}.row a{color:#3498DB}.row form{letter-spacing:-5px}.row form>*{letter-spacing:normal}.row p{margin:0}.row fieldset{width:48%;vertical-align:top}.row fieldset:last-of-type{display:block;width:auto;background:0 0;padding:0}fieldset,table tr:nth-child(odd){background:#CCC}.row fieldset:nth-child(odd){margin-right:2%}.row fieldset:nth-child(2){min-height:10.5em}@media screen and (max-width:900px){.row{margin:0 1em}.row fieldset{width:49%}.row fieldset,.row fieldset:nth-child(odd){margin-right:0}.row fieldset:first-child{width:100%;margin-right:0}.row fieldset:nth-child(even){margin-right:2%}}@media screen and (max-width:800px){.row fieldset,select{width:100%}table{font-size:.8em}#sidebar,.right{display:none}#results{padding:0 2em}.search.center{padding-right:2em}}@media screen and (max-width:400px){.row #categories_container>div{width:100%;margin-left:0}}fieldset{border:0;margin:1em 0;padding:1.5em}table{width:100%;text-align:left;border:1px solid #CCC;border-collapse:collapse}table th{background:#999;color:#FFF}table td,table th{padding:.5em 1em;border:1px solid #FFF}.engine_checkbox label{padding:.5em;background:#3498DB;color:#FFF;cursor:pointer}.engine_checkbox .deny{background:#3498DB}.engine_checkbox .allow{display:none;background:#666}.engine_checkbox input{display:none}.engine_checkbox input:checked+.allow{display:inline}.engine_checkbox input:checked+.allow+.deny{display:none}.row input[type=submit]{font-size:1em;margin:1em 0 2em}.row .right{position:static;display:inline-block}.row .right a{color:#333;width:auto;text-align:left;padding:0}.small_font{font-size:.8em}table th{padding:1em}legend{background:#EEE;padding:0 1em;position:relative}select{border:1px solid #DDD;padding:.5em .8em;font-size:1em}.highlight .hll{background-color:#ffc}.highlight{font-weight:700;background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.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,.highlight .kd,.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,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{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,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.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} \ No newline at end of file +a,h2{color:#666}.center,html{position:relative}#categories_container>div,.top_margin a{display:inline-block}#categories,.center{text-align:center}#categories .hidden,.cache_link,.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]{-webkit-appearance:textfield}h2{text-transform:uppercase}body{font-family:sans-serif;line-height:1.5;margin:0;background:#EEE}html{min-height:100%}.title h1{font-size:7em;color:#3498DB;margin:-20px auto 0;line-height:100px;padding-bottom:20px}.center{max-width:70em;background:rgba(255,255,255,.6);padding:2em;margin:7% auto 0}.center.search{position:static;width:auto;background:0 0;margin:auto;padding-top:1.8em}@media screen and (min-width:1001px){.center:after{content:"";z-index:-1;background:url(../img/bg-body-index.jpg) no-repeat;background-size:cover;width:100%;height:100%;top:0;left:0;position:fixed}.center.search:after{content:none}}.autocompleter-choices{position:absolute;margin:0;padding:0;background:#FFF}.autocompleter-choices li{padding:.5em 1em}.autocompleter-choices li:hover{background:#3498DB;color:#FFF;cursor:pointer}.top_margin{position:absolute;bottom:-3.5em;width:100%;left:0}.top_margin a{margin-right:1em;color:#FFF;text-decoration:none}.top_margin a:focus,.top_margin a:hover{text-decoration:underline}@media screen and (max-width:1000px){.center{background:0 0}.top_margin a{color:#333}}.checkbox_container{margin-top:1.5em}.checkbox_container label{padding:.5em 1em;color:#333;cursor:pointer;font-size:.9em}.checkbox_container input[type=checkbox]:checked+label,.checkbox_container label:hover{background:#3498DB;color:#FFF}.checkbox_container input[type=checkbox]{position:absolute;top:-9999px}#categories .hidden{display:none;position:absolute;bottom:1em;left:0;text-align:center;width:100%;font-size:.9em;color:#333}#categories:hover .hidden,.right a{display:block}@media screen and (max-width:900px){#categories_container{letter-spacing:-5px}#categories_container>div{letter-spacing:normal;margin-top:1em}.checkbox_container{margin:0}.checkbox_container label{display:block;background:#CCC;padding:1em;border:1px solid #FFF}.top_margin{position:static}#categories .hidden{position:static;display:block}}@media screen and (max-width:900px) and (min-width:501px){#categories_container>div{width:31%;margin-left:2.333%}#categories_container>div:nth-child(3n+1){margin-left:0}}@media screen and (max-width:500px){#categories_container>div{width:48%;margin-left:2%;font-size:.9em}#categories_container>div:nth-child(2n+1){margin-left:0}.title h1{background:url(../img/searx-mobile.png) no-repeat;width:200px;height:39px}}#search_wrapper{position:relative}.q{padding:.5em 3em .5em 1em;width:100%;font-size:1.5em;border:0;color:#666}.cache_link,.result p{font-size:.9em}#search_submit{position:absolute;top:0;right:0;border:0;background:url(../img/search-icon.png) center center/65% auto no-repeat #3498DB;text-indent:-9999px;width:5em;height:100%;cursor:pointer}#sidebar,.right{position:fixed;width:15em;right:0;text-align:right}#search_submit:focus,#search_submit:hover{background-color:#0665A2}#sidebar{background:#3498DB;top:0;height:100%;padding:1.5em}.right{bottom:1.5em;z-index:1;padding:0 1.5em}.right a{color:#FFF;text-decoration:none}#sidebar form,#suggestions form,.row fieldset{display:inline-block}.right a:focus,.right a:hover{text-decoration:underline}#preferences{background:url(../img/preference-icon.png) right center/12% auto no-repeat;padding-right:1.8em}#search_url input{border:0;padding:.5em}#sidebar>div{margin-bottom:1em;color:#FFF}#sidebar input[type=submit]{background:#CCC;border:0;padding:.5em 1em;cursor:pointer;margin-top:.5em}#sidebar input[type=submit]:focus,#sidebar input[type=submit]:hover{color:#FFF;background-color:#0665A2}#results{padding:0 17em 0 2em}.result .engines{text-align:right}.result .content{margin:0;color:#666}.result .url{margin-top:0;color:#FF6530}.result .favicon{float:left;position:relative;top:.5em;margin-right:.5em}.definition_result{background:#CCC;padding:1em}.definition_result .result_title,.definition_result p{margin:0}.result_title{margin-bottom:0;font-weight:400}.result_title a{color:#3498DB;text-decoration:none}#answers,#suggestions span{color:#666}.result_title a:focus,.result_title a:hover{text-decoration:underline}.cache_link{color:#666}.search.center{padding-right:17em}#answers{border:2px solid #3498DB;padding:20px;text-align:center;max-width:70em;margin:0 auto 20px}#suggestions{margin-bottom:1em}#suggestions form{vertical-align:top;margin-bottom:.5em}#suggestions input[type=submit]{color:#333;padding:.5em 1em;border:0;background:#CCC;cursor:pointer}#suggestions input[type=submit]:focus,#suggestions input[type=submit]:hover{background:#3498DB;color:#FFF}#pagination{margin:1.5em 0 2em}#pagination form+form{float:right;margin-top:-2em}input[type=submit]{display:inline-block;background:#3498DB;color:#FFF;border:0;padding:.6em 1em;cursor:pointer}input[type=submit]:focus,input[type=submit]:hover{background:#0665A2}.row{max-width:60em;margin:auto}.row a{color:#3498DB}.row form{letter-spacing:-5px}.row form>*{letter-spacing:normal}.row p{margin:0}.row fieldset{width:48%;vertical-align:top}.row fieldset:last-of-type{display:block;width:auto;background:0 0;padding:0}fieldset,table tr:nth-child(odd){background:#CCC}.row fieldset:nth-child(odd){margin-right:2%}.row fieldset:nth-child(2){min-height:10.5em}@media screen and (max-width:900px){.row{margin:0 1em}.row fieldset{width:49%}.row fieldset,.row fieldset:nth-child(odd){margin-right:0}.row fieldset:first-child{width:100%;margin-right:0}.row fieldset:nth-child(even){margin-right:2%}}@media screen and (max-width:800px){.row fieldset,select{width:100%}table{font-size:.8em}#sidebar,.right{display:none}#results{padding:0 2em}.search.center{padding-right:2em}}@media screen and (max-width:400px){.row #categories_container>div{width:100%;margin-left:0}}fieldset{border:0;margin:1em 0;padding:1.5em}table{width:100%;text-align:left;border:1px solid #CCC;border-collapse:collapse}table th{background:#999;color:#FFF}table td,table th{padding:.5em 1em;border:1px solid #FFF}.engine_checkbox label{padding:.5em;background:#3498DB;color:#FFF;cursor:pointer}.engine_checkbox .deny{background:#3498DB}.engine_checkbox .allow{display:none;background:#666}.engine_checkbox input{display:none}.engine_checkbox input:checked+.allow{display:inline}.engine_checkbox input:checked+.allow+.deny{display:none}.row input[type=submit]{font-size:1em;margin:1em 0 2em}.row .right{position:static;display:inline-block}.row .right a{color:#333;width:auto;text-align:left;padding:0}.small_font{font-size:.8em}table th{padding:1em}legend{background:#EEE;padding:0 1em;position:relative}select{border:1px solid #DDD;padding:.5em .8em;font-size:1em}.highlight .hll{background-color:#ffc}.highlight{font-weight:700;background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.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,.highlight .kd,.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,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{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,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.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} \ No newline at end of file diff --git a/searx/static/themes/courgette/less/style.less b/searx/static/themes/courgette/less/style.less index 0387af5c..26da7281 100644 --- a/searx/static/themes/courgette/less/style.less +++ b/searx/static/themes/courgette/less/style.less @@ -325,6 +325,10 @@ a { font-size: 0.9em; } +.result .engines { + text-align: right; +} + .result .content { margin: 0; color: #666; diff --git a/searx/static/themes/legacy/css/style.css b/searx/static/themes/legacy/css/style.css index f434148b..ca746a36 100644 --- a/searx/static/themes/legacy/css/style.css +++ b/searx/static/themes/legacy/css/style.css @@ -1 +1 @@ -.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}#categories,.highlight .lineno{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#container,.search,body,html{padding:0;margin:0}div.title h1,input[type=checkbox]{visibility:hidden}#categories,.checkbox_container label,.engine_checkbox label,.highlight .lineno{-webkit-touch-callout:none;-khtml-user-select:none}#answers input[type=submit],#infoboxes input[type=submit],#sidebar input[type=submit],#suggestions input[type=submit],.result_title a:hover,.torrent_result a:hover{text-decoration:underline}#infoboxes,.result .content,.result .url,.result h3{word-wrap:break-word}#apis,#infoboxes .infobox br,#pagination,#pagination br,.result,.result .content br.last{clear:both}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.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,.highlight .kd,.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,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{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,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}html{font-family:sans-serif;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444}#container{width:100%;position:absolute;top:0}.search .checkbox_container label{font-size:.9em;border-bottom:2px solid #E8E7E6}.search .checkbox_container label:hover{border-bottom:2px solid #3498DB}.search .checkbox_container input[type=checkbox]:checked+label{border-bottom:2px solid #2980B9}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q,ul.autocompleter-choices{margin:0;border:1px solid #3498DB}.q{background:#FFF;color:#222;font-size:16px;height:28px;outline:0;padding:2px 2px 2px 8px;padding-right:0!important;width:100%;z-index:2}#search_submit{position:absolute;top:13px;right:1px;padding:0;border:0;background:url(../img/search-icon.png) no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:30px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}ul.autocompleter-choices{position:absolute;padding:0;list-style:none;border-left-color:#3498DB;border-right-color:#3498DB;border-bottom-color:#3498DB;text-align:left;font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;z-index:50;background-color:#FFF;color:#444}ul.autocompleter-choices li{position:relative;margin:-2px 0 0;padding:.2em 1.5em .2em 1em;display:block;float:none!important;cursor:pointer;font-weight:400;white-space:nowrap;font-size:1em;line-height:1.5em}ul.autocompleter-choices li.autocompleter-selected{background-color:#444;color:#FFF}ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried{color:#9FCFFF}ul.autocompleter-choices span.autocompleter-queried{display:inline;float:none;font-weight:700;margin:0;padding:0}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498DB;padding:4px 10px}a:active.hmarg,a:hover.hmarg,a:link.hmarg,a:visited.hmarg{color:#3498DB}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url(../img/searx.png) center no-repeat;width:100%;min-height:80px}input[type=submit]{padding:2px 6px;margin:2px 4px;display:inline-block;background:#3498DB;color:#FFF;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}fieldset{margin:8px;border:1px solid #3498DB}#categories{margin:0 10px;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type=checkbox]:checked+label{background:#3498DB;color:#FFF}.engine_checkbox{padding:4px}label.allow{background:#E74C3C;padding:4px 8px;color:#FFF;display:none}label.deny{background:#2ECC71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type=checkbox]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type=checkbox]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8E44AD}.result{margin:19px 0 18px;padding:0}.result_title{margin-bottom:0}.result_title a{color:#2980B9;font-weight:400;font-size:1.1em}.result_title a:visited{color:#8E44AD}.cache_link{font-size:10px!important}.result h3{font-size:1em;margin:5px 0 1px;padding:0}.result .content,.result .url,.small_font{font-size:.8em}.result .content{margin:0;padding:0;max-width:54em;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}.result .url{margin:0 0 3px;padding:0;max-width:54em;color:#C0392B}.result .published_date{font-size:.8em;color:#888;Margin:5px 20px}.result .thumbnail{width:400px}.engines{color:#888}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.image_result{display:inline-block;margin:10px;position:relative;max-height:160px}.image_result img{border:0;max-height:160px}.image_result p{margin:0;padding:0}.image_result p span a{display:none;color:#FFF}.image_result p:hover span a{display:block;position:absolute;bottom:0;right:0;padding:4px;background-color:rgba(0,0,0,.6);font-size:.7em}#categories_container,.percentage{position:relative}.torrent_result{border-left:10px solid #d3d3d3;padding-left:3px}.torrent_result p{margin:3px;font-size:.8em}.torrent_result a{color:#2980B9}.torrent_result a:visited{color:#8E44AD}.definition_result{border-left:10px solid gray;padding-left:3px}#infoboxes,#sidebar{margin:0 2px 5px 5px;padding:0 2px 2px}.percentage{width:300px}.percentage div{background:#444}table{width:100%}td{padding:0 4px}tr:hover{background:#DDD}#results{margin:auto auto 20px;padding:0;width:50em}#sidebar{position:fixed;bottom:10px;left:10px;width:14em}#answers input,#infoboxes input,#sidebar input,#suggestions input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:0 0;color:#444;cursor:pointer}#suggestions form{display:inline}#answers,#suggestions{margin-top:20px;max-width:45em}#suggestions-title{color:#888}#answers{border:2px solid #2980B9;padding:20px}#answers form,#infoboxes form{min-width:210px}#infoboxes{position:absolute;top:100px;right:20px;max-width:21em}#infoboxes .infobox{margin:10px 0;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:90%;max-heigt:12em;display:block;margin:5px;padding:5px}#infoboxes .infobox h2{margin:0}#apis,#search_url{margin-top:8px}#infoboxes .infobox table{table-layout:fixed}#infoboxes .infobox table td{vertical-align:top}#infoboxes .infobox input{font-size:1em}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url(../img/preference-icon.png) no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.github{display:none}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed!important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#answers,#suggestions{margin-top:5px}#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin:auto}#sidebar{position:static;max-width:50em;margin:0 0 2px;padding:0;float:none;border:none;width:auto}#sidebar input{border:0}#apis,#search_url{display:none}.result{border-top:1px solid #E8E7E6;margin:8px 0}.image_result,.image_result img,.result .thumbnail{max-width:98%}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:#3498DB;border:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#FFF}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:#fff;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8} \ No newline at end of file +.highlight .c,.highlight .cm,.highlight .ge,.highlight .sd{font-style:italic}#categories,.highlight .lineno{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#container,.search,body,html{padding:0;margin:0}div.title h1,input[type=checkbox]{visibility:hidden}#categories,.checkbox_container label,.engine_checkbox label,.highlight .lineno{-webkit-touch-callout:none;-khtml-user-select:none}#answers input[type=submit],#infoboxes input[type=submit],#sidebar input[type=submit],#suggestions input[type=submit],.result_title a:hover,.torrent_result a:hover{text-decoration:underline}#infoboxes,.result .content,.result .url,.result h3{word-wrap:break-word}#apis,#infoboxes .infobox br,#pagination,#pagination br,.result,.result .content br.last{clear:both}.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080}.highlight .err{border:1px solid red}.highlight .k{color:green;font-weight:700}.highlight .o{color:#666}.highlight .cm{color:#408080}.highlight .cp{color:#BC7A00}.highlight .c1,.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#A00000}.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,.highlight .kd,.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,.highlight .mh,.highlight .mi,.highlight .mo{color:#666}.highlight .s2,.highlight .sb,.highlight .sc{color:#BA2121}.highlight .sd{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,.highlight .vg,.highlight .vi{color:#19177C}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{user-select:none;cursor:default}.highlight .lineno::selection{background:0 0}.highlight .lineno::-moz-selection{background:0 0}html{font-family:sans-serif;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444}#container{width:100%;position:absolute;top:0}.search .checkbox_container label{font-size:.9em;border-bottom:2px solid #E8E7E6}.search .checkbox_container label:hover{border-bottom:2px solid #3498DB}.search .checkbox_container input[type=checkbox]:checked+label{border-bottom:2px solid #2980B9}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q,ul.autocompleter-choices{margin:0;border:1px solid #3498DB}.q{background:#FFF;color:#222;font-size:16px;height:28px;outline:0;padding:2px 2px 2px 8px;padding-right:0!important;width:100%;z-index:2}#search_submit{position:absolute;top:13px;right:1px;padding:0;border:0;background:url(../img/search-icon.png) no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:30px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}ul.autocompleter-choices{position:absolute;padding:0;list-style:none;border-left-color:#3498DB;border-right-color:#3498DB;border-bottom-color:#3498DB;text-align:left;font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;z-index:50;background-color:#FFF;color:#444}ul.autocompleter-choices li{position:relative;margin:-2px 0 0;padding:.2em 1.5em .2em 1em;display:block;float:none!important;cursor:pointer;font-weight:400;white-space:nowrap;font-size:1em;line-height:1.5em}ul.autocompleter-choices li.autocompleter-selected{background-color:#444;color:#FFF}ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried{color:#9FCFFF}ul.autocompleter-choices span.autocompleter-queried{display:inline;float:none;font-weight:700;margin:0;padding:0}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498DB;padding:4px 10px}a:active.hmarg,a:hover.hmarg,a:link.hmarg,a:visited.hmarg{color:#3498DB}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url(../img/searx.png) center no-repeat;width:100%;min-height:80px}input[type=submit]{padding:2px 6px;margin:2px 4px;display:inline-block;background:#3498DB;color:#FFF;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}fieldset{margin:8px;border:1px solid #3498DB}#categories{margin:0 10px;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type=checkbox]:checked+label{background:#3498DB;color:#FFF}.engine_checkbox{padding:4px}label.allow{background:#E74C3C;padding:4px 8px;color:#FFF;display:none}label.deny{background:#2ECC71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type=checkbox]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type=checkbox]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8E44AD}.result{margin:19px 0 18px;padding:0}.result_title{margin-bottom:0}.result_title a{color:#2980B9;font-weight:400;font-size:1.1em}.result_title a:visited{color:#8E44AD}.cache_link{font-size:10px!important}.result h3{font-size:1em;margin:5px 0 1px;padding:0}.result .content,.result .url,.small_font{font-size:.8em}.result .content{margin:0;padding:0;max-width:54em;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}.result .url{margin:0 0 3px;padding:0;max-width:54em;color:#C0392B}.result .published_date{font-size:.8em;color:#888;Margin:5px 20px}.result .thumbnail{width:400px}.engines{color:#888}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.image_result{display:inline-block;margin:10px;position:relative;max-height:160px}.image_result img{border:0;max-height:160px}.image_result p{margin:0;padding:0}.image_result p span a{display:none;color:#FFF}.image_result p:hover span a{display:block;position:absolute;bottom:0;right:0;padding:4px;background-color:rgba(0,0,0,.6);font-size:.7em}#categories_container,.percentage{position:relative}.torrent_result{border-left:10px solid #d3d3d3;padding-left:3px}.torrent_result p{margin:3px;font-size:.8em}.torrent_result a{color:#2980B9}.torrent_result a:visited{color:#8E44AD}.definition_result{border-left:10px solid gray;padding-left:3px}.percentage{width:300px}.percentage div{background:#444}table{width:100%}.result-table{margin-bottom:10px}#infoboxes,#sidebar{margin:0 2px 5px 5px;padding:0 2px 2px}td{padding:0 4px}tr:hover{background:#DDD}#results{margin:auto auto 20px;padding:0;width:50em}#sidebar{position:fixed;bottom:10px;left:10px;width:14em}#answers input,#infoboxes input,#sidebar input,#suggestions input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:0 0;color:#444;cursor:pointer}#suggestions form{display:inline}#answers,#suggestions{margin-top:20px;max-width:45em}#suggestions-title{color:#888}#answers{border:2px solid #2980B9;padding:20px}#answers form,#infoboxes form{min-width:210px}#infoboxes{position:absolute;top:100px;right:20px;max-width:21em}#infoboxes .infobox{margin:10px 0;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:90%;max-heigt:12em;display:block;margin:5px;padding:5px}#infoboxes .infobox h2{margin:0}#apis,#search_url{margin-top:8px}#infoboxes .infobox table{table-layout:fixed}#infoboxes .infobox table td{vertical-align:top}#infoboxes .infobox input{font-size:1em}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url(../img/preference-icon.png) no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.github{display:none}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed!important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#answers,#suggestions{margin-top:5px}#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin:auto}#sidebar{position:static;max-width:50em;margin:0 0 2px;padding:0;float:none;border:none;width:auto}#sidebar input{border:0}#apis,#search_url{display:none}.result{border-top:1px solid #E8E7E6;margin:8px 0}.image_result,.image_result img,.result .thumbnail{max-width:98%}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:#3498DB;border:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#FFF}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:#fff;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8} \ No newline at end of file diff --git a/searx/static/themes/legacy/less/style.less b/searx/static/themes/legacy/less/style.less index 4374f7d6..bbeaf105 100644 --- a/searx/static/themes/legacy/less/style.less +++ b/searx/static/themes/legacy/less/style.less @@ -376,6 +376,10 @@ table { width: 100%; } +.result-table { + margin-bottom: 10px; +} + td { padding: 0 4px; } diff --git a/searx/templates/courgette/result_templates/key-value.html b/searx/templates/courgette/result_templates/key-value.html new file mode 100644 index 00000000..789e8de9 --- /dev/null +++ b/searx/templates/courgette/result_templates/key-value.html @@ -0,0 +1,13 @@ +
    + + {% for key, value in result.items() %} + {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %} + {% continue %} + {% endif %} + + + + {% endfor %} +
    {{ key|upper }}: {{ value|safe }}
    +

    {{ result.engines|join(', ') }}

    +
    diff --git a/searx/templates/legacy/result_templates/key-value.html b/searx/templates/legacy/result_templates/key-value.html new file mode 100644 index 00000000..a5bb509d --- /dev/null +++ b/searx/templates/legacy/result_templates/key-value.html @@ -0,0 +1,13 @@ + + {% for key, value in result.items() %} + {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %} + {% continue %} + {% endif %} + + + + {% endfor %} + + + +
    {{ key|upper }}: {{ value|safe }}
    ENGINES: {{ result.engines|join(', ') }}
    diff --git a/searx/templates/oscar/macros.html b/searx/templates/oscar/macros.html index 0ff95752..5f646364 100644 --- a/searx/templates/oscar/macros.html +++ b/searx/templates/oscar/macros.html @@ -14,7 +14,7 @@ {% macro result_header(result, favicons) -%} -

    {% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result_link(result.url, result.title|safe) }}

    +

    {% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{% if result.url %}{{ result_link(result.url, result.title|safe) }}{% else %}{{ result.title|safe}}{% endif %}

    {%- endmacro %} @@ -31,12 +31,16 @@ {% for engine in result.engines %} {{ engine }} {% endfor %} + {% if result.url %} {{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }} + {% endif %} {% if proxify %} {{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }} {% endif %}
    +{% if result.pretty_url %} +{% endif %} {%- endmacro %} @@ -45,11 +49,15 @@ {% for engine in result.engines %} {{ engine }} {% endfor %} + {% if result.url %} {{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }} + {% endif %} {% if proxify %} {{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }} {% endif %} + {% if result.pretty_url %} + {% endif %} {%- endmacro %} {% macro preferences_item_header(info, label, rtl) -%} diff --git a/searx/templates/oscar/result_templates/key-value.html b/searx/templates/oscar/result_templates/key-value.html new file mode 100644 index 00000000..67c748e7 --- /dev/null +++ b/searx/templates/oscar/result_templates/key-value.html @@ -0,0 +1,19 @@ +{% from 'oscar/macros.html' import result_footer, result_footer_rtl with context %} +
    + + {% for key, value in result.items() %} + {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %} + {% continue %} + {% endif %} + + + + {% endfor %} +
    {{ key|upper }}: {{ value }}
    + +{% if rtl %} +{{ result_footer_rtl(result) }} +{% else %} +{{ result_footer(result) }} +{% endif %} +
    diff --git a/searx/templates/simple/result_templates/key-value.html b/searx/templates/simple/result_templates/key-value.html new file mode 100644 index 00000000..eebaa2c8 --- /dev/null +++ b/searx/templates/simple/result_templates/key-value.html @@ -0,0 +1,11 @@ + + {% for key, value in result.items() %} + {% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions'] %} + {% continue %} + {% endif %} + + + + {% endfor %} +
    {{ key|upper }}: {{ value }}
    +
    {% for engine in result.engines %}{{ engine }}{% endfor %}
    {{- '' -}} diff --git a/searx/utils.py b/searx/utils.py index eb5da2fa..4029cf2a 100644 --- a/searx/utils.py +++ b/searx/utils.py @@ -435,3 +435,18 @@ def ecma_unescape(s): # "%20" becomes " ", "%F3" becomes "ó" s = ecma_unescape2_re.sub(lambda e: unichr(int(e.group(1), 16)), s) return s + + +def get_engine_from_settings(name): + """Return engine configuration from settings.yml of a given engine name""" + + if 'engines' not in settings: + return {} + + for engine in settings['engines']: + if 'name' not in engine: + continue + if name == engine['name']: + return engine + + return {} diff --git a/searx/webapp.py b/searx/webapp.py index ffe9b4da..505e93ae 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -124,6 +124,7 @@ app = Flask( app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True +app.jinja_env.add_extension('jinja2.ext.loopcontrols') app.secret_key = settings['server']['secret_key'] if not searx_debug \ @@ -538,14 +539,16 @@ def index(): if output_format == 'html': if 'content' in result and result['content']: result['content'] = highlight_content(escape(result['content'][:1024]), search_query.query) - result['title'] = highlight_content(escape(result['title'] or u''), search_query.query) + if 'title' in result and result['title']: + result['title'] = highlight_content(escape(result['title'] or u''), search_query.query) else: if result.get('content'): result['content'] = html_to_text(result['content']).strip() # removing html content and whitespace duplications result['title'] = ' '.join(html_to_text(result['title']).strip().split()) - result['pretty_url'] = prettify_url(result['url']) + if 'url' in result: + result['pretty_url'] = prettify_url(result['url']) # TODO, check if timezone is calculated right if 'publishedDate' in result: From 5796dc60c9de4f8c54452bb0bd64ed993378e503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Mon, 30 Sep 2019 14:27:13 +0200 Subject: [PATCH 088/144] fix pep 8 check --- searx/engines/__init__.py | 1 - searx/search.py | 2 +- searx/utils.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/searx/engines/__init__.py b/searx/engines/__init__.py index 2585711a..2393f52b 100644 --- a/searx/engines/__init__.py +++ b/searx/engines/__init__.py @@ -139,7 +139,6 @@ def load_engine(engine_data): engine.stats['page_load_time'] = 0 engine.stats['page_load_count'] = 0 - for category_name in engine.categories: categories.setdefault(category_name, []).append(engine) diff --git a/searx/search.py b/searx/search.py index 45f54bb0..5c268cc5 100644 --- a/searx/search.py +++ b/searx/search.py @@ -103,7 +103,7 @@ def search_one_offline_request(engine, query, request_params): def search_one_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit): if engines[engine_name].offline: - return search_one_offline_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit) + return search_one_offline_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit) # noqa return search_one_http_request_safe(engine_name, query, request_params, result_container, start_time, timeout_limit) diff --git a/searx/utils.py b/searx/utils.py index 4029cf2a..e61a134f 100644 --- a/searx/utils.py +++ b/searx/utils.py @@ -443,7 +443,7 @@ def get_engine_from_settings(name): if 'engines' not in settings: return {} - for engine in settings['engines']: + for engine in settings['engines']: if 'name' not in engine: continue if name == engine['name']: From b0f89ed4771a4238d3546323c707b1331baf5c97 Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Tue, 22 Oct 2019 21:38:21 -0700 Subject: [PATCH 089/144] [fix] preserve bangs in corrections --- searx/templates/oscar/results.html | 4 ++-- searx/templates/simple/results.html | 4 ++-- searx/webapp.py | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/searx/templates/oscar/results.html b/searx/templates/oscar/results.html index 9a95265b..fce7f97d 100644 --- a/searx/templates/oscar/results.html +++ b/searx/templates/oscar/results.html @@ -22,8 +22,8 @@ {{ _('Try searching for:') }} {% for correction in corrections %} {% endfor %} diff --git a/searx/templates/simple/results.html b/searx/templates/simple/results.html index 770eebe8..8885abc3 100644 --- a/searx/templates/simple/results.html +++ b/searx/templates/simple/results.html @@ -95,13 +95,13 @@ {% for correction in corrections %}
    - + {% if timeout_limit %}{% endif %} - +
    {% endfor %} diff --git a/searx/webapp.py b/searx/webapp.py index 505e93ae..3bb29140 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -610,6 +610,12 @@ def index(): 'title': suggestion }, result_container.suggestions) + + correction_urls = list(map(lambda correction: { + 'url': raw_text_query.changeSearchQuery(correction).getFullQuery(), + 'title': correction + }, + result_container.corrections)) # return render( 'results.html', @@ -622,7 +628,7 @@ def index(): advanced_search=advanced_search, suggestions=suggestion_urls, answers=result_container.answers, - corrections=result_container.corrections, + corrections=correction_urls, infoboxes=result_container.infoboxes, paging=result_container.paging, unresponsive_engines=result_container.unresponsive_engines, From dede46a2d5ad1504991b05b8edab4d1ffd781f46 Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Tue, 22 Oct 2019 23:17:00 -0700 Subject: [PATCH 090/144] fix out of range error in tracker remover plugin --- searx/plugins/tracker_url_remover.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/searx/plugins/tracker_url_remover.py b/searx/plugins/tracker_url_remover.py index 9e18867b..33dd621e 100644 --- a/searx/plugins/tracker_url_remover.py +++ b/searx/plugins/tracker_url_remover.py @@ -39,16 +39,14 @@ def on_result(request, search, result): return True parsed_query = parse_qsl(query) - changed = False + changes = 0 for i, (param_name, _) in enumerate(list(parsed_query)): for reg in regexes: if reg.match(param_name): - parsed_query.pop(i) - changed = True + parsed_query.pop(i - changes) + changes += 1 + result['parsed_url'] = result['parsed_url']._replace(query=urlencode(parsed_query)) + result['url'] = urlunparse(result['parsed_url']) break - if changed: - result['parsed_url'] = result['parsed_url']._replace(query=urlencode(parsed_query)) - result['url'] = urlunparse(result['parsed_url']) - return True From 021e1da4c9a8eca7293ad62608cc55da5dfde73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Wed, 23 Oct 2019 13:06:19 +0200 Subject: [PATCH 091/144] add post about introducing offline engines --- docs/blog/blog.rst | 1 + docs/blog/intro-offline.rst | 65 ++++++++++++++++++++++++++++++++++++ docs/conf.py | 2 +- docs/dev/engine_overview.rst | 2 ++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 docs/blog/intro-offline.rst diff --git a/docs/blog/blog.rst b/docs/blog/blog.rst index 77553d5c..2ccaaa5d 100644 --- a/docs/blog/blog.rst +++ b/docs/blog/blog.rst @@ -6,3 +6,4 @@ Blog python3 admin + intro-offline diff --git a/docs/blog/intro-offline.rst b/docs/blog/intro-offline.rst new file mode 100644 index 00000000..91452171 --- /dev/null +++ b/docs/blog/intro-offline.rst @@ -0,0 +1,65 @@ +Preparation for offline engines +=============================== + +Offline engines +--------------- + +To extend the functionality of searx, offline engines are going to be introduced. An offline engine is an engine which does not need Internet connection to perform a search and does not use HTTP to communicate. + +Offline engines can be configured as online engines, by adding those to the `engines` list of `settings.yml`. Thus, searx finds the engine file and imports it. + +Example skeleton for the new engines: + +.. code:: python + + from subprocess import PIPE, Popen + + categories = ['general'] + offline = True + + def init(settings): + pass + + def search(query, params): + process = Popen(['ls', query], stdout=PIPE) + return_code = process.wait() + if return_code != 0: + raise RuntimeError('non-zero return code', return_code) + + results = [] + line = process.stdout.readline() + while line: + result = parse_line(line) + results.append(results) + + line = process.stdout.readline() + + return results + + +Development progress +-------------------- + +First, a proposal has been created as a Github issue. Then it was moved to the wiki as a design document. You can read it here: https://github.com/asciimoo/searx/wiki/Offline-engines + +In this development step, searx core was prepared to accept and perform offline searches. Offline search requests are scheduled together with regular offline requests. + +As offline searches can return arbitrary results depending on the engine, the current result templates were insufficient to present such results. Thus, a new template is introduced which is caplable of presenting arbitrary key value pairs as a table. You can check out the pull request for more details: https://github.com/asciimoo/searx/pull/1700 + +Next steps +---------- + +Today, it is possible to create/run an offline engine. However, it is going to be publicly available for everyone who knows the searx instance. So the next step is to introduce token based access for engines. This way administrators are able to limit the access to private engines. + +Acknowledgement +--------------- + +This development was sponsored by `Search and Discovery Fund`_ of `NLnet Foundation`_ . + +.. _Search and Discovery Fund: https://nlnet.nl/discovery +.. _NLnet Foundation: https://nlnet.nl/ + + +| Happy hacking. +| kvch // 2019.10.21 17:03 + diff --git a/docs/conf.py b/docs/conf.py index 52aad663..e71ea13a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ master_doc = 'index' # General information about the project. project = u'searx' -copyright = u'2015-2017, Adam Tauber' +copyright = u'2015-2019, Adam Tauber, Noémi Ványi' author = u'Adam Tauber' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/dev/engine_overview.rst b/docs/dev/engine_overview.rst index d57ca5b3..a6867b5d 100644 --- a/docs/dev/engine_overview.rst +++ b/docs/dev/engine_overview.rst @@ -41,6 +41,8 @@ engine file +----------------------+-----------+-----------------------------------------+ | time\_range\_support | boolean | support search time range | +----------------------+-----------+-----------------------------------------+ +| offline | boolean | engine runs offline | ++----------------------+-----------+-----------------------------------------+ settings.yml ~~~~~~~~~~~~ From 3ea5ea8faebab3f5cecc05757dec4eced82af900 Mon Sep 17 00:00:00 2001 From: x250 <49014564+x250@users.noreply.github.com> Date: Sun, 3 Nov 2019 13:21:41 -0500 Subject: [PATCH 092/144] Mojeek engine hotfix --- searx/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/settings.yml b/searx/settings.yml index 835fbe5f..0384052c 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -744,7 +744,7 @@ engines: title_xpath : ./h2 content_xpath : ./p[@class="s"] suggestion_xpath : /html/body//div[@class="top-info"]/p[@class="top-info spell"]/a - first_page_num : 1 + first_page_num : 0 page_size : 10 disabled : True From 85b37233458c21b775bf98568c0a5c9260aa14fe Mon Sep 17 00:00:00 2001 From: Dalf Date: Fri, 15 Nov 2019 09:31:37 +0100 Subject: [PATCH 093/144] [mod] speed optimization compile XPath only once avoid redundant call to urlparse get_locale(webapp.py): avoid useless call to request.accept_languages.best_match --- searx/engines/bing.py | 20 ++++++++++---------- searx/engines/dictzone.py | 8 ++++---- searx/engines/doku.py | 15 ++++++++------- searx/engines/duckduckgo.py | 10 +++++----- searx/engines/duden.py | 15 ++++++++------- searx/engines/gigablast.py | 5 +++-- searx/engines/google.py | 32 ++++++++++++++++---------------- searx/engines/startpage.py | 9 +++++---- searx/engines/wikidata.py | 18 +----------------- searx/engines/xpath.py | 20 ++++++++++---------- searx/engines/yahoo.py | 18 +++++++++--------- searx/results.py | 8 +++++--- searx/utils.py | 15 +++++++++++++++ searx/webapp.py | 16 +++++++--------- 14 files changed, 106 insertions(+), 103 deletions(-) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index 1e614867..ed0b87db 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -18,7 +18,7 @@ from lxml import html from searx import logger, utils from searx.engines.xpath import extract_text from searx.url_utils import urlencode -from searx.utils import match_language, gen_useragent +from searx.utils import match_language, gen_useragent, eval_xpath logger = logger.getChild('bing engine') @@ -65,11 +65,11 @@ def response(resp): dom = html.fromstring(resp.text) # parse results - for result in dom.xpath('//div[@class="sa_cc"]'): - link = result.xpath('.//h3/a')[0] + for result in eval_xpath(dom, '//div[@class="sa_cc"]'): + link = eval_xpath(result, './/h3/a')[0] url = link.attrib.get('href') title = extract_text(link) - content = extract_text(result.xpath('.//p')) + content = extract_text(eval_xpath(result, './/p')) # append result results.append({'url': url, @@ -77,11 +77,11 @@ def response(resp): 'content': content}) # parse results again if nothing is found yet - for result in dom.xpath('//li[@class="b_algo"]'): - link = result.xpath('.//h2/a')[0] + for result in eval_xpath(dom, '//li[@class="b_algo"]'): + link = eval_xpath(result, './/h2/a')[0] url = link.attrib.get('href') title = extract_text(link) - content = extract_text(result.xpath('.//p')) + content = extract_text(eval_xpath(result, './/p')) # append result results.append({'url': url, @@ -89,7 +89,7 @@ def response(resp): 'content': content}) try: - result_len_container = "".join(dom.xpath('//span[@class="sb_count"]/text()')) + result_len_container = "".join(eval_xpath(dom, '//span[@class="sb_count"]/text()')) result_len_container = utils.to_string(result_len_container) if "-" in result_len_container: # Remove the part "from-to" for paginated request ... @@ -113,9 +113,9 @@ def response(resp): def _fetch_supported_languages(resp): supported_languages = [] dom = html.fromstring(resp.text) - options = dom.xpath('//div[@id="limit-languages"]//input') + options = eval_xpath(dom, '//div[@id="limit-languages"]//input') for option in options: - code = option.xpath('./@id')[0].replace('_', '-') + code = eval_xpath(option, './@id')[0].replace('_', '-') if code == 'nb': code = 'no' supported_languages.append(code) diff --git a/searx/engines/dictzone.py b/searx/engines/dictzone.py index 09db048c..423af097 100644 --- a/searx/engines/dictzone.py +++ b/searx/engines/dictzone.py @@ -11,7 +11,7 @@ import re from lxml import html -from searx.utils import is_valid_lang +from searx.utils import is_valid_lang, eval_xpath from searx.url_utils import urljoin categories = ['general'] @@ -47,14 +47,14 @@ def response(resp): dom = html.fromstring(resp.text) - for k, result in enumerate(dom.xpath(results_xpath)[1:]): + for k, result in enumerate(eval_xpath(dom, results_xpath)[1:]): try: - from_result, to_results_raw = result.xpath('./td') + from_result, to_results_raw = eval_xpath(result, './td') except: continue to_results = [] - for to_result in to_results_raw.xpath('./p/a'): + for to_result in eval_xpath(to_results_raw, './p/a'): t = to_result.text_content() if t.strip(): to_results.append(to_result.text_content()) diff --git a/searx/engines/doku.py b/searx/engines/doku.py index a391be44..d20e6602 100644 --- a/searx/engines/doku.py +++ b/searx/engines/doku.py @@ -11,6 +11,7 @@ from lxml.html import fromstring from searx.engines.xpath import extract_text +from searx.utils import eval_xpath from searx.url_utils import urlencode # engine dependent config @@ -45,16 +46,16 @@ def response(resp): # parse results # Quickhits - for r in doc.xpath('//div[@class="search_quickresult"]/ul/li'): + for r in eval_xpath(doc, '//div[@class="search_quickresult"]/ul/li'): try: - res_url = r.xpath('.//a[@class="wikilink1"]/@href')[-1] + res_url = eval_xpath(r, './/a[@class="wikilink1"]/@href')[-1] except: continue if not res_url: continue - title = extract_text(r.xpath('.//a[@class="wikilink1"]/@title')) + title = extract_text(eval_xpath(r, './/a[@class="wikilink1"]/@title')) # append result results.append({'title': title, @@ -62,13 +63,13 @@ def response(resp): 'url': base_url + res_url}) # Search results - for r in doc.xpath('//dl[@class="search_results"]/*'): + for r in eval_xpath(doc, '//dl[@class="search_results"]/*'): try: if r.tag == "dt": - res_url = r.xpath('.//a[@class="wikilink1"]/@href')[-1] - title = extract_text(r.xpath('.//a[@class="wikilink1"]/@title')) + res_url = eval_xpath(r, './/a[@class="wikilink1"]/@href')[-1] + title = extract_text(eval_xpath(r, './/a[@class="wikilink1"]/@title')) elif r.tag == "dd": - content = extract_text(r.xpath('.')) + content = extract_text(eval_xpath(r, '.')) # append result results.append({'title': title, diff --git a/searx/engines/duckduckgo.py b/searx/engines/duckduckgo.py index e77ef012..0d2c0af2 100644 --- a/searx/engines/duckduckgo.py +++ b/searx/engines/duckduckgo.py @@ -18,7 +18,7 @@ from json import loads from searx.engines.xpath import extract_text from searx.poolrequests import get from searx.url_utils import urlencode -from searx.utils import match_language +from searx.utils import match_language, eval_xpath # engine dependent config categories = ['general'] @@ -106,19 +106,19 @@ def response(resp): doc = fromstring(resp.text) # parse results - for i, r in enumerate(doc.xpath(result_xpath)): + for i, r in enumerate(eval_xpath(doc, result_xpath)): if i >= 30: break try: - res_url = r.xpath(url_xpath)[-1] + res_url = eval_xpath(r, url_xpath)[-1] except: continue if not res_url: continue - title = extract_text(r.xpath(title_xpath)) - content = extract_text(r.xpath(content_xpath)) + title = extract_text(eval_xpath(r, title_xpath)) + content = extract_text(eval_xpath(r, content_xpath)) # append result results.append({'title': title, diff --git a/searx/engines/duden.py b/searx/engines/duden.py index 444f18c1..cf2f1a27 100644 --- a/searx/engines/duden.py +++ b/searx/engines/duden.py @@ -11,6 +11,7 @@ from lxml import html, etree import re from searx.engines.xpath import extract_text +from searx.utils import eval_xpath from searx.url_utils import quote, urljoin from searx import logger @@ -52,9 +53,9 @@ def response(resp): dom = html.fromstring(resp.text) try: - number_of_results_string = re.sub('[^0-9]', '', dom.xpath( - '//a[@class="active" and contains(@href,"/suchen/dudenonline")]/span/text()')[0] - ) + number_of_results_string =\ + re.sub('[^0-9]', '', + eval_xpath(dom, '//a[@class="active" and contains(@href,"/suchen/dudenonline")]/span/text()')[0]) results.append({'number_of_results': int(number_of_results_string)}) @@ -62,12 +63,12 @@ def response(resp): logger.debug("Couldn't read number of results.") pass - for result in dom.xpath('//section[not(contains(@class, "essay"))]'): + for result in eval_xpath(dom, '//section[not(contains(@class, "essay"))]'): try: - url = result.xpath('.//h2/a')[0].get('href') + url = eval_xpath(result, './/h2/a')[0].get('href') url = urljoin(base_url, url) - title = result.xpath('string(.//h2/a)').strip() - content = extract_text(result.xpath('.//p')) + title = eval_xpath(result, 'string(.//h2/a)').strip() + content = extract_text(eval_xpath(result, './/p')) # append result results.append({'url': url, 'title': title, diff --git a/searx/engines/gigablast.py b/searx/engines/gigablast.py index 6b040223..a84f3f69 100644 --- a/searx/engines/gigablast.py +++ b/searx/engines/gigablast.py @@ -15,6 +15,7 @@ from json import loads from time import time from lxml.html import fromstring from searx.url_utils import urlencode +from searx.utils import eval_xpath # engine dependent config categories = ['general'] @@ -99,9 +100,9 @@ def response(resp): def _fetch_supported_languages(resp): supported_languages = [] dom = fromstring(resp.text) - links = dom.xpath('//span[@id="menu2"]/a') + links = eval_xpath(dom, '//span[@id="menu2"]/a') for link in links: - href = link.xpath('./@href')[0].split('lang%3A') + href = eval_xpath(link, './@href')[0].split('lang%3A') if len(href) == 2: code = href[1].split('_') if len(code) == 2: diff --git a/searx/engines/google.py b/searx/engines/google.py index 03f0523e..a82b1f1f 100644 --- a/searx/engines/google.py +++ b/searx/engines/google.py @@ -14,7 +14,7 @@ from lxml import html, etree from searx.engines.xpath import extract_text, extract_url from searx import logger from searx.url_utils import urlencode, urlparse, parse_qsl -from searx.utils import match_language +from searx.utils import match_language, eval_xpath logger = logger.getChild('google engine') @@ -156,7 +156,7 @@ def parse_url(url_string, google_hostname): # returns extract_text on the first result selected by the xpath or None def extract_text_from_dom(result, xpath): - r = result.xpath(xpath) + r = eval_xpath(result, xpath) if len(r) > 0: return extract_text(r[0]) return None @@ -226,21 +226,21 @@ def response(resp): # convert the text to dom dom = html.fromstring(resp.text) - instant_answer = dom.xpath('//div[@id="_vBb"]//text()') + instant_answer = eval_xpath(dom, '//div[@id="_vBb"]//text()') if instant_answer: results.append({'answer': u' '.join(instant_answer)}) try: - results_num = int(dom.xpath('//div[@id="resultStats"]//text()')[0] + results_num = int(eval_xpath(dom, '//div[@id="resultStats"]//text()')[0] .split()[1].replace(',', '')) results.append({'number_of_results': results_num}) except: pass # parse results - for result in dom.xpath(results_xpath): + for result in eval_xpath(dom, results_xpath): try: - title = extract_text(result.xpath(title_xpath)[0]) - url = parse_url(extract_url(result.xpath(url_xpath), google_url), google_hostname) + title = extract_text(eval_xpath(result, title_xpath)[0]) + url = parse_url(extract_url(eval_xpath(result, url_xpath), google_url), google_hostname) parsed_url = urlparse(url, google_hostname) # map result @@ -249,7 +249,7 @@ def response(resp): continue # if parsed_url.path.startswith(maps_path) or parsed_url.netloc.startswith(map_hostname_start): # print "yooooo"*30 - # x = result.xpath(map_near) + # x = eval_xpath(result, map_near) # if len(x) > 0: # # map : near the location # results = results + parse_map_near(parsed_url, x, google_hostname) @@ -286,11 +286,11 @@ def response(resp): continue # parse suggestion - for suggestion in dom.xpath(suggestion_xpath): + for suggestion in eval_xpath(dom, suggestion_xpath): # append suggestion results.append({'suggestion': extract_text(suggestion)}) - for correction in dom.xpath(spelling_suggestion_xpath): + for correction in eval_xpath(dom, spelling_suggestion_xpath): results.append({'correction': extract_text(correction)}) # return results @@ -299,9 +299,9 @@ def response(resp): def parse_images(result, google_hostname): results = [] - for image in result.xpath(images_xpath): - url = parse_url(extract_text(image.xpath(image_url_xpath)[0]), google_hostname) - img_src = extract_text(image.xpath(image_img_src_xpath)[0]) + for image in eval_xpath(result, images_xpath): + url = parse_url(extract_text(eval_xpath(image, image_url_xpath)[0]), google_hostname) + img_src = extract_text(eval_xpath(image, image_img_src_xpath)[0]) # append result results.append({'url': url, @@ -388,10 +388,10 @@ def attributes_to_html(attributes): def _fetch_supported_languages(resp): supported_languages = {} dom = html.fromstring(resp.text) - options = dom.xpath('//*[@id="langSec"]//input[@name="lr"]') + options = eval_xpath(dom, '//*[@id="langSec"]//input[@name="lr"]') for option in options: - code = option.xpath('./@value')[0].split('_')[-1] - name = option.xpath('./@data-name')[0].title() + code = eval_xpath(option, './@value')[0].split('_')[-1] + name = eval_xpath(option, './@data-name')[0].title() supported_languages[code] = {"name": name} return supported_languages diff --git a/searx/engines/startpage.py b/searx/engines/startpage.py index 0f0ec6e1..76567396 100644 --- a/searx/engines/startpage.py +++ b/searx/engines/startpage.py @@ -16,6 +16,7 @@ from datetime import datetime, timedelta import re from searx.engines.xpath import extract_text from searx.languages import language_codes +from searx.utils import eval_xpath # engine dependent config categories = ['general'] @@ -70,8 +71,8 @@ def response(resp): dom = html.fromstring(resp.text) # parse results - for result in dom.xpath(results_xpath): - links = result.xpath(link_xpath) + for result in eval_xpath(dom, results_xpath): + links = eval_xpath(result, link_xpath) if not links: continue link = links[0] @@ -87,8 +88,8 @@ def response(resp): title = extract_text(link) - if result.xpath(content_xpath): - content = extract_text(result.xpath(content_xpath)) + if eval_xpath(result, content_xpath): + content = extract_text(eval_xpath(result, content_xpath)) else: content = '' diff --git a/searx/engines/wikidata.py b/searx/engines/wikidata.py index 5ea2b995..e913b391 100644 --- a/searx/engines/wikidata.py +++ b/searx/engines/wikidata.py @@ -16,7 +16,7 @@ from searx.poolrequests import get from searx.engines.xpath import extract_text from searx.engines.wikipedia import _fetch_supported_languages, supported_languages_url from searx.url_utils import urlencode -from searx.utils import match_language +from searx.utils import match_language, eval_xpath from json import loads from lxml.html import fromstring @@ -57,22 +57,6 @@ language_fallback_xpath = '//sup[contains(@class,"wb-language-fallback-indicator calendar_name_xpath = './/sup[contains(@class,"wb-calendar-name")]' media_xpath = value_xpath + '//div[contains(@class,"commons-media-caption")]//a' -# xpath_cache -xpath_cache = {} - - -def get_xpath(xpath_str): - result = xpath_cache.get(xpath_str, None) - if not result: - result = etree.XPath(xpath_str) - xpath_cache[xpath_str] = result - return result - - -def eval_xpath(element, xpath_str): - xpath = get_xpath(xpath_str) - return xpath(element) - def get_id_cache(result): id_cache = {} diff --git a/searx/engines/xpath.py b/searx/engines/xpath.py index 61494ce4..b75896cc 100644 --- a/searx/engines/xpath.py +++ b/searx/engines/xpath.py @@ -1,6 +1,6 @@ from lxml import html from lxml.etree import _ElementStringResult, _ElementUnicodeResult -from searx.utils import html_to_text +from searx.utils import html_to_text, eval_xpath from searx.url_utils import unquote, urlencode, urljoin, urlparse search_url = None @@ -104,15 +104,15 @@ def response(resp): results = [] dom = html.fromstring(resp.text) if results_xpath: - for result in dom.xpath(results_xpath): - url = extract_url(result.xpath(url_xpath), search_url) - title = extract_text(result.xpath(title_xpath)) - content = extract_text(result.xpath(content_xpath)) + for result in eval_xpath(dom, results_xpath): + url = extract_url(eval_xpath(result, url_xpath), search_url) + title = extract_text(eval_xpath(result, title_xpath)) + content = extract_text(eval_xpath(result, content_xpath)) tmp_result = {'url': url, 'title': title, 'content': content} # add thumbnail if available if thumbnail_xpath: - thumbnail_xpath_result = result.xpath(thumbnail_xpath) + thumbnail_xpath_result = eval_xpath(result, thumbnail_xpath) if len(thumbnail_xpath_result) > 0: tmp_result['img_src'] = extract_url(thumbnail_xpath_result, search_url) @@ -120,14 +120,14 @@ def response(resp): else: for url, title, content in zip( (extract_url(x, search_url) for - x in dom.xpath(url_xpath)), - map(extract_text, dom.xpath(title_xpath)), - map(extract_text, dom.xpath(content_xpath)) + x in eval_xpath(dom, url_xpath)), + map(extract_text, eval_xpath(dom, title_xpath)), + map(extract_text, eval_xpath(dom, content_xpath)) ): results.append({'url': url, 'title': title, 'content': content}) if not suggestion_xpath: return results - for suggestion in dom.xpath(suggestion_xpath): + for suggestion in eval_xpath(dom, suggestion_xpath): results.append({'suggestion': extract_text(suggestion)}) return results diff --git a/searx/engines/yahoo.py b/searx/engines/yahoo.py index 73b78bcf..36c1a11f 100644 --- a/searx/engines/yahoo.py +++ b/searx/engines/yahoo.py @@ -14,7 +14,7 @@ from lxml import html from searx.engines.xpath import extract_text, extract_url from searx.url_utils import unquote, urlencode -from searx.utils import match_language +from searx.utils import match_language, eval_xpath # engine dependent config categories = ['general'] @@ -109,21 +109,21 @@ def response(resp): dom = html.fromstring(resp.text) try: - results_num = int(dom.xpath('//div[@class="compPagination"]/span[last()]/text()')[0] + results_num = int(eval_xpath(dom, '//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): + for result in eval_xpath(dom, results_xpath): try: - url = parse_url(extract_url(result.xpath(url_xpath), search_url)) - title = extract_text(result.xpath(title_xpath)[0]) + url = parse_url(extract_url(eval_xpath(result, url_xpath), search_url)) + title = extract_text(eval_xpath(result, title_xpath)[0]) except: continue - content = extract_text(result.xpath(content_xpath)[0]) + content = extract_text(eval_xpath(result, content_xpath)[0]) # append result results.append({'url': url, @@ -131,7 +131,7 @@ def response(resp): 'content': content}) # if no suggestion found, return results - suggestions = dom.xpath(suggestion_xpath) + suggestions = eval_xpath(dom, suggestion_xpath) if not suggestions: return results @@ -148,9 +148,9 @@ def response(resp): def _fetch_supported_languages(resp): supported_languages = [] dom = html.fromstring(resp.text) - options = dom.xpath('//div[@id="yschlang"]/span/label/input') + options = eval_xpath(dom, '//div[@id="yschlang"]/span/label/input') for option in options: - code_parts = option.xpath('./@value')[0][5:].split('_') + code_parts = eval_xpath(option, './@value')[0][5:].split('_') if len(code_parts) == 2: code = code_parts[0] + '-' + code_parts[1].upper() else: diff --git a/searx/results.py b/searx/results.py index a127024c..3b1e4bd6 100644 --- a/searx/results.py +++ b/searx/results.py @@ -67,8 +67,9 @@ def merge_two_infoboxes(infobox1, infobox2): for url2 in infobox2.get('urls', []): unique_url = True - for url1 in infobox1.get('urls', []): - if compare_urls(urlparse(url1.get('url', '')), urlparse(url2.get('url', ''))): + parsed_url2 = urlparse(url2.get('url', '')) + for url1 in urls1: + if compare_urls(urlparse(url1.get('url', '')), parsed_url2): unique_url = False break if unique_url: @@ -188,8 +189,9 @@ class ResultContainer(object): add_infobox = True infobox_id = infobox.get('id', None) if infobox_id is not None: + parsed_url_infobox_id = urlparse(infobox_id) for existingIndex in self.infoboxes: - if compare_urls(urlparse(existingIndex.get('id', '')), urlparse(infobox_id)): + if compare_urls(urlparse(existingIndex.get('id', '')), parsed_url_infobox_id): merge_two_infoboxes(existingIndex, infobox) add_infobox = False diff --git a/searx/utils.py b/searx/utils.py index e61a134f..5ea9dc89 100644 --- a/searx/utils.py +++ b/searx/utils.py @@ -13,6 +13,7 @@ from numbers import Number from os.path import splitext, join from io import open from random import choice +from lxml.etree import XPath import sys import json @@ -51,6 +52,7 @@ ecma_unescape2_re = re.compile(r'%([0-9a-fA-F]{2})', re.UNICODE) useragents = json.loads(open(os.path.dirname(os.path.realpath(__file__)) + "/data/useragents.json", 'r', encoding='utf-8').read()) +xpath_cache = dict() lang_to_lc_cache = dict() @@ -450,3 +452,16 @@ def get_engine_from_settings(name): return engine return {} + + +def get_xpath(xpath_str): + result = xpath_cache.get(xpath_str, None) + if result is None: + result = XPath(xpath_str) + xpath_cache[xpath_str] = result + return result + + +def eval_xpath(element, xpath_str): + xpath = get_xpath(xpath_str) + return xpath(element) diff --git a/searx/webapp.py b/searx/webapp.py index 3bb29140..00895d96 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -154,20 +154,18 @@ outgoing_proxies = settings['outgoing'].get('proxies') or None @babel.localeselector def get_locale(): - locale = request.accept_languages.best_match(settings['locales'].keys()) - - if request.preferences.get_value('locale') != '': - locale = request.preferences.get_value('locale') + if 'locale' in request.form\ + and request.form['locale'] in settings['locales']: + return request.form['locale'] if 'locale' in request.args\ and request.args['locale'] in settings['locales']: - locale = request.args['locale'] + return request.args['locale'] - if 'locale' in request.form\ - and request.form['locale'] in settings['locales']: - locale = request.form['locale'] + if request.preferences.get_value('locale') != '': + return request.preferences.get_value('locale') - return locale + return request.accept_languages.best_match(settings['locales'].keys()) # code-highlighter From 5e5ff0cbf83fc6929545e1ca3f936a162019a2aa Mon Sep 17 00:00:00 2001 From: lorddavidiii Date: Sat, 16 Nov 2019 21:05:08 +0100 Subject: [PATCH 094/144] webapp.py: use html.escape if cgi.escape is not available - cgi.escape was removed in python 3.8 - also use html.escape in framalibre.py --- searx/engines/framalibre.py | 5 ++++- searx/webapp.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/searx/engines/framalibre.py b/searx/engines/framalibre.py index 146cdaee..f3441fa5 100644 --- a/searx/engines/framalibre.py +++ b/searx/engines/framalibre.py @@ -10,7 +10,10 @@ @parse url, title, content, thumbnail, img_src """ -from cgi import escape +try: + from cgi import escape +except: + from html import escape from lxml import html from searx.engines.xpath import extract_text from searx.url_utils import urljoin, urlencode diff --git a/searx/webapp.py b/searx/webapp.py index 3bb29140..183bf197 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -41,7 +41,10 @@ except: logger.critical("cannot import dependency: pygments") from sys import exit exit(1) -from cgi import escape +try: + from cgi import escape +except: + from html import escape from datetime import datetime, timedelta from time import time from werkzeug.contrib.fixers import ProxyFix From 8f51430f5c64e23dba0d518064d4972efec7fe83 Mon Sep 17 00:00:00 2001 From: Emilien Devos Date: Fri, 22 Nov 2019 21:34:44 +0000 Subject: [PATCH 095/144] [fix] Force Google old UI with a new user agent --- searx/engines/google.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/searx/engines/google.py b/searx/engines/google.py index 03f0523e..0a0d6ccb 100644 --- a/searx/engines/google.py +++ b/searx/engines/google.py @@ -199,8 +199,9 @@ def request(query, params): params['headers']['Accept-Language'] = language + ',' + language + '-' + country params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' - # Force Internet Explorer 12 user agent to avoid loading the new UI that Searx can't parse - params['headers']['User-Agent'] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" + # Force Safari 3.1 on Mac OS X (Leopard) user agent to avoid loading the new UI that Searx can't parse + params['headers']['User-Agent'] = ("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_4)" + "AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1") params['google_hostname'] = google_hostname From 94ca30de1b53ce8a03a83bfe3545ec6b642d560b Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Thu, 27 Jun 2019 21:11:57 -0500 Subject: [PATCH 096/144] add a shortcut for utils/fetch_firefox_version.py in manage.sh --- manage.sh | 6 ++++++ searx/data/useragents.json | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/manage.sh b/manage.sh index dbe48d31..a450c6df 100755 --- a/manage.sh +++ b/manage.sh @@ -70,6 +70,11 @@ locales() { pybabel compile -d "$SEARX_DIR/translations" } +update_useragents() { + echo '[!] Updating user agent versions' + python utils/fetch_firefox_version.py +} + pep8_check() { echo '[!] Running pep8 check' # ignored rules: @@ -246,6 +251,7 @@ Commands update_dev_packages - Check & update development and production dependency changes install_geckodriver - Download & install geckodriver if not already installed (required for robot_tests) npm_packages - Download & install npm dependencies + update_useragents - Update useragents.json with the most recent versions of Firefox Build ----- diff --git a/searx/data/useragents.json b/searx/data/useragents.json index 850bc418..f7bfd0b6 100644 --- a/searx/data/useragents.json +++ b/searx/data/useragents.json @@ -1,14 +1,20 @@ { - "ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}", "versions": [ - "61.0.1", - "61.0", - "60.0.2", - "60.0.1", - "60.0" + "67.0.4", + "67.0.3", + "67.0.2", + "67.0.1", + "67.0", + "66.0.5", + "66.0.4", + "66.0.3", + "66.0.2", + "66.0.1", + "66.0" ], "os": [ "Windows NT 10; WOW64", "X11; Linux x86_64" - ] + ], + "ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}" } \ No newline at end of file From d0d91fa331684b37b495c11631ec16a0def04165 Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Sat, 23 Nov 2019 19:27:49 -0700 Subject: [PATCH 097/144] update user agents --- searx/data/useragents.json | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/searx/data/useragents.json b/searx/data/useragents.json index f7bfd0b6..abb81000 100644 --- a/searx/data/useragents.json +++ b/searx/data/useragents.json @@ -1,16 +1,11 @@ { "versions": [ - "67.0.4", - "67.0.3", - "67.0.2", - "67.0.1", - "67.0", - "66.0.5", - "66.0.4", - "66.0.3", - "66.0.2", - "66.0.1", - "66.0" + "70.0.1", + "70.0", + "69.0.3", + "69.0.2", + "69.0.1", + "69.0" ], "os": [ "Windows NT 10; WOW64", From 9299355570e32c4d24d7274d716eca1a93119d13 Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Sun, 24 Nov 2019 20:21:37 -0700 Subject: [PATCH 098/144] add seedpeer again --- searx/engines/seedpeer.py | 78 +++++++++++++++++++ searx/settings.yml | 5 ++ .../courgette/result_templates/torrent.html | 2 +- .../legacy/result_templates/torrent.html | 2 +- .../oscar/result_templates/torrent.html | 2 +- .../simple/result_templates/torrent.html | 2 +- tests/unit/engines/test_seedpeer.py | 66 ++++++++++++++++ 7 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 searx/engines/seedpeer.py create mode 100644 tests/unit/engines/test_seedpeer.py diff --git a/searx/engines/seedpeer.py b/searx/engines/seedpeer.py new file mode 100644 index 00000000..f9b1f99c --- /dev/null +++ b/searx/engines/seedpeer.py @@ -0,0 +1,78 @@ +# Seedpeer (Videos, Music, Files) +# +# @website https://seedpeer.me +# @provide-api no (nothing found) +# +# @using-api no +# @results HTML (using search portal) +# @stable yes (HTML can change) +# @parse url, title, content, seed, leech, magnetlink + +from lxml import html +from json import loads +from operator import itemgetter +from searx.url_utils import quote, urljoin +from searx.engines.xpath import extract_text + + +url = 'https://seedpeer.me/' +search_url = url + 'search/{search_term}?page={page_no}' +torrent_file_url = url + 'torrent/{torrent_hash}' + +# specific xpath variables +script_xpath = '//script[@type="text/javascript"][not(@src)]' +torrent_xpath = '(//table)[2]/tbody/tr' +link_xpath = '(./td)[1]/a/@href' +age_xpath = '(./td)[2]' +size_xpath = '(./td)[3]' + + +# do search-request +def request(query, params): + params['url'] = search_url.format(search_term=quote(query), + page_no=params['pageno']) + return params + + +# get response from search-request +def response(resp): + results = [] + dom = html.fromstring(resp.text) + result_rows = dom.xpath(torrent_xpath) + + try: + script_element = dom.xpath(script_xpath)[0] + json_string = script_element.text[script_element.text.find('{'):] + torrents_json = loads(json_string) + except: + return [] + + # parse results + for torrent_row, torrent_json in zip(result_rows, torrents_json['data']['list']): + title = torrent_json['name'] + seed = int(torrent_json['seeds']) + leech = int(torrent_json['peers']) + size = int(torrent_json['size']) + torrent_hash = torrent_json['hash'] + + torrentfile = torrent_file_url.format(torrent_hash=torrent_hash) + magnetlink = 'magnet:?xt=urn:btih:{}'.format(torrent_hash) + + age = extract_text(torrent_row.xpath(age_xpath)) + link = torrent_row.xpath(link_xpath)[0] + + href = urljoin(url, link) + + # append result + results.append({'url': href, + 'title': title, + 'content': age, + 'seed': seed, + 'leech': leech, + 'filesize': size, + 'torrentfile': torrentfile, + 'magnetlink': magnetlink, + 'template': 'torrent.html'}) + + # return results sorted by seeder + return sorted(results, key=itemgetter('seed'), reverse=True) diff --git a/searx/settings.yml b/searx/settings.yml index 835fbe5f..25d90d4d 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -748,6 +748,11 @@ engines: page_size : 10 disabled : True + - name : seedpeer + shortcut : speu + engine : seedpeer + categories: files, music, videos + # - name : yacy # engine : yacy # shortcut : ya diff --git a/searx/templates/courgette/result_templates/torrent.html b/searx/templates/courgette/result_templates/torrent.html index d659064d..7f94a221 100644 --- a/searx/templates/courgette/result_templates/torrent.html +++ b/searx/templates/courgette/result_templates/torrent.html @@ -4,7 +4,7 @@ {% endif %}

    {{ result.title|safe }}

    {% if result.content %}{{ result.content|safe }}
    {% endif %} - {% if result.seed %}{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}
    {% endif %} + {% if result.seed is defined %}{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}
    {% endif %} {% if result.magnetlink %}{{ _('magnet link') }}{% endif %} {% if result.torrentfile %}{{ _('torrent file') }}{% endif %} diff --git a/searx/templates/legacy/result_templates/torrent.html b/searx/templates/legacy/result_templates/torrent.html index 7a8ac33d..068e0537 100644 --- a/searx/templates/legacy/result_templates/torrent.html +++ b/searx/templates/legacy/result_templates/torrent.html @@ -8,6 +8,6 @@

    {% if result.magnetlink %}{{ _('magnet link') }}{% endif %} {% if result.torrentfile %}{{ _('torrent file') }}{% endif %} - - {% if result.seed %}{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}{% endif %} + {% if result.seed is defined %}{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}{% endif %}

    diff --git a/searx/templates/oscar/result_templates/torrent.html b/searx/templates/oscar/result_templates/torrent.html index f5ea415e..089367e3 100644 --- a/searx/templates/oscar/result_templates/torrent.html +++ b/searx/templates/oscar/result_templates/torrent.html @@ -3,7 +3,7 @@ {{ result_header(result, favicons) }} {{ result_sub_header(result) }} -{% if result.seed %}

    {{ icon('transfer') }} {{ _('Seeder') }} {{ result.seed }} • {{ _('Leecher') }} {{ result.leech }}{% endif %} +{% if result.seed is defined %}

    {{ icon('transfer') }} {{ _('Seeder') }} {{ result.seed }} • {{ _('Leecher') }} {{ result.leech }}{% endif %} {% if result.filesize %}
    {{ icon('floppy-disk') }} {{ _('Filesize') }} {% if result.filesize < 1024 %}{{ result.filesize }} {{ _('Bytes') }} diff --git a/searx/templates/simple/result_templates/torrent.html b/searx/templates/simple/result_templates/torrent.html index 3c7fd15e..71c775bc 100644 --- a/searx/templates/simple/result_templates/torrent.html +++ b/searx/templates/simple/result_templates/torrent.html @@ -6,7 +6,7 @@ {% if result.magnetlink %}

    {% endif %} {% if result.torrentfile %}{% endif %} -{% if result.seed %}

    • {{ icon('arrow-swap') }} {{ _('Seeder') }} {{ result.seed }} • {{ _('Leecher') }} {{ result.leech }}

    {% endif %} +{% if result.seed is defined %}

    • {{ icon('arrow-swap') }} {{ _('Seeder') }} {{ result.seed }} • {{ _('Leecher') }} {{ result.leech }}

    {% endif %} {%- if result.filesize %}

    {{ icon('floppy-disk') }} {{ _('Filesize') }} {%- if result.filesize < 1024 %}{{ result.filesize }} {{ _('Bytes') }} diff --git a/tests/unit/engines/test_seedpeer.py b/tests/unit/engines/test_seedpeer.py new file mode 100644 index 00000000..2057c1cb --- /dev/null +++ b/tests/unit/engines/test_seedpeer.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +from collections import defaultdict +import mock +from searx.engines import seedpeer +from searx.testing import SearxTestCase + + +class TestBtdiggEngine(SearxTestCase): + + def test_request(self): + query = 'test_query' + dicto = defaultdict(dict) + dicto['pageno'] = 1 + params = seedpeer.request(query, dicto) + self.assertIn('url', params) + self.assertIn(query, params['url']) + self.assertIn('seedpeer', params['url']) + + def test_response(self): + self.assertRaises(AttributeError, seedpeer.response, None) + self.assertRaises(AttributeError, seedpeer.response, []) + self.assertRaises(AttributeError, seedpeer.response, '') + self.assertRaises(AttributeError, seedpeer.response, '[]') + + response = mock.Mock(text='') + self.assertEqual(seedpeer.response(response), []) + + html = u""" + + + + + + + +
    + + + + + + + + + + + + +
    Title1 year1 KB1020
    + + + """ + response = mock.Mock(text=html) + results = seedpeer.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['title'], 'Title') + self.assertEqual(results[0]['url'], 'https://seedpeer.me/link') + self.assertEqual(results[0]['seed'], 10) + self.assertEqual(results[0]['leech'], 20) + self.assertEqual(results[0]['filesize'], 1024) + self.assertEqual(results[0]['torrentfile'], 'https://seedpeer.me/torrent/abc123') + self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:abc123') From a56c56ead319ab150dcb5c9f055e62f4103a59a7 Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 28 Nov 2019 19:22:03 +0100 Subject: [PATCH 099/144] seatup.py: fix [dev_]requirements and open file with context setup(..) named arguments 'install_requires' and 'extras_require' need lists arguments, the is ignored when installing extra environment 'test':: pip install -e .\[test\] Signed-off-by: Markus Heiser --- setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 7333551f..bd3dd5d1 100644 --- a/setup.py +++ b/setup.py @@ -11,14 +11,14 @@ import sys sys.path.insert(0, './searx') from version import VERSION_STRING +with open('README.rst') as f: + long_description = f.read() -def read(*rnames): - return open(os.path.join(os.path.dirname(__file__), *rnames)).read() +with open('requirements.txt') as f: + requirements = [ l.strip() for l in f.readlines()] - -long_description = read('README.rst') -requirements = map(str.strip, open('requirements.txt').readlines()) -dev_requirements = map(str.strip, open('requirements-dev.txt').readlines()) +with open('requirements-dev.txt') as f: + dev_requirements = [ l.strip() for l in f.readlines()] setup( name='searx', From b55a8004ed4c3d11d0d820480eca4769665ef38b Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 28 Nov 2019 19:54:57 +0100 Subject: [PATCH 100/144] boilerplate: add inital Makefile with run & install targets Add *Makefile* boilerplate useful for python projects. All python tasks are using a virtualenv from ./local/py3 $ make help run - run developer instance install - developer install (./local) uninstall - uninstall (./local) Signed-off-by: Markus Heiser --- .gitignore | 3 + Makefile | 42 ++++++ utils/makefile.include | 128 ++++++++++++++++++ utils/makefile.python | 290 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+) create mode 100644 Makefile create mode 100644 utils/makefile.include create mode 100644 utils/makefile.python diff --git a/.gitignore b/.gitignore index db20da83..828856f4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ setup.cfg node_modules/ .tx/ + +local/ +searx.egg-info/ diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..42e492d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +# -*- coding: utf-8; mode: makefile-gmake -*- + +PYOBJECTS = searx +PY_SETUP_EXTRAS ?= \[test\] + +include utils/makefile.include +include utils/makefile.python + +all: clean install + +PHONY += help +help: + @echo ' run - run developer instance' + @echo ' install - developer install (./local)' + @echo ' uninstall - uninstall (./local)' + @echo '' + @$(MAKE) -s -f utils/makefile.include make-help + @echo '' + @$(MAKE) -s -f utils/makefile.python python-help + +PHONY += install +install: pyenvinstall + +PHONY += uninstall +uninstall: pyenvuninstall + +PHONY += clean +clean: pyclean + $(call cmd,common_clean) + +PHONY += run +run: pyenvinstall + $(Q) ( \ + sed -i -e "s/debug : False/debug : True/g" ./searx/settings.yml ; \ + sleep 2 ; \ + xdg-open http://127.0.0.1:8888/ ; \ + sleep 3 ; \ + sed -i -e "s/debug : True/debug : False/g" ./searx/settings.yml ; \ + ) & + $(PY_ENV)/bin/python ./searx/webapp.py + +.PHONY: $(PHONY) diff --git a/utils/makefile.include b/utils/makefile.include new file mode 100644 index 00000000..716889c0 --- /dev/null +++ b/utils/makefile.include @@ -0,0 +1,128 @@ +# -*- coding: utf-8; mode: makefile-gmake -*- + +make-help: + @echo ' make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build' + @echo ' make V=2 [targets] 2 => give reason for rebuild of target' + +quiet_cmd_common_clean = CLEAN $@ + cmd_common_clean = \ + rm -rf tests/build ;\ + find . -name '*.orig' -exec rm -f {} + ;\ + find . -name '*.rej' -exec rm -f {} + ;\ + find . -name '*~' -exec rm -f {} + ;\ + find . -name '*.bak' -exec rm -f {} + ;\ + +FMT = cat +ifeq ($(shell which fmt >/dev/null 2>&1; echo $$?), 0) +FMT = fmt +endif + +# MS-Windows +# +# For a minimal *make-environment*, I'am using the gnu-tools from: +# +# - GNU MCU Eclipse Windows Build Tools, which brings 'make', 'rm' etc. +# https://github.com/gnu-mcu-eclipse/windows-build-tools/releases +# +# - git for Windows, which brings 'find', 'grep' etc. +# https://git-scm.com/download/win + + +# normpath +# +# System-dependent normalization of the path name +# +# usage: $(call normpath,/path/to/file) + +normpath = $1 +ifeq ($(OS),Windows_NT) + normpath = $(subst /,\,$1) +endif + + +# stolen from linux/Makefile +# + +ifeq ("$(origin V)", "command line") + KBUILD_VERBOSE = $(V) +endif +ifndef KBUILD_VERBOSE + KBUILD_VERBOSE = 0 +endif + +ifeq ($(KBUILD_VERBOSE),1) + quiet = + Q = +else + quiet=quiet_ + Q = @ +endif + +# stolen from linux/scripts/Kbuild.include +# + +# Convenient variables +comma := , +quote := " +#" this comment is only for emacs highlighting +squote := ' +#' this comment is only for emacs highlighting +empty := +space := $(empty) $(empty) +space_escape := _-_SPACE_-_ + +# Find any prerequisites that is newer than target or that does not exist. +# PHONY targets skipped in both cases. +any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^) +# +### +# why - tell why a a target got build +# enabled by make V=2 +# Output (listed in the order they are checked): +# (1) - due to target is PHONY +# (2) - due to target missing +# (3) - due to: file1.h file2.h +# (4) - due to command line change +# (5) - due to missing .cmd file +# (6) - due to target not in $(targets) +# (1) PHONY targets are always build +# (2) No target, so we better build it +# (3) Prerequisite is newer than target +# (4) The command line stored in the file named dir/.target.cmd +# differed from actual command line. This happens when compiler +# options changes +# (5) No dir/.target.cmd file (used to store command line) +# (6) No dir/.target.cmd file and target not listed in $(targets) +# This is a good hint that there is a bug in the kbuild file +ifeq ($(KBUILD_VERBOSE),2) +why = \ + $(if $(filter $@, $(PHONY)),- due to target is PHONY, \ + $(if $(wildcard $@), \ + $(if $(strip $(any-prereq)),- due to: $(any-prereq), \ + $(if $(arg-check), \ + $(if $(cmd_$@),- due to command line change, \ + $(if $(filter $@, $(targets)), \ + - due to missing .cmd file, \ + - due to $(notdir $@) not in $$(targets) \ + ) \ + ) \ + ) \ + ), \ + - due to target missing \ + ) \ + ) + +echo-why = $(call escsq, $(strip $(why))) +endif +# +### +# Escape single quote for use in echo statements +escsq = $(subst $(squote),'\$(squote)',$1) +# +# echo command. +# Short version is used, if $(quiet) equals `quiet_', otherwise full one. +echo-cmd = $(if $($(quiet)cmd_$(1)),echo '$(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';) +# +# printing commands +cmd = @$(echo-cmd) $(cmd_$(1)) + diff --git a/utils/makefile.python b/utils/makefile.python new file mode 100644 index 00000000..228eb3f8 --- /dev/null +++ b/utils/makefile.python @@ -0,0 +1,290 @@ +# -*- coding: utf-8; mode: makefile-gmake -*- + +# list of python packages (folders) or modules (files) of this build +PYOBJECTS ?= + +SITE_PYTHON ?=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))site-python +export PYTHONPATH := $(SITE_PYTHON):$$PYTHONPATH + +# folder where the python distribution takes place +PYDIST ?= ./py_dist +# folder where the python intermediate build files take place +PYBUILD ?= ./py_build +# python version to use +PY ?=3 +PYTHON ?= python$(PY) +PIP ?= pip$(PY) +PIP_INST ?= --user + +# https://www.python.org/dev/peps/pep-0508/#extras +#PY_SETUP_EXTRAS ?= \[develop,test\] +PY_SETUP_EXTRAS ?= + +PYDEBUG ?= --pdb +PYLINT_RC ?= .pylintrc + +TEST_FOLDER ?= ./tests +TEST ?= . + +VTENV_OPTS = "--no-site-packages" +PY_ENV = ./local/py$(PY) +PY_ENV_BIN = $(PY_ENV)/bin +PY_ENV_ACT = . $(PY_ENV_BIN)/activate + +ifeq ($(OS),Windows_NT) + PYTHON = python + PY_ENV_BIN = $(PY_ENV)/Scripts + PY_ENV_ACT = $(PY_ENV_BIN)/activate +endif + +ifeq ($(PYTHON),python) + VIRTUALENV = virtualenv +else + VIRTUALENV = virtualenv --python=$(PYTHON) +endif + +ifeq ($(KBUILD_VERBOSE),1) + PIP_VERBOSE = + VIRTUALENV_VERBOSE = +else + PIP_VERBOSE = "-q" + VIRTUALENV_VERBOSE = "-q" +endif + +python-help:: + @echo 'makefile.python:' + @echo ' pyenv | pyenv[un]install' + @echo ' build $(PY_ENV) & [un]install python objects' + @echo ' targts using pyenv $(PY_ENV):' + @echo ' pylint - run pylint *linting*' + @echo ' pytest - run *tox* test on python objects' + @echo ' pydebug - run tests within a PDB debug session' + @echo ' pybuild - build python packages' + @echo ' pyclean - clean intermediate python objects' + @echo ' targets using system users environment:' + @echo ' py[un]install - [un]install python objects in editable mode' + @echo ' upload-pypi - upload $(PYDIST)/* files to PyPi' + @echo 'options:' + @echo ' make PY=2 [targets] => to eval targets with python 2 ($(PY))' + @echo ' make PIP_INST= => to set/unset pip install options ($(PIP_INST))' + @echo ' make TEST=. => choose test from $(TEST_FOLDER) (default "." runs all)' + @echo ' make DEBUG= => target "debug": do not invoke PDB on errors' + @echo ' make PY_SETUP_EXTRAS => also install extras_require from setup.py \[develop,test\]' + @echo ' when using target "pydebug", set breakpoints within py-source by adding::' + @echo ' DEBUG()' + +# ------------------------------------------------------------------------------ +# OS requirements +# ------------------------------------------------------------------------------ + +PHONY += msg-python-exe python-exe +msg-python-exe: + @echo "\n $(PYTHON) is required\n\n\ + Make sure you have $(PYTHON) installed, grab it from\n\ + https://www.python.org or install it from your package\n\ + manager. On debian based OS these requirements are\n\ + installed by::\n\n\ + sudo -H apt-get install $(PYTHON)\n" | $(FMT) + +ifeq ($(shell which $(PYTHON) >/dev/null 2>&1; echo $$?), 1) +python-exe: msg-python-exe + $(error The '$(PYTHON)' command was not found) +else +python-exe: + @: +endif + +msg-pip-exe: + @echo "\n $(PIP) is required\n\n\ + Make sure you have updated pip installed, grab it from\n\ + https://pip.pypa.io or install it from your package\n\ + manager. On debian based OS these requirements are\n\ + installed by::\n\n\ + sudo -H apt-get install python$(PY)-pip\n" | $(FMT) + +ifeq ($(shell which $(PIP) >/dev/null 2>&1; echo $$?), 1) +pip-exe: msg-pip-exe + $(error The '$(PIP)' command was not found) +else +pip-exe: + @: +endif + +PHONY += msg-virtualenv-exe virtualenv-exe +msg-virtualenv-exe: + @echo "\n virtualenv is required\n\n\ + Make sure you have an updated virtualenv installed, grab it from\n\ + https://virtualenv.pypa.io/en/stable/installation/ or install it\n\ + via pip by::\n\n\ + pip install --user https://github.com/pypa/virtualenv/tarball/master\n" | $(FMT) + +ifeq ($(shell which virtualenv >/dev/null 2>&1; echo $$?), 1) +virtualenv-exe: msg-virtualenv-exe + $(error The 'virtualenv' command was not found) +else +virtualenv-exe: + @: +endif + +# ------------------------------------------------------------------------------ +# commands +# ------------------------------------------------------------------------------ + +# $2 path to folder with setup.py, this uses pip from the OS +quiet_cmd_pyinstall = INSTALL $2 + cmd_pyinstall = $(PIP) $(PIP_VERBOSE) install $(PIP_INST) -e $2$(PY_SETUP_EXTRAS) + +# $2 path to folder with setup.py, this uses pip from pyenv (not OS!) +quiet_cmd_pyenvinstall = PYENV install $2 + cmd_pyenvinstall = $(PY_ENV_BIN)/pip $(PIP_VERBOSE) install -e $2$(PY_SETUP_EXTRAS) + +# Uninstall the package. Since pip does not uninstall the no longer needed +# depencies (something like autoremove) the depencies remain. + +# $2 package name to uninstall, this uses pip from the OS. +quiet_cmd_pyuninstall = UNINSTALL $2 + cmd_pyuninstall = $(PIP) $(PIP_VERBOSE) uninstall --yes $2 + +# $2 path to folder with setup.py, this uses pip from pyenv (not OS!) +quiet_cmd_pyenvuninstall = PYENV uninstall $2 + cmd_pyenvuninstall = $(PY_ENV_BIN)/pip $(PIP_VERBOSE) uninstall --yes $2 + +# $2 path to folder where virtualenv take place +quiet_cmd_virtualenv = PYENV usage: $ source ./$@/bin/activate + cmd_virtualenv = \ + if [ ! -d "./$(PY_ENV)" ];then \ + $(VIRTUALENV) $(VIRTUALENV_VERBOSE) $(VTENV_OPTS) $2; \ + else \ + echo " PYENV using virtualenv from $2"; \ + fi + +# $2 path to lint +quiet_cmd_pylint = LINT $@ + cmd_pylint = $(PY_ENV_BIN)/pylint --rcfile $(PYLINT_RC) $2 + +quiet_cmd_pytest = TEST $@ + cmd_pytest = $(PY_ENV_BIN)/tox -vv + +# setuptools, pip, easy_install its a mess full of cracks, a documentation hell +# and broken by design ... all sucks, I really, really hate all this ... aaargh! +# +# About python packaging see `Python Packaging Authority`_. Most of the names +# here are mapped to ``setup(=..., =...)`` arguments in +# ``setup.py``. See `Packaging and distributing projects`_ about ``setup(...)`` +# arguments. If this is all new for you, start with `PyPI Quick and Dirty`_. +# +# Further read: +# +# - pythonwheels_ +# - setuptools_ +# - packaging_ +# - sdist_ +# - installing_ +# +# .. _`Python Packaging Authority`: https://www.pypa.io +# .. _`Packaging and distributing projects`: https://packaging.python.org/guides/distributing-packages-using-setuptools/ +# .. _`PyPI Quick and Dirty`: https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/ +# .. _pythonwheels: https://pythonwheels.com/ +# .. _setuptools: https://setuptools.readthedocs.io/en/latest/setuptools.html +# .. _packaging: https://packaging.python.org/guides/distributing-packages-using-setuptools/#packaging-and-distributing-projects +# .. _sdist: https://packaging.python.org/guides/distributing-packages-using-setuptools/#source-distributions +# .. _bdist_wheel: https://packaging.python.org/guides/distributing-packages-using-setuptools/#pure-python-wheels +# .. _installing: https://packaging.python.org/tutorials/installing-packages/ +# +quiet_cmd_pybuild = BUILD $@ + cmd_pybuild = $(PY_ENV_BIN)/$(PYTHON) setup.py \ + sdist -d $(PYDIST) \ + bdist_wheel --bdist-dir $(PYBUILD) -d $(PYDIST) + +quiet_cmd_pyclean = CLEAN $@ +# remove 'build' folder since bdist_wheel does not care the --bdist-dir + cmd_pyclean = \ + rm -rf $(PYDIST) $(PYBUILD) ./local ./.tox *.egg-info ;\ + find . -name '*.pyc' -exec rm -f {} + ;\ + find . -name '*.pyo' -exec rm -f {} + ;\ + find . -name __pycache__ -exec rm -rf {} + + +# ------------------------------------------------------------------------------ +# targets +# ------------------------------------------------------------------------------ + +# for installation use the pip from the OS! +PHONY += pyinstall +pyinstall: pip-exe + $(call cmd,pyinstall,.) + +PHONY += pyuninstall +pyuninstall: pip-exe + $(call cmd,pyuninstall,$(PYOBJECTS)) + +# for installation use the pip from PY_ENV (not the OS)! +PHONY += pyenvinstall +pyenvinstall: $(PY_ENV) + $(call cmd,pyenvinstall,.) + +PHONY += pyenvuninstall +pyenvuninstall: $(PY_ENV) + $(call cmd,pyenvuninstall,$(PYOBJECTS)) + +PHONY += pyclean +pyclean: + $(call cmd,pyclean) + +# to build *local* environment, python and virtualenv from the OS is needed! +pyenv: $(PY_ENV) +$(PY_ENV): virtualenv-exe python-exe + $(call cmd,virtualenv,$(PY_ENV)) + @$(PY_ENV_BIN)/pip install $(PIP_VERBOSE) -r requirements.txt + +PHONY += pylint-exe +pylint-exe: $(PY_ENV) + @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) install pylint + +PHONY += pylint +pylint: pylint-exe + $(call cmd,pylint,$(PYOBJECTS)) + +PHONY += pybuild +pybuild: $(PY_ENV) + $(call cmd,pybuild) + +PHONY += pytest +pytest: $(PY_ENV) + $(call cmd,pytest) + +PHONY += pydebug +# set breakpoint with: +# DEBUG() +# e.g. to run tests in debug mode in emacs use: +# 'M-x pdb' ... 'make pydebug' +pydebug: $(PY_ENV) + DEBUG=$(DEBUG) $(PY_ENV_BIN)/pytest $(DEBUG) -v $(TEST_FOLDER)/$(TEST) + +# install / uninstall python objects into virtualenv (PYENV) +pyenv-install: $(PY_ENV) + @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) install -e . + @echo " ACTIVATE $(call normpath,$(PY_ENV_ACT)) " + +pyenv-uninstall: $(PY_ENV) + @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) uninstall --yes . + +# runs python interpreter from ./local/py/bin/python +pyenv-python: pyenv-install + cd ./local; ../$(PY_ENV_BIN)/python -i + +# With 'dependency_links=' setuptools supports dependencies on packages hosted +# on other reposetories then PyPi, see "Packages Not On PyPI" [1]. The big +# drawback is, due to security reasons (I don't know where the security gate on +# PyPi is), this feature is not supported by pip [2]. Thats why an upload to +# PyPi is required and since uploads via setuptools is not recommended, we have +# to imstall / use twine ... its really a mess. +# +# [1] http://python-packaging.readthedocs.io/en/latest/dependencies.html#packages-not-on-pypi +# [2] https://github.com/pypa/pip/pull/1519 + +# https://github.com/pypa/twine +PHONY += upload-pypi +upload-pypi: pyclean pybuild + @$(PY_ENV_BIN)/twine upload $(PYDIST)/* + +.PHONY: $(PHONY) From 7e572a2453d18bdb3f44b65cbc1441004c4faa5f Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 28 Nov 2019 20:05:29 +0100 Subject: [PATCH 101/144] Makefile: add test.pep8 test.unit test.robot (from manage.sh) Signed-off-by: Markus Heiser --- Makefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Makefile b/Makefile index 42e492d0..604cdb8a 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ all: clean install PHONY += help help: + @echo ' test - run developer tests' @echo ' run - run developer instance' @echo ' install - developer install (./local)' @echo ' uninstall - uninstall (./local)' @@ -39,4 +40,21 @@ run: pyenvinstall ) & $(PY_ENV)/bin/python ./searx/webapp.py +# test +# ---- + +PHONY += test test.pep8 test.unit test.robot + +test: test.pep8 test.unit test.robot + +test.pep8: pyenvinstall + $(PY_ENV_ACT); ./manage.sh pep8_check + +test.unit: pyenvinstall + $(PY_ENV_ACT); ./manage.sh unit_tests + +test.robot: pyenvinstall + $(PY_ENV_ACT); ./manage.sh install_geckodriver + $(PY_ENV_ACT); ./manage.sh robot_tests + .PHONY: $(PHONY) From 796197db2d135fe3342e4355aeb57a74dfa6beca Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 28 Nov 2019 20:07:10 +0100 Subject: [PATCH 102/144] boilerplate: add pylint / WIP: balance linting with pylint Signed-off-by: Markus Heiser --- .pylintrc | 444 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 4 +- 2 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..3b4adb2c --- /dev/null +++ b/.pylintrc @@ -0,0 +1,444 @@ +# -*- coding: utf-8; mode: conf -*- +# lint Python modules using external checkers. +# +# This is the main checker controlling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS, .git, .svn + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=bad-whitespace, duplicate-code + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details + +# HINT: do not set this here, use argument --msg-template=... +#msg-template={path}:{line}: [{msg_id}({symbol}),{obj}] {msg} + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. + +# HINT: do not set this here, use argument --output-format=... +#output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Naming hint for argument names +argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct argument names +argument-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for attribute names +attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct attribute names +attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*)|([A-Z0-9_]*))$ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct constant names +const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ +#const-rgx=[f]?[A-Z_][a-zA-Z0-9_]{2,30}$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming hint for function names +function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct function names +function-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_,log,cfg,id + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for method names +method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct method names +method-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct module names +#module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +module-rgx=([a-z_][a-z0-9_]*)$ + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming hint for variable names +variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*)|([a-z]))$ + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module +max-module-lines=2000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement.No config file found, using default configuration + +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=8 + +# Maximum number of attributes for a class (see R0902). +max-attributes=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=20 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/Makefile b/Makefile index 604cdb8a..77ffe489 100644 --- a/Makefile +++ b/Makefile @@ -43,9 +43,11 @@ run: pyenvinstall # test # ---- -PHONY += test test.pep8 test.unit test.robot +PHONY += test test.pylint test.pep8 test.unit test.robot +# TODO: balance linting with pylint test: test.pep8 test.unit test.robot + - make pylint test.pep8: pyenvinstall $(PY_ENV_ACT); ./manage.sh pep8_check From 62a4b7443fab7ea5d9deaa11b8ea06f0bdb26069 Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 28 Nov 2019 20:08:44 +0100 Subject: [PATCH 103/144] boilerplate: add .dir-locals.el with emacs python enviroment The .dir-locals.el set the project's python enviroment for the emacs tasks like flycheck or jedi. The py-environment has to be next to /.dir-locals.el:: ./local/py3 To setup such an environment build target 'pyenv' or 'pyenvinstall':: $ make pyenvinstall TL;DR Alternatively create the virtualenv, source it and install jedi + epc (required by `emacs-jedi `_):: $ virtualenv --python=python3 "--no-site-packages" ./local/py3 ... $ source ./local/py3/bin/activate (py3)$ # now install into the activated 'py3' environment .. (py3)$ pip install jedi epc ... Signed-off-by: Markus Heiser --- .dir-locals.el | 117 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 00000000..b5562240 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,117 @@ +;;; .dir-locals.el +;; +;; If you get ``*** EPC Error ***`` with this setup in your emacs session, +;; mostly you have jedi-mode enabled but the python enviroment is missed. The +;; python environment has to be next to the ``/.dir-locals.el`` in:: +;; +;; ./local/py3 +;; +;; In Emacs, some buffer locals are referencing the project environment: +;; +;; - prj-root --> / +;; - python-environment-directory --> /local +;; - python-environment-default-root-name --> py3 +;; - python-shell-virtualenv-root --> /local/py3 +;; When this variable is set with the path of the virtualenv to use, +;; `process-environment' and `exec-path' get proper values in order to run +;; shells inside the specified virtualenv, example:: +;; (setq python-shell-virtualenv-root "/path/to/env/") +;; +;; To setup such an environment build target 'pyenv' or 'pyenvinstall':: +;; +;; $ make pyenvinstall +;; +;; Alternatively create the virtualenv, source it and install jedi + epc +;; (required by `emacs-jedi `_):: +;; +;; $ virtualenv --python=python3 "--no-site-packages" ./local/py3 +;; ... +;; $ source ./local/py3/bin/activate +;; (py3)$ # now install into the activated 'py3' environment .. +;; (py3)$ pip install jedi epc +;; ... + +((nil + . ((fill-column . 80) + )) + (python-mode + . ((indent-tabs-mode . nil) + + ;; project root folder is where the `.dir-locals.el' is located + (eval . (setq-local + prj-root (locate-dominating-file default-directory ".dir-locals.el"))) + + (eval . (setq-local + python-environment-directory (expand-file-name "./local" prj-root))) + + ;; use 'py3' enviroment as default + (eval . (setq-local + python-environment-default-root-name "py3")) + + (eval . (setq-local + python-shell-virtualenv-root + (concat python-environment-directory + "/" + python-environment-default-root-name))) + + ;; python-shell-virtualenv-path is obsolete, use python-shell-virtualenv-root! + ;; (eval . (setq-local + ;; python-shell-virtualenv-path python-shell-virtualenv-root)) + + (eval . (setq-local + python-shell-interpreter + (expand-file-name "bin/python" python-shell-virtualenv-root))) + + (eval . (setq-local + python-environment-virtualenv + (list (expand-file-name "bin/virtualenv" python-shell-virtualenv-root) + ;;"--system-site-packages" + "--quiet"))) + + (eval . (setq-local + pylint-command + (expand-file-name "bin/pylint" python-shell-virtualenv-root))) + + ;; pylint will find the '.pylintrc' file next to the CWD + ;; https://pylint.readthedocs.io/en/latest/user_guide/run.html#command-line-options + (eval . (setq-local + flycheck-pylintrc ".pylintrc")) + + ;; flycheck & other python stuff should use the local py3 environment + (eval . (setq-local + flycheck-python-pylint-executable python-shell-interpreter)) + + ;; use 'M-x jedi:show-setup-info' and 'M-x epc:controller' to inspect jedi server + + ;; https://tkf.github.io/emacs-jedi/latest/#jedi:environment-root -- You + ;; can specify a full path instead of a name (relative path). In that case, + ;; python-environment-directory is ignored and Python virtual environment + ;; is created at the specified path. + (eval . (setq-local jedi:environment-root python-shell-virtualenv-root)) + + ;; https://tkf.github.io/emacs-jedi/latest/#jedi:server-command + (eval .(setq-local + jedi:server-command + (list python-shell-interpreter + jedi:server-script) + )) + + ;; jedi:environment-virtualenv --> see above 'python-environment-virtualenv' + ;; is set buffer local! No need to setup jedi:environment-virtualenv: + ;; + ;; Virtualenv command to use. A list of string. If it is nil, + ;; python-environment-virtualenv is used instead. You must set non-nil + ;; value to jedi:environment-root in order to make this setting work. + ;; + ;; https://tkf.github.io/emacs-jedi/latest/#jedi:environment-virtualenv + ;; + ;; (eval . (setq-local + ;; jedi:environment-virtualenv + ;; (list (expand-file-name "bin/virtualenv" python-shell-virtualenv-root) + ;; ;;"--python" + ;; ;;"/usr/bin/python3.4" + ;; ))) + + ;; jedi:server-args + + ))) From 52450fd08a3c70f6cc0ef806fc450f70dc867d49 Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Fri, 29 Nov 2019 13:15:15 +0100 Subject: [PATCH 104/144] .dir-locals.el: add some comments about jedi & EPC Signed-off-by: Markus Heiser --- .dir-locals.el | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index b5562240..d7ec8792 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,10 +1,11 @@ ;;; .dir-locals.el ;; -;; If you get ``*** EPC Error ***`` with this setup in your emacs session, -;; mostly you have jedi-mode enabled but the python enviroment is missed. The -;; python environment has to be next to the ``/.dir-locals.el`` in:: +;; If you get ``*** EPC Error ***`` (even after a jedi:install-server) in your +;; emacs session, mostly you have jedi-mode enabled but the python enviroment is +;; missed. The python environment has to be next to the +;; ``/.dir-locals.el`` in:: ;; -;; ./local/py3 +;; ./local/py3 ;; ;; In Emacs, some buffer locals are referencing the project environment: ;; @@ -30,6 +31,21 @@ ;; (py3)$ # now install into the activated 'py3' environment .. ;; (py3)$ pip install jedi epc ;; ... +;; +;; Here is what also I found useful to add to my .emacs:: +;; +;; (global-set-key [f6] 'flycheck-mode) +;; (add-hook 'python-mode-hook 'my:python-mode-hook) +;; +;; (defun my:python-mode-hook () +;; (add-to-list 'company-backends 'company-jedi) +;; (require 'jedi-core) +;; (jedi:setup) +;; (define-key python-mode-map (kbd "C-c C-d") 'jedi:show-doc) +;; (define-key python-mode-map (kbd "M-.") 'jedi:goto-definition) +;; (define-key python-mode-map (kbd "M-,") 'jedi:goto-definition-pop-marker) +;; ) +;; ((nil . ((fill-column . 80) From 2aa95c16e31ff0d688a06492863b9f2a0c209bfb Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Fri, 29 Nov 2019 14:47:43 +0100 Subject: [PATCH 105/144] [fix] soundcloud: URLs of JS sources has been moved The client_id is found under (new) URL: https://a-v2.sndcdn.com/assets/49-a0c01933-3.js Signed-off-by: Markus Heiser --- searx/engines/soundcloud.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/searx/engines/soundcloud.py b/searx/engines/soundcloud.py index 87099854..284689bf 100644 --- a/searx/engines/soundcloud.py +++ b/searx/engines/soundcloud.py @@ -51,7 +51,9 @@ def get_client_id(): if response.ok: tree = html.fromstring(response.content) - script_tags = tree.xpath("//script[contains(@src, '/assets/app')]") + # script_tags has been moved from /assets/app/ to /assets/ path. I + # found client_id in https://a-v2.sndcdn.com/assets/49-a0c01933-3.js + script_tags = tree.xpath("//script[contains(@src, '/assets/')]") app_js_urls = [script_tag.get('src') for script_tag in script_tags if script_tag is not None] # extracts valid app_js urls from soundcloud.com content From 4998e9ec856479d0c619e54f7100c295c7c5851c Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Fri, 29 Nov 2019 18:56:29 +0100 Subject: [PATCH 106/144] [fix] duckduckgo_definitions - where 'AnswerType' is 'calc' Do not try to get text when 'AnswerType' is 'calc'. Signed-off-by: Markus Heiser --- searx/engines/duckduckgo_definitions.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/searx/engines/duckduckgo_definitions.py b/searx/engines/duckduckgo_definitions.py index 957a13ea..2899b50f 100644 --- a/searx/engines/duckduckgo_definitions.py +++ b/searx/engines/duckduckgo_definitions.py @@ -1,11 +1,25 @@ +""" +DuckDuckGo (definitions) + +- `Instant Answer API`_ +- `DuckDuckGo query`_ + +.. _Instant Answer API: https://duckduckgo.com/api +.. _DuckDuckGo query: https://api.duckduckgo.com/?q=DuckDuckGo&format=json&pretty=1 + +""" + import json from lxml import html from re import compile +import logging from searx.engines.xpath import extract_text from searx.engines.duckduckgo import _fetch_supported_languages, supported_languages_url, language_aliases from searx.url_utils import urlencode from searx.utils import html_to_text, match_language +logger = logging.getLogger('searx.engines.'+ __name__) + url = 'https://api.duckduckgo.com/'\ + '?{query}&format=json&pretty=0&no_redirect=1&d=1' @@ -25,7 +39,9 @@ def result_to_text(url, text, htmlResult): def request(query, params): params['url'] = url.format(query=urlencode({'q': query})) language = match_language(params['language'], supported_languages, language_aliases) - params['headers']['Accept-Language'] = language.split('-')[0] + language = language.split('-')[0] + params['headers']['Accept-Language'] = language + logger.debug("query %s: // headers: %s", params['url'], params['headers']) return params @@ -43,8 +59,9 @@ def response(resp): # add answer if there is one answer = search_res.get('Answer', '') - if answer != '': - results.append({'answer': html_to_text(answer)}) + if answer: + if search_res.get('AnswerType', '') not in ['calc']: + results.append({'answer': html_to_text(answer)}) # add infobox if 'Definition' in search_res: From b6d9f5aa71de43ff9a46f0d4d94baad429e2275d Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Fri, 29 Nov 2019 19:55:16 +0100 Subject: [PATCH 107/144] [fix] duckduckgo_definition issues reported by 'manage.sh test' Fix this error while travis build:: /home/travis/build/asciimoo/searx/searx/engines/duckduckgo_definitions.py:21:44: E225 missing whitespace around operator Signed-off-by: Markus Heiser --- searx/engines/duckduckgo_definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/engines/duckduckgo_definitions.py b/searx/engines/duckduckgo_definitions.py index 2899b50f..49def2a2 100644 --- a/searx/engines/duckduckgo_definitions.py +++ b/searx/engines/duckduckgo_definitions.py @@ -18,7 +18,7 @@ from searx.engines.duckduckgo import _fetch_supported_languages, supported_langu from searx.url_utils import urlencode from searx.utils import html_to_text, match_language -logger = logging.getLogger('searx.engines.'+ __name__) +logger = logging.getLogger('searx.engines.' + __name__) url = 'https://api.duckduckgo.com/'\ + '?{query}&format=json&pretty=0&no_redirect=1&d=1' From 30ad0c666df6644c4d05949f7bc5d7b1485f3fd6 Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Mon, 2 Dec 2019 14:26:11 +0100 Subject: [PATCH 108/144] duckduckgo_definitions: remove the debug message Signed-off-by: Markus Heiser --- searx/engines/duckduckgo_definitions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/searx/engines/duckduckgo_definitions.py b/searx/engines/duckduckgo_definitions.py index 49def2a2..79d10c30 100644 --- a/searx/engines/duckduckgo_definitions.py +++ b/searx/engines/duckduckgo_definitions.py @@ -12,14 +12,11 @@ DuckDuckGo (definitions) import json from lxml import html from re import compile -import logging from searx.engines.xpath import extract_text from searx.engines.duckduckgo import _fetch_supported_languages, supported_languages_url, language_aliases from searx.url_utils import urlencode from searx.utils import html_to_text, match_language -logger = logging.getLogger('searx.engines.' + __name__) - url = 'https://api.duckduckgo.com/'\ + '?{query}&format=json&pretty=0&no_redirect=1&d=1' @@ -41,7 +38,6 @@ def request(query, params): language = match_language(params['language'], supported_languages, language_aliases) language = language.split('-')[0] params['headers']['Accept-Language'] = language - logger.debug("query %s: // headers: %s", params['url'], params['headers']) return params From ccaf6ca02c5bdc63f78e01a66429afaa5fb3cb68 Mon Sep 17 00:00:00 2001 From: Marc Abonce Seguin Date: Wed, 26 Jun 2019 00:45:20 -0500 Subject: [PATCH 109/144] [fix] update xpaths for new google results page --- searx/engines/google.py | 21 ++---- tests/unit/engines/test_google.py | 117 ++++++++++-------------------- 2 files changed, 44 insertions(+), 94 deletions(-) diff --git a/searx/engines/google.py b/searx/engines/google.py index 19bde710..eed3a044 100644 --- a/searx/engines/google.py +++ b/searx/engines/google.py @@ -107,13 +107,12 @@ images_path = '/images' supported_languages_url = 'https://www.google.com/preferences?#languages' # specific xpath variables -results_xpath = '//div[@class="g"]' -url_xpath = './/h3/a/@href' -title_xpath = './/h3' -content_xpath = './/span[@class="st"]' -content_misc_xpath = './/div[@class="f slp"]' -suggestion_xpath = '//p[@class="_Bmc"]' -spelling_suggestion_xpath = '//a[@class="spell"]' +results_xpath = '//div[contains(@class, "ZINbbc")]' +url_xpath = './/div[@class="kCrYT"][1]/a/@href' +title_xpath = './/div[@class="kCrYT"][1]/a/div[1]' +content_xpath = './/div[@class="kCrYT"][2]//div[contains(@class, "BNeawe")]//div[contains(@class, "BNeawe")]' +suggestion_xpath = '//div[contains(@class, "ZINbbc")][last()]//div[@class="rVLSBd"]/a//div[contains(@class, "BNeawe")]' +spelling_suggestion_xpath = '//div[@id="scc"]//a' # map : detail location map_address_xpath = './/div[@class="s"]//table//td[2]/span/text()' @@ -199,10 +198,6 @@ def request(query, params): params['headers']['Accept-Language'] = language + ',' + language + '-' + country params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' - # Force Safari 3.1 on Mac OS X (Leopard) user agent to avoid loading the new UI that Searx can't parse - params['headers']['User-Agent'] = ("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_4)" - "AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1") - params['google_hostname'] = google_hostname return params @@ -274,9 +269,7 @@ def response(resp): content = extract_text_from_dom(result, content_xpath) if content is None: continue - content_misc = extract_text_from_dom(result, content_misc_xpath) - if content_misc is not None: - content = content_misc + "
    " + content + # append result results.append({'url': url, 'title': title, diff --git a/tests/unit/engines/test_google.py b/tests/unit/engines/test_google.py index a73e9d2b..9d0edd43 100644 --- a/tests/unit/engines/test_google.py +++ b/tests/unit/engines/test_google.py @@ -58,93 +58,50 @@ class TestGoogleEngine(SearxTestCase): self.assertEqual(google.response(response), []) html = """ -

    -

    - - This is the title - -

    -
    -
    - - test.psychologies.com/ - -
    ‎ -