on master help sign in
Merge branch 'master' into fix-infinite-scroll
commite64ff38217a1ba49afd4bb1c595121d94cbb2e33 authorMarkus Heiser <markus.heiser@darmarIT.de> dateTue 28 Jan 2020, 10:59 (7 years ago) parent0e7b6c9bda1895
157 files changed+6509−10928
.gitignore+3 Makefile+27 README.rst+3−3 docs/_themes/searx/static/searx.css+130 docs/_themes/searx/theme.conf+6 docs/admin/api.rst+96 docs/admin/arch_public.dot+33 docs/admin/architecture.rst+24 docs/admin/buildhosts.rst+103 docs/admin/engines.rst+71 docs/admin/filtron.rst+148 docs/admin/index.rst+16 docs/admin/installation.rst+349 docs/admin/morty.rst+26 docs/admin/plugins.rst+39 docs/admin/settings.rst+181 docs/blog/admin.rst+43 docs/blog/index.rst+10 docs/blog/intro-offline.rst+77 docs/blog/python3.rst+68 docs/blog/searx-admin-engines.png docs/blog/searxpy3.png docs/conf.py+114 docs/dev/contribution_guide.rst+180 docs/dev/csv_table.txt+6 docs/dev/engine_overview.rst+267 docs/dev/hello.dot+3 docs/dev/index.rst+15 docs/dev/makefile.rst+221 docs/dev/plugins.rst+54 docs/dev/quickstart.rst+132 docs/dev/reST.rst+1428 docs/dev/search_api.rst+120 docs/dev/svg_image.svg+10 docs/dev/translation.rst+71 docs/index.rst+32 docs/static/img/searx_logo_small.png docs/user/index.rst+10 docs/user/own-instance.rst+77 docs/user/public_instances.rst+318 docs/user/search_syntax.rst+42 manage.sh+8−2 requirements-dev.txt+6 searx/data/useragents.json+8−7 searx/engines/bing.py+2−3 searx/engines/flickr_noapi.py+17−9 searx/engines/gigablast.py+28−13 searx/engines/ina.py+6−3 searx/engines/microsoft_academic.py+2 searx/engines/openstreetmap.py+1−1 searx/engines/qwant.py+1 searx/engines/scanr_structures.py+1−1 searx/engines/spotify.py+14 searx/engines/wikipedia.py+7−2 searx/settings.yml+20−15 searx/settings_robot.yml+2−2 searx/static/plugins/js/vim_hotkeys.js+10−2 searx/static/themes/legacy/less/autocompleter.less+61−61 searx/static/themes/oscar/gruntfile.js+3−3 searx/static/themes/oscar/js/searx.js+326−326 searx/static/themes/oscar/js/searx_src/00_requirejs_config.js+23−23 searx/static/themes/oscar/js/searx_src/autocompleter.js+37−37 searx/static/themes/oscar/js/searx_src/element_modifiers.js+99−99 searx/static/themes/oscar/js/searx_src/leaflet_map.js+167−167 searx/static/themes/oscar/less/logicodev-dark/oscar.less+2−2 searx/static/themes/oscar/less/logicodev/code.less+1−3 searx/static/themes/oscar/less/logicodev/infobox.less+1−1 searx/static/themes/oscar/less/logicodev/navbar.less−1 searx/static/themes/oscar/less/pointhi/code.less+1−1 searx/static/themes/oscar/less/pointhi/infobox.less+1−1 searx/static/themes/simple/leaflet/leaflet.css+636−636 searx/templates/oscar/infobox.html+1−1 searx/templates/oscar/languages.html+8−12 searx/templates/oscar/preferences.html+33−33 searx/templates/oscar/result_templates/code.html+18−18 searx/templates/oscar/result_templates/images.html+1−1 searx/templates/oscar/result_templates/map.html+72−72 searx/templates/oscar/result_templates/videos.html+27−27 searx/templates/oscar/results.html+62−62 searx/templates/oscar/search.html+1−1 searx/templates/oscar/search_full.html+18−18 searx/templates/oscar/time-range.html+1−1 searx/webapp.py+5−5 tests/unit/engines/__init__.py tests/unit/engines/pubmed.py−37 tests/unit/engines/seedpeer_fixture.html−110 tests/unit/engines/test_acgsou.py−78 tests/unit/engines/test_archlinux.py−111 tests/unit/engines/test_arxiv.py−58 tests/unit/engines/test_base.py−91 tests/unit/engines/test_bing.py−178 tests/unit/engines/test_bing_images.py−132 tests/unit/engines/test_bing_news.py−147 tests/unit/engines/test_bing_videos.py−72 tests/unit/engines/test_btdigg.py−112 tests/unit/engines/test_currency_convert.py−56 tests/unit/engines/test_dailymotion.py−112 tests/unit/engines/test_deezer.py−57 tests/unit/engines/test_deviantart.py−24 tests/unit/engines/test_digbt.py−61 tests/unit/engines/test_digg.py−16 tests/unit/engines/test_doku.py−79 tests/unit/engines/test_duckduckgo.py−106 tests/unit/engines/test_duckduckgo_definitions.py−255 tests/unit/engines/test_duckduckgo_images.py−75 tests/unit/engines/test_duden.py−47 tests/unit/engines/test_dummy.py−26 tests/unit/engines/test_faroo.py−113 tests/unit/engines/test_fdroid.py−60 tests/unit/engines/test_flickr.py−142 tests/unit/engines/test_flickr_noapi.py−357 tests/unit/engines/test_framalibre.py−103 tests/unit/engines/test_frinkiac.py−50 tests/unit/engines/test_genius.py−231 tests/unit/engines/test_gigablast.py−119 tests/unit/engines/test_github.py−61 tests/unit/engines/test_google.py−194 tests/unit/engines/test_google_images.py−27 tests/unit/engines/test_google_news.py−102 tests/unit/engines/test_google_videos.py−79 tests/unit/engines/test_ina.py−64 tests/unit/engines/test_kickass.py−397 tests/unit/engines/test_mediawiki.py−130 tests/unit/engines/test_mixcloud.py−67 tests/unit/engines/test_nyaa.py−124 tests/unit/engines/test_openstreetmap.py−199 tests/unit/engines/test_pdbe.py−109 tests/unit/engines/test_photon.py−166 tests/unit/engines/test_piratebay.py−166 tests/unit/engines/test_qwant.py−339 tests/unit/engines/test_reddit.py−71 tests/unit/engines/test_scanr_structures.py−175 tests/unit/engines/test_searchcode_code.py−75 tests/unit/engines/test_searchcode_doc.py−70 tests/unit/engines/test_seedpeer.py−66 tests/unit/engines/test_soundcloud.py−192 tests/unit/engines/test_spotify.py−124 tests/unit/engines/test_stackoverflow.py−106 tests/unit/engines/test_startpage.py−67 tests/unit/engines/test_tokyotoshokan.py−110 tests/unit/engines/test_torrentz.py−87 tests/unit/engines/test_twitter.py−502 tests/unit/engines/test_unsplash.py−38 tests/unit/engines/test_vimeo.py−36 tests/unit/engines/test_wikidata.py−514 tests/unit/engines/test_wikipedia.py−263 tests/unit/engines/test_wolframalpha_api.py−166 tests/unit/engines/test_wolframalpha_noapi.py−224 tests/unit/engines/test_www1x.py−14 tests/unit/engines/test_yacy.py−96 tests/unit/engines/test_yahoo.py−190 tests/unit/engines/test_yahoo_news.py−150 tests/unit/engines/test_youtube_api.py−111 tests/unit/engines/test_youtube_noapi.py−124 tests/unit/engines/unsplash_fixture.json−241 utils/makefile.python+2−2 utils/makefile.sphinx+216
.gitignore+3
@@ -19,5 +19,8 @@ node_modules/
1919
2020.tx/
2121
22+build/
23+dist/
2224local/
25+gh-pages/
2326searx.egg-info/
Makefile+27
@@ -1,19 +1,32 @@
11# -*- coding: utf-8; mode: makefile-gmake -*-
22
3+export GIT_URL=https://github.com/asciimoo/searx
4+export SEARX_URL=https://searx.me
5+export DOCS_URL=https://asciimoo.github.io/searx
6+
37PYOBJECTS = searx
8+DOC = docs
49PY_SETUP_EXTRAS ?= \[test\]
510
11+PYDIST=./dist/py
12+PYBUILD=./build/py
13+
614include utils/makefile.include
715include utils/makefile.python
16+include utils/makefile.sphinx
817
918all: clean install
1019
1120PHONY += help
1221help:
1322 @echo ' test - run developer tests'
23+ @echo ' docs - build documentation'
24+ @echo ' docs-live - autobuild HTML documentation while editing'
1425 @echo ' run - run developer instance'
1526 @echo ' install - developer install (./local)'
1627 @echo ' uninstall - uninstall (./local)'
28+ @echo ' gh-pages - build docs & deploy on gh-pages branch'
29+ @echo ' clean - drop builds and environments'
1730 @echo ''
1831 @$(MAKE) -s -f utils/makefile.include make-help
1932 @echo ''
@@ -40,6 +53,20 @@ run: pyenvinstall
4053 ) &
4154 $(PY_ENV)/bin/python ./searx/webapp.py
4255
56+# docs
57+# ----
58+
59+PHONY += docs
60+docs: pyenvinstall sphinx-doc
61+ $(call cmd,sphinx,html,docs,docs)
62+
63+PHONY += docs-live
64+docs-live: pyenvinstall sphinx-live
65+ $(call cmd,sphinx_autobuild,html,docs,docs)
66+
67+$(GH_PAGES)::
68+ @echo "doc available at --> $(DOCS_URL)"
69+
4370# test
4471# ----
4572
README.rst+3−3
@@ -23,13 +23,13 @@ Go to the `searx-docker <https://github.com/searx/searx-docker>`__ project.
2323
2424Without Docker
2525------
26−For all the details, follow this `step by step installation <https://asciimoo.github.io/searx/dev/install/installation.html>`__.
26+For all of the details, follow this `step by step installation <https://asciimoo.github.io/searx/dev/install/installation.html>`__.
2727
2828Note: the documentation needs to be updated.
2929
30−If you are in hurry
30+If you are in a hurry
3131------
32−- clone source:
32+- clone the source:
3333 ``git clone https://github.com/asciimoo/searx.git && cd searx``
3434- install dependencies: ``./manage.sh update_packages``
3535- edit your
docs/_themes/searx/static/searx.cssnew file+130
@@ -0,0 +1,130 @@
1+@import url("pocoo.css");
2+
3+a, a.reference, a.footnote-reference {
4+ color: #004b6b;
5+ border-color: #004b6b;
6+}
7+
8+a:hover {
9+ color: #6d4100;
10+ border-color: #6d4100;
11+}
12+
13+p.version-warning {
14+ background-color: #004b6b;
15+}
16+
17+div.sidebar {
18+ background-color: whitesmoke;
19+ border-color: lightsteelblue;
20+ border-radius: 3pt;
21+}
22+
23+p.sidebar-title, .sidebar p {
24+ margin: 6pt;
25+}
26+
27+.sidebar li,
28+.hlist li {
29+ list-style-type: disclosure-closed;
30+}
31+
32+
33+/* admonitions
34+*/
35+
36+div.admonition, div.topic {
37+ background-color: #fafafa;
38+ margin: 8px 0px;
39+ padding: 1em;
40+ border-radius: 3pt 0 0 3pt;
41+ border-top: none;
42+ border-right: none;
43+ border-bottom: none;
44+ border-left: 5pt solid #ccc;
45+}
46+
47+p.admonition-title:after {
48+ content: none;
49+}
50+
51+.admonition.hint { border-color: #416dc0b0; }
52+.admonition.note { border-color: #6c856cb0; }
53+.admonition.tip { border-color: #85c5c2b0; }
54+.admonition.attention { border-color: #ecec97b0; }
55+.admonition.caution { border-color: #a6c677b0; }
56+.admonition.danger { border-color: #d46262b0; }
57+.admonition.important { border-color: #dfa3a3b0; }
58+.admonition.error { border-color: red; }
59+.admonition.warning { border-color: darkred; }
60+
61+.admonition.admonition-generic-admonition-title {
62+ border-color: #416dc0b0;
63+}
64+
65+
66+/* admonitions with (rendered) reST markup examples (:class: rst-example)
67+ *
68+ * .. admonition:: title of the example
69+ * :class: rst-example
70+ * ....
71+*/
72+
73+div.rst-example {
74+ background-color: inherit;
75+ margin: 0;
76+ border-top: none;
77+ border-right: 1px solid #ccc;
78+ border-bottom: none;
79+ border-left: none;
80+ border-radius: none;
81+ padding: 0;
82+}
83+
84+div.rst-example > p.admonition-title {
85+ font-family: Sans Serif;
86+ font-style: italic;
87+ font-size: 0.8em;
88+ display: block;
89+ border-bottom: 1px solid #ccc;
90+ padding: 0.5em 1em;
91+ text-align: right;
92+}
93+
94+/* code block in figures
95+ */
96+
97+div.highlight pre {
98+ text-align: left;
99+}
100+
101+/* Table theme
102+*/
103+
104+thead, tfoot {
105+ background-color: #fff;
106+}
107+
108+th:hover, td:hover {
109+ background-color: #ffc;
110+}
111+
112+thead th, tfoot th, tfoot td, tbody th {
113+ background-color: #fffaef;
114+}
115+
116+tbody tr:nth-child(odd) {
117+ background-color: #fff;
118+}
119+
120+tbody tr:nth-child(even) {
121+ background-color: #fafafa;
122+}
123+
124+caption {
125+ font-family: Sans Serif;
126+ padding: 0.5em;
127+ margin: 0.5em 0 0.5em 0;
128+ caption-side: top;
129+ text-align: left;
130+}
docs/_themes/searx/theme.confnew file+6
@@ -0,0 +1,6 @@
1+[theme]
2+inherit = pocoo
3+stylesheet = searx.css
4+
5+[options]
6+touch_icon =
docs/admin/api.rstnew file+96
@@ -0,0 +1,96 @@
1+.. _adminapi:
2+
3+==================
4+Administration API
5+==================
6+
7+Get configuration data
8+======================
9+
10+.. code:: http
11+
12+ GET /config HTTP/1.1
13+
14+Sample response
15+---------------
16+
17+.. code:: json
18+
19+ {
20+ "autocomplete": "",
21+ "categories": [
22+ "map",
23+ "it",
24+ "images",
25+ ],
26+ "default_locale": "",
27+ "default_theme": "oscar",
28+ "engines": [
29+ {
30+ "categories": [
31+ "map"
32+ ],
33+ "enabled": true,
34+ "name": "openstreetmap",
35+ "shortcut": "osm"
36+ },
37+ {
38+ "categories": [
39+ "it"
40+ ],
41+ "enabled": true,
42+ "name": "arch linux wiki",
43+ "shortcut": "al"
44+ },
45+ {
46+ "categories": [
47+ "images"
48+ ],
49+ "enabled": true,
50+ "name": "google images",
51+ "shortcut": "goi"
52+ },
53+ {
54+ "categories": [
55+ "it"
56+ ],
57+ "enabled": false,
58+ "name": "bitbucket",
59+ "shortcut": "bb"
60+ },
61+ ],
62+ "instance_name": "searx",
63+ "locales": {
64+ "de": "Deutsch (German)",
65+ "en": "English",
66+ "eo": "Esperanto (Esperanto)",
67+ },
68+ "plugins": [
69+ {
70+ "enabled": true,
71+ "name": "HTTPS rewrite"
72+ },
73+ {
74+ "enabled": false,
75+ "name": "Vim-like hotkeys"
76+ }
77+ ],
78+ "safe_search": 0
79+ }
80+
81+
82+Embed search bar
83+================
84+
85+The search bar can be embedded into websites. Just paste the example into the
86+HTML of the site. URL of the searx instance and values are customizable.
87+
88+.. code:: html
89+
90+ <form method="post" action="https://searx.me/">
91+ <!-- search --> <input type="text" name="q" />
92+ <!-- categories --> <input type="hidden" name="categories" value="general,social media" />
93+ <!-- language --> <input type="hidden" name="lang" value="all" />
94+ <!-- locale --> <input type="hidden" name="locale" value="en" />
95+ <!-- date filter --> <input type="hidden" name="time_range" value="month" />
96+ </form>
docs/admin/arch_public.dotnew file+33
@@ -0,0 +1,33 @@
1+digraph G {
2+
3+ node [style=filled, shape=box, fillcolor="#ffffcc", fontname="Sans"];
4+ edge [fontname="Sans"];
5+
6+ browser [label="Browser", shape=Mdiamond];
7+ rp [label="Reverse Proxy", href="url to configure reverse proxy"];
8+ filtron [label="Filtron", href="https://github.com/asciimoo/filtron"];
9+ morty [label="Morty", href="https://github.com/asciimoo/morty"];
10+ static [label="Static files", href="url to configure static files"];
11+ uwsgi [label="uwsgi", href="url to configure uwsgi"]
12+ searx1 [label="Searx #1"];
13+ searx2 [label="Searx #2"];
14+ searx3 [label="Searx #3"];
15+ searx4 [label="Searx #4"];
16+
17+ browser -> rp [label="HTTPS"]
18+
19+ subgraph cluster_searx {
20+ label = "Searx instance" fontname="Sans";
21+ bgcolor="#fafafa";
22+ { rank=same; static rp };
23+ rp -> morty [label="optional: images and HTML pages proxy"];
24+ rp -> static [label="optional: reverse proxy serves directly static files"];
25+ rp -> filtron [label="HTTP"];
26+ filtron -> uwsgi [label="HTTP"];
27+ uwsgi -> searx1;
28+ uwsgi -> searx2;
29+ uwsgi -> searx3;
30+ uwsgi -> searx4;
31+ }
32+
33+}
docs/admin/architecture.rstnew file+24
@@ -0,0 +1,24 @@
1+.. _architecture:
2+
3+============
4+Architecture
5+============
6+
7+.. sidebar:: Needs work!
8+
9+ This article needs some work / Searx is a collaborative effort. If you have
10+ any contribution, feel welcome to send us your :pull:`PR <../pulls>`, see
11+ :ref:`how to contribute`.
12+
13+Herein you will find some hints and suggestions about typical architectures of
14+searx infrastructures.
15+
16+We start with a contribution from :pull:`@dalf <1776#issuecomment-567917320>`.
17+It shows a *reference* setup for public searx instances.
18+
19+.. _arch public:
20+
21+.. kernel-figure:: arch_public.dot
22+ :alt: arch_public.dot
23+
24+ Reference architecture of a public searx setup.
docs/admin/buildhosts.rstnew file+103
@@ -0,0 +1,103 @@
1+.. _buildhosts:
2+
3+==========
4+Buildhosts
5+==========
6+
7+.. sidebar:: This article needs some work
8+
9+ If you have any contribution send us your :pull:`PR <../pulls>`, see
10+ :ref:`how to contribute`.
11+
12+To get best results from build, its recommend to install additional packages
13+on build hosts.
14+
15+.. _docs build:
16+
17+Build docs
18+==========
19+
20+.. _Graphviz: https://graphviz.gitlab.io
21+.. _ImageMagick: https://www.imagemagick.org
22+.. _XeTeX: https://tug.org/xetex/
23+.. _dvisvgm: https://dvisvgm.de/
24+
25+.. sidebar:: Sphinx build needs
26+
27+ - ImageMagick_
28+ - Graphviz_
29+ - XeTeX_
30+ - dvisvgm_
31+
32+Most of the sphinx requirements are installed from :origin:`setup.py` and the
33+docs can be build from scratch with ``make docs``. For better math and image
34+processing additional packages are needed. The XeTeX_ needed not only for PDF
35+creation, its also needed for :ref:`math` when HTML output is build.
36+
37+To be able to do :ref:`sphinx:math-support` without CDNs, the math are rendered
38+as images (``sphinx.ext.imgmath`` extension). If your docs build (``make
39+docs``) shows warnings like this::
40+
41+ WARNING: dot(1) not found, for better output quality install \
42+ graphviz from http://www.graphviz.org
43+ ..
44+ WARNING: LaTeX command 'latex' cannot be run (needed for math \
45+ display), check the imgmath_latex setting
46+
47+you need to install additional packages on your build host, to get better HTML
48+output.
49+
50+.. _system requirements:
51+
52+.. tabs::
53+
54+ .. group-tab:: Ubuntu / debian
55+
56+ .. code-block:: sh
57+
58+ $ sudo apt install graphviz imagemagick texlive-xetex librsvg2-bin
59+
60+ .. group-tab:: Arch Linux
61+
62+ .. code-block:: sh
63+
64+ $ sudo pacman -S graphviz imagemagick texlive-bin extra/librsvg
65+
66+ .. group-tab:: Fedora / RHEL
67+
68+ .. code-block:: sh
69+
70+ $ sudo dnf install graphviz graphviz-gd texlive-xetex-bin librsvg2-tools
71+
72+
73+For PDF output you also need:
74+
75+.. tabs::
76+
77+ .. group-tab:: Ubuntu / debian
78+
79+ .. code:: sh
80+
81+ $ sudo apt texlive-latex-recommended texlive-extra-utils ttf-dejavu
82+
83+ .. group-tab:: Arch Linux
84+
85+ .. code:: sh
86+
87+ $ sudo pacman -S texlive-core texlive-latexextra ttf-dejavu
88+
89+ .. group-tab:: Fedora / RHEL
90+
91+ .. code:: sh
92+
93+ $ sudo dnf install \
94+ texlive-collection-fontsrecommended texlive-collection-latex \
95+ dejavu-sans-fonts dejavu-serif-fonts dejavu-sans-mono-fonts
96+
97+.. _system requirements END:
98+
99+.. literalinclude:: ../conf.py
100+ :language: python
101+ :start-after: # sphinx.ext.imgmath setup
102+ :end-before: # sphinx.ext.imgmath setup END
103+
docs/admin/engines.rstnew file+71
@@ -0,0 +1,71 @@
1+.. _engines generic:
2+
3+=======
4+Engines
5+=======
6+
7+.. sidebar:: Further reading ..
8+
9+ - :ref:`settings engine`
10+ - :ref:`engine settings`
11+ - :ref:`engine file`
12+
13+============= =========== ==================== ============
14+:ref:`engine settings` :ref:`engine file`
15+------------------------- ---------------------------------
16+Name (cfg) Categories
17+------------------------- ---------------------------------
18+Engine .. Paging support **P**
19+------------------------- -------------------- ------------
20+Shortcut **S** Language support **L**
21+Timeout **TO** Time range support **TR**
22+Disabled **D** Offline **O**
23+------------- ----------- -------------------- ------------
24+Safe search **SS**
25+------------- ----------- ---------------------------------
26+Weigth **W**
27+------------- ----------- ---------------------------------
28+Disabled **D**
29+============= =========== =================================
30+
31+Configuration defaults (at built time):
32+
33+.. _configured engines:
34+
35+.. jinja:: webapp
36+
37+ .. flat-table:: Engines configured at built time (defaults)
38+ :header-rows: 1
39+ :stub-columns: 2
40+
41+ * - Name (cfg)
42+ - S
43+ - Engine
44+ - TO
45+ - Categories
46+ - P
47+ - L
48+ - SS
49+ - D
50+ - TR
51+ - O
52+ - W
53+ - D
54+
55+ {% for name, mod in engines.items() %}
56+
57+ * - {{name}}
58+ - !{{mod.shortcut}}
59+ - {{mod.__name__}}
60+ - {{mod.timeout}}
61+ - {{", ".join(mod.categories)}}
62+ - {{(mod.paging and "y") or ""}}
63+ - {{(mod.language_support and "y") or ""}}
64+ - {{(mod.safesearch and "y") or ""}}
65+ - {{(mod.disabled and "y") or ""}}
66+ - {{(mod.time_range_support and "y") or ""}}
67+ - {{(mod.offline and "y") or ""}}
68+ - {{mod.weight or 1 }}
69+ - {{(mod.disabled and "y") or ""}}
70+
71+ {% endfor %}
docs/admin/filtron.rstnew file+148
@@ -0,0 +1,148 @@
1+==========================
2+How to protect an instance
3+==========================
4+
5+Searx depens on external search services. To avoid the abuse of these services
6+it is advised to limit the number of requests processed by searx.
7+
8+An application firewall, ``filtron`` solves exactly this problem. Information
9+on how to install it can be found at the `project page of filtron
10+<https://github.com/asciimoo/filtron>`__.
11+
12+
13+Sample configuration of filtron
14+===============================
15+
16+An example configuration can be find below. This configuration limits the access
17+of:
18+
19+- scripts or applications (roboagent limit)
20+- webcrawlers (botlimit)
21+- IPs which send too many requests (IP limit)
22+- too many json, csv, etc. requests (rss/json limit)
23+- the same UserAgent of if too many requests (useragent limit)
24+
25+.. code:: json
26+
27+ [{
28+ "name":"search request",
29+ "filters":[
30+ "Param:q",
31+ "Path=^(/|/search)$"
32+ ],
33+ "interval":"<time-interval-in-sec (int)>",
34+ "limit":"<max-request-number-in-interval (int)>",
35+ "subrules":[
36+ {
37+ "name":"roboagent limit",
38+ "interval":"<time-interval-in-sec (int)>",
39+ "limit":"<max-request-number-in-interval (int)>",
40+ "filters":[
41+ "Header:User-Agent=(curl|cURL|Wget|python-requests|Scrapy|FeedFetcher|Go-http-client)"
42+ ],
43+ "actions":[
44+ {
45+ "name":"block",
46+ "params":{
47+ "message":"Rate limit exceeded"
48+ }
49+ }
50+ ]
51+ },
52+ {
53+ "name":"botlimit",
54+ "limit":0,
55+ "stop":true,
56+ "filters":[
57+ "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)"
58+ ],
59+ "actions":[
60+ {
61+ "name":"block",
62+ "params":{
63+ "message":"Rate limit exceeded"
64+ }
65+ }
66+ ]
67+ },
68+ {
69+ "name":"IP limit",
70+ "interval":"<time-interval-in-sec (int)>",
71+ "limit":"<max-request-number-in-interval (int)>",
72+ "stop":true,
73+ "aggregations":[
74+ "Header:X-Forwarded-For"
75+ ],
76+ "actions":[
77+ {
78+ "name":"block",
79+ "params":{
80+ "message":"Rate limit exceeded"
81+ }
82+ }
83+ ]
84+ },
85+ {
86+ "name":"rss/json limit",
87+ "interval":"<time-interval-in-sec (int)>",
88+ "limit":"<max-request-number-in-interval (int)>",
89+ "stop":true,
90+ "filters":[
91+ "Param:format=(csv|json|rss)"
92+ ],
93+ "actions":[
94+ {
95+ "name":"block",
96+ "params":{
97+ "message":"Rate limit exceeded"
98+ }
99+ }
100+ ]
101+ },
102+ {
103+ "name":"useragent limit",
104+ "interval":"<time-interval-in-sec (int)>",
105+ "limit":"<max-request-number-in-interval (int)>",
106+ "aggregations":[
107+ "Header:User-Agent"
108+ ],
109+ "actions":[
110+ {
111+ "name":"block",
112+ "params":{
113+ "message":"Rate limit exceeded"
114+ }
115+ }
116+ ]
117+ }
118+ ]
119+ }]
120+
121+
122+
123+Route request through filtron
124+=============================
125+
126+Filtron can be started using the following command:
127+
128+.. code:: sh
129+
130+ $ filtron -rules rules.json
131+
132+It listens on ``127.0.0.1:4004`` and forwards filtered requests to
133+``127.0.0.1:8888`` by default.
134+
135+Use it along with ``nginx`` with the following example configuration.
136+
137+.. code:: nginx
138+
139+ location / {
140+ proxy_set_header Host $http_host;
141+ proxy_set_header X-Real-IP $remote_addr;
142+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
143+ proxy_set_header X-Scheme $scheme;
144+ proxy_pass http://127.0.0.1:4004/;
145+ }
146+
147+Requests are coming from port 4004 going through filtron and then forwarded to
148+port 8888 where a searx is being run.
docs/admin/index.rstnew file+16
@@ -0,0 +1,16 @@
1+===========================
2+Administrator documentation
3+===========================
4+
5+.. toctree::
6+ :maxdepth: 1
7+
8+ installation
9+ settings
10+ api
11+ architecture
12+ filtron
13+ morty
14+ engines
15+ plugins
16+ buildhosts
docs/admin/installation.rstnew file+349
@@ -0,0 +1,349 @@
1+.. _installation:
2+
3+============
4+Installation
5+============
6+
7+.. contents::
8+ :depth: 3
9+
10+Basic installation
11+==================
12+
13+Step by step installation for Debian/Ubuntu with virtualenv. For Ubuntu, be sure
14+to have enable universe repository.
15+
16+Install packages:
17+
18+.. code:: sh
19+
20+ $ sudo -H apt-get install \
21+ git build-essential libxslt-dev \
22+ python-dev python-virtualenv python-babel \
23+ zlib1g-dev libffi-dev libssl-dev
24+
25+Install searx:
26+
27+.. code:: sh
28+
29+ cd /usr/local
30+ sudo -H git clone https://github.com/asciimoo/searx.git
31+ sudo -H useradd searx -d /usr/local/searx
32+ sudo -H chown searx:searx -R /usr/local/searx
33+
34+Install dependencies in a virtualenv:
35+
36+.. code:: sh
37+
38+ cd /usr/local/searx
39+ sudo -H -u searx -i
40+
41+.. code:: sh
42+
43+ (searx)$ virtualenv searx-ve
44+ (searx)$ . ./searx-ve/bin/activate
45+ (searx)$ ./manage.sh update_packages
46+
47+Configuration
48+==============
49+
50+.. code:: sh
51+
52+ sed -i -e "s/ultrasecretkey/`openssl rand -hex 16`/g" searx/settings.yml
53+
54+Edit searx/settings.yml if necessary.
55+
56+Check
57+=====
58+
59+Start searx:
60+
61+.. code:: sh
62+
63+ python searx/webapp.py
64+
65+Go to http://localhost:8888
66+
67+If everything works fine, disable the debug option in settings.yml:
68+
69+.. code:: sh
70+
71+ sed -i -e "s/debug : True/debug : False/g" searx/settings.yml
72+
73+At this point searx is not demonized ; uwsgi allows this.
74+
75+You can exit the virtualenv and the searx user bash (enter exit command
76+twice).
77+
78+uwsgi
79+=====
80+
81+Install packages:
82+
83+.. code:: sh
84+
85+ sudo -H apt-get install \
86+ uwsgi uwsgi-plugin-python
87+
88+Create the configuration file ``/etc/uwsgi/apps-available/searx.ini`` with this
89+content:
90+
91+.. code:: ini
92+
93+ [uwsgi]
94+ # Who will run the code
95+ uid = searx
96+ gid = searx
97+
98+ # disable logging for privacy
99+ disable-logging = true
100+
101+ # Number of workers (usually CPU count)
102+ workers = 4
103+
104+ # The right granted on the created socket
105+ chmod-socket = 666
106+
107+ # Plugin to use and interpretor config
108+ single-interpreter = true
109+ master = true
110+ plugin = python
111+ lazy-apps = true
112+ enable-threads = true
113+
114+ # Module to import
115+ module = searx.webapp
116+
117+ # Support running the module from a webserver subdirectory.
118+ route-run = fixpathinfo:
119+
120+ # Virtualenv and python path
121+ virtualenv = /usr/local/searx/searx-ve/
122+ pythonpath = /usr/local/searx/
123+ chdir = /usr/local/searx/searx/
124+
125+Activate the uwsgi application and restart:
126+
127+.. code:: sh
128+
129+ cd /etc/uwsgi/apps-enabled
130+ ln -s ../apps-available/searx.ini
131+ /etc/init.d/uwsgi restart
132+
133+Web server
134+==========
135+
136+with nginx
137+----------
138+
139+If nginx is not installed (uwsgi will not work with the package
140+nginx-light):
141+
142+.. code:: sh
143+
144+ sudo -H apt-get install nginx
145+
146+Hosted at /
147+~~~~~~~~~~~
148+
149+Create the configuration file ``/etc/nginx/sites-available/searx`` with this
150+content:
151+
152+.. code:: nginx
153+
154+ server {
155+ listen 80;
156+ server_name searx.example.com;
157+ root /usr/local/searx/searx;
158+
159+ location /static {
160+ }
161+
162+ location / {
163+ include uwsgi_params;
164+ uwsgi_pass unix:/run/uwsgi/app/searx/socket;
165+ }
166+ }
167+
168+Create a symlink to sites-enabled:
169+
170+.. code:: sh
171+
172+ sudo -H ln -s /etc/nginx/sites-available/searx /etc/nginx/sites-enabled/searx
173+
174+Restart service:
175+
176+.. code:: sh
177+
178+ sudo -H service nginx restart
179+ sudo -H service uwsgi restart
180+
181+from subdirectory URL (/searx)
182+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
183+
184+Add this configuration in the server config file
185+``/etc/nginx/sites-enabled/default``:
186+
187+.. code:: nginx
188+
189+ location /searx/static {
190+ alias /usr/local/searx/searx/static;
191+ }
192+
193+ location /searx {
194+ uwsgi_param SCRIPT_NAME /searx;
195+ include uwsgi_params;
196+ uwsgi_pass unix:/run/uwsgi/app/searx/socket;
197+ }
198+
199+
200+**OR** using reverse proxy (Please, note that reverse proxy advised to be used
201+in case of single-user or low-traffic instances.)
202+
203+.. code:: nginx
204+
205+ location /searx/static {
206+ alias /usr/local/searx/searx/static;
207+ }
208+
209+ location /searx {
210+ proxy_pass http://127.0.0.1:8888;
211+ proxy_set_header Host $host;
212+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
213+ proxy_set_header X-Scheme $scheme;
214+ proxy_set_header X-Script-Name /searx;
215+ proxy_buffering off;
216+ }
217+
218+
219+Enable ``base_url`` in ``searx/settings.yml``
220+
221+.. code:: yaml
222+
223+ base_url : http://your.domain.tld/searx/
224+
225+Restart service:
226+
227+.. code:: sh
228+
229+ sudo -H service nginx restart
230+ sudo -H service uwsgi restart
231+
232+disable logs
233+^^^^^^^^^^^^
234+
235+for better privacy you can disable nginx logs about searx.
236+
237+how to proceed: below ``uwsgi_pass`` in ``/etc/nginx/sites-available/default``
238+add:
239+
240+.. code:: nginx
241+
242+ access_log /dev/null;
243+ error_log /dev/null;
244+
245+Restart service:
246+
247+.. code:: sh
248+
249+ sudo -H service nginx restart
250+
251+with apache
252+-----------
253+
254+Add wsgi mod:
255+
256+.. code:: sh
257+
258+ sudo -H apt-get install libapache2-mod-uwsgi
259+ sudo -H a2enmod uwsgi
260+
261+Add this configuration in the file ``/etc/apache2/apache2.conf``:
262+
263+.. code:: apache
264+
265+ <Location />
266+ Options FollowSymLinks Indexes
267+ SetHandler uwsgi-handler
268+ uWSGISocket /run/uwsgi/app/searx/socket
269+ </Location>
270+
271+Note that if your instance of searx is not at the root, you should change
272+``<Location />`` by the location of your instance, like ``<Location /searx>``.
273+
274+Restart Apache:
275+
276+.. code:: sh
277+
278+ sudo -H /etc/init.d/apache2 restart
279+
280+disable logs
281+~~~~~~~~~~~~
282+
283+For better privacy you can disable Apache logs.
284+
285+.. warning::
286+
287+ You can only disable logs for the whole (virtual) server not for a specific
288+ path.
289+
290+Go back to ``/etc/apache2/apache2.conf`` and above ``<Location />`` add:
291+
292+.. code:: apache
293+
294+ CustomLog /dev/null combined
295+
296+Restart Apache:
297+
298+.. code:: sh
299+
300+ sudo -H /etc/init.d/apache2 restart
301+
302+How to update
303+=============
304+
305+.. code:: sh
306+
307+ cd /usr/local/searx
308+ sudo -H -u searx -i
309+
310+.. code:: sh
311+
312+ (searx)$ . ./searx-ve/bin/activate
313+ (searx)$ git stash
314+ (searx)$ git pull origin master
315+ (searx)$ git stash apply
316+ (searx)$ ./manage.sh update_packages
317+
318+.. code:: sh
319+
320+ sudo -H service uwsgi restart
321+
322+Docker
323+======
324+
325+Make sure you have installed Docker. For instance, you can deploy searx like this:
326+
327+.. code:: sh
328+
329+ docker pull wonderfall/searx
330+ docker run -d --name searx -p $PORT:8888 wonderfall/searx
331+
332+Go to ``http://localhost:$PORT``.
333+
334+See https://hub.docker.com/r/wonderfall/searx/ for more informations. It's also
335+possible to build searx from the embedded Dockerfile.
336+
337+.. code:: sh
338+
339+ git clone https://github.com/asciimoo/searx.git
340+ cd searx
341+ docker build -t whatever/searx .
342+
343+References
344+==========
345+
346+* https://about.okhin.fr/posts/Searx/ with some additions
347+
348+* How to: `Setup searx in a couple of hours with a free SSL certificate
349+ <https://www.reddit.com/r/privacytoolsIO/comments/366kvn/how_to_setup_your_own_privacy_respecting_search/>`__
docs/admin/morty.rstnew file+26
@@ -0,0 +1,26 @@
1+=========================
2+How to setup result proxy
3+=========================
4+
5+.. _morty: https://github.com/asciimoo/morty
6+.. _morty's README: https://github.com/asciimoo/morty
7+
8+By default searx can only act as an image proxy for result images, but it is
9+possible to proxify all the result URLs with an external service, morty_.
10+
11+To use this feature, morty has to be installed and activated in searx's
12+``settings.yml``.
13+
14+Add the following snippet to your ``settings.yml`` and restart searx:
15+
16+.. code:: yaml
17+
18+ result_proxy:
19+ url : http://127.0.0.1:3000/
20+ key : your_morty_proxy_key
21+
22+``url``
23+ Is the address of the running morty service.
24+
25+``key``
26+ Is an optional argument, see `morty's README`_ for more information.
docs/admin/plugins.rstnew file+39
@@ -0,0 +1,39 @@
1+.. _plugins generic:
2+
3+===============
4+Plugins builtin
5+===============
6+
7+.. sidebar:: Further reading ..
8+
9+ - :ref:`dev plugin`
10+
11+Configuration defaults (at built time):
12+
13+:DO: Default on
14+
15+.. _configured plugins:
16+
17+.. jinja:: webapp
18+
19+ .. flat-table:: Plugins configured at built time (defaults)
20+ :header-rows: 1
21+ :stub-columns: 1
22+ :widths: 3 1 9
23+
24+ * - Name
25+ - DO
26+ - Description
27+
28+ JS & CSS dependencies
29+
30+ {% for plgin in plugins %}
31+
32+ * - {{plgin.name}}
33+ - {{(plgin.default_on and "y") or ""}}
34+ - {{plgin.description}}
35+
36+ {% for dep in (plgin.js_dependencies + plgin.css_dependencies) %}
37+ | ``{{dep}}`` {% endfor %}
38+
39+ {% endfor %}
docs/admin/settings.rstnew file+181
@@ -0,0 +1,181 @@
1+.. _settings.yml:
2+
3+================
4+``settings.yml``
5+================
6+
7+.. sidebar:: Further reading ..
8+
9+ - :ref:`search API`
10+
11+This page describe the options possibilities of the settings.yml file.
12+
13+.. _settings global:
14+
15+Global Settings
16+===============
17+
18+.. code:: yaml
19+
20+ server:
21+ port : 8888
22+ secret_key : "ultrasecretkey" # change this!
23+ debug : False # debug mode, only for development
24+ request_timeout : 2.0 # seconds
25+ base_url : False # set custom base_url (or False)
26+ themes_path : "" # custom ui themes path
27+ default_theme : oscar # ui theme
28+ useragent_suffix : "" # suffix of searx_useragent, could contain
29+ # informations like admins email address
30+ image_proxy : False # proxying image results through searx
31+ default_locale : "" # default interface locale
32+
33+ # uncomment below section if you want to use a proxy
34+
35+ #outgoing_proxies :
36+ # http : http://127.0.0.1:8080
37+ # https: http://127.0.0.1:8080
38+
39+ # uncomment below section only if you have more than one network interface
40+ # which can be the source of outgoing search requests
41+
42+ #source_ips:
43+ # - 1.1.1.1
44+ # - 1.1.1.2
45+
46+ locales:
47+ en : English
48+ de : Deutsch
49+ he : Hebrew
50+ hu : Magyar
51+ fr : Français
52+ es : Español
53+ it : Italiano
54+ nl : Nederlands
55+ ja : 日本語 (Japanese)
56+ tr : Türkçe
57+ ru : Russian
58+ ro : Romanian
59+
60+
61+``port`` :
62+ Port number of the searx web application if you run it directly using ``python
63+ searx/webapp.py``. Doesn't apply to searx running on Apache or Nginx.
64+
65+``secret_key`` :
66+ Used for cryptography purpose.
67+
68+``debug`` :
69+ Allow a more detailed log if you run searx directly. Display *detailed* error
70+ messages in the browser too, so this must be deactivated in production.
71+
72+``request_timeout`` :
73+ Global timeout of the requests made to others engines in seconds. A bigger
74+ timeout will allow to wait for answers from slow engines, but in consequence
75+ will slow searx reactivity (the result page may take the time specified in the
76+ timeout to load)
77+
78+``base_url`` :
79+ The base URL where searx is deployed. Used to create correct inbound links.
80+
81+``themes_path`` :
82+ Path to where the themes are located. If you didn't develop anything, leave it
83+ blank.
84+
85+``default_theme`` :
86+ Name of the theme you want to use by default on you searx instance.
87+
88+``useragent_suffix`` :
89+ Suffix to the user-agent searx uses to send requests to others engines. If an
90+ engine wish to block you, a contact info here may be useful to avoid that.
91+
92+``image_proxy`` :
93+ Allow your instance of searx of being able to proxy images. Uses memory space.
94+
95+``default_locale`` :
96+ Aearx interface language. If blank, the locale is detected by using the
97+ browser language. If it doesn't work, or you are deploying a language
98+ specific instance of searx, a locale can be defined using an ISO language
99+ code, like ``fr``, ``en``, ``de``.
100+
101+.. _requests proxies: http://docs.python-requests.org/en/latest/user/advanced/#proxies
102+.. _PR SOCKS support: https://github.com/kennethreitz/requests/pull/478
103+
104+``outgoing_proxies`` :
105+ Define a proxy you wish to use, see `requests proxies`_. SOCKS proxies are
106+ not supported / see `PR SOCKS support`.
107+
108+``source_ips`` :
109+ If you use multiple nework interfaces, define from which IP the requests must
110+ be made.
111+
112+``locales`` :
113+ Locales codes and their names. Available translations of searx interface.
114+
115+
116+.. _settings engine:
117+
118+Engine settings
119+===============
120+
121+.. sidebar:: Further reading ..
122+
123+ - :ref:`engines-dev`
124+
125+.. code:: yaml
126+
127+ - name : bing
128+ engine : bing
129+ shortcut : bi
130+ base_url : 'https://{language}.wikipedia.org/'
131+ categories : general
132+ timeout : 3.0
133+ api_key : 'apikey'
134+ disabled : True
135+ language : en_US
136+
137+``name`` :
138+ Name that will be used accross searx to define this engine. In settings, on
139+ the result page...
140+
141+``engine`` :
142+ Name of the python file used to handle requests and responses to and from this
143+ search engine.
144+
145+``shortcut`` :
146+ Code used to execute bang requests (in this case using ``!bi`` or ``?bi``)
147+
148+``base_url`` : optional
149+ Part of the URL that should be stable accross every request. Can be useful to
150+ use multiple sites using only one engine, or updating the site URL without
151+ touching at the code.
152+
153+``categories`` : optional
154+ Define in which categories this engine will be active. Most of the time, it is
155+ defined in the code of the engine, but in a few cases it is useful, like when
156+ describing multiple search engine using the same code.
157+
158+``timeout`` : optional
159+ Timeout of the search with the current search engine. **Be careful, it will
160+ modify the global timeout of searx.**
161+
162+``api_key`` : optional
163+ In a few cases, using an API needs the use of a secret key. How to obtain them
164+ is described in the file.
165+
166+``disabled`` : optional
167+ To disable by default the engine, but not deleting it. It will allow the user
168+ to manually activate it in the settings.
169+
170+``language`` : optional
171+ If you want to use another language for a specific engine, you can define it
172+ by using the full ISO code of language and country, like ``fr_FR``, ``en_US``,
173+ ``de_DE``.
174+
175+``weigth`` : default ``1``
176+ Weighting of the results of this engine.
177+
178+.. note::
179+
180+ A few more options are possible, but they are pretty specific to some
181+ engines, and so won't be described here.
docs/blog/admin.rstnew file+43
@@ -0,0 +1,43 @@
1+=============================================================
2+Searx admin interface
3+=============================================================
4+
5+.. _searx-admin: https://github.com/kvch/searx-admin#searx-admin
6+.. _NLnet Foundation: https://nlnet.nl/
7+
8+ manage your instance from your browser
9+
10+.. sidebar:: Installation
11+
12+ Installation guide can be found in the repository of searx-admin_.
13+
14+One of the biggest advantages of searx is being extremely customizable. But at
15+first it can be daunting to newcomers. A barrier of taking advantage of this
16+feature is our ugly settings file which is sometimes hard to understand and
17+edit.
18+
19+To make self-hosting searx more accessible a new tool is introduced, called
20+``searx-admin``. It is a web application which is capable of managing your
21+instance and manipulating its settings via a web UI. It aims to replace editing
22+of ``settings.yml`` for less experienced administrators or people who prefer
23+graphical admin interfaces.
24+
25+.. figure:: searx-admin-engines.png
26+ :alt: Screenshot of engine list
27+
28+ Configuration page of engines
29+
30+Since ``searx-admin`` acts as a supervisor for searx, we have decided to
31+implement it as a standalone tool instead of part of searx. Another reason for
32+making it a standalone tool is that the codebase and dependencies of searx
33+should not grow because of a fully optional feature, which does not affect
34+existing instances.
35+
36+
37+Acknowledgements
38+================
39+
40+This development was sponsored by `NLnet Foundation`_.
41+
42+| Happy hacking.
43+| kvch // 2017.08.22 21:25
docs/blog/index.rstnew file+10
@@ -0,0 +1,10 @@
1+====
2+Blog
3+====
4+
5+.. toctree::
6+ :maxdepth: 1
7+
8+ python3
9+ admin
10+ intro-offline
docs/blog/intro-offline.rstnew file+77
@@ -0,0 +1,77 @@
1+===============================
2+Preparation for offline engines
3+===============================
4+
5+Offline engines
6+===============
7+
8+To extend the functionality of searx, offline engines are going to be
9+introduced. An offline engine is an engine which does not need Internet
10+connection to perform a search and does not use HTTP to communicate.
11+
12+Offline engines can be configured as online engines, by adding those to the
13+`engines` list of :origin:`settings.yml <searx/settings.yml>`. Thus, searx
14+finds the engine file and imports it.
15+
16+Example skeleton for the new engines:
17+
18+.. code:: python
19+
20+ from subprocess import PIPE, Popen
21+
22+ categories = ['general']
23+ offline = True
24+
25+ def init(settings):
26+ pass
27+
28+ def search(query, params):
29+ process = Popen(['ls', query], stdout=PIPE)
30+ return_code = process.wait()
31+ if return_code != 0:
32+ raise RuntimeError('non-zero return code', return_code)
33+
34+ results = []
35+ line = process.stdout.readline()
36+ while line:
37+ result = parse_line(line)
38+ results.append(results)
39+
40+ line = process.stdout.readline()
41+
42+ return results
43+
44+
45+Development progress
46+====================
47+
48+First, a proposal has been created as a Github issue. Then it was moved to the
49+wiki as a design document. You can read it here: :wiki:`Offline-engines`.
50+
51+In this development step, searx core was prepared to accept and perform offline
52+searches. Offline search requests are scheduled together with regular offline
53+requests.
54+
55+As offline searches can return arbitrary results depending on the engine, the
56+current result templates were insufficient to present such results. Thus, a new
57+template is introduced which is caplable of presenting arbitrary key value pairs
58+as a table. You can check out the pull request for more details see
59+:pull:`1700`.
60+
61+Next steps
62+==========
63+
64+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.
65+
66+Acknowledgement
67+===============
68+
69+This development was sponsored by `Search and Discovery Fund`_ of `NLnet Foundation`_ .
70+
71+.. _Search and Discovery Fund: https://nlnet.nl/discovery
72+.. _NLnet Foundation: https://nlnet.nl/
73+
74+
75+| Happy hacking.
76+| kvch // 2019.10.21 17:03
77+
docs/blog/python3.rstnew file+68
@@ -0,0 +1,68 @@
1+============================
2+Introducing Python 3 support
3+============================
4+
5+.. _Python 2.7 clock: https://pythonclock.org/
6+
7+.. sidebar:: Python 2.7 to 3 upgrade
8+
9+ This chapter exists of historical reasons. Python 2.7 release schedule ends
10+ (`Python 2.7 clock`_) after 11 years Python 3 exists
11+
12+As most operation systems are coming with Python3 installed by default. So it is
13+time for searx to support Python3. But don't worry support of Python2.7 won't be
14+dropped.
15+
16+.. image:: searxpy3.png
17+ :scale: 50 %
18+ :alt: hurray
19+ :align: center
20+
21+
22+How to run searx using Python 3
23+===============================
24+
25+Please make sure that you run at least Python 3.5.
26+
27+To run searx, first a Python3 virtualenv should be created. After entering the
28+virtualenv, dependencies must be installed. Then run searx with python3 instead
29+of the usual python command.
30+
31+.. code:: sh
32+
33+ virtualenv -p python3 venv3
34+ source venv3/bin/activate
35+ pip3 install -r requirements.txt
36+ python3 searx/webapp.py
37+
38+
39+If you want to run searx using Python2.7, you don't have to do anything
40+differently as before.
41+
42+Fun facts
43+=========
44+
45+- 115 files were changed when implementing the support for both Python versions.
46+
47+- All of the dependencies was compatible except for the robotframework used for
48+ browser tests. Thus, these tests were migrated to splinter. So from now on
49+ both versions are being tested on Travis and can be tested locally.
50+
51+If you found bugs
52+=================
53+
54+Please open an issue on `GitHub`_. Make sure that you mention your Python
55+version in your issue, so we can investigate it properly.
56+
57+.. _GitHub: https://github.com/asciimoo/searx/issues
58+
59+Acknowledgment
60+==============
61+
62+This development was sponsored by `NLnet Foundation`_.
63+
64+.. _NLnet Foundation: https://nlnet.nl/
65+
66+
67+| Happy hacking.
68+| kvch // 2017.05.13 22:57
docs/blog/searx-admin-engines.pngnew file

Binary file not shown.

docs/blog/searxpy3.pngnew file

Binary file not shown.

docs/conf.pynew file+114
@@ -0,0 +1,114 @@
1+# -*- coding: utf-8 -*-
2+
3+import sys, os
4+from searx.version import VERSION_STRING
5+from pallets_sphinx_themes import ProjectLink
6+
7+GIT_URL = os.environ.get("GIT_URL", "https://github.com/asciimoo/searx")
8+SEARX_URL = os.environ.get("SEARX_URL", "https://searx.me")
9+DOCS_URL = os.environ.get("DOCS_URL", "https://asciimoo.github.io/searx/")
10+
11+# Project --------------------------------------------------------------
12+
13+project = u'searx'
14+copyright = u'2015-2019, Adam Tauber, Noémi Ványi'
15+author = u'Adam Tauber'
16+release, version = VERSION_STRING, VERSION_STRING
17+highlight_language = 'none'
18+
19+# General --------------------------------------------------------------
20+
21+master_doc = "index"
22+source_suffix = '.rst'
23+numfig = True
24+
25+from searx import webapp
26+jinja_contexts = {
27+ 'webapp': dict(**webapp.__dict__)
28+}
29+
30+# usage:: lorem :patch:`f373169` ipsum
31+extlinks = {}
32+
33+# upstream links
34+extlinks['wiki'] = ('https://github.com/asciimoo/searx/wiki/%s', ' ')
35+extlinks['pull'] = ('https://github.com/asciimoo/searx/pull/%s', 'PR ')
36+
37+# links to custom brand
38+extlinks['origin'] = (GIT_URL + '/blob/master/%s', 'git://')
39+extlinks['patch'] = (GIT_URL + '/commit/%s', '#')
40+extlinks['search'] = (SEARX_URL + '/%s', '#')
41+extlinks['docs'] = (DOCS_URL + '/%s', 'docs: ')
42+extlinks['pypi'] = ('https://pypi.org/project/%s', 'PyPi: ')
43+extlinks['man'] = ('https://manpages.debian.org/jump?q=%s', '')
44+#extlinks['role'] = (
45+# 'https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-%s', '')
46+extlinks['duref'] = (
47+ 'http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#%s', '')
48+extlinks['durole'] = (
49+ 'http://docutils.sourceforge.net/docs/ref/rst/roles.html#%s', '')
50+extlinks['dudir'] = (
51+ 'http://docutils.sourceforge.net/docs/ref/rst/directives.html#%s', '')
52+extlinks['ctan'] = (
53+ 'https://ctan.org/pkg/%s', 'CTAN: ')
54+
55+extensions = [
56+ 'sphinx.ext.imgmath',
57+ 'sphinx.ext.extlinks',
58+ 'sphinx.ext.viewcode',
59+ "sphinx.ext.autodoc",
60+ "sphinx.ext.intersphinx",
61+ "pallets_sphinx_themes",
62+ "sphinx_issues", # https://github.com/sloria/sphinx-issues/blob/master/README.rst
63+ "sphinxcontrib.jinja", # https://github.com/tardyp/sphinx-jinja
64+ 'linuxdoc.rstFlatTable', # Implementation of the 'flat-table' reST-directive.
65+ 'linuxdoc.kfigure', # Sphinx extension which implements scalable image handling.
66+ "sphinx_tabs.tabs", # https://github.com/djungelorm/sphinx-tabs
67+]
68+
69+intersphinx_mapping = {
70+ "python": ("https://docs.python.org/3/", None),
71+ "flask": ("https://flask.palletsprojects.com/", None),
72+ # "werkzeug": ("https://werkzeug.palletsprojects.com/", None),
73+ "jinja": ("https://jinja.palletsprojects.com/", None),
74+ "linuxdoc" : ("https://return42.github.io/linuxdoc/", None),
75+ "sphinx" : ("https://www.sphinx-doc.org/en/master/", None),
76+}
77+
78+issues_github_path = "asciimoo/searx"
79+
80+# HTML -----------------------------------------------------------------
81+
82+sys.path.append(os.path.abspath('_themes'))
83+html_theme_path = ['_themes']
84+html_theme = "searx"
85+
86+# sphinx.ext.imgmath setup
87+html_math_renderer = 'imgmath'
88+imgmath_image_format = 'svg'
89+imgmath_font_size = 14
90+# sphinx.ext.imgmath setup END
91+
92+html_theme_options = {"index_sidebar_logo": True}
93+html_context = {
94+ "project_links": [
95+ ProjectLink("Source", GIT_URL),
96+ ProjectLink("Wiki", "https://github.com/asciimoo/searx/wiki"),
97+ ProjectLink("Public instances", "https://asciimoo.github.io/searx/user/public_instances.html"),
98+ ProjectLink("Twitter", "https://twitter.com/Searx_engine"),
99+ ]
100+}
101+html_sidebars = {
102+ "**": ["project.html", "relations.html", "searchbox.html"],
103+}
104+singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
105+html_static_path = ["static"]
106+html_logo = "static/img/searx_logo_small.png"
107+html_title = "Searx Documentation ({})".format("Searx-{}.tex".format(VERSION_STRING))
108+html_show_sourcelink = False
109+
110+# LaTeX ----------------------------------------------------------------
111+
112+latex_documents = [
113+ (master_doc, "searx-{}.tex".format(VERSION_STRING), html_title, author, "manual")
114+]
docs/dev/contribution_guide.rstnew file+180
@@ -0,0 +1,180 @@
1+.. _how to contribute:
2+
3+=================
4+How to contribute
5+=================
6+
7+Prime directives: Privacy, Hackability
8+======================================
9+
10+Searx has two prime directives, **privacy-by-design and hackability** . The
11+hackability comes in three levels:
12+
13+- support of search engines
14+- plugins to alter search behaviour
15+- hacking searx itself
16+
17+Note the lack of "world domination" among the directives. Searx has no
18+intention of wide mass-adoption, rounded corners, etc. The prime directive
19+"privacy" deserves a separate chapter, as it's quite uncommon unfortunately.
20+
21+Privacy-by-design
22+-----------------
23+
24+Searx was born out of the need for a **privacy-respecting** search tool which
25+can be extended easily to maximize both, its search and its privacy protecting
26+capabilities.
27+
28+A few widely used features work differently or turned off by default or not
29+implemented at all **as a consequence of privacy-by-design**.
30+
31+If a feature reduces the privacy preserving aspects of searx, it should be
32+switched off by default or should not implemented at all. There are plenty of
33+search engines already providing such features. If a feature reduces the
34+protection of searx, users must be informed about the effect of choosing to
35+enable it. Features that protect privacy but differ from the expectations of
36+the user should also be explained.
37+
38+Also, if you think that something works weird with searx, it's might be because
39+of the tool you use is designed in a way to interfere with the privacy respect.
40+Submitting a bugreport to the vendor of the tool that misbehaves might be a good
41+feedback to reconsider the disrespect to its customers (e.g. ``GET`` vs ``POST``
42+requests in various browsers).
43+
44+Remember the other prime directive of searx is to be hackable, so if the above
45+privacy concerns do not fancy you, simply fork it.
46+
47+ *Happy hacking.*
48+
49+Code
50+====
51+
52+.. _PEP8: https://www.python.org/dev/peps/pep-0008/
53+.. _Conventional Commits: https://www.conventionalcommits.org/
54+.. _Git Commit Good Practice: https://wiki.openstack.org/wiki/GitCommitMessages
55+.. _Structural split of changes:
56+ https://wiki.openstack.org/wiki/GitCommitMessages#Structural_split_of_changes
57+.. _gitmoji: https://gitmoji.carloscuesta.me/
58+.. _Semantic PR: https://github.com/zeke/semantic-pull-requests
59+
60+.. sidebar:: Create good commits!
61+
62+ - `Structural split of changes`_
63+ - `Conventional Commits`_
64+ - `Git Commit Good Practice`_
65+ - some like to use: gitmoji_
66+ - not yet active: `Semantic PR`_
67+
68+In order to submit a patch, please follow the steps below:
69+
70+- Follow coding conventions.
71+
72+ - PEP8_ standards apply, except the convention of line length
73+ - Maximum line length is 120 characters
74+
75+- The cardinal rule for creating good commits is to ensure there is only one
76+ *logical change* per commit / read `Structural split of changes`_
77+
78+- Check if your code breaks existing tests. If so, update the tests or fix your
79+ code.
80+
81+- If your code can be unit-tested, add unit tests.
82+
83+- Add yourself to the :origin:`AUTHORS.rst` file.
84+
85+- Choose meaning full commit messages, read `Conventional Commits`_
86+
87+ .. code::
88+
89+ <type>[optional scope]: <description>
90+
91+ [optional body]
92+
93+ [optional footer(s)]
94+
95+- Create a pull request.
96+
97+For more help on getting started with searx development, see :ref:`devquickstart`.
98+
99+
100+Translation
101+===========
102+
103+Translation currently takes place on :ref:`transifex <translation>`.
104+
105+.. caution::
106+
107+ Please, do not update translation files in the repo.
108+
109+
110+.. _contrib docs:
111+
112+Documentation
113+=============
114+
115+.. _Sphinx: http://www.sphinx-doc.org
116+.. _reST: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
117+
118+.. sidebar:: The reST sources
119+
120+ has been moved from ``gh-branch`` into ``master`` (:origin:`docs`).
121+
122+The documentation is built using Sphinx_. So in order to be able to generate
123+the required files, you have to install it on your system. Much easier, use
124+our :ref:`makefile`.
125+
126+Here is an example which makes a complete rebuild:
127+
128+.. code:: sh
129+
130+ $ make docs-clean docs
131+ ...
132+ The HTML pages are in dist/docs.
133+
134+.. _make docs-live:
135+
136+live build
137+----------
138+
139+.. sidebar:: docs-clean
140+
141+ It is recommended to assert a complete rebuild before deploying (use
142+ ``docs-clean``).
143+
144+Live build is like WYSIWYG. If you want to edit the documentation, its
145+recommended to use. The Makefile target ``docs-live`` builds the docs, opens
146+URL in your favorite browser and rebuilds every time a reST file has been
147+changed.
148+
149+.. code:: sh
150+
151+ $ make docs-live
152+ ...
153+ The HTML pages are in dist/docs.
154+ ... Serving on http://0.0.0.0:8080
155+ ... Start watching changes
156+
157+
158+.. _deploy on github.io:
159+
160+deploy on github.io
161+-------------------
162+
163+To deploy documentation at :docs:`github.io <.>` use Makefile target
164+:ref:`make gh-pages`, which will builds the documentation, clones searx into a sub
165+folder ``gh-pages``, cleans it, copies the doc build into and runs all the
166+needed git add, commit and push:
167+
168+.. code:: sh
169+
170+ $ make docs-clean gh-pages
171+ ...
172+ SPHINX docs --> file://<...>/dist/docs
173+ The HTML pages are in dist/docs.
174+ ...
175+ Cloning into 'gh-pages' ...
176+ ...
177+ cd gh-pages; git checkout gh-pages >/dev/null
178+ Switched to a new branch 'gh-pages'
179+ ...
180+ doc available at --> https://asciimoo.github.io/searx
docs/dev/csv_table.txtnew file+6
@@ -0,0 +1,6 @@
1+stub col row 1, column, "loremLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
2+eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
3+voluptua."
4+stub col row 1, "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
5+kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.", column
6+stub col row 1, column, column
docs/dev/engine_overview.rstnew file+267
@@ -0,0 +1,267 @@
1+
2+.. _engines-dev:
3+
4+===============
5+Engine overview
6+===============
7+
8+.. _metasearch-engine: https://en.wikipedia.org/wiki/Metasearch_engine
9+
10+searx is a metasearch-engine_, so it uses different search engines to provide
11+better results.
12+
13+Because there is no general search API which could be used for every search
14+engine, an adapter has to be built between searx and the external search
15+engines. Adapters are stored under the folder :origin:`searx/engines`.
16+
17+.. contents::
18+ :depth: 3
19+ :backlinks: entry
20+
21+general engine configuration
22+============================
23+
24+It is required to tell searx the type of results the engine provides. The
25+arguments can be set in the engine file or in the settings file
26+(normally ``settings.yml``). The arguments in the settings file override
27+the ones in the engine file.
28+
29+It does not matter if an option is stored in the engine file or in the
30+settings. However, the standard way is the following:
31+
32+.. _engine file:
33+
34+engine file
35+-----------
36+
37+======================= =========== ===========================================
38+argument type information
39+======================= =========== ===========================================
40+categories list pages, in which the engine is working
41+paging boolean support multible pages
42+language_support boolean support language choosing
43+time_range_support boolean support search time range
44+offline boolean engine runs offline
45+======================= =========== ===========================================
46+
47+.. _engine settings:
48+
49+settings.yml
50+------------
51+
52+======================= =========== ===========================================
53+argument type information
54+======================= =========== ===========================================
55+name string name of search-engine
56+engine string name of searx-engine
57+ (filename without ``.py``)
58+shortcut string shortcut of search-engine
59+timeout string specific timeout for search-engine
60+======================= =========== ===========================================
61+
62+
63+overrides
64+---------
65+
66+A few of the options have default values in the engine, but are often
67+overwritten by the settings. If ``None`` is assigned to an option in the engine
68+file, it has to be redefined in the settings, otherwise searx will not start
69+with that engine.
70+
71+The naming of overrides is arbitrary. But the recommended overrides are the
72+following:
73+
74+======================= =========== ===========================================
75+argument type information
76+======================= =========== ===========================================
77+base_url string base-url, can be overwritten to use same
78+ engine on other URL
79+number_of_results int maximum number of results per request
80+language string ISO code of language and country like en_US
81+api_key string api-key if required by engine
82+======================= =========== ===========================================
83+
84+example code
85+------------
86+
87+.. code:: python
88+
89+ # engine dependent config
90+ categories = ['general']
91+ paging = True
92+ language_support = True
93+
94+
95+making a request
96+================
97+
98+To perform a search an URL have to be specified. In addition to specifying an
99+URL, arguments can be passed to the query.
100+
101+passed arguments
102+----------------
103+
104+These arguments can be used to construct the search query. Furthermore,
105+parameters with default value can be redefined for special purposes.
106+
107+====================== ============ ========================================================================
108+argument type default-value, information
109+====================== ============ ========================================================================
110+url string ``''``
111+method string ``'GET'``
112+headers set ``{}``
113+data set ``{}``
114+cookies set ``{}``
115+verify boolean ``True``
116+headers.User-Agent string a random User-Agent
117+category string current category, like ``'general'``
118+started datetime current date-time
119+pageno int current pagenumber
120+language string specific language code like ``'en_US'``, or ``'all'`` if unspecified
121+====================== ============ ========================================================================
122+
123+parsed arguments
124+----------------
125+
126+The function ``def request(query, params):`` always returns the ``params``
127+variable. Inside searx, the following paramters can be used to specify a search
128+request:
129+
130+============ =========== =========================================================
131+argument type information
132+============ =========== =========================================================
133+url string requested url
134+method string HTTP request method
135+headers set HTTP header information
136+data set HTTP data information (parsed if ``method != 'GET'``)
137+cookies set HTTP cookies
138+verify boolean Performing SSL-Validity check
139+============ =========== =========================================================
140+
141+
142+example code
143+------------
144+
145+.. code:: python
146+
147+ # search-url
148+ base_url = 'https://example.com/'
149+ search_string = 'search?{query}&page={page}'
150+
151+ # do search-request
152+ def request(query, params):
153+ search_path = search_string.format(
154+ query=urlencode({'q': query}),
155+ page=params['pageno'])
156+
157+ params['url'] = base_url + search_path
158+
159+ return params
160+
161+
162+returned results
163+================
164+
165+Searx is able to return results of different media-types. Currently the
166+following media-types are supported:
167+
168+- default_
169+- images_
170+- videos_
171+- torrent_
172+- map_
173+
174+To set another media-type as default, the parameter ``template`` must be set to
175+the desired type.
176+
177+default
178+-------
179+
180+========================= =====================================================
181+result-parameter information
182+========================= =====================================================
183+url string, url of the result
184+title string, title of the result
185+content string, general result-text
186+publishedDate :py:class:`datetime.datetime`, time of publish
187+========================= =====================================================
188+
189+images
190+------
191+
192+To use this template, the parameter:
193+
194+========================= =====================================================
195+result-parameter information
196+========================= =====================================================
197+template is set to ``images.html``
198+url string, url to the result site
199+title string, title of the result *(partly implemented)*
200+content *(partly implemented)*
201+publishedDate :py:class:`datetime.datetime`,
202+ time of publish *(partly implemented)*
203+img\_src string, url to the result image
204+thumbnail\_src string, url to a small-preview image
205+========================= =====================================================
206+
207+videos
208+------
209+
210+========================= =====================================================
211+result-parameter information
212+========================= =====================================================
213+template is set to ``videos.html``
214+url string, url of the result
215+title string, title of the result
216+content *(not implemented yet)*
217+publishedDate :py:class:`datetime.datetime`, time of publish
218+thumbnail string, url to a small-preview image
219+========================= =====================================================
220+
221+torrent
222+-------
223+
224+.. _magnetlink: https://en.wikipedia.org/wiki/Magnet_URI_scheme
225+
226+========================= =====================================================
227+result-parameter information
228+========================= =====================================================
229+template is set to ``torrent.html``
230+url string, url of the result
231+title string, title of the result
232+content string, general result-text
233+publishedDate :py:class:`datetime.datetime`,
234+ time of publish *(not implemented yet)*
235+seed int, number of seeder
236+leech int, number of leecher
237+filesize int, size of file in bytes
238+files int, number of files
239+magnetlink string, magnetlink_ of the result
240+torrentfile string, torrentfile of the result
241+========================= =====================================================
242+
243+
244+map
245+---
246+
247+========================= =====================================================
248+result-parameter information
249+========================= =====================================================
250+url string, url of the result
251+title string, title of the result
252+content string, general result-text
253+publishedDate :py:class:`datetime.datetime`, time of publish
254+latitude latitude of result (in decimal format)
255+longitude longitude of result (in decimal format)
256+boundingbox boundingbox of result (array of 4. values
257+ ``[lat-min, lat-max, lon-min, lon-max]``)
258+geojson geojson of result (http://geojson.org)
259+osm.type type of osm-object (if OSM-Result)
260+osm.id id of osm-object (if OSM-Result)
261+address.name name of object
262+address.road street name of object
263+address.house_number house number of object
264+address.locality city, place of object
265+address.postcode postcode of object
266+address.country country of object
267+========================= =====================================================
docs/dev/hello.dotnew file+3
@@ -0,0 +1,3 @@
1+graph G {
2+ Hello -- World
3+}
docs/dev/index.rstnew file+15
@@ -0,0 +1,15 @@
1+=======================
2+Developer documentation
3+=======================
4+
5+.. toctree::
6+ :maxdepth: 1
7+
8+ quickstart
9+ contribution_guide
10+ engine_overview
11+ search_api
12+ plugins
13+ translation
14+ makefile
15+ reST
docs/dev/makefile.rstnew file+221
@@ -0,0 +1,221 @@
1+.. _makefile:
2+
3+================
4+Makefile Targets
5+================
6+
7+.. _gnu-make: https://www.gnu.org/software/make/manual/make.html#Introduction
8+
9+.. sidebar:: build environment
10+
11+ Before looking deeper at the targets, first read about :ref:`makefile setup`
12+ and :ref:`make pyenv`.
13+
14+With the aim to simplify development cycles, started with :pull:`1756` a
15+``Makefile`` based boilerplate was added. If you are not familiar with
16+Makefiles, we recommend to read gnu-make_ introduction.
17+
18+The usage is simple, just type ``make {target-name}`` to *build* a target.
19+Calling the ``help`` target gives a first overview::
20+
21+ $ make help
22+ test - run developer tests
23+ docs - build documentation
24+ docs-live - autobuild HTML documentation while editing
25+ run - run developer instance
26+ install - developer install (./local)
27+ uninstall - uninstall (./local)
28+ gh-pages - build docs & deploy on gh-pages branch
29+ clean - drop builds and environments
30+ ...
31+
32+.. contents:: Contents
33+ :depth: 2
34+ :local:
35+ :backlinks: entry
36+
37+
38+.. _makefile setup:
39+
40+Setup
41+=====
42+
43+.. _git stash: https://git-scm.com/docs/git-stash
44+
45+The main setup is done in the :origin:`Makefile`::
46+
47+ export GIT_URL=https://github.com/asciimoo/searx
48+ export SEARX_URL=https://searx.me
49+ export DOCS_URL=https://asciimoo.github.io/searx
50+
51+.. sidebar:: fork & upstream
52+
53+ Commit changes in your (local) branch, fork or whatever, but do not push them
54+ upstream / `git stash`_ is your friend.
55+
56+:GIT_URL: Changes this, to point to your searx fork.
57+
58+:SEARX_URL: Changes this, to point to your searx instance.
59+
60+:DOCS_URL: If you host your own (branded) documentation, change this URL.
61+
62+.. _make pyenv:
63+
64+Python environment
65+==================
66+
67+.. sidebar:: activate environment
68+
69+ ``source ./local/py3/bin/activate``
70+
71+With Makefile we do no longer need to build up the virualenv manually (as
72+described in the :ref:`devquickstart` guide). Jump into your git working tree
73+and release a ``make pyenv``:
74+
75+.. code:: sh
76+
77+ $ cd ~/searx-clone
78+ $ make pyenv
79+ PYENV usage: source ./local/py3/bin/activate
80+ ...
81+
82+With target ``pyenv`` a development environment (aka virtualenv) was build up in
83+``./local/py3/``. To make a *developer install* of searx (:origin:`setup.py`)
84+into this environment, use make target ``install``:
85+
86+.. code:: sh
87+
88+ $ make install
89+ PYENV usage: source ./local/py3/bin/activate
90+ PYENV using virtualenv from ./local/py3
91+ PYENV install .
92+
93+You have never to think about intermediate targets like ``pyenv`` or
94+``install``, the ``Makefile`` chains them as requisites. Just run your main
95+target.
96+
97+.. sidebar:: drop environment
98+
99+ To get rid of the existing environment before re-build use :ref:`clean target
100+ <make clean>` first.
101+
102+If you think, something goes wrong with your ./local environment or you change
103+the :origin:`setup.py` file (or the requirements listed in
104+:origin:`requirements-dev.txt` and :origin:`requirements.txt`), you have to call
105+:ref:`make clean`.
106+
107+
108+.. _make run:
109+
110+``make run``
111+============
112+
113+To get up a running a developer instance simply call ``make run``. This enables
114+*debug* option in :origin:`searx/settings.yml`, starts a ``./searx/webapp.py``
115+instance, disables *debug* option again and opens the URL in your favorite WEB
116+browser (:man:`xdg-open`):
117+
118+.. code:: sh
119+
120+ $ make run
121+ PYENV usage: source ./local/py3/bin/activate
122+ PYENV install .
123+ ./local/py3/bin/python ./searx/webapp.py
124+ ...
125+ INFO:werkzeug: * Running on http://127.0.0.1:8888/ (Press CTRL+C to quit)
126+ ...
127+
128+.. _make clean:
129+
130+``make clean``
131+==============
132+
133+Drop all intermediate files, all builds, but keep sources untouched. Includes
134+target ``pyclean`` which drops ./local environment. Before calling ``make
135+clean`` stop all processes using :ref:`make pyenv`.
136+
137+.. code:: sh
138+
139+ $ make clean
140+ CLEAN pyclean
141+ CLEAN clean
142+
143+.. _make docs:
144+
145+``make docs docs-live docs-clean``
146+==================================
147+
148+We describe the usage of the ``doc*`` targets in the :ref:`How to contribute /
149+Documentation <contrib docs>` section. If you want to edit the documentation
150+read our :ref:`make docs-live` section. If you are working in your own brand,
151+adjust your :ref:`Makefile setup <makefile setup>`.
152+
153+
154+.. _make gh-pages:
155+
156+``make gh-pages``
157+=================
158+
159+To deploy on github.io first adjust your :ref:`Makefile setup <makefile
160+setup>`. For any further read :ref:`deploy on github.io`.
161+
162+.. _make test:
163+
164+``make test``
165+=============
166+
167+Runs a series of tests: ``test.pep8``, ``test.unit``, ``test.robot`` and does
168+additional :ref:`pylint checks <make pylint>`. You can run tests selective,
169+e.g.:
170+
171+.. code:: sh
172+
173+ $ make test.pep8 test.unit
174+ . ./local/py3/bin/activate; ./manage.sh pep8_check
175+ [!] Running pep8 check
176+ . ./local/py3/bin/activate; ./manage.sh unit_tests
177+ [!] Running unit tests
178+
179+.. _make pylint:
180+
181+``make pylint``
182+===============
183+
184+.. _Pylint: https://www.pylint.org/
185+
186+Before commiting its recommend to do some (more) linting. Pylint_ is known as
187+one of the best source-code, bug and quality checker for the Python programming
188+language. Pylint_ is not yet a quality gate within our searx project (like
189+:ref:`test.pep8 <make test>` it is), but Pylint_ can help to improve code
190+quality anyway. The pylint profile we use at searx project is found in
191+project's root folder :origin:`.pylintrc`.
192+
193+Code quality is a ongoing process. Don't try to fix all messages from Pylint,
194+run Pylint and check if your changed lines are bringing up new messages. If so,
195+fix it. By this, code quality gets incremental better and if there comes the
196+day, the linting is balanced out, we might decide to add Pylint as a quality
197+gate.
198+
199+
200+``make pybuild``
201+================
202+
203+.. _PyPi: https://pypi.org/
204+.. _twine: https://twine.readthedocs.io/en/latest/
205+
206+Build Python packages in ``./dist/py``.
207+
208+.. code:: sh
209+
210+ $ make pybuild
211+ ...
212+ BUILD pybuild
213+ running sdist
214+ running egg_info
215+ ...
216+ $ ls ./dist/py/
217+ searx-0.15.0-py3-none-any.whl searx-0.15.0.tar.gz
218+
219+To upload packages to PyPi_, there is also a ``upload-pypi`` target. It needs
220+twine_ to be installed. Since you are not the owner of :pypi:`searx` you will
221+never need the latter.
docs/dev/plugins.rstnew file+54
@@ -0,0 +1,54 @@
1+.. _dev plugin:
2+
3+=======
4+Plugins
5+=======
6+
7+.. sidebar:: Further reading ..
8+
9+ - :ref:`plugins generic`
10+
11+Plugins can extend or replace functionality of various components of searx.
12+
13+Example plugin
14+==============
15+
16+.. code:: python
17+
18+ name = 'Example plugin'
19+ description = 'This plugin extends the suggestions with the word "example"'
20+ default_on = False # disabled by default
21+
22+ js_dependencies = tuple() # optional, list of static js files
23+ css_dependencies = tuple() # optional, list of static css files
24+
25+
26+ # attach callback to the post search hook
27+ # request: flask request object
28+ # ctx: the whole local context of the post search hook
29+ def post_search(request, ctx):
30+ ctx['search'].suggestions.add('example')
31+ return True
32+
33+Plugin entry points
34+===================
35+
36+Entry points (hooks) define when a plugin runs. Right now only three hooks are
37+implemented. So feel free to implement a hook if it fits the behaviour of your
38+plugin.
39+
40+Pre search hook
41+---------------
42+
43+Runs BEFORE the search request. Function to implement: ``pre_search``
44+
45+Post search hook
46+----------------
47+
48+Runs AFTER the search request. Function to implement: ``post_search``
49+
50+Result hook
51+-----------
52+
53+Runs when a new result is added to the result list. Function to implement:
54+``on_result``
docs/dev/quickstart.rstnew file+132
@@ -0,0 +1,132 @@
1+.. _devquickstart:
2+
3+======================
4+Development Quickstart
5+======================
6+
7+.. sidebar:: :ref:`makefile`
8+
9+ For additional developer purpose there are :ref:`makefile`.
10+
11+This quickstart guide gets your environment set up with searx. Furthermore, it
12+gives a short introduction to the ``manage.sh`` script.
13+
14+How to setup your development environment
15+=========================================
16+
17+.. sidebar:: :ref:`make pyenv <make pyenv>`
18+
19+ Alternatively use the :ref:`make pyenv`.
20+
21+First, clone the source code of searx to the desired folder. In this case the
22+source is cloned to ``~/myprojects/searx``. Then create and activate the
23+searx-ve virtualenv and install the required packages using ``manage.sh``.
24+
25+.. code:: sh
26+
27+ cd ~/myprojects
28+ git clone https://github.com/asciimoo/searx.git
29+ cd searx
30+ virtualenv searx-ve
31+ . ./searx-ve/bin/activate
32+ ./manage.sh update_dev_packages
33+
34+
35+How to run tests
36+================
37+
38+.. sidebar:: :ref:`make test.unit <make test>`
39+
40+ Alternatively use the ``test.pep8``, ``test.unit``, ``test.robot`` targets.
41+
42+Tests can be run using the ``manage.sh`` script. Following tests and checks are
43+available:
44+
45+- Unit tests
46+- Selenium tests
47+- PEP8 validation
48+- Unit test coverage check
49+
50+For example unit tests are run with the command below:
51+
52+.. code:: sh
53+
54+ ./manage.sh unit_tests
55+
56+For further test options, please consult the help of the ``manage.sh`` script or
57+read :ref:`make test`.
58+
59+
60+How to compile styles and javascript
61+====================================
62+
63+.. _less: http://lesscss.org/
64+.. _NodeJS: https://nodejs.org
65+
66+How to build styles
67+-------------------
68+
69+Less_ is required to build the styles of searx. Less_ can be installed using
70+either NodeJS_ or Apt.
71+
72+.. code:: sh
73+
74+ sudo -H apt-get install nodejs
75+ sudo -H npm install -g less
76+
77+OR
78+
79+.. code:: sh
80+
81+ sudo -H apt-get install node-less
82+
83+After satisfying the requirements styles can be build using ``manage.sh``
84+
85+.. code:: sh
86+
87+ ./manage.sh styles
88+
89+
90+How to build the source of the oscar theme
91+==========================================
92+
93+.. _grunt: https://gruntjs.com/
94+
95+Grunt_ must be installed in order to build the javascript sources. It depends on
96+NodeJS, so first Node has to be installed.
97+
98+.. code:: sh
99+
100+ sudo -H apt-get install nodejs
101+ sudo -H npm install -g grunt-cli
102+
103+After installing grunt, the files can be built using the following command:
104+
105+.. code:: sh
106+
107+ ./manage.sh grunt_build
108+
109+
110+Tips for debugging/development
111+==============================
112+
113+.. sidebar:: :ref:`make run`
114+
115+ Makefile target ``run`` already enables debug option for your developer
116+ session / see :ref:`make run`.
117+
118+Turn on debug logging
119+ Whether you are working on a new engine or trying to eliminate a bug, it is
120+ always a good idea to turn on debug logging. When debug logging is enabled a
121+ stack trace appears, instead of the cryptic ``Internal Server Error``
122+ message. It can be turned on by setting ``debug: False`` to ``debug: True`` in
123+ :origin:`settings.yml <searx/settings.yml>`.
124+
125+.. sidebar:: :ref:`make test`
126+
127+ Alternatively use the :ref:`make test` targets.
128+
129+Run ``./manage.sh tests`` before creating a PR.
130+ Failing build on Travis is common because of PEP8 checks. So a new commit
131+ must be created containing these format fixes. This phase can be skipped if
132+ ``./manage.sh tests`` is run locally before creating a PR.
docs/dev/reST.rstnew file+1428
@@ -0,0 +1,1428 @@
1+.. _reST primer:
2+
3+===========
4+reST primer
5+===========
6+
7+.. sidebar:: KISS_ and readability_
8+
9+ Instead of defining more and more roles, we at searx encourage our
10+ contributors to follow principles like KISS_ and readability_.
11+
12+We at searx are using reStructuredText (aka reST_) markup for all kind of
13+documentation, with the builders from the Sphinx_ project a HTML output is
14+generated and deployed at :docs:`github.io <.>`. For build prerequisites read
15+:ref:`docs build`.
16+
17+The source files of Searx's documentation are located at :origin:`docs`. Sphinx
18+assumes source files to be encoded in UTF-8 by defaul. Run :ref:`make docs-live
19+<make docs-live>` to build HTML while editing.
20+
21+.. sidebar:: Further reading
22+
23+ - Sphinx-Primer_
24+ - `Sphinx markup constructs`_
25+ - reST_, docutils_, `docutils FAQ`_
26+ - Sphinx_, `sphinx-doc FAQ`_
27+ - `sphinx config`_, doctree_
28+ - `sphinx cross references`_
29+ - linuxdoc_
30+ - intersphinx_
31+ - sphinx-jinja_
32+ - `Sphinx's autodoc`_
33+ - `Sphinx's Python domain`_, `Sphinx's C domain`_
34+ - SVG_, ImageMagick_
35+ - DOT_, `Graphviz's dot`_, Graphviz_
36+
37+
38+.. contents:: Contents
39+ :depth: 3
40+ :local:
41+ :backlinks: entry
42+
43+Sphinx_ and reST_ have their place in the python ecosystem. Over that reST is
44+used in popular projects, e.g the Linux kernel documentation `[kernel doc]`_.
45+
46+.. _[kernel doc]: https://www.kernel.org/doc/html/latest/doc-guide/sphinx.html
47+
48+.. sidebar:: Content matters
49+
50+ The readability_ of the reST sources has its value, therefore we recommend to
51+ make sparse usage of reST markup / .. content matters!
52+
53+**reST** is a plaintext markup language, its markup is *mostly* intuitive and
54+you will not need to learn much to produce well formed articles with. I use the
55+word *mostly*: like everything in live, reST has its advantages and
56+disadvantages, some markups feel a bit grumpy (especially if you are used to
57+other plaintext markups).
58+
59+Soft skills
60+===========
61+
62+Before going any deeper into the markup let's face on some **soft skills** a
63+trained author brings with, to reach a well feedback from readers:
64+
65+- Documentation is dedicated to an audience and answers questions from the
66+ audience point of view.
67+- Don't detail things which are general knowledge from the audience point of
68+ view.
69+- Limit the subject, use cross links for any further reading.
70+
71+To be more concrete what a *point of view* means. In the (:origin:`docs`)
72+folder we have three sections (and the *blog* folder), each dedicate to a
73+different group of audience.
74+
75+User's POV: :origin:`docs/user`
76+ A typical user knows about search engines and might have heard about
77+ meta crawlers and privacy.
78+
79+Admin's POV: :origin:`docs/admin`
80+ A typical Admin knows about setting up services on a linux system, but he does
81+ not know all the pros and cons of a searx setup.
82+
83+Developer's POV: :origin:`docs/dev`
84+ Depending on the readability_ of code, a typical developer is able to read and
85+ understand source code. Describe what a item aims to do (e.g. a function).
86+ If the chronological order matters, describe it. Name the *out-of-limits
87+ conditions* and all the side effects a external developer will not know.
88+
89+.. _reST inline markup:
90+
91+Basic inline markup
92+===================
93+
94+.. sidebar:: Inline markup
95+
96+ - :ref:`reST roles`
97+ - :ref:`reST smart ref`
98+
99+Basic inline markup is done with asterisks and backquotes. If asterisks or
100+backquotes appear in running text and could be confused with inline markup
101+delimiters, they have to be escaped with a backslash (``\*pointer``).
102+
103+.. table:: basic inline markup
104+ :widths: 4 3 7
105+
106+ ================================================ ==================== ========================
107+ description rendered markup
108+ ================================================ ==================== ========================
109+ one asterisk for emphasis *italics* ``*italics*``
110+ two asterisks for strong emphasis **boldface** ``**boldface**``
111+ backquotes for code samples and literals ``foo()`` ````foo()````
112+ quote asterisks or backquotes \*foo is a pointer ``\*foo is a pointer``
113+ ================================================ ==================== ========================
114+
115+.. _reST basic structure:
116+
117+Basic article structure
118+=======================
119+
120+The basic structure of an article makes use of heading adornments to markup
121+chapter, sections and subsections.
122+
123+.. _reST template:
124+
125+reST template
126+-------------
127+
128+reST template for an simple article:
129+
130+.. code:: reST
131+
132+ .. _doc refname:
133+
134+ ==============
135+ Document title
136+ ==============
137+
138+ Lorem ipsum dolor sit amet, consectetur adipisici elit .. Further read
139+ :ref:`chapter refname`.
140+
141+ .. _chapter refname:
142+
143+ Chapter
144+ =======
145+
146+ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
147+ aliquid ex ea commodi consequat ...
148+
149+ .. _section refname:
150+
151+ Section
152+ -------
153+
154+ lorem ..
155+
156+ .. _subsection refname:
157+
158+ Subsection
159+ ~~~~~~~~~~
160+
161+ lorem ..
162+
163+
164+Headings
165+--------
166+
167+#. title - with overline for document title:
168+
169+ .. code:: reST
170+
171+ ==============
172+ Document title
173+ ==============
174+
175+
176+#. chapter - with anchor named ``anchor name``:
177+
178+ .. code:: reST
179+
180+ .. _anchor name:
181+
182+ Chapter
183+ =======
184+
185+#. section
186+
187+ .. code:: reST
188+
189+ Section
190+ -------
191+
192+#. subsection
193+
194+ .. code:: reST
195+
196+ Subsection
197+ ~~~~~~~~~~
198+
199+
200+
201+Anchors & Links
202+===============
203+
204+.. _reST anchor:
205+
206+Anchors
207+-------
208+
209+.. _ref role:
210+ https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-ref
211+
212+To refer a point in the documentation a anchor is needed. The :ref:`reST
213+template <reST template>` shows an example where a chapter titled *"Chapters"*
214+gets an anchor named ``chapter title``. Another example from *this* document,
215+where the anchor named ``reST anchor``:
216+
217+.. code:: reST
218+
219+ .. _reST anchor:
220+
221+ Anchors
222+ -------
223+
224+ To refer a point in the documentation a anchor is needed ...
225+
226+To refer anchors use the `ref role`_ markup:
227+
228+.. code:: reST
229+
230+ Visit chapter :ref:`reST anchor`. Or set hyperlink text manualy :ref:`foo
231+ bar <reST anchor>`.
232+
233+.. admonition:: ``:ref:`` role
234+ :class: rst-example
235+
236+ Visist chapter :ref:`reST anchor`. Or set hyperlink text manualy :ref:`foo
237+ bar <reST anchor>`.
238+
239+.. _reST ordinary ref:
240+
241+Link ordinary URL
242+-----------------
243+
244+If you need to reference external URLs use *named* hyperlinks to maintain
245+readability of reST sources. Here is a example taken from *this* article:
246+
247+.. code:: reST
248+
249+ .. _Sphinx Field Lists:
250+ https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html
251+
252+ With the *named* hyperlink `Sphinx Field Lists`_, the raw text is much more
253+ readable.
254+
255+ And this shows the alternative (less readable) hyperlink markup `Sphinx Field
256+ Lists
257+ <https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html>`__.
258+
259+.. admonition:: Named hyperlink
260+ :class: rst-example
261+
262+ With the *named* hyperlink `Sphinx Field Lists`_, the raw text is much more
263+ readable.
264+
265+ And this shows the alternative (less readable) hyperlink markup `Sphinx Field
266+ Lists
267+ <https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html>`__.
268+
269+
270+.. _reST smart ref:
271+
272+Smart refs
273+----------
274+
275+With the power of sphinx.ext.extlinks_ and intersphinx_ referencing external
276+content becomes smart.
277+
278+.. table:: smart refs with sphinx.ext.extlinks_ and intersphinx_
279+ :widths: 4 3 7
280+
281+ ========================== ================================== ====================================
282+ refer ... rendered example markup
283+ ========================== ================================== ====================================
284+ :rst:role:`rfc` :rfc:`822` ``:rfc:`822```
285+ :rst:role:`pep` :pep:`8` ``:pep:`8```
286+ sphinx.ext.extlinks_
287+ --------------------------------------------------------------------------------------------------
288+ project's wiki article :wiki:`Searx-instances` ``:wiki:`Searx-instances```
289+ to docs public URL :docs:`dev/reST.html` ``:docs:`dev/reST.html```
290+ files & folders origin :origin:`docs/dev/reST.rst` ``:origin:`docs/dev/reST.rst```
291+ pull request :pull:`1756` ``:pull:`1756```
292+ patch :patch:`af2cae6` ``:patch:`af2cae6```
293+ PyPi package :pypi:`searx` ``:pypi:`searx```
294+ manual page man :man:`bash` ``:man:`bash```
295+ intersphinx_
296+ --------------------------------------------------------------------------------------------------
297+ external anchor :ref:`python:and` ``:ref:`python:and```
298+ external doc anchor :doc:`jinja:templates` ``:doc:`jinja:templates```
299+ python code object :py:obj:`datetime.datetime` ``:py:obj:`datetime.datetime```
300+ flask code object :py:obj:`flask.Flask` ``:py:obj:`flask.Flask```
301+ ========================== ================================== ====================================
302+
303+
304+Intersphinx is configured in :origin:`docs/conf.py`:
305+
306+.. code:: python
307+
308+ intersphinx_mapping = {
309+ "python": ("https://docs.python.org/3/", None),
310+ "flask": ("https://flask.palletsprojects.com/", None),
311+ "jinja": ("https://jinja.palletsprojects.com/", None),
312+ "linuxdoc" : ("https://return42.github.io/linuxdoc/", None),
313+ "sphinx" : ("https://www.sphinx-doc.org/en/master/", None),
314+ }
315+
316+
317+To list all anchors of the inventory (e.g. ``python``) use:
318+
319+.. code:: sh
320+
321+ $ python -m sphinx.ext.intersphinx https://docs.python.org/3/objects.inv
322+
323+Literal blocks
324+==============
325+
326+The simplest form of :duref:`literal-blocks` is a indented block introduced by
327+two colons (``::``). For highlighting use :dudir:`highlight` or :ref:`reST
328+code` directive. To include literals from external files use directive
329+:dudir:`literalinclude`.
330+
331+.. _reST literal:
332+
333+``::``
334+------
335+
336+.. code:: reST
337+
338+ ::
339+
340+ Literal block
341+
342+ Lorem ipsum dolor::
343+
344+ Literal block
345+
346+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
347+ eirmod tempor invidunt ut labore ::
348+
349+ Literal block
350+
351+.. admonition:: Literal block
352+ :class: rst-example
353+
354+ ::
355+
356+ Literal block
357+
358+ Lorem ipsum dolor::
359+
360+ Literal block
361+
362+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
363+ eirmod tempor invidunt ut labore ::
364+
365+ Literal block
366+
367+
368+.. _reST code:
369+
370+``code-block``
371+--------------
372+
373+.. _pygments: https://pygments.org/languages/
374+
375+.. sidebar:: Syntax highlighting
376+
377+ is handled by pygments_.
378+
379+The :rst:dir:`code-block` directive is a variant of the :dudir:`code` directive
380+with additional options. To learn more about code literals visit
381+:ref:`sphinx:code-examples`.
382+
383+.. code-block:: reST
384+
385+ The URL ``/stats`` handle is shown in :ref:`stats-handle`
386+
387+ .. code-block:: Python
388+ :caption: python code block
389+ :name: stats-handle
390+
391+ @app.route('/stats', methods=['GET'])
392+ def stats():
393+ """Render engine statistics page."""
394+ stats = get_engines_stats()
395+ return render(
396+ 'stats.html'
397+ , stats = stats )
398+
399+.. code-block:: reST
400+
401+.. admonition:: Code block
402+ :class: rst-example
403+
404+ The URL ``/stats`` handle is shown in :ref:`stats-handle`
405+
406+ .. code-block:: Python
407+ :caption: python code block
408+ :name: stats-handle
409+
410+ @app.route('/stats', methods=['GET'])
411+ def stats():
412+ """Render engine statistics page."""
413+ stats = get_engines_stats()
414+ return render(
415+ 'stats.html'
416+ , stats = stats )
417+
418+Unicode substitution
419+====================
420+
421+The :dudir:`unicode directive <unicode-character-codes>` converts Unicode
422+character codes (numerical values) to characters. This directive can only be
423+used within a substitution definition.
424+
425+.. code-block:: reST
426+
427+ .. |copy| unicode:: 0xA9 .. copyright sign
428+ .. |(TM)| unicode:: U+2122
429+
430+ Trademark |(TM)| and copyright |copy| glyphs.
431+
432+.. admonition:: Unicode
433+ :class: rst-example
434+
435+ .. |copy| unicode:: 0xA9 .. copyright sign
436+ .. |(TM)| unicode:: U+2122
437+
438+ Trademark |(TM)| and copyright |copy| glyphs.
439+
440+
441+.. _reST roles:
442+
443+Roles
444+=====
445+
446+.. sidebar:: Further reading
447+
448+ - `Sphinx Roles`_
449+ - :doc:`sphinx:usage/restructuredtext/domains`
450+
451+
452+A *custom interpreted text role* (:duref:`ref <roles>`) is an inline piece of
453+explicit markup. It signifies that that the enclosed text should be interpreted
454+in a specific way.
455+
456+The general markup is one of:
457+
458+.. code:: reST
459+
460+ :rolename:`ref-name`
461+ :rolename:`ref text <ref-name>`
462+
463+.. table:: smart refs with sphinx.ext.extlinks_ and intersphinx_
464+ :widths: 4 3 7
465+
466+ ========================== ================================== ====================================
467+ role rendered example markup
468+ ========================== ================================== ====================================
469+ :rst:role:`guilabel` :guilabel:`&Cancel` ``:guilabel:`&Cancel```
470+ :rst:role:`kbd` :kbd:`C-x C-f` ``:kbd:`C-x C-f```
471+ :rst:role:`menuselection` :menuselection:`Open --> File` ``:menuselection:`Open --> File```
472+ :rst:role:`download` :download:`this file <reST.rst>` ``:download:`this file <reST.rst>```
473+ math_ :math:`a^2 + b^2 = c^2` ``:math:`a^2 + b^2 = c^2```
474+ :rst:role:`ref` :ref:`svg image example` ``:ref:`svg image example```
475+ :rst:role:`command` :command:`ls -la` ``:command:`ls -la```
476+ :durole:`emphasis` :emphasis:`italic` ``:emphasis:`italic```
477+ :durole:`strong` :strong:`bold` ``:strong:`bold```
478+ :durole:`literal` :literal:`foo()` ``:literal:`foo()```
479+ :durole:`subscript` H\ :sub:`2`\ O ``H\ :sub:`2`\ O``
480+ :durole:`superscript` E = mc\ :sup:`2` ``E = mc\ :sup:`2```
481+ :durole:`title-reference` :title:`Time` ``:title:`Time```
482+ ========================== ================================== ====================================
483+
484+Figures & Images
485+================
486+
487+.. sidebar:: Image processing
488+
489+ With the directives from :ref:`linuxdoc <linuxdoc:kfigure>` the build process
490+ is flexible. To get best results in the generated output format, install
491+ ImageMagick_ and Graphviz_.
492+
493+Searx's sphinx setup includes: :ref:`linuxdoc:kfigure`. Scaleable here means;
494+scaleable in sense of the build process. Normally in absence of a converter
495+tool, the build process will break. From the authors POV it’s annoying to care
496+about the build process when handling with images, especially since he has no
497+access to the build process. With :ref:`linuxdoc:kfigure` the build process
498+continues and scales output quality in dependence of installed image processors.
499+
500+If you want to add an image, you should use the ``kernel-figure`` (inheritance
501+of :dudir:`figure`) and ``kernel-image`` (inheritance of :dudir:`image`)
502+directives. E.g. to insert a figure with a scaleable image format use SVG
503+(:ref:`svg image example`):
504+
505+.. code:: reST
506+
507+ .. _svg image example:
508+
509+ .. kernel-figure:: svg_image.svg
510+ :alt: SVG image example
511+
512+ Simple SVG image
513+
514+ To refer the figure, a caption block is needed: :ref:`svg image example`.
515+
516+.. _svg image example:
517+
518+.. kernel-figure:: svg_image.svg
519+ :alt: SVG image example
520+
521+ Simple SVG image.
522+
523+To refer the figure, a caption block is needed: :ref:`svg image example`.
524+
525+DOT files (aka Graphviz)
526+------------------------
527+
528+With :ref:`linuxdoc:kernel-figure` reST support for **DOT** formatted files is
529+given.
530+
531+- `Graphviz's dot`_
532+- DOT_
533+- Graphviz_
534+
535+A simple example is shown in :ref:`dot file example`:
536+
537+.. code:: reST
538+
539+ .. _dot file example:
540+
541+ .. kernel-figure:: hello.dot
542+ :alt: hello world
543+
544+ DOT's hello world example
545+
546+.. admonition:: hello.dot
547+ :class: rst-example
548+
549+ .. _dot file example:
550+
551+ .. kernel-figure:: hello.dot
552+ :alt: hello world
553+
554+ DOT's hello world example
555+
556+``kernel-render`` DOT
557+---------------------
558+
559+Embed *render* markups (or languages) like Graphviz's **DOT** is provided by the
560+:ref:`linuxdoc:kernel-render` directive. A simple example of embedded DOT_ is
561+shown in figure :ref:`dot render example`:
562+
563+.. code:: reST
564+
565+ .. _dot render example:
566+
567+ .. kernel-render:: DOT
568+ :alt: digraph
569+ :caption: Embedded DOT (Graphviz) code
570+
571+ digraph foo {
572+ "bar" -> "baz";
573+ }
574+
575+ Attribute ``caption`` is needed, if you want to refer the figure: :ref:`dot
576+ render example`.
577+
578+Please note :ref:`build tools <linuxdoc:kfigure_build_tools>`. If Graphviz_ is
579+installed, you will see an vector image. If not, the raw markup is inserted as
580+*literal-block*.
581+
582+.. admonition:: kernel-render DOT
583+ :class: rst-example
584+
585+ .. _dot render example:
586+
587+ .. kernel-render:: DOT
588+ :alt: digraph
589+ :caption: Embedded DOT (Graphviz) code
590+
591+ digraph foo {
592+ "bar" -> "baz";
593+ }
594+
595+ Attribute ``caption`` is needed, if you want to refer the figure: :ref:`dot
596+ render example`.
597+
598+``kernel-render`` SVG
599+---------------------
600+
601+A simple example of embedded SVG_ is shown in figure :ref:`svg render example`:
602+
603+.. code:: reST
604+
605+ .. _svg render example:
606+
607+ .. kernel-render:: SVG
608+ :caption: Embedded **SVG** markup
609+ :alt: so-nw-arrow
610+..
611+
612+ .. code:: xml
613+
614+ <?xml version="1.0" encoding="UTF-8"?>
615+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
616+ baseProfile="full" width="70px" height="40px"
617+ viewBox="0 0 700 400"
618+ >
619+ <line x1="180" y1="370"
620+ x2="500" y2="50"
621+ stroke="black" stroke-width="15px"
622+ />
623+ <polygon points="585 0 525 25 585 50"
624+ transform="rotate(135 525 25)"
625+ />
626+ </svg>
627+
628+.. admonition:: kernel-render SVG
629+ :class: rst-example
630+
631+ .. _svg render example:
632+
633+ .. kernel-render:: SVG
634+ :caption: Embedded **SVG** markup
635+ :alt: so-nw-arrow
636+
637+ <?xml version="1.0" encoding="UTF-8"?>
638+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
639+ baseProfile="full" width="70px" height="40px"
640+ viewBox="0 0 700 400"
641+ >
642+ <line x1="180" y1="370"
643+ x2="500" y2="50"
644+ stroke="black" stroke-width="15px"
645+ />
646+ <polygon points="585 0 525 25 585 50"
647+ transform="rotate(135 525 25)"
648+ />
649+ </svg>
650+
651+
652+
653+
654+.. _reST lists:
655+
656+List markups
657+============
658+
659+Bullet list
660+-----------
661+
662+List markup (:duref:`ref <bullet-lists>`) is simple:
663+
664+.. code:: reST
665+
666+ - This is a bulleted list.
667+
668+ 1. Nested lists are possible, but be aware that they must be separated from
669+ the parent list items by blank line
670+ 2. Second item of nested list
671+
672+ - It has two items, the second
673+ item uses two lines.
674+
675+ #. This is a numbered list.
676+ #. It has two items too.
677+
678+.. admonition:: bullet list
679+ :class: rst-example
680+
681+ - This is a bulleted list.
682+
683+ 1. Nested lists are possible, but be aware that they must be separated from
684+ the parent list items by blank line
685+ 2. Second item of nested list
686+
687+ - It has two items, the second
688+ item uses two lines.
689+
690+ #. This is a numbered list.
691+ #. It has two items too.
692+
693+
694+Horizontal list
695+---------------
696+
697+The :rst:dir:`.. hlist:: <hlist>` transforms a bullet list into a more compact
698+list.
699+
700+.. code:: reST
701+
702+ .. hlist::
703+
704+ - first list item
705+ - second list item
706+ - third list item
707+ ...
708+
709+.. admonition:: hlist
710+ :class: rst-example
711+
712+ .. hlist::
713+
714+ - first list item
715+ - second list item
716+ - third list item
717+ - next list item
718+ - next list item xxxx
719+ - next list item yyyy
720+ - next list item zzzz
721+
722+
723+Definition list
724+---------------
725+
726+.. sidebar:: Note ..
727+
728+ - the term cannot have more than one line of text
729+
730+ - there is **no blank line between term and definition block** // this
731+ distinguishes definition lists (:duref:`ref <definition-lists>`) from block
732+ quotes (:duref:`ref <block-quotes>`).
733+
734+Each definition list (:duref:`ref <definition-lists>`) item contains a term,
735+optional classifiers and a definition. A term is a simple one-line word or
736+phrase. Optional classifiers may follow the term on the same line, each after
737+an inline ' : ' (**space, colon, space**). A definition is a block indented
738+relative to the term, and may contain multiple paragraphs and other body
739+elements. There may be no blank line between a term line and a definition block
740+(*this distinguishes definition lists from block quotes*). Blank lines are
741+required before the first and after the last definition list item, but are
742+optional in-between.
743+
744+Definition lists are created as follows:
745+
746+.. code:: reST
747+
748+ term 1 (up to a line of text)
749+ Definition 1.
750+
751+ See the typo : this line is not a term!
752+
753+ And this is not term's definition. **There is a blank line** in between
754+ the line above and this paragraph. That's why this paragraph is taken as
755+ **block quote** (:duref:`ref <block-quotes>`) and not as term's definition!
756+
757+ term 2
758+ Definition 2, paragraph 1.
759+
760+ Definition 2, paragraph 2.
761+
762+ term 3 : classifier
763+ Definition 3.
764+
765+ term 4 : classifier one : classifier two
766+ Definition 4.
767+
768+.. admonition:: definition list
769+ :class: rst-example
770+
771+ term 1 (up to a line of text)
772+ Definition 1.
773+
774+ See the typo : this line is not a term!
775+
776+ And this is not term's definition. **There is a blank line** in between
777+ the line above and this paragraph. That's why this paragraph is taken as
778+ **block quote** (:duref:`ref <block-quotes>`) and not as term's definition!
779+
780+
781+ term 2
782+ Definition 2, paragraph 1.
783+
784+ Definition 2, paragraph 2.
785+
786+ term 3 : classifier
787+ Definition 3.
788+
789+ term 4 : classifier one : classifier two
790+
791+
792+Quoted paragraphs
793+-----------------
794+
795+Quoted paragraphs (:duref:`ref <block-quotes>`) are created by just indenting
796+them more than the surrounding paragraphs. Line blocks (:duref:`ref
797+<line-blocks>`) are a way of preserving line breaks:
798+
799+.. code:: reST
800+
801+ normal paragraph ...
802+ lorem ipsum.
803+
804+ Quoted paragraph ...
805+ lorem ipsum.
806+
807+ | These lines are
808+ | broken exactly like in
809+ | the source file.
810+
811+
812+.. admonition:: Quoted paragraph and line block
813+ :class: rst-example
814+
815+ normal paragraph ...
816+ lorem ipsum.
817+
818+ Quoted paragraph ...
819+ lorem ipsum.
820+
821+ | These lines are
822+ | broken exactly like in
823+ | the source file.
824+
825+
826+.. _reST field list:
827+
828+Field Lists
829+-----------
830+
831+.. _Sphinx Field Lists:
832+ https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html
833+
834+.. sidebar:: bibliographic fields
835+
836+ First lines fields are bibliographic fields, see `Sphinx Field Lists`_.
837+
838+Field lists are used as part of an extension syntax, such as options for
839+directives, or database-like records meant for further processing. Field lists
840+are mappings from field names to field bodies. They marked up like this:
841+
842+.. code:: reST
843+
844+ :fieldname: Field content
845+ :foo: first paragraph in field foo
846+
847+ second paragraph in field foo
848+
849+ :bar: Field content
850+
851+.. admonition:: Field List
852+ :class: rst-example
853+
854+ :fieldname: Field content
855+ :foo: first paragraph in field foo
856+
857+ second paragraph in field foo
858+
859+ :bar: Field content
860+
861+
862+They are commonly used in Python documentation:
863+
864+.. code:: python
865+
866+ def my_function(my_arg, my_other_arg):
867+ """A function just for me.
868+
869+ :param my_arg: The first of my arguments.
870+ :param my_other_arg: The second of my arguments.
871+
872+ :returns: A message (just for me, of course).
873+ """
874+
875+Further list blocks
876+-------------------
877+
878+- field lists (:duref:`ref <field-lists>`, with caveats noted in
879+ :ref:`reST field list`)
880+- option lists (:duref:`ref <option-lists>`)
881+- quoted literal blocks (:duref:`ref <quoted-literal-blocks>`)
882+- doctest blocks (:duref:`ref <doctest-blocks>`)
883+
884+
885+Admonitions
886+===========
887+
888+Sidebar
889+-------
890+
891+Sidebar is an eye catcher, often used for admonitions pointing further stuff or
892+site effects. Here is the source of the sidebar :ref:`on top of this page <reST
893+primer>`.
894+
895+.. code:: reST
896+
897+ .. sidebar:: KISS_ and readability_
898+
899+ Instead of defining more and more roles, we at searx encourage our
900+ contributors to follow principles like KISS_ and readability_.
901+
902+Generic admonition
903+------------------
904+
905+The generic :dudir:`admonition <admonitions>` needs a title:
906+
907+.. code:: reST
908+
909+ .. admonition:: generic admonition title
910+
911+ lorem ipsum ..
912+
913+
914+.. admonition:: generic admonition title
915+
916+ lorem ipsum ..
917+
918+
919+Specific admonitions
920+--------------------
921+
922+Specific admonitions: :dudir:`hint`, :dudir:`note`, :dudir:`tip` :dudir:`attention`,
923+:dudir:`caution`, :dudir:`danger`, :dudir:`error`, , :dudir:`important`, and
924+:dudir:`warning` .
925+
926+.. code:: reST
927+
928+ .. hint::
929+
930+ lorem ipsum ..
931+
932+ .. note::
933+
934+ lorem ipsum ..
935+
936+ .. warning::
937+
938+ lorem ipsum ..
939+
940+
941+.. hint::
942+
943+ lorem ipsum ..
944+
945+.. note::
946+
947+ lorem ipsum ..
948+
949+.. tip::
950+
951+ lorem ipsum ..
952+
953+.. attention::
954+
955+ lorem ipsum ..
956+
957+.. caution::
958+
959+ lorem ipsum ..
960+
961+.. danger::
962+
963+ lorem ipsum ..
964+
965+.. important::
966+
967+ lorem ipsum ..
968+
969+.. error::
970+
971+ lorem ipsum ..
972+
973+.. warning::
974+
975+ lorem ipsum ..
976+
977+
978+Tables
979+======
980+
981+.. sidebar:: Nested tables
982+
983+ Nested tables are ugly! Not all builder support nested tables, don't use
984+ them!
985+
986+ASCII-art tables like :ref:`reST simple table` and :ref:`reST grid table` might
987+be comfortable for readers of the text-files, but they have huge disadvantages
988+in the creation and modifying. First, they are hard to edit. Think about
989+adding a row or a column to a ASCII-art table or adding a paragraph in a cell,
990+it is a nightmare on big tables.
991+
992+
993+.. sidebar:: List tables
994+
995+ For meaningful patch and diff use :ref:`reST flat table`.
996+
997+Second the diff of modifying ASCII-art tables is not meaningful, e.g. widening a
998+cell generates a diff in which also changes are included, which are only
999+ascribable to the ASCII-art. Anyway, if you prefer ASCII-art for any reason,
1000+here are some helpers:
1001+
1002+* `Emacs Table Mode`_
1003+* `Online Tables Generator`_
1004+
1005+.. _reST simple table:
1006+
1007+Simple tables
1008+-------------
1009+
1010+:duref:`Simple tables <simple-tables>` allow *colspan* but not *rowspan*. If
1011+your table need some metadata (e.g. a title) you need to add the ``.. table::
1012+directive`` :dudir:`(ref) <table>` in front and place the table in its body:
1013+
1014+.. code:: reST
1015+
1016+ .. table:: foo gate truth table
1017+ :widths: grid
1018+ :align: left
1019+
1020+ ====== ====== ======
1021+ Inputs Output
1022+ ------------- ------
1023+ A B A or B
1024+ ====== ====== ======
1025+ False
1026+ --------------------
1027+ True
1028+ --------------------
1029+ True False True
1030+ (foo)
1031+ ------ ------ ------
1032+ False True
1033+ (foo)
1034+ ====== =============
1035+
1036+.. admonition:: Simple ASCII table
1037+ :class: rst-example
1038+
1039+ .. table:: foo gate truth table
1040+ :widths: grid
1041+ :align: left
1042+
1043+ ====== ====== ======
1044+ Inputs Output
1045+ ------------- ------
1046+ A B A or B
1047+ ====== ====== ======
1048+ False
1049+ --------------------
1050+ True
1051+ --------------------
1052+ True False True
1053+ (foo)
1054+ ------ ------ ------
1055+ False True
1056+ (foo)
1057+ ====== =============
1058+
1059+
1060+
1061+.. _reST grid table:
1062+
1063+Grid tables
1064+-----------
1065+
1066+:duref:`Grid tables <grid-tables>` allow colspan *colspan* and *rowspan*:
1067+
1068+.. code:: reST
1069+
1070+ .. table:: grid table example
1071+ :widths: 1 1 5
1072+
1073+ +------------+------------+-----------+
1074+ | Header 1 | Header 2 | Header 3 |
1075+ +============+============+===========+
1076+ | body row 1 | column 2 | column 3 |
1077+ +------------+------------+-----------+
1078+ | body row 2 | Cells may span columns.|
1079+ +------------+------------+-----------+
1080+ | body row 3 | Cells may | - Cells |
1081+ +------------+ span rows. | - contain |
1082+ | body row 4 | | - blocks. |
1083+ +------------+------------+-----------+
1084+
1085+.. admonition:: ASCII grid table
1086+ :class: rst-example
1087+
1088+ .. table:: grid table example
1089+ :widths: 1 1 5
1090+
1091+ +------------+------------+-----------+
1092+ | Header 1 | Header 2 | Header 3 |
1093+ +============+============+===========+
1094+ | body row 1 | column 2 | column 3 |
1095+ +------------+------------+-----------+
1096+ | body row 2 | Cells may span columns.|
1097+ +------------+------------+-----------+
1098+ | body row 3 | Cells may | - Cells |
1099+ +------------+ span rows. | - contain |
1100+ | body row 4 | | - blocks. |
1101+ +------------+------------+-----------+
1102+
1103+
1104+.. _reST flat table:
1105+
1106+flat-table
1107+----------
1108+
1109+The ``flat-table`` is a further developed variant of the :ref:`list tables
1110+<linuxdoc:list-table-directives>`. It is a double-stage list similar to the
1111+:dudir:`list-table` with some additional features:
1112+
1113+column-span: ``cspan``
1114+ with the role ``cspan`` a cell can be extended through additional columns
1115+
1116+row-span: ``rspan``
1117+ with the role ``rspan`` a cell can be extended through additional rows
1118+
1119+auto-span:
1120+ spans rightmost cell of a table row over the missing cells on the right side
1121+ of that table-row. With Option ``:fill-cells:`` this behavior can changed
1122+ from *auto span* to *auto fill*, which automatically inserts (empty) cells
1123+ instead of spanning the last cell.
1124+
1125+options:
1126+ :header-rows: [int] count of header rows
1127+ :stub-columns: [int] count of stub columns
1128+ :widths: [[int] [int] ... ] widths of columns
1129+ :fill-cells: instead of auto-span missing cells, insert missing cells
1130+
1131+roles:
1132+ :cspan: [int] additional columns (*morecols*)
1133+ :rspan: [int] additional rows (*morerows*)
1134+
1135+The example below shows how to use this markup. The first level of the staged
1136+list is the *table-row*. In the *table-row* there is only one markup allowed,
1137+the list of the cells in this *table-row*. Exception are *comments* ( ``..`` )
1138+and *targets* (e.g. a ref to :ref:`row 2 of table's body <row body 2>`).
1139+
1140+.. code:: reST
1141+
1142+ .. flat-table:: ``flat-table`` example
1143+ :header-rows: 2
1144+ :stub-columns: 1
1145+ :widths: 1 1 1 1 2
1146+
1147+ * - :rspan:`1` head / stub
1148+ - :cspan:`3` head 1.1-4
1149+
1150+ * - head 2.1
1151+ - head 2.2
1152+ - head 2.3
1153+ - head 2.4
1154+
1155+ * .. row body 1 / this is a comment
1156+
1157+ - row 1
1158+ - :rspan:`2` cell 1-3.1
1159+ - cell 1.2
1160+ - cell 1.3
1161+ - cell 1.4
1162+
1163+ * .. Comments and targets are allowed on *table-row* stage.
1164+ .. _`row body 2`:
1165+
1166+ - row 2
1167+ - cell 2.2
1168+ - :rspan:`1` :cspan:`1`
1169+ cell 2.3 with a span over
1170+
1171+ * col 3-4 &
1172+ * row 2-3
1173+
1174+ * - row 3
1175+ - cell 3.2
1176+
1177+ * - row 4
1178+ - cell 4.1
1179+ - cell 4.2
1180+ - cell 4.3
1181+ - cell 4.4
1182+
1183+ * - row 5
1184+ - cell 5.1 with automatic span to rigth end
1185+
1186+ * - row 6
1187+ - cell 6.1
1188+ - ..
1189+
1190+
1191+.. admonition:: List table
1192+ :class: rst-example
1193+
1194+ .. flat-table:: ``flat-table`` example
1195+ :header-rows: 2
1196+ :stub-columns: 1
1197+ :widths: 1 1 1 1 2
1198+
1199+ * - :rspan:`1` head / stub
1200+ - :cspan:`3` head 1.1-4
1201+
1202+ * - head 2.1
1203+ - head 2.2
1204+ - head 2.3
1205+ - head 2.4
1206+
1207+ * .. row body 1 / this is a comment
1208+
1209+ - row 1
1210+ - :rspan:`2` cell 1-3.1
1211+ - cell 1.2
1212+ - cell 1.3
1213+ - cell 1.4
1214+
1215+ * .. Comments and targets are allowed on *table-row* stage.
1216+ .. _`row body 2`:
1217+
1218+ - row 2
1219+ - cell 2.2
1220+ - :rspan:`1` :cspan:`1`
1221+ cell 2.3 with a span over
1222+
1223+ * col 3-4 &
1224+ * row 2-3
1225+
1226+ * - row 3
1227+ - cell 3.2
1228+
1229+ * - row 4
1230+ - cell 4.1
1231+ - cell 4.2
1232+ - cell 4.3
1233+ - cell 4.4
1234+
1235+ * - row 5
1236+ - cell 5.1 with automatic span to rigth end
1237+
1238+ * - row 6
1239+ - cell 6.1
1240+ - ..
1241+
1242+
1243+CSV table
1244+---------
1245+
1246+CSV table might be the choice if you want to include CSV-data from a outstanding
1247+(build) process into your documentation.
1248+
1249+.. code:: reST
1250+
1251+ .. csv-table:: CSV table example
1252+ :header: .. , Column 1, Column 2
1253+ :widths: 2 5 5
1254+ :stub-columns: 1
1255+ :file: csv_table.txt
1256+
1257+Content of file ``csv_table.txt``:
1258+
1259+.. literalinclude:: csv_table.txt
1260+
1261+.. admonition:: CSV table
1262+ :class: rst-example
1263+
1264+ .. csv-table:: CSV table example
1265+ :header: .. , Column 1, Column 2
1266+ :widths: 3 5 5
1267+ :stub-columns: 1
1268+ :file: csv_table.txt
1269+
1270+Templating
1271+==========
1272+
1273+.. sidebar:: Build environment
1274+
1275+ All *generic-doc* tasks are running in the :ref:`build environment <make
1276+ pyenv>`.
1277+
1278+Templating is suitable for documentation which is created generic at the build
1279+time. The sphinx-jinja_ extension evaluates jinja_ templates in the :ref:`build
1280+environment <make pyenv>` (with searx modules installed). We use this e.g. to
1281+build chapter: :ref:`engines generic`. Below the jinja directive from the
1282+:origin:`docs/admin/engines.rst` is shown:
1283+
1284+.. literalinclude:: ../admin/engines.rst
1285+ :language: reST
1286+ :start-after: .. _configured engines:
1287+
1288+The context for the template is selected in the line ``.. jinja:: webapp``. In
1289+sphinx's build configuration (:origin:`docs/conf.py`) the ``webapp`` context
1290+points to the name space of the python module: ``webapp``.
1291+
1292+.. code:: py
1293+
1294+ from searx import webapp
1295+ jinja_contexts = {
1296+ 'webapp': dict(**webapp.__dict__)
1297+ }
1298+
1299+
1300+Tabbed views
1301+============
1302+
1303+.. _sphinx-tabs: https://github.com/djungelorm/sphinx-tabs
1304+.. _basic-tabs: https://github.com/djungelorm/sphinx-tabs#basic-tabs
1305+.. _group-tabs: https://github.com/djungelorm/sphinx-tabs#group-tabs
1306+.. _code-tabs: https://github.com/djungelorm/sphinx-tabs#code-tabs
1307+
1308+With `sphinx-tabs`_ extension we have *tabbed views*. To provide installation
1309+instructions with one tab per distribution we use the `group-tabs`_ directive,
1310+others are basic-tabs_ and code-tabs_. Below a *group-tab* example from
1311+:ref:`docs build` is shown:
1312+
1313+.. literalinclude:: ../admin/buildhosts.rst
1314+ :language: reST
1315+ :start-after: .. _system requirements:
1316+ :end-before: .. _system requirements END:
1317+
1318+
1319+.. _math:
1320+
1321+Math equations
1322+==============
1323+
1324+.. _Mathematics: https://en.wikibooks.org/wiki/LaTeX/Mathematics
1325+.. _amsmath user guide:
1326+ http://vesta.informatik.rwth-aachen.de/ftp/pub/mirror/ctan/macros/latex/required/amsmath/amsldoc.pdf
1327+
1328+.. sidebar:: About LaTeX
1329+
1330+ - `amsmath user guide`_
1331+ - Mathematics_
1332+ - :ref:`docs build`
1333+
1334+The input language for mathematics is LaTeX markup using the :ctan:`amsmath`
1335+package.
1336+
1337+To embed LaTeX markup in reST documents, use role :rst:role:`:math: <math>` for
1338+inline and directive :rst:dir:`.. math:: <math>` for block markup.
1339+
1340+.. code:: reST
1341+
1342+ In :math:numref:`schroedinger general` the time-dependent Schrödinger equation
1343+ is shown.
1344+
1345+ .. math::
1346+ :label: schroedinger general
1347+
1348+ \mathrm{i}\hbar\dfrac{\partial}{\partial t} |\,\psi (t) \rangle =
1349+ \hat{H} |\,\psi (t) \rangle.
1350+
1351+.. admonition:: LaTeX math equation
1352+ :class: rst-example
1353+
1354+ In :math:numref:`schroedinger general` the time-dependent Schrödinger equation
1355+ is shown.
1356+
1357+ .. math::
1358+ :label: schroedinger general
1359+
1360+ \mathrm{i}\hbar\dfrac{\partial}{\partial t} |\,\psi (t) \rangle =
1361+ \hat{H} |\,\psi (t) \rangle.
1362+
1363+
1364+The next example shows the difference of ``\tfrac`` (*textstyle*) and ``\dfrac``
1365+(*displaystyle*) used in a inline markup or another fraction.
1366+
1367+.. code:: reST
1368+
1369+ ``\tfrac`` **inline example** :math:`\tfrac{\tfrac{1}{x}+\tfrac{1}{y}}{y-z}`
1370+ ``\dfrac`` **inline example** :math:`\dfrac{\dfrac{1}{x}+\dfrac{1}{y}}{y-z}`
1371+
1372+.. admonition:: Line spacing
1373+ :class: rst-example
1374+
1375+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
1376+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
1377+ voluptua. ...
1378+ ``\tfrac`` **inline example** :math:`\tfrac{\tfrac{1}{x}+\tfrac{1}{y}}{y-z}`
1379+ At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
1380+ gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
1381+
1382+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
1383+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
1384+ voluptua. ...
1385+ ``\tfrac`` **inline example** :math:`\dfrac{\dfrac{1}{x}+\dfrac{1}{y}}{y-z}`
1386+ At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
1387+ gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
1388+
1389+
1390+.. _KISS: https://en.wikipedia.org/wiki/KISS_principle
1391+
1392+.. _readability: https://docs.python-guide.org/writing/style/
1393+.. _Sphinx-Primer:
1394+ http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
1395+.. _reST: https://docutils.sourceforge.io/rst.html
1396+.. _Sphinx Roles:
1397+ https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html
1398+.. _Sphinx: http://www.sphinx-doc.org
1399+.. _`sphinx-doc FAQ`: http://www.sphinx-doc.org/en/stable/faq.html
1400+.. _Sphinx markup constructs:
1401+ http://www.sphinx-doc.org/en/stable/markup/index.html
1402+.. _`sphinx cross references`:
1403+ http://www.sphinx-doc.org/en/stable/markup/inline.html#cross-referencing-arbitrary-locations
1404+.. _sphinx.ext.extlinks:
1405+ https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
1406+.. _intersphinx: http://www.sphinx-doc.org/en/stable/ext/intersphinx.html
1407+.. _sphinx config: http://www.sphinx-doc.org/en/stable/config.html
1408+.. _Sphinx's autodoc: http://www.sphinx-doc.org/en/stable/ext/autodoc.html
1409+.. _Sphinx's Python domain:
1410+ http://www.sphinx-doc.org/en/stable/domains.html#the-python-domain
1411+.. _Sphinx's C domain:
1412+ http://www.sphinx-doc.org/en/stable/domains.html#cross-referencing-c-constructs
1413+.. _doctree:
1414+ http://www.sphinx-doc.org/en/master/extdev/tutorial.html?highlight=doctree#build-phases
1415+.. _docutils: http://docutils.sourceforge.net/docs/index.html
1416+.. _docutils FAQ: http://docutils.sourceforge.net/FAQ.html
1417+.. _linuxdoc: https://return42.github.io/linuxdoc
1418+.. _jinja: https://jinja.palletsprojects.com/
1419+.. _sphinx-jinja: https://github.com/tardyp/sphinx-jinja
1420+.. _SVG: https://www.w3.org/TR/SVG11/expanded-toc.html
1421+.. _DOT: https://graphviz.gitlab.io/_pages/doc/info/lang.html
1422+.. _`Graphviz's dot`: https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf
1423+.. _Graphviz: https://graphviz.gitlab.io
1424+.. _ImageMagick: https://www.imagemagick.org
1425+
1426+.. _`Emacs Table Mode`: https://www.emacswiki.org/emacs/TableMode
1427+.. _`Online Tables Generator`: http://www.tablesgenerator.com/text_tables
1428+.. _`OASIS XML Exchange Table Model`: https://www.oasis-open.org/specs/tm9901.html
docs/dev/search_api.rstnew file+120
@@ -0,0 +1,120 @@
1+.. _search API:
2+
3+==========
4+Search API
5+==========
6+
7+The search supports both ``GET`` and ``POST``.
8+
9+Furthermore, two enpoints ``/`` and ``/search`` are available for querying.
10+
11+
12+``GET /``
13+
14+``GET /search``
15+
16+Parameters
17+==========
18+
19+.. sidebar:: Further reading ..
20+
21+ - :ref:`engines-dev`
22+ - :ref:`settings.yml`
23+ - :ref:`engines generic`
24+
25+``q`` : required
26+ The search query. This string is passed to external search services. Thus,
27+ searx supports syntax of each search service. For example, ``site:github.com
28+ searx`` is a valid query for Google. However, if simply the query above is
29+ passed to any search engine which does not filter its results based on this
30+ syntax, you might not get the results you wanted.
31+
32+ See more at :ref:`search-syntax`
33+
34+``categories`` : optional
35+ Comma separated list, specifies the active search categories
36+
37+``engines`` : optional
38+ Comma separated list, specifies the active search engines.
39+
40+``lang`` : default ``all``
41+ Code of the language.
42+
43+``pageno`` : default ``1``
44+ Search page number.
45+
46+``time_range`` : optional
47+ [ ``day``, ``month``, ``year`` ]
48+
49+ Time range of search for engines which support it. See if an engine supports
50+ time range search in the preferences page of an instance.
51+
52+``format`` : optional
53+ [ ``json``, ``csv``, ``rss`` ]
54+
55+ Output format of results.
56+
57+``results_on_new_tab`` : default ``0``
58+ [ ``0``, ``1`` ]
59+
60+ Open search results on new tab.
61+
62+``image_proxy`` : default ``False``
63+ [ ``True``, ``False`` ]
64+
65+ Proxy image results through searx.
66+
67+``autocomplete`` : default *empty*
68+ [ ``google``, ``dbpedia``, ``duckduckgo``, ``startpage``, ``wikipedia`` ]
69+
70+ Service which completes words as you type.
71+
72+``safesearch`` : default ``None``
73+ [ ``0``, ``1``, ``None`` ]
74+
75+ Filter search results of engines which support safe search. See if an engine
76+ supports safe search in the preferences page of an instance.
77+
78+``theme`` : default ``oscar``
79+ [ ``oscar``, ``simple``, ``legacy``, ``pix-art``, ``courgette`` ]
80+
81+ Theme of instance.
82+
83+ Please note, available themes depend on an instance. It is possible that an
84+ instance administrator deleted, created or renamed themes on his/her instance.
85+ See the available options in the preferences page of the instance.
86+
87+``oscar-style`` : default ``logicodev``
88+ [ ``pointhi``, ``logicodev`` ]
89+
90+ Style of Oscar theme. It is only parsed if the theme of an instance is
91+ ``oscar``.
92+
93+ Please note, available styles depend on an instance. It is possible that an
94+ instance administrator deleted, created or renamed styles on his/her
95+ instance. See the available options in the preferences page of the instance.
96+
97+``enabled_plugins`` : optional
98+ List of enabled plugins.
99+
100+ :default: ``HTTPS_rewrite``, ``Self_Informations``,
101+ ``Search_on_category_select``, ``Tracker_URL_remover``
102+
103+ :values: [ ``DOAI_rewrite``, ``HTTPS_rewrite``, ``Infinite_scroll``,
104+ ``Vim-like_hotkeys``, ``Self_Informations``, ``Tracker_URL_remover``,
105+ ``Search_on_category_select`` ]
106+
107+``disabled_plugins``: optional
108+ List of disabled plugins.
109+
110+ :default: ``DOAI_rewrite``, ``Infinite_scroll``, ``Vim-like_hotkeys``
111+ :values: ``DOAI_rewrite``, ``HTTPS_rewrite``, ``Infinite_scroll``,
112+ ``Vim-like_hotkeys``, ``Self_Informations``, ``Tracker_URL_remover``,
113+ ``Search_on_category_select``
114+
115+``enabled_engines`` : optional : *all* :origin:`engines <searx/engines>`
116+ List of enabled engines.
117+
118+``disabled_engines`` : optional : *all* :origin:`engines <searx/engines>`
119+ List of disabled engines.
120+
docs/dev/svg_image.svgnew file+10
@@ -0,0 +1,10 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<!-- originate: https://commons.wikimedia.org/wiki/File:Variable_Resistor.svg -->
3+<svg xmlns="http://www.w3.org/2000/svg"
4+ version="1.1" baseProfile="full"
5+ width="70px" height="40px" viewBox="0 0 700 400">
6+ <line x1="0" y1="200" x2="700" y2="200" stroke="black" stroke-width="20px"/>
7+ <rect x="100" y="100" width="500" height="200" fill="white" stroke="black" stroke-width="20px"/>
8+ <line x1="180" y1="370" x2="500" y2="50" stroke="black" stroke-width="15px"/>
9+ <polygon points="585 0 525 25 585 50" transform="rotate(135 525 25)"/>
10+</svg>
docs/dev/translation.rstnew file+71
@@ -0,0 +1,71 @@
1+.. _translation:
2+
3+===========
4+Translation
5+===========
6+
7+.. _searx@transifex: https://www.transifex.com/asciimoo/searx/
8+
9+Translation currently takes place on `searx@transifex`_
10+
11+Requirements
12+============
13+
14+* Transifex account
15+* Installed CLI tool of Transifex
16+
17+Init Transifex project
18+======================
19+
20+After installing ``transifex`` using pip, run the following command to
21+initialize the project.
22+
23+.. code:: sh
24+
25+ tx init # Transifex instance: https://www.transifex.com/asciimoo/searx/
26+
27+
28+After ``$HOME/.transifexrc`` is created, get a Transifex API key and insert it
29+into the configuration file.
30+
31+Create a configuration file for ``tx`` named ``$HOME/.tx/config``.
32+
33+.. code:: ini
34+
35+ [main]
36+ host = https://www.transifex.com
37+ [searx.messagespo]
38+ file_filter = searx/translations/<lang>/LC_MESSAGES/messages.po
39+ source_file = messages.pot
40+ source_lang = en
41+ type = PO
42+
43+
44+Then run ``tx set``:
45+
46+.. code:: shell
47+
48+ tx set --auto-local -r searx.messagespo 'searx/translations/<lang>/LC_MESSAGES/messages.po' \
49+ --source-lang en --type PO --source-file messages.pot --execute
50+
51+
52+Update translations
53+===================
54+
55+To retrieve the latest translations, pull it from Transifex.
56+
57+.. code:: sh
58+
59+ tx pull -a
60+
61+Then check the new languages. If strings translated are not enough, delete those
62+folders, because those should not be compiled. Call the command below to compile
63+the ``.po`` files.
64+
65+.. code:: shell
66+
67+ pybabel compile -d searx/translations
68+
69+
70+After the compilation is finished commit the ``.po`` and ``.mo`` files and
71+create a PR.
docs/index.rstnew file+32
@@ -0,0 +1,32 @@
1+================
2+Welcome to searx
3+================
4+
5+Search without being tracked.
6+
7+.. sidebar:: Features
8+
9+ - Self hosted
10+ - No user tracking
11+ - No user profiling
12+ - About 70 supported search engines
13+ - Easy integration with any search engine
14+ - Cookies are not used by default
15+ - Secure, encrypted connections (HTTPS/SSL)
16+ - Hosted by organizations, such as *La Quadrature du Net*, which promote
17+ digital rights
18+
19+Searx is a free internet metasearch engine which aggregates results from more
20+than 70 search services. Users are neither tracked nor profiled. Additionally,
21+searx can be used over Tor for online anonymity.
22+
23+Get started with searx by using one of the :wiki:`Searx-instances`. If you
24+don't trust anyone, you can set up your own, see :ref:`installation`.
25+
26+.. toctree::
27+ :maxdepth: 2
28+
29+ user/index
30+ admin/index
31+ dev/index
32+ blog/index
docs/static/img/searx_logo_small.pngnew file

Binary file not shown.

docs/user/index.rstnew file+10
@@ -0,0 +1,10 @@
1+==================
2+User documentation
3+==================
4+
5+.. toctree::
6+ :maxdepth: 1
7+
8+ public_instances
9+ search_syntax
10+ own-instance
docs/user/own-instance.rstnew file+77
@@ -0,0 +1,77 @@
1+===========================
2+Why use a private instance?
3+===========================
4+
5+"Is it worth to run my own instance?" is a common question among searx users.
6+Before answering this question, see what options a searx user has.
7+
8+Public instances are open to everyone who has access to its URL. Usually, these
9+are operated by unknown parties (from the users' point of view). Private
10+instances can be used by a select group of people. It is for example a searx of
11+group of friends or a company which can be accessed through VPN. Also it can be
12+single user one which runs on the user's laptop.
13+
14+To gain more insight on how these instances work let's dive into how searx
15+protects its users.
16+
17+How does searx protect privacy?
18+===============================
19+
20+Searx protects the privacy of its users in multiple ways regardless of the type
21+of the instance (private, public). Removal of private data from search requests
22+comes in three forms:
23+
24+ 1. removal of private data from requests going to search services
25+ 2. not forwarding anything from a third party services through search services
26+ (e.g. advertisement)
27+ 3. removal of private data from requests going to the result pages
28+
29+Removing private data means not sending cookies to external search engines and
30+generating a random browser profile for every request. Thus, it does not matter
31+if a public or private instance handles the request, because it is anonymized in
32+both cases. IP addresses will be the IP of the instance. But searx can be
33+configured to use proxy or Tor. `Result proxy
34+<https://github.com/asciimoo/morty>`__ is supported, too.
35+
36+Searx does not serve ads or tracking content unlike most search services. So
37+private data is not forwarded to third parties who might monetize it. Besides
38+protecting users from search services, both referring page and search query are
39+hidden from visited result pages.
40+
41+
42+What are the consequences of using public instances?
43+----------------------------------------------------
44+
45+If someone uses a public instance, he/she has to trust the administrator of that
46+instance. This means that the user of the public instance does not know whether
47+his/her requests are logged, aggregated and sent or sold to a third party.
48+
49+Also, public instances without proper protection are more vulnerable to abusing
50+the search service, In this case the external service in exchange returns
51+CAPTCHAs or bans the IP of the instance. Thus, search requests return less
52+results.
53+
54+I see. What about private instances?
55+------------------------------------
56+
57+If users run their own instances, everything is in their control: the source
58+code, logging settings and private data. Unknown instance administrators do not
59+have to be trusted.
60+
61+Furthermore, as the default settings of their instance is editable, there is no
62+need to use cookies to tailor searx to their needs. So preferences will not be
63+reset to defaults when clearing browser cookies. As settings are stored on
64+their computer, it will not be accessible to others as long as their computer is
65+not compromised.
66+
67+Conclusion
68+==========
69+
70+Always use an instance which is operated by people you trust. The privacy
71+features of searx are available to users no matter what kind of instance they
72+use.
73+
74+If someone is on the go or just wants to try searx for the first time public
75+instances are the best choices. Additionally, public instance are making a
76+world a better place, because those who cannot or do not want to run an
77+instance, have access to a privacy respecting search service.
docs/user/public_instances.rstnew file+318
@@ -0,0 +1,318 @@
1+.. _public instances:
2+
3+..
4+ links has been ported from markdown to reST by::
5+
6+ regexpr: \[([^\]]*)\]\(([^)]*)\)
7+ substitution: `\1 <\2>`__
8+
9+
10+======================
11+Public Searx instances
12+======================
13+
14+.. _mailing list: mailto:searx-instances@autistici.org
15+.. _subscription page: https://www.autistici.org/mailman/listinfo/searx-instances
16+
17+
18+Useful information
19+==================
20+
21+* Up-to-date health report available on https://stats.searx.xyz [1]_, for onion
22+ (tor) services: https://stats.searx.xyz/tor.html
23+
24+* Searx instances `mailing list`_ & `subscription page`_.
25+
26+* Some of the Searx instances have a CAcert SSL certificate. You can install the
27+ missing root cert `from here <http://www.cacert.org/index.php?id=3>`__.
28+
29+* To add your own Searx instance to this page send us your PR. A GitHub account
30+ is required to send PR or add an issue.
31+
32+.. [1] Note that most of the instances with a A+ grade in CSP column in this
33+ site are not fully functional - for example auto-completion may not work.
34+
35+
36+List of public Searx instances
37+==============================
38+
39+Meta-searx instances
40+====================
41+
42+These are websites that source from other searx instances. These are useful if
43+you can't decide which Searx instance to use:
44+
45+
46+.. flat-table:: Meta-searx instances
47+ :header-rows: 1
48+ :stub-columns: 0
49+ :widths: 2 1 2 4 4
50+
51+ * - clearnet host
52+ - onion host
53+ - issuer
54+ - source selection method
55+ - extra privacy features
56+
57+ * - `Neocities <https://searx.neocities.org/>`__
58+ - n/a
59+ - Comodo (`Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.neocities.org>`__)
60+ - Redirects users directly to a random selection of any known running
61+ server after entering query. Requires
62+ Javascript. `Changelog <https://searx.neocities.org/changelog.html>`__.
63+ - Excludes servers with user tracking and analytics or are proxied through
64+ Cloudflare.
65+
66+ * - `Searxes <https://searxes.danwin1210.me/>`__ @Danwin
67+ - onion v3 `hidden service
68+ <http://searxes.nmqnkngye4ct7bgss4bmv5ca3wpa55yugvxen5kz2bbq67lwy6ps54yd.onion/>`__
69+ - Let's Encrypt (`Verification
70+ <https://www.ssllabs.com/ssltest/analyze.html?d=searxes.danwin1210.me>`__)
71+ - sources data from a randomly selected running server that satisfies
72+ admin's quality standards which is used for post-processing
73+ - filters out privacy-hostile websites (like CloudFlare) and either marks
74+ them as such or folds them below the high ranking results.
75+
76+
77+Alive and running
78+=================
79+
80+**BEFORE EDITING**: Please add your Searx instance by respecting the alphabetic order.
81+
82+.. note::
83+
84+ Public instances listed here may yield less accurate results as they have
85+ much higher traffic and consequently have a higher chance of being blocked by
86+ search providers such as Google, Qwant, Bing, Startpage, etc. Hosting your
87+ own instance or using an instance that isn't listed here may give you a more
88+ consistent search experience.
89+
90+* `ai.deafpray.wtf/searx <https://ai.deafpray.wtf/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=ai.deafpray.wtf/searx>`__
91+* `bamboozle.it <https://bamboozle.it/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=bamboozle.it>`__
92+* `bee.jaekr.dev <https://bee.jaekr.dev>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=bee.jaekr.dev>`__
93+* `beezboo.com <https://beezboo.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=beezboo.com>`__
94+* `burtrum.org/searx <https://burtrum.org/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=burtrum.org/searx>`__
95+* `darmarit.cloud/searx <https://darmarit.cloud/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=darmarit.cloud/searx>`__
96+* `dc.ax <https://dc.ax>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=dc.ax>`__
97+* `dynabyte.ca <https://dynabyte.ca>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=dynabyte.ca>`__
98+* `goso.ga <https://goso.ga/search>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=goso.ga>`__
99+* `gruble.de <https://www.gruble.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=www.gruble.de>`__
100+* `haku.ahmia.fi <https://haku.ahmia.fi/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=haku.ahmia.fi&latest>`__
101+* `haku.lelux.fi <https://haku.lelux.fi/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=haku.lelux.fi>`__
102+* `huyo.me <https://huyo.me/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=huyo.me>`__
103+* `jsearch.pw <https://jsearch.pw>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=jsearch.pw>`__
104+* `le-dahut.com/searx <https://le-dahut.com/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=le-dahut.com/searx>`__
105+* `mijisou.com <https://mijisou.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=mijisou.com>`__
106+* `null.media <https://null.media>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=null.media>`__
107+* `openworlds.info <https://openworlds.info/>`__ - Issuer: Let's Encrypt
108+* `perfectpixel.de/searx/ <https://www.perfectpixel.de/searx/>`__ - Issuer: LetsEncrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=www.perfectpixel.de>`__
109+* `ransack.i2p <http://ransack.i2p/>`__ - I2P eepsite, only accessible with `I2P <https://geti2p.net/>`__ (`base32 address <http://mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq.b32.i2p>`__)
110+* `rapu.nz <https://rapu.nz/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=rapu.nz>`__
111+* `roflcopter.fr <https://wtf.roflcopter.fr/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=wtf.roflcopter.fr>`__
112+* `roteserver.de/searx <https://roteserver.de/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=roteserver.de>`__
113+* `s.cmd.gg <https://s.cmd.gg>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=s.cmd.gg>`__
114+* `search.activemail.de <https://search.activemail.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.activemail.de&latest>`__
115+* `search.anonymize.com <https://search.anonymize.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.anonymize.com>`__
116+* `search.azkware.net <https://search.azkware.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.azkware.net>`__
117+* `search.biboumail.fr <https://search.biboumail.fr/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.biboumail.fr>`__
118+* `search.blankenberg.eu <https://search.blankenberg.eu>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.blankenberg.eu>`__
119+* `search.d4networks.com <https://search.d4networks.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.d4networks.com>`__
120+* `search.datensturm.net <https://search.datensturm.net>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.datensturm.net>`__
121+* `search.disroot.org <https://search.disroot.org/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.disroot.org>`__
122+* `search.ethibox.fr <https://search.ethibox.fr>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.ethibox.fr>`__
123+* `search.fossdaily.xyz <https://search.fossdaily.xyz>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.fossdaily.xyz>`__
124+* `search.galaxy.cat <https://search.galaxy.cat>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.galaxy.cat>`__
125+* `search.gibberfish.org <https://search.gibberfish.org/>`__ (as `Hidden Service <http://o2jdk5mdsijm2b7l.onion/>`__ or `Proxied through Tor <https://search.gibberfish.org/tor/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.gibberfish.org>`__
126+* `search.koehn.com <https://search.koehn.com>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.koehn.com>`__
127+* `search.lgbtq.cool <https://search.lgbtq.cool/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.lgbtq.cool>`__
128+* `search.mdosch.de <https://search.mdosch.de/>`__ (as `Hidden Service <http://search.4bkxscubgtxwvhpe.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.mdosch.de>`__
129+* `search.modalogi.com <https://search.modalogi.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.modalogi.com&latest>`__
130+* `search.moravit.com <https://search.moravit.com>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.moravit.com>`__
131+* `search.nebulacentre.net <https://search.nebulacentre.net>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.nebulacentre.net>`__
132+* `search.paulla.asso.fr <https://search.paulla.asso.fr/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.paulla.asso.fr>`__
133+* `search.pifferi.info <https://search.pifferi.info/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.pifferi.info&latest>`__
134+* `search.poal.co <https://search.poal.co/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.poal.co>`__
135+* `search.privacytools.io <https://search.privacytools.io/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.privacytools.io>`__ - Uses Matomo for user tracking and analytics
136+* `search.seds.nl <https://search.seds.nl/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.seds.nl&latest>`__
137+* `search.snopyta.org <https://search.snopyta.org/>`__ (as `Hidden Service <http://juy4e6eicawzdrz7.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.snopyta.org>`__
138+* `search.spaeth.me <https://search.spaeth.me/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.spaeth.me&latest>`__
139+* `search.st8.at <https://search.st8.at/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.st8.at>`__
140+* `search.stinpriza.org <https://search.stinpriza.org>`__ (as `Hidden Service <http://z5vawdol25vrmorm4yydmohsd4u6rdoj2sylvoi3e3nqvxkvpqul7bqd.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.stinpriza.org&hideResults=on>`__
141+* `search.sudo-i.net <https://search.sudo-i.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.sudo-i.net>`__
142+* `search.tolstoevsky.ml <https://search.tolstoevsky.ml>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.tolstoevsky.ml>`__
143+* `searchsin.com/searx <https://searchsin.com/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searchsin.com/searx>`__
144+* `searx.anongoth.pl <https://searx.anongoth.pl>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.anongoth.pl&latest>`__
145+* `searx.be <https://searx.be>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.be>`__
146+* `searx.ca <https://searx.ca/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.ca>`__
147+* `searx.canox.net <https://searx.canox.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.canox.net>`__
148+* `searx.cybt.de <https://searx.cybt.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.cybt.de>`__
149+* `searx.de <https://www.searx.de/>`__ - Issuer: COMODO `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.de>`__
150+* `searx.decatec.de <https://searx.decatec.de>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.decatec.de>`__
151+* `searx.devol.it <https://searx.devol.it/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=sears.devol.it>`__
152+* `searx.dnswarden.com <https://searx.dnswarden.com>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.dnswarden.com>`__
153+* `searx.drakonix.net <https://searx.drakonix.net/>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.drakonix.net>`__
154+* `searx.dresden.network <https://searx.dresden.network/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.dresden.network>`__
155+* `searx.elukerio.org <https://searx.elukerio.org/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.elukerio.org/>`__
156+* `searx.everdot.org <https://searx.everdot.org/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.everdot.org/>`__ - Crawls using YaCy
157+* `searx.foo.li <https://searx.foo.li>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.foo.li&hideResults=on>`__
158+* `searx.fossencdi.org <https://searx.fossencdi.org>`__ (as `Hidden Service <http://searx.cwuzdtzlubq5uual.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.fossencdi.org>`__
159+* `searx.fr32k.de <https://searx.fr32k.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.fr32k.de>`__
160+* `searx.good.one.pl <https://searx.good.one.pl>`__ (as `Hidden Service <http://searxl7u2y6gvonm.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.good.one.pl>`__
161+* `searx.gotrust.de <https://searx.gotrust.de/>`__ (as `Hidden Service <http://nxhhwbbxc4khvvlw.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.gotrust.de>`__
162+* `searx.hardwired.link <https://searx.hardwired.link/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.hardwired.link>`__
163+* `searx.hlfh.space <https://searx.hlfh.space>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.hlfh.space>`__
164+* `searx.info <https://searx.info>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.info>`__
165+* `searx.itunix.eu <https://searx.itunix.eu/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.itunix.eu>`__
166+* `searx.john-at-me.net <https://searx.john-at-me.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.john-at-me.net>`__
167+* `searx.kvch.me <https://searx.kvch.me>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.kvch.me>`__
168+* `searx.laquadrature.net <https://searx.laquadrature.net>`__ (as `Hidden Service <http://searchb5a7tmimez.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.laquadrature.net>`__
169+* `searx.lelux.fi <https://searx.lelux.fi/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=haku.lelux.fi>`__
170+* `searx.lhorn.de <https://searx.lhorn.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.lhorn.de&latest>`__
171+* `searx.li <https://searx.li/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.li>`__
172+* `searx.libmail.eu <https://searx.libmail.eu/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.libmail.eu/>`__
173+* `searx.linux.pizza <https://searx.linux.pizza>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.linux.pizza>`__
174+* `searx.lynnesbian.space <https://searx.lynnesbian.space/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.lynnesbian.space>`__
175+* `searx.mastodontech.de <https://searx.mastodontech.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.mastodontech.de>`__
176+* `searx.me <https://searx.me>`__ (as `Hidden Service <http://ulrn6sryqaifefld.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.me>`__
177+* `searx.mxchange.org <https://searx.mxchange.org/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.mxchange.org>`__
178+* `searx.nakhan.net <https://searx.nakhan.net>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.nakhan.net>`__
179+* `searx.nixnet.xyz <https://searx.nixnet.xyz>`__ (as `Hidden Service <http://searx.l4qlywnpwqsluw65ts7md3khrivpirse744un3x7mlskqauz5pyuzgqd.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.nixnet.xyz>`__
180+* `searx.nnto.net <https://searx.nnto.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.nnto.net>`__
181+* `searx.openhoofd.nl <https://searx.openhoofd.nl/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=openhoofd.nl>`__
182+* `searx.openpandora.org <https://searx.openpandora.org/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.openpandora.org&latest>`__
183+* `searx.operationtulip.com <https://searx.operationtulip.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.operationtulip.com>`__
184+* `searx.orcadian.net <https://searx.orcadian.net/>`__ - Issuer: Comodo CA Limited `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.orcadian.net>`__
185+* `searx.ouahpit.info <https://searx.ouahpiti.info/>`__ - Issuer: Let's Encrypt
186+* `searx.pofilo.fr <https://searx.pofilo.fr/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.pofilo.fr>`__
187+* `searx.prvcy.eu <https://searx.prvcy.eu/>`__ (as `Hidden Service <http://hmfztxt3pfhevucl.onion/>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.prvcy.eu>`__
188+* `searx.pwoss.org <https://searx.pwoss.org>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.pwoss.org>`__
189+* `searx.ro <https://searx.ro/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.ro>`__
190+* `searx.ru <https://searx.ru/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.ru>`__
191+* `searx.solusar.de <https://searx.solusar.de/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.solusar.de>`__
192+* `searx.targaryen.house <https://searx.targaryen.house/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.targaryen.house>`__
193+* `searx.tuxcloud.net <https://searx.tuxcloud.net>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.tuxcloud.net>`__
194+* `searx.tyil.nl <https://searx.tyil.nl>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.tyil.nl>`__
195+* `searx.wegeeks.win <https://searx.wegeeks.win>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.wegeeks.win>`__
196+* `searx.win <https://searx.win/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.win&latest>`__
197+* `searx.xyz <https://searx.xyz/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.xyz&latest>`__
198+* `searx.zareldyn.net <https://searx.zareldyn.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.zareldyn.net>`__
199+* `searx.zdechov.net <https://searx.zdechov.net>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.zdechov.net>`__
200+* `searxs.eu <https://www.searxs.eu>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=www.searxs.eu&hideResults=on>`__
201+* `seeks.hsbp.org <https://seeks.hsbp.org/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=seeks.hsbp.org>`__ - `PGP signed fingerprints of cert <https://seeks.hsbp.org/cert>`__
202+* `skyn3t.in/srx <https://skyn3t.in/srx/>`__ - Issuer: Let's Encrypt | onion `hidden service <http://skyn3tb3bas655mw.onion/srx/>`__
203+* `spot.ecloud.global <https://spot.ecloud.global/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=spot.ecloud.global>`__
204+* `srx.sx <https://srx.sx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=srx.sx>`__
205+* `stemy.me/searx <https://stemy.me/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=stemy.me>`__
206+* `suche.dasnetzundich.de <https://suche.dasnetzundich.de>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=suche.dasnetzundich.de>`__
207+* `suche.elaon.de <https://suche.elaon.de>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=suche.elaon.de>`__
208+* `suche.xyzco456vwisukfg.onion <http://suche.xyzco456vwisukfg.onion/>`__
209+* `suche.uferwerk.org <https://suche.uferwerk.org>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=suche.uferwerk.org>`__
210+* `timdor.noip.me/searx <https://timdor.noip.me/searx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=timdor.noip.me/searx>`__
211+* `trovu.komun.org <https://trovu.komun.org>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=trovu.komun.org>`__
212+* `unmonito.red <https://unmonito.red/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=unmonito.red>`__
213+* `www.finden.tk <https://www.finden.tk/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=www.finden.tk>`__
214+* `zoek.anchel.nl <https://zoek.anchel.nl/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=zoek.anchel.nl>`__
215+
216+
217+
218+Running in exclusive private walled-gardens
219+===========================================
220+
221+These instances run in walled-gardens that exclude some segment of the general
222+public (e.g. Tor users and users sharing IPs with many other users). Caution:
223+privacy is also compromised on these sites due to exposure of cleartext traffic
224+to a third party other than the website operator.
225+
226+* `intelme.com <https://intelme.com>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=intelme.com>`__
227+* `search404.io <https://www.search404.io/>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search404.io>`__
228+* `searx.com.au <https://searx.com.au/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.com.au>`__
229+* `searx.lavatech.top <https://searx.lavatech.top/>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.lavatech.top>`__
230+* `searchx.mobi <https://searchx.mobi/>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searchx.mobi>`__
231+* `searx.org <https://searx.org/>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.org>`__
232+* `searx.run <https://searx.run/>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.run>`__
233+* `searx.world <https://searx.world>`__ - Issuer: Cloudflare `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.world>`__ - Adds Amazon affiliate links
234+
235+
236+Running with an incorrect SSL certificate
237+=========================================
238+
239+* `listi.me <https://listi.me/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=listi.me&latest>`__
240+* `s.matejc.com <https://s.matejc.com/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=s.matejc.com>`__
241+* `search.jollausers.de <https://search.jollausers.de>`__ - Incorrectly configured `SSL certificate <https://www.ssllabs.com/ssltest/analyze.html?d=search.jollausers.de>`__
242+* `search.paviro.de <https://search.paviro.de>`__ - Issuer: LetsEncrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.paviro.de>`__
243+* `searx.abenthung.it <https://searx.abenthung.it/>`__ - Issuer: Comodo CA Limited `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.abenthung.it>`__
244+* `searx.coding4schoki.org <https://searx.coding4schoki.org/>`__ - Incorrectly configured `SSL Certificate <https://www.ssllabs.com/ssltest/analyze.html?d=searx.coding4schoki.org>`__
245+* `searx.haxors.club <https://searx.haxors.club/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.haxors.club>`__
246+* `searx.nulltime.net <https://searx.nulltime.net/>`__ (as `Hidden Service <http://searx7gwtu5rh6wr.onion>`__) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.nulltime.net>`__
247+* `searx.ch <https://searx.ch/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.ch>`__ (cert clock problems)
248+
249+
250+Offline
251+=======
252+
253+* `a.searx.space <https://a.searx.space>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=a.searx.space>`__ (unstable, under construction).
254+* `anyonething.de <https://anyonething.de>`__ - (was found to have become a pastebin on or before 2019-03-01) Issuer: Comodo CA Limited (Warning: uses Cloudflare) `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=anyonething.de>`__
255+* `h7jwxg5rakyfvikpi.onion <http://7jwxg5rakyfvikpi.onion/>`__ - available only as Tor Hidden Service (down on 2019-06-26)
256+* `hacktivis.me/searx <https://hacktivis.me/searx>`__ - (down) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=hacktivis.me/searx>`__
257+* `icebal.com <https://icebal.com>`__ - (down) Issuer: Let's Encrypt
258+* `netrangler.host <https://netrangler.host>`__ - (down) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=netrangler.host>`__
259+* `opengo.nl <https://www.opengo.nl>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=www.opengo.nl>`__
260+* `p9e.de <https://p9e.de/>`__ - (down - timeout) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=p9e.de>`__
261+* `rubri.co <https://rubri.co>`__ - (down) Issuer: Let's Encrypt
262+* `s.bacafe.xyz <https://s.bacafe.xyz/>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=s.bacafe.xyz&latest>`__
263+* `search.alecpap.com <https://search.alecpap.com/>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.alecpap.com>`__
264+* `search.blackit.de <https://search.blackit.de/>`__ - (down) Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.blackit.de>`__
265+* `search.deblan.org <https://search.deblan.org/>`__ (down) - Issuer: COMODO via GANDI `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.deblan.org>`__
266+* `search.homecomputing.fr <https://search.homecomputing.fr/>`__ - (down) Issuer: CAcert `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.homecomputing.fr>`__
267+* `search.jpope.org <https://search.jpope.org>`__ - (down - timeout) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.jpope.org>`__
268+* `search.kakise.xyz <https://search.kakise.xyz/>`__ - down
269+* `search.kosebamse.com <https://search.kosebamse.com>`__ - Issuer: LetsEncrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.kosebamse.com>`__
270+* `search.kujiu.org <https://search.kujiu.org>`__ - (down) Issuer: Let's Encrypt
271+* `search.mailaender.coffee <https://search.mailaender.coffee/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.mailaender.coffee>`__
272+* `search.matrix.ac <https://search.matrix.ac>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=matrix.ac>`__
273+* `search.mypsc.ca <https://search.mypsc.ca/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.mypsc.ca>`__
274+* `search.namedkitten.pw <https://search.namedkitten.pw>`__ - (SSL error) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.namedkitten.pw>`__
275+* `search.opentunisia.org <https://search.opentunisia.org>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.opentunisia.org>`__
276+* `search.r3d007.com <https://search.r3d007.com/>`__ - (down) Issuer: Let's Encrypt
277+* `search.static.lu <https://search.static.lu/>`__ - (down) Issuer: StartCom `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.static.lu>`__
278+* `search.teej.xyz <https://search.teej.xyz>`__ - (down) Issuer: LetsEncrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.teej.xyz>`__
279+* `search.wxzm.sx <https://search.wxzm.sx>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=search.wxzm.sx>`__
280+* `searx.4ray.co <https://searx.4ray.co/>`__ - (no longer an instance, redirects to main page) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.4ray.co>`__
281+* `searx.32bitflo.at <https://searx.32bitflo.at/>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.32bitflo.at>`__
282+* `searx.ahh.si <https://searx.ahh.si/>`__ - (down) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.ahh.si>`__
283+* `searx.angristan.xyz <https://searx.angristan.xyz/>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.angristan.xyz>`__
284+* `searx.antirep.net <https://searx.antirep.net/>`__ - (return a 502 HTTP error) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.antirep.net>`__
285+* `searx.aquilenet.fr <https://searx.aquilenet.fr/>`__ - (down - 429 HTTP error) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.aquilenet.fr>`__
286+* `searx.at <https://searx.at/>`__ - (return "request exception" at every search) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.at>`__
287+* `searx.cc <https://searx.cc/>`__ - (down on 2019-06-26) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.cc>`__
288+* `searx.dk <https://searx.dk/>`__ - (down - 429 HTTP error) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.dk>`__
289+* `searx.ehrmanns.ch <https://searx.ehrmanns.ch>`__ - (down) Issuer: Let's Encrypt
290+* `searx.glibre.net <https://searx.glibre.net>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.glibre.net>`__
291+* `searx.infini.fr <https://searx.infini.fr>`__ - (return a page stating that the website is not installed) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.infini.fr>`__
292+* `searx.jeanphilippemorvan.info <https://searx.jeanphilippemorvan.info/>`__ - (down) Issuer: StartCom `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.jeanphilippemorvan.info>`__
293+* `searx.lhorn.de <https://searx.lhorn.de/>`__ - (redirect the Searx's github repository page) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.lhorn.de&latest>`__ (only reachable from european countries)
294+* `searx.lvweb.host <https://searx.lvweb.host>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.lvweb.host>`__
295+* `searx.mrtino.eu <https://searx.mrtino.eu>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.mrtino.eu>`__
296+* `searx.netzspielplatz.de <https://searx.netzspielplatz.de/>`__ - (error page about GDPR even when browsing it from USA and Asia) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.netzspielplatz.de>`__
297+* `searx.new-admin.net <https://searx.new-admin.net>`__ - (down) Issuer: Let's Encrypt
298+* `searx.nogafa.org <https://searx.nogafa.org/>`__ - (broken CSS) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.nogafa.org>`__
299+* `searx.potato.hu <https://searx.potato.hu>`__ - (not a searx instance) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.potato.hu>`__
300+* `searx.rubbeldiekatz.info <https://searx.rubbeldiekatz.info/>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.rubbeldiekatz.info/>`__
301+* `searx.s42.space <https://searx.s42.space>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.s42.space>`__
302+* `searx.salcay.hu <https://searx.salcay.hu/>`__ - (down - blank page) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.salcay.hu>`__
303+* `searx.selea.se <https://searx.selea.se>`__ - (Leads to default Apache page) Issuer: RapidSSL (HSTS preloaded, DNSSEC) `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.selea.se>`__ | `HSTS Preload <https://hstspreload.org/?domain=searx.selea.se>`__
304+* `searx.steinscraft.net <https://searx.steinscraft.net/>`__ - (down) Issuer: Cloudflare
305+* `searx.techregion.de <https://searx.techregion.de/>`__ - (domain expired) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.techregion.de>`__
306+* `searx.tognella.com <https://searx.tognella.com/>`__ - (down) Issuer: Cloudflare
307+* `searx.xi.ht <https://searx.xi.ht/>`__ - (return a 502 HTTP error) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searx.xi.ht>`__
308+* `searxist.com <https://searxist.com/>`__ - (down) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=searxist.com>`__
309+* `so.sb <https://so.sb/>`__ - (down) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=so.sb>`__
310+* `srx.stdout.net <https://srx.stdout.net/>`__ - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=srx.stdout.net>`__
311+* `w6f7cgdm54cyvohcuhraaafhajctyj3ihenrovuxogoagrr5g43qmoid.onion <http://w6f7cgdm54cyvohcuhraaafhajctyj3ihenrovuxogoagrr5g43qmoid.onion/>`__ - Hidden Service
312+* `win8linux.nohost.me <https://win8linux.nohost.me/searx/>`__ - (down) Issuer: Let's Encrypt
313+* `wiznet.tech <https://wiznet.tech>`__ - (down) - Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=wiznet.tech>`__
314+* `www.mercurius.space <https://www.mercurius.space/>`__ - (down) Issuer: Let's Encrypt
315+* `www.ready.pm <https://www.ready.pm>`__ - Issuer: WoSign `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=www.ready.pm>`__
316+* `z.awsmppl.com <https://z.awsmppl.com>`__ - (down) Issuer: Let's Encrypt `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=z.awsmppl.com>`__
317+* `zlsdzh.tk <https://zlsdzh.tk>`__ - (down - 404 HTTP error) Issuer: TrustAsia Technologies, Inc. `Verification <https://www.ssllabs.com/ssltest/analyze.html?d=zlsdzh.tk>`__ *
318+
docs/user/search_syntax.rstnew file+42
@@ -0,0 +1,42 @@
1+
2+.. _search-syntax:
3+
4+=============
5+Search syntax
6+=============
7+
8+Searx allows you to modify the default categories, engines and search language
9+via the search query.
10+
11+Prefix ``!``
12+ to set Category/engine
13+
14+Prefix: ``:``
15+ to set language
16+
17+Prefix: ``?``
18+ to add engines and categories to the currently selected categories
19+
20+Abbrevations of the engines and languages are also accepted. Engine/category
21+modifiers are chainable and inclusive (e.g. with :search:`!it !ddg !wp qwer
22+<?q=%21it%20%21ddg%20%21wp%20qwer>` search in IT category **and** duckduckgo
23+**and** wikipedia for ``qwer``).
24+
25+See the :search:`/preferences page <preferences>` for the list of engines,
26+categories and languages.
27+
28+Examples
29+========
30+
31+Search in wikipedia for ``qwer``:
32+
33+- :search:`!wp qwer <?q=%21wp%20qwer>` or
34+- :search:`!wikipedia qwer :search:<?q=%21wikipedia%20qwer>`
35+
36+Image search:
37+
38+- :search:`!images Cthulhu <?q=%21images%20Cthulhu>`
39+
40+Custom language in wikipedia:
41+
42+- :search:`:hu !wp hackerspace <?q=%3Ahu%20%21wp%20hackerspace>`
manage.sh+8−2
@@ -18,12 +18,12 @@ ACTION="$1"
1818update_packages() {
1919 pip install --upgrade pip
2020 pip install --upgrade setuptools
21− pip install -r "$BASE_DIR/requirements.txt"
21+ pip install -Ur "$BASE_DIR/requirements.txt"
2222}
2323
2424update_dev_packages() {
2525 update_packages
26− pip install -r "$BASE_DIR/requirements-dev.txt"
26+ pip install -Ur "$BASE_DIR/requirements-dev.txt"
2727}
2828
2929install_geckodriver() {
@@ -70,6 +70,11 @@ locales() {
7070 pybabel compile -d "$SEARX_DIR/translations"
7171}
7272
73+update_useragents() {
74+ echo '[!] Updating user agent versions'
75+ python utils/fetch_firefox_version.py
76+}
77+
7378pep8_check() {
7479 echo '[!] Running pep8 check'
7580 # ignored rules:
@@ -246,6 +251,7 @@ Commands
246251 update_dev_packages - Check & update development and production dependency changes
247252 install_geckodriver - Download & install geckodriver if not already installed (required for robot_tests)
248253 npm_packages - Download & install npm dependencies
254+ update_useragents - Update useragents.json with the most recent versions of Firefox
249255
250256 Build
251257 -----
requirements-dev.txt+6
@@ -1,3 +1,6 @@
1+pallets-sphinx-themes
2+Sphinx
3+sphinx-issues
14mock==2.0.0
25nose2[coverage_plugin]
36cov-core==1.15.0
@@ -8,3 +11,6 @@ transifex-client==0.12.2
811unittest2==1.1.0
912zope.testrunner==4.5.1
1013selenium==3.141.0
14+linuxdoc @ git+http://github.com/return42/linuxdoc.git
15+sphinx-jinja
16+sphinx-tabs
searx/data/useragents.json+8−7
@@ -1,14 +1,15 @@
11{
2− "ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
32 "versions": [
4− "61.0.1",
5− "61.0",
6− "60.0.2",
7− "60.0.1",
8− "60.0"
3+ "70.0.1",
4+ "70.0",
5+ "69.0.3",
6+ "69.0.2",
7+ "69.0.1",
8+ "69.0"
99 ],
1010 "os": [
1111 "Windows NT 10; WOW64",
1212 "X11; Linux x86_64"
13− ]
13+ ],
14+ "ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}"
1415}
searx/engines/bing.py+2−3
@@ -89,8 +89,7 @@ def response(resp):
8989 'content': content})
9090
9191 try:
92− result_len_container = "".join(eval_xpath(dom, '//span[@class="sb_count"]/text()'))
93− result_len_container = utils.to_string(result_len_container)
92+ result_len_container = "".join(eval_xpath(dom, '//span[@class="sb_count"]//text()'))
9493 if "-" in result_len_container:
9594 # Remove the part "from-to" for paginated request ...
9695 result_len_container = result_len_container[result_len_container.find("-") * 2 + 2:]
@@ -102,7 +101,7 @@ def response(resp):
102101 logger.debug('result error :\n%s', e)
103102 pass
104103
105− if _get_offset_from_pageno(resp.search_params.get("pageno", 0)) > result_len:
104+ if result_len and _get_offset_from_pageno(resp.search_params.get("pageno", 0)) > result_len:
106105 return []
107106
108107 results.append({'number_of_results': result_len})
searx/engines/flickr_noapi.py+17−9
@@ -109,14 +109,22 @@ def response(resp):
109109 else:
110110 url = build_flickr_url(photo['ownerNsid'], photo['id'])
111111
112− results.append({'url': url,
113− 'title': title,
114− 'img_src': img_src,
115− 'thumbnail_src': thumbnail_src,
116− 'content': content,
117− 'author': author,
118− 'source': source,
119− 'img_format': img_format,
120− 'template': 'images.html'})
112+ result = {
113+ 'url': url,
114+ 'img_src': img_src,
115+ 'thumbnail_src': thumbnail_src,
116+ 'source': source,
117+ 'img_format': img_format,
118+ 'template': 'images.html'
119+ }
120+ try:
121+ result['author'] = author
122+ result['title'] = title
123+ result['content'] = content
124+ except:
125+ result['author'] = ''
126+ result['title'] = ''
127+ result['content'] = ''
128+ results.append(result)
121129
122130 return results
searx/engines/gigablast.py+28−13
@@ -14,6 +14,7 @@ import random
1414from json import loads
1515from time import time
1616from lxml.html import fromstring
17+from searx.poolrequests import get
1718from searx.url_utils import urlencode
1819from searx.utils import eval_xpath
1920
@@ -31,13 +32,9 @@ search_string = 'search?{query}'\
3132 '&c=main'\
3233 '&s={offset}'\
3334 '&format=json'\
34− '&qh=0'\
35− '&qlang={lang}'\
35+ '&langcountry={lang}'\
3636 '&ff={safesearch}'\
37− '&rxiec={rxieu}'\
38− '&ulse={ulse}'\
39− '&rand={rxikd}'\
40− '&dbez={dbez}'
37+ '&rand={rxikd}'
4138# specific xpath variables
4239results_xpath = '//response//result'
4340url_xpath = './/url'
@@ -46,9 +43,26 @@ content_xpath = './/sum'
4643
4744supported_languages_url = 'https://gigablast.com/search?&rxikd=1'
4845
46+extra_param = '' # gigablast requires a random extra parameter
47+# which can be extracted from the source code of the search page
48+
49+
50+def parse_extra_param(text):
51+ global extra_param
52+ param_lines = [x for x in text.splitlines() if x.startswith('var url=') or x.startswith('url=url+')]
53+ extra_param = ''
54+ for l in param_lines:
55+ extra_param += l.split("'")[1]
56+ extra_param = extra_param.split('&')[-1]
57+
58+
59+def init(engine_settings=None):
60+ parse_extra_param(get('http://gigablast.com/search?c=main&qlangcountry=en-us&q=south&s=10').text)
61+
4962
5063# do search-request
5164def request(query, params):
65+ print("EXTRAPARAM:", extra_param)
5266 offset = (params['pageno'] - 1) * number_of_results
5367
5468 if params['language'] == 'all':
@@ -67,14 +81,11 @@ def request(query, params):
6781 search_path = search_string.format(query=urlencode({'q': query}),
6882 offset=offset,
6983 number_of_results=number_of_results,
70− rxikd=int(time() * 1000),
71− rxieu=random.randint(1000000000, 9999999999),
72− ulse=random.randint(100000000, 999999999),
7384 lang=language,
74− safesearch=safesearch,
75− dbez=random.randint(100000000, 999999999))
85+ rxikd=int(time() * 1000),
86+ safesearch=safesearch)
7687
77− params['url'] = base_url + search_path
88+ params['url'] = base_url + search_path + '&' + extra_param
7889
7990 return params
8091
@@ -84,7 +95,11 @@ def response(resp):
8495 results = []
8596
8697 # parse results
87− response_json = loads(resp.text)
98+ try:
99+ response_json = loads(resp.text)
100+ except:
101+ parse_extra_param(resp.text)
102+ raise Exception('extra param expired, please reload')
88103
89104 for result in response_json['results']:
90105 # append result
searx/engines/ina.py+6−3
@@ -32,7 +32,7 @@ base_url = 'https://www.ina.fr'
3232search_url = base_url + '/layout/set/ajax/recherche/result?autopromote=&hf={ps}&b={start}&type=Video&r=&{query}'
3333
3434# specific xpath variables
35−results_xpath = '//div[contains(@class,"search-results--list")]/div[@class="media"]'
35+results_xpath = '//div[contains(@class,"search-results--list")]//div[@class="media-body"]'
3636url_xpath = './/a/@href'
3737title_xpath = './/h3[@class="h3--title media-heading"]'
3838thumbnail_xpath = './/img/@src'
@@ -65,8 +65,11 @@ def response(resp):
6565 videoid = result.xpath(url_xpath)[0]
6666 url = base_url + videoid
6767 title = p.unescape(extract_text(result.xpath(title_xpath)))
68− thumbnail = extract_text(result.xpath(thumbnail_xpath)[0])
69− if thumbnail[0] == '/':
68+ try:
69+ thumbnail = extract_text(result.xpath(thumbnail_xpath)[0])
70+ except:
71+ thumbnail = ''
72+ if thumbnail and thumbnail[0] == '/':
7073 thumbnail = base_url + thumbnail
7174 d = extract_text(result.xpath(publishedDate_xpath)[0])
7275 d = d.split('/')
searx/engines/microsoft_academic.py+2
@@ -45,6 +45,8 @@ def request(query, params):
4545def response(resp):
4646 results = []
4747 response_data = loads(resp.text)
48+ if not response_data:
49+ return results
4850
4951 for result in response_data['results']:
5052 url = _get_url(result)
searx/engines/openstreetmap.py+1−1
@@ -24,7 +24,7 @@ result_base_url = 'https://openstreetmap.org/{osm_type}/{osm_id}'
2424
2525# do search-request
2626def request(query, params):
27− params['url'] = base_url + search_string.format(query=query)
27+ params['url'] = base_url + search_string.format(query=query.decode('utf-8'))
2828
2929 return params
3030
searx/engines/qwant.py+1
@@ -50,6 +50,7 @@ def request(query, params):
5050 language = match_language(params['language'], supported_languages, language_aliases)
5151 params['url'] += '&locale=' + language.replace('-', '_').lower()
5252
53+ params['headers']['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0'
5354 return params
5455
5556
searx/engines/scanr_structures.py+1−1
@@ -29,7 +29,7 @@ def request(query, params):
2929 params['url'] = search_url
3030 params['method'] = 'POST'
3131 params['headers']['Content-type'] = "application/json"
32− params['data'] = dumps({"query": query,
32+ params['data'] = dumps({"query": query.decode('utf-8'),
3333 "searchField": "ALL",
3434 "sortDirection": "ASC",
3535 "sortOrder": "RELEVANCY",
searx/engines/spotify.py+14
@@ -12,10 +12,14 @@
1212
1313from json import loads
1414from searx.url_utils import urlencode
15+import requests
16+import base64
1517
1618# engine dependent config
1719categories = ['music']
1820paging = True
21+api_client_id = None
22+api_client_secret = None
1923
2024# search-url
2125url = 'https://api.spotify.com/'
@@ -31,6 +35,16 @@ def request(query, params):
3135
3236 params['url'] = search_url.format(query=urlencode({'q': query}), offset=offset)
3337
38+ r = requests.post(
39+ 'https://accounts.spotify.com/api/token',
40+ data={'grant_type': 'client_credentials'},
41+ headers={'Authorization': 'Basic ' + base64.b64encode(
42+ "{}:{}".format(api_client_id, api_client_secret).encode('utf-8')
43+ ).decode('utf-8')}
44+ )
45+ j = loads(r.text)
46+ params['headers'] = {'Authorization': 'Bearer {}'.format(j.get('access_token'))}
47+
3448 return params
3549
3650
searx/engines/wikipedia.py+7−2
@@ -21,7 +21,8 @@ search_url = base_url + u'w/api.php?'\
2121 'action=query'\
2222 '&format=json'\
2323 '&{query}'\
24− '&prop=extracts|pageimages'\
24+ '&prop=extracts|pageimages|pageprops'\
25+ '&ppprop=disambiguation'\
2526 '&exintro'\
2627 '&explaintext'\
2728 '&pithumbsize=300'\
@@ -79,12 +80,15 @@ def response(resp):
7980
8081 # wikipedia article's unique id
8182 # first valid id is assumed to be the requested article
83+ if 'pages' not in search_result['query']:
84+ return results
85+
8286 for article_id in search_result['query']['pages']:
8387 page = search_result['query']['pages'][article_id]
8488 if int(article_id) > 0:
8589 break
8690
87− if int(article_id) < 0:
91+ if int(article_id) < 0 or 'disambiguation' in page.get('pageprops', {}):
8892 return []
8993
9094 title = page.get('title')
@@ -96,6 +100,7 @@ def response(resp):
96100 extract = page.get('extract')
97101
98102 summary = extract_first_paragraph(extract, title, image)
103+ summary = summary.replace('() ', '')
99104
100105 # link to wikipedia article
101106 wikipedia_link = base_url.format(language=url_lang(resp.search_params['language'])) \
searx/settings.yml+20−15
@@ -79,9 +79,10 @@ engines:
7979 categories : science
8080 timeout : 4.0
8181
82− - name : base
83− engine : base
84− shortcut : bs
82+# tmp suspended: dh key too small
83+# - name : base
84+# engine : base
85+# shortcut : bs
8586
8687 - name : wikipedia
8788 engine : wikipedia
@@ -408,7 +409,7 @@ engines:
408409
409410 - name : library genesis
410411 engine : xpath
411− search_url : http://libgen.io/search.php?req={query}
412+ search_url : https://libgen.is/search.php?req={query}
412413 url_xpath : //a[contains(@href,"bookfi.net")]/@href
413414 title_xpath : //a[contains(@href,"book/")]/text()[1]
414415 content_xpath : //td/a[1][contains(@href,"=author")]/text()
@@ -464,7 +465,7 @@ engines:
464465 - name : openairedatasets
465466 engine : json_engine
466467 paging : True
467− search_url : http://api.openaire.eu/search/datasets?format=json&page={pageno}&size=10&title={query}
468+ search_url : https://api.openaire.eu/search/datasets?format=json&page={pageno}&size=10&title={query}
468469 results_query : response/results/result
469470 url_query : metadata/oaf:entity/oaf:result/children/instance/webresource/url/$
470471 title_query : metadata/oaf:entity/oaf:result/title/$
@@ -476,7 +477,7 @@ engines:
476477 - name : openairepublications
477478 engine : json_engine
478479 paging : True
479− search_url : http://api.openaire.eu/search/publications?format=json&page={pageno}&size=10&title={query}
480+ search_url : https://api.openaire.eu/search/publications?format=json&page={pageno}&size=10&title={query}
480481 results_query : response/results/result
481482 url_query : metadata/oaf:entity/oaf:result/children/instance/webresource/url/$
482483 title_query : metadata/oaf:entity/oaf:result/title/$
@@ -552,10 +553,11 @@ engines:
552553 timeout : 10.0
553554 disabled : True
554555
555− - name : scanr structures
556− shortcut: scs
557− engine : scanr_structures
558− disabled : True
556+# tmp suspended: bad certificate
557+# - name : scanr structures
558+# shortcut: scs
559+# engine : scanr_structures
560+# disabled : True
559561
560562 - name : soundcloud
561563 engine : soundcloud
@@ -598,9 +600,12 @@ engines:
598600 shortcut : se
599601 categories : science
600602
601− - name : spotify
602− engine : spotify
603− shortcut : stf
603+# Spotify needs API credentials
604+# - name : spotify
605+# engine : spotify
606+# shortcut : stf
607+# api_client_id : *******
608+# api_client_secret : *******
604609
605610 - name : startpage
606611 engine : startpage
@@ -812,7 +817,7 @@ locales:
812817doi_resolvers :
813818 oadoi.org : 'https://oadoi.org/'
814819 doi.org : 'https://doi.org/'
815− doai.io : 'http://doai.io/'
816− sci-hub.tw : 'http://sci-hub.tw/'
820+ doai.io : 'https://doai.io/'
821+ sci-hub.tw : 'https://sci-hub.tw/'
817822
818823default_doi_resolver : 'oadoi.org'
searx/settings_robot.yml+2−2
@@ -43,7 +43,7 @@ locales:
4343doi_resolvers :
4444 oadoi.org : 'https://oadoi.org/'
4545 doi.org : 'https://doi.org/'
46− doai.io : 'http://doai.io/'
47− sci-hub.tw : 'http://sci-hub.tw/'
46+ doai.io : 'https://doai.io/'
47+ sci-hub.tw : 'https://sci-hub.tw/'
4848
4949default_doi_resolver : 'oadoi.org'
searx/static/plugins/js/vim_hotkeys.js+10−2
@@ -125,6 +125,14 @@ $(document).ready(function() {
125125 }
126126 });
127127
128+ function nextResult(current, direction) {
129+ var next = current[direction]();
130+ while (!next.is('.result') && next.length !== 0) {
131+ next = next[direction]();
132+ }
133+ return next
134+ }
135+
128136 function highlightResult(which) {
129137 return function() {
130138 var current = $('.result[data-vim-selected]');
@@ -157,13 +165,13 @@ $(document).ready(function() {
157165 }
158166 break;
159167 case 'down':
160− next = current.next('.result');
168+ next = nextResult(current, 'next');
161169 if (next.length === 0) {
162170 next = $('.result:first');
163171 }
164172 break;
165173 case 'up':
166− next = current.prev('.result');
174+ next = nextResult(current, 'prev');
167175 if (next.length === 0) {
168176 next = $('.result:last');
169177 }
searx/static/themes/legacy/less/autocompleter.less+61−61
@@ -1,61 +1,61 @@
1−/*
2− * searx, A privacy-respecting, hackable metasearch engine
3− */
4−
5−ul {
6− &.autocompleter-choices {
7− position: absolute;
8− margin: 0;
9− padding: 0;
10− list-style: none;
11− border: 1px solid @color-autocompleter-choices-border;
12− border-left-color: @color-autocompleter-choices-border-left-right;
13− border-right-color: @color-autocompleter-choices-border-left-right;
14− border-bottom-color: @color-autocompleter-choices-border-bottom;
15− text-align: left;
16− font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
17− z-index: 50;
18− background-color: @color-autocompleter-choices-background;
19− color: @color-autocompleter-choices-font;
20−
21− li {
22− position: relative;
23− margin: -2px 0 0 0;
24− padding: 0.2em 1.5em 0.2em 1em;
25− display: block;
26− float: none !important;
27− cursor: pointer;
28− font-weight: normal;
29− white-space: nowrap;
30− font-size: 1em;
31− line-height: 1.5em;
32−
33− &.autocompleter-selected {
34− background-color: @color-autocompleter-selected-background;
35− color: @color-autocompleter-selected-font;
36−
37− span.autocompleter-queried {
38− color: @color-autocompleter-selected-queried-font;
39− }
40− }
41− }
42−
43− span.autocompleter-queried {
44− display: inline;
45− float: none;
46− font-weight: bold;
47− margin: 0;
48− padding: 0;
49− }
50− }
51−}
52−
53−/*.autocompleter-loading {
54− //background-image: url(images/spinner.gif);
55− background-repeat: no-repeat;
56− background-position: right 50%;
57−}*/
58−
59−/*textarea.autocompleter-loading {
60− background-position: right bottom;
61−}*/
1+/*
2+ * searx, A privacy-respecting, hackable metasearch engine
3+ */
4+
5+ul {
6+ &.autocompleter-choices {
7+ position: absolute;
8+ margin: 0;
9+ padding: 0;
10+ list-style: none;
11+ border: 1px solid @color-autocompleter-choices-border;
12+ border-left-color: @color-autocompleter-choices-border-left-right;
13+ border-right-color: @color-autocompleter-choices-border-left-right;
14+ border-bottom-color: @color-autocompleter-choices-border-bottom;
15+ text-align: left;
16+ font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
17+ z-index: 50;
18+ background-color: @color-autocompleter-choices-background;
19+ color: @color-autocompleter-choices-font;
20+
21+ li {
22+ position: relative;
23+ margin: -2px 0 0 0;
24+ padding: 0.2em 1.5em 0.2em 1em;
25+ display: block;
26+ float: none !important;
27+ cursor: pointer;
28+ font-weight: normal;
29+ white-space: nowrap;
30+ font-size: 1em;
31+ line-height: 1.5em;
32+
33+ &.autocompleter-selected {
34+ background-color: @color-autocompleter-selected-background;
35+ color: @color-autocompleter-selected-font;
36+
37+ span.autocompleter-queried {
38+ color: @color-autocompleter-selected-queried-font;
39+ }
40+ }
41+ }
42+
43+ span.autocompleter-queried {
44+ display: inline;
45+ float: none;
46+ font-weight: bold;
47+ margin: 0;
48+ padding: 0;
49+ }
50+ }
51+}
52+
53+/*.autocompleter-loading {
54+ //background-image: url(images/spinner.gif);
55+ background-repeat: no-repeat;
56+ background-position: right 50%;
57+}*/
58+
59+/*textarea.autocompleter-loading {
60+ background-position: right bottom;
61+}*/
searx/static/themes/oscar/gruntfile.js+3−3
@@ -24,7 +24,7 @@ module.exports = function(grunt) {
2424 jshint: {
2525 files: ['gruntfile.js', 'js/searx_src/*.js'],
2626 options: {
27− reporterOutput: "",
27+ reporterOutput: "",
2828 // options here to override JSHint defaults
2929 globals: {
3030 jQuery: true,
@@ -55,7 +55,7 @@ module.exports = function(grunt) {
5555 "css/logicodev-dark.min.css": "less/logicodev-dark/oscar.less"}
5656 },
5757 /*
58− // built with ./manage.sh styles
58+ // built with ./manage.sh styles
5959 bootstrap: {
6060 options: {
6161 paths: ["less/bootstrap"],
@@ -90,7 +90,7 @@ module.exports = function(grunt) {
9090 grunt.registerTask('test', ['jshint']);
9191
9292 grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'less']);
93−
93+
9494 grunt.registerTask('styles', ['less']);
9595
9696};
searx/static/themes/oscar/js/searx.js+326−326
@@ -1,26 +1,26 @@
1−/**
2− * searx is free software: you can redistribute it and/or modify
3− * it under the terms of the GNU Affero General Public License as published by
4− * the Free Software Foundation, either version 3 of the License, or
5− * (at your option) any later version.
6− *
7− * searx is distributed in the hope that it will be useful,
8− * but WITHOUT ANY WARRANTY; without even the implied warranty of
9− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10− * GNU Affero General Public License for more details.
11− *
12− * You should have received a copy of the GNU Affero General Public License
13− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14− *
15− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16− */
17−
18−requirejs.config({
19− baseUrl: './static/themes/oscar/js',
20− paths: {
21− app: '../app'
22− }
23−});
1+/**
2+ * searx is free software: you can redistribute it and/or modify
3+ * it under the terms of the GNU Affero General Public License as published by
4+ * the Free Software Foundation, either version 3 of the License, or
5+ * (at your option) any later version.
6+ *
7+ * searx is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU Affero General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU Affero General Public License
13+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14+ *
15+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16+ */
17+
18+requirejs.config({
19+ baseUrl: './static/themes/oscar/js',
20+ paths: {
21+ app: '../app'
22+ }
23+});
2424;/**
2525 * searx is free software: you can redistribute it and/or modify
2626 * it under the terms of the GNU Affero General Public License as published by
@@ -51,306 +51,306 @@ window.searx = (function(d) {
5151 method: script.getAttribute('data-method')
5252 };
5353})(document);
54−;/**
55− * searx is free software: you can redistribute it and/or modify
56− * it under the terms of the GNU Affero General Public License as published by
57− * the Free Software Foundation, either version 3 of the License, or
58− * (at your option) any later version.
59− *
60− * searx is distributed in the hope that it will be useful,
61− * but WITHOUT ANY WARRANTY; without even the implied warranty of
62− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63− * GNU Affero General Public License for more details.
64− *
65− * You should have received a copy of the GNU Affero General Public License
66− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
67− *
68− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
69− */
70−
71−if(searx.autocompleter) {
72− searx.searchResults = new Bloodhound({
73− datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
74− queryTokenizer: Bloodhound.tokenizers.whitespace,
75− remote: './autocompleter?q=%QUERY'
76− });
77− searx.searchResults.initialize();
78−}
79−
80−$(document).ready(function(){
81− if(searx.autocompleter) {
82− $('#q').typeahead(null, {
83− name: 'search-results',
84− displayKey: function(result) {
85− return result;
86− },
87− source: searx.searchResults.ttAdapter()
88− });
89− }
90−});
91−;/**
92− * searx is free software: you can redistribute it and/or modify
93− * it under the terms of the GNU Affero General Public License as published by
94− * the Free Software Foundation, either version 3 of the License, or
95− * (at your option) any later version.
96− *
97− * searx is distributed in the hope that it will be useful,
98− * but WITHOUT ANY WARRANTY; without even the implied warranty of
99− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100− * GNU Affero General Public License for more details.
101− *
102− * You should have received a copy of the GNU Affero General Public License
103− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
104− *
105− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
106− */
107−
108−$(document).ready(function(){
109− /**
110− * focus element if class="autofocus" and id="q"
111− */
112− $('#q.autofocus').focus();
113−
114− /**
115− * select full content on click if class="select-all-on-click"
116− */
117− $(".select-all-on-click").click(function () {
118− $(this).select();
119− });
120−
121− /**
122− * change text during btn-collapse click if possible
123− */
124− $('.btn-collapse').click(function() {
125− var btnTextCollapsed = $(this).data('btn-text-collapsed');
126− var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed');
127−
128− if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') {
129− if($(this).hasClass('collapsed')) {
130− new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed);
131− } else {
132− new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed);
133− }
134− $(this).html(new_html);
135− }
136− });
137−
138− /**
139− * change text during btn-toggle click if possible
140− */
141− $('.btn-toggle .btn').click(function() {
142− var btnClass = 'btn-' + $(this).data('btn-class');
143− var btnLabelDefault = $(this).data('btn-label-default');
144− var btnLabelToggled = $(this).data('btn-label-toggled');
145− if(btnLabelToggled !== '') {
146− if($(this).hasClass('btn-default')) {
147− new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled);
148− } else {
149− new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault);
150− }
151− $(this).html(new_html);
152− }
153− $(this).toggleClass(btnClass);
154− $(this).toggleClass('btn-default');
155− });
156−
157− /**
158− * change text during btn-toggle click if possible
159− */
160− $('.media-loader').click(function() {
161− var target = $(this).data('target');
162− var iframe_load = $(target + ' > iframe');
163− var srctest = iframe_load.attr('src');
164− if(srctest === undefined || srctest === false){
165− iframe_load.attr('src', iframe_load.data('src'));
166− }
167− });
168−
169− /**
170− * Select or deselect every categories on double clic
171− */
172− $(".btn-sm").dblclick(function() {
173− var btnClass = 'btn-' + $(this).data('btn-class'); // primary
174− if($(this).hasClass('btn-default')) {
175− $(".btn-sm > input").attr('checked', 'checked');
176− $(".btn-sm > input").prop("checked", true);
177− $(".btn-sm").addClass(btnClass);
178− $(".btn-sm").addClass('active');
179− $(".btn-sm").removeClass('btn-default');
180− } else {
181− $(".btn-sm > input").attr('checked', '');
182− $(".btn-sm > input").removeAttr('checked');
183− $(".btn-sm > input").checked = false;
184− $(".btn-sm").removeClass(btnClass);
185− $(".btn-sm").removeClass('active');
186− $(".btn-sm").addClass('btn-default');
187− }
188− });
189−});
190−;/**
191− * searx is free software: you can redistribute it and/or modify
192− * it under the terms of the GNU Affero General Public License as published by
193− * the Free Software Foundation, either version 3 of the License, or
194− * (at your option) any later version.
195− *
196− * searx is distributed in the hope that it will be useful,
197− * but WITHOUT ANY WARRANTY; without even the implied warranty of
198− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
199− * GNU Affero General Public License for more details.
200− *
201− * You should have received a copy of the GNU Affero General Public License
202− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
203− *
204− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
205− */
206−
207−$(document).ready(function(){
208− $(".searx_overpass_request").on( "click", function( event ) {
209− var overpass_url = "https://overpass-api.de/api/interpreter?data=";
210− var query_start = overpass_url + "[out:json][timeout:25];(";
211− var query_end = ");out meta;";
212−
213− var osm_id = $(this).data('osm-id');
214− var osm_type = $(this).data('osm-type');
215− var result_table = $(this).data('result-table');
216− var result_table_loadicon = "#" + $(this).data('result-table-loadicon');
217−
218− // tags which can be ignored
219− var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ];
220−
221− if(osm_id && osm_type && result_table) {
222− result_table = "#" + result_table;
223− var query = null;
224− switch(osm_type) {
225− case 'node':
226− query = query_start + "node(" + osm_id + ");" + query_end;
227− break;
228− case 'way':
229− query = query_start + "way(" + osm_id + ");" + query_end;
230− break;
231− case 'relation':
232− query = query_start + "relation(" + osm_id + ");" + query_end;
233− break;
234− default:
235− break;
236− }
237− if(query) {
238− //alert(query);
239− var ajaxRequest = $.ajax( query )
240− .done(function( html) {
241− if(html && html.elements && html.elements[0]) {
242− var element = html.elements[0];
243− var newHtml = $(result_table).html();
244− for (var row in element.tags) {
245− if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) {
246− newHtml += "<tr><td>" + row + "</td><td>";
247− switch(row) {
248− case "phone":
249− case "fax":
250− newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>";
251− break;
252− case "email":
253− newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>";
254− break;
255− case "website":
256− case "url":
257− newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>";
258− break;
259− case "wikidata":
260− newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>";
261− break;
262− case "wikipedia":
263− if(element.tags[row].indexOf(":") != -1) {
264− newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>";
265− break;
266− }
267− /* jshint ignore:start */
268− default:
269− /* jshint ignore:end */
270− newHtml += element.tags[row];
271− break;
272− }
273− newHtml += "</td></tr>";
274− }
275− }
276− $(result_table).html(newHtml);
277− $(result_table).removeClass('hidden');
278− $(result_table_loadicon).addClass('hidden');
279− }
280− })
281− .fail(function() {
282− $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>");
283− });
284− }
285− }
286−
287− // this event occour only once per element
288− $( this ).off( event );
289− });
290−
291− $(".searx_init_map").on( "click", function( event ) {
292− var leaflet_target = $(this).data('leaflet-target');
293− var map_lon = $(this).data('map-lon');
294− var map_lat = $(this).data('map-lat');
295− var map_zoom = $(this).data('map-zoom');
296− var map_boundingbox = $(this).data('map-boundingbox');
297− var map_geojson = $(this).data('map-geojson');
298−
299− require(['leaflet-0.7.3.min'], function(leaflet) {
300− if(map_boundingbox) {
301− southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
302− northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
303− map_bounds = L.latLngBounds(southWest, northEast);
304− }
305−
306− // TODO hack
307− // change default imagePath
308− L.Icon.Default.imagePath = "./static/themes/oscar/img/map";
309−
310− // init map
311− var map = L.map(leaflet_target);
312−
313− // create the tile layer with correct attribution
314− var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
315− var osmMapnikAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
316− var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib});
317−
318− var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png';
319− var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
320− var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
321−
322− // init map view
323− if(map_bounds) {
324− // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021
325− setTimeout(function () {
326− map.fitBounds(map_bounds, {
327− maxZoom:17
328− });
329− }, 0);
330− } else if (map_lon && map_lat) {
331− if(map_zoom)
332− map.setView(new L.LatLng(map_lat, map_lon),map_zoom);
333− else
334− map.setView(new L.LatLng(map_lat, map_lon),8);
335− }
336−
337− map.addLayer(osmMapnik);
338−
339− var baseLayers = {
340− "OSM Mapnik": osmMapnik/*,
341− "OSM Wikimedia": osmWikimedia*/
342− };
343−
344− L.control.layers(baseLayers).addTo(map);
345−
346−
347− if(map_geojson)
348− L.geoJson(map_geojson).addTo(map);
349− /*else if(map_bounds)
350− L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/
351− });
352−
353− // this event occour only once per element
354− $( this ).off( event );
355− });
356−});
54+;/**
55+ * searx is free software: you can redistribute it and/or modify
56+ * it under the terms of the GNU Affero General Public License as published by
57+ * the Free Software Foundation, either version 3 of the License, or
58+ * (at your option) any later version.
59+ *
60+ * searx is distributed in the hope that it will be useful,
61+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
62+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63+ * GNU Affero General Public License for more details.
64+ *
65+ * You should have received a copy of the GNU Affero General Public License
66+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
67+ *
68+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
69+ */
70+
71+if(searx.autocompleter) {
72+ searx.searchResults = new Bloodhound({
73+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
74+ queryTokenizer: Bloodhound.tokenizers.whitespace,
75+ remote: './autocompleter?q=%QUERY'
76+ });
77+ searx.searchResults.initialize();
78+}
79+
80+$(document).ready(function(){
81+ if(searx.autocompleter) {
82+ $('#q').typeahead(null, {
83+ name: 'search-results',
84+ displayKey: function(result) {
85+ return result;
86+ },
87+ source: searx.searchResults.ttAdapter()
88+ });
89+ }
90+});
91+;/**
92+ * searx is free software: you can redistribute it and/or modify
93+ * it under the terms of the GNU Affero General Public License as published by
94+ * the Free Software Foundation, either version 3 of the License, or
95+ * (at your option) any later version.
96+ *
97+ * searx is distributed in the hope that it will be useful,
98+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
99+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100+ * GNU Affero General Public License for more details.
101+ *
102+ * You should have received a copy of the GNU Affero General Public License
103+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
104+ *
105+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
106+ */
107+
108+$(document).ready(function(){
109+ /**
110+ * focus element if class="autofocus" and id="q"
111+ */
112+ $('#q.autofocus').focus();
113+
114+ /**
115+ * select full content on click if class="select-all-on-click"
116+ */
117+ $(".select-all-on-click").click(function () {
118+ $(this).select();
119+ });
120+
121+ /**
122+ * change text during btn-collapse click if possible
123+ */
124+ $('.btn-collapse').click(function() {
125+ var btnTextCollapsed = $(this).data('btn-text-collapsed');
126+ var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed');
127+
128+ if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') {
129+ if($(this).hasClass('collapsed')) {
130+ new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed);
131+ } else {
132+ new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed);
133+ }
134+ $(this).html(new_html);
135+ }
136+ });
137+
138+ /**
139+ * change text during btn-toggle click if possible
140+ */
141+ $('.btn-toggle .btn').click(function() {
142+ var btnClass = 'btn-' + $(this).data('btn-class');
143+ var btnLabelDefault = $(this).data('btn-label-default');
144+ var btnLabelToggled = $(this).data('btn-label-toggled');
145+ if(btnLabelToggled !== '') {
146+ if($(this).hasClass('btn-default')) {
147+ new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled);
148+ } else {
149+ new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault);
150+ }
151+ $(this).html(new_html);
152+ }
153+ $(this).toggleClass(btnClass);
154+ $(this).toggleClass('btn-default');
155+ });
156+
157+ /**
158+ * change text during btn-toggle click if possible
159+ */
160+ $('.media-loader').click(function() {
161+ var target = $(this).data('target');
162+ var iframe_load = $(target + ' > iframe');
163+ var srctest = iframe_load.attr('src');
164+ if(srctest === undefined || srctest === false){
165+ iframe_load.attr('src', iframe_load.data('src'));
166+ }
167+ });
168+
169+ /**
170+ * Select or deselect every categories on double clic
171+ */
172+ $(".btn-sm").dblclick(function() {
173+ var btnClass = 'btn-' + $(this).data('btn-class'); // primary
174+ if($(this).hasClass('btn-default')) {
175+ $(".btn-sm > input").attr('checked', 'checked');
176+ $(".btn-sm > input").prop("checked", true);
177+ $(".btn-sm").addClass(btnClass);
178+ $(".btn-sm").addClass('active');
179+ $(".btn-sm").removeClass('btn-default');
180+ } else {
181+ $(".btn-sm > input").attr('checked', '');
182+ $(".btn-sm > input").removeAttr('checked');
183+ $(".btn-sm > input").checked = false;
184+ $(".btn-sm").removeClass(btnClass);
185+ $(".btn-sm").removeClass('active');
186+ $(".btn-sm").addClass('btn-default');
187+ }
188+ });
189+});
190+;/**
191+ * searx is free software: you can redistribute it and/or modify
192+ * it under the terms of the GNU Affero General Public License as published by
193+ * the Free Software Foundation, either version 3 of the License, or
194+ * (at your option) any later version.
195+ *
196+ * searx is distributed in the hope that it will be useful,
197+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
198+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
199+ * GNU Affero General Public License for more details.
200+ *
201+ * You should have received a copy of the GNU Affero General Public License
202+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
203+ *
204+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
205+ */
206+
207+$(document).ready(function(){
208+ $(".searx_overpass_request").on( "click", function( event ) {
209+ var overpass_url = "https://overpass-api.de/api/interpreter?data=";
210+ var query_start = overpass_url + "[out:json][timeout:25];(";
211+ var query_end = ");out meta;";
212+
213+ var osm_id = $(this).data('osm-id');
214+ var osm_type = $(this).data('osm-type');
215+ var result_table = $(this).data('result-table');
216+ var result_table_loadicon = "#" + $(this).data('result-table-loadicon');
217+
218+ // tags which can be ignored
219+ var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ];
220+
221+ if(osm_id && osm_type && result_table) {
222+ result_table = "#" + result_table;
223+ var query = null;
224+ switch(osm_type) {
225+ case 'node':
226+ query = query_start + "node(" + osm_id + ");" + query_end;
227+ break;
228+ case 'way':
229+ query = query_start + "way(" + osm_id + ");" + query_end;
230+ break;
231+ case 'relation':
232+ query = query_start + "relation(" + osm_id + ");" + query_end;
233+ break;
234+ default:
235+ break;
236+ }
237+ if(query) {
238+ //alert(query);
239+ var ajaxRequest = $.ajax( query )
240+ .done(function( html) {
241+ if(html && html.elements && html.elements[0]) {
242+ var element = html.elements[0];
243+ var newHtml = $(result_table).html();
244+ for (var row in element.tags) {
245+ if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) {
246+ newHtml += "<tr><td>" + row + "</td><td>";
247+ switch(row) {
248+ case "phone":
249+ case "fax":
250+ newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>";
251+ break;
252+ case "email":
253+ newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>";
254+ break;
255+ case "website":
256+ case "url":
257+ newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>";
258+ break;
259+ case "wikidata":
260+ newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>";
261+ break;
262+ case "wikipedia":
263+ if(element.tags[row].indexOf(":") != -1) {
264+ newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>";
265+ break;
266+ }
267+ /* jshint ignore:start */
268+ default:
269+ /* jshint ignore:end */
270+ newHtml += element.tags[row];
271+ break;
272+ }
273+ newHtml += "</td></tr>";
274+ }
275+ }
276+ $(result_table).html(newHtml);
277+ $(result_table).removeClass('hidden');
278+ $(result_table_loadicon).addClass('hidden');
279+ }
280+ })
281+ .fail(function() {
282+ $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>");
283+ });
284+ }
285+ }
286+
287+ // this event occour only once per element
288+ $( this ).off( event );
289+ });
290+
291+ $(".searx_init_map").on( "click", function( event ) {
292+ var leaflet_target = $(this).data('leaflet-target');
293+ var map_lon = $(this).data('map-lon');
294+ var map_lat = $(this).data('map-lat');
295+ var map_zoom = $(this).data('map-zoom');
296+ var map_boundingbox = $(this).data('map-boundingbox');
297+ var map_geojson = $(this).data('map-geojson');
298+
299+ require(['leaflet-0.7.3.min'], function(leaflet) {
300+ if(map_boundingbox) {
301+ southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
302+ northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
303+ map_bounds = L.latLngBounds(southWest, northEast);
304+ }
305+
306+ // TODO hack
307+ // change default imagePath
308+ L.Icon.Default.imagePath = "./static/themes/oscar/img/map";
309+
310+ // init map
311+ var map = L.map(leaflet_target);
312+
313+ // create the tile layer with correct attribution
314+ var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
315+ var osmMapnikAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
316+ var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib});
317+
318+ var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png';
319+ var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
320+ var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
321+
322+ // init map view
323+ if(map_bounds) {
324+ // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021
325+ setTimeout(function () {
326+ map.fitBounds(map_bounds, {
327+ maxZoom:17
328+ });
329+ }, 0);
330+ } else if (map_lon && map_lat) {
331+ if(map_zoom)
332+ map.setView(new L.LatLng(map_lat, map_lon),map_zoom);
333+ else
334+ map.setView(new L.LatLng(map_lat, map_lon),8);
335+ }
336+
337+ map.addLayer(osmMapnik);
338+
339+ var baseLayers = {
340+ "OSM Mapnik": osmMapnik/*,
341+ "OSM Wikimedia": osmWikimedia*/
342+ };
343+
344+ L.control.layers(baseLayers).addTo(map);
345+
346+
347+ if(map_geojson)
348+ L.geoJson(map_geojson).addTo(map);
349+ /*else if(map_bounds)
350+ L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/
351+ });
352+
353+ // this event occour only once per element
354+ $( this ).off( event );
355+ });
356+});
searx/static/themes/oscar/js/searx_src/00_requirejs_config.js+23−23
@@ -1,23 +1,23 @@
1−/**
2− * searx is free software: you can redistribute it and/or modify
3− * it under the terms of the GNU Affero General Public License as published by
4− * the Free Software Foundation, either version 3 of the License, or
5− * (at your option) any later version.
6− *
7− * searx is distributed in the hope that it will be useful,
8− * but WITHOUT ANY WARRANTY; without even the implied warranty of
9− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10− * GNU Affero General Public License for more details.
11− *
12− * You should have received a copy of the GNU Affero General Public License
13− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14− *
15− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16− */
17−
18−requirejs.config({
19− baseUrl: './static/themes/oscar/js',
20− paths: {
21− app: '../app'
22− }
23−});
1+/**
2+ * searx is free software: you can redistribute it and/or modify
3+ * it under the terms of the GNU Affero General Public License as published by
4+ * the Free Software Foundation, either version 3 of the License, or
5+ * (at your option) any later version.
6+ *
7+ * searx is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU Affero General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU Affero General Public License
13+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14+ *
15+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16+ */
17+
18+requirejs.config({
19+ baseUrl: './static/themes/oscar/js',
20+ paths: {
21+ app: '../app'
22+ }
23+});
searx/static/themes/oscar/js/searx_src/autocompleter.js+37−37
@@ -1,37 +1,37 @@
1−/**
2− * searx is free software: you can redistribute it and/or modify
3− * it under the terms of the GNU Affero General Public License as published by
4− * the Free Software Foundation, either version 3 of the License, or
5− * (at your option) any later version.
6− *
7− * searx is distributed in the hope that it will be useful,
8− * but WITHOUT ANY WARRANTY; without even the implied warranty of
9− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10− * GNU Affero General Public License for more details.
11− *
12− * You should have received a copy of the GNU Affero General Public License
13− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14− *
15− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16− */
17−
18−if(searx.autocompleter) {
19− searx.searchResults = new Bloodhound({
20− datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
21− queryTokenizer: Bloodhound.tokenizers.whitespace,
22− remote: './autocompleter?q=%QUERY'
23− });
24− searx.searchResults.initialize();
25−}
26−
27−$(document).ready(function(){
28− if(searx.autocompleter) {
29− $('#q').typeahead(null, {
30− name: 'search-results',
31− displayKey: function(result) {
32− return result;
33− },
34− source: searx.searchResults.ttAdapter()
35− });
36− }
37−});
1+/**
2+ * searx is free software: you can redistribute it and/or modify
3+ * it under the terms of the GNU Affero General Public License as published by
4+ * the Free Software Foundation, either version 3 of the License, or
5+ * (at your option) any later version.
6+ *
7+ * searx is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU Affero General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU Affero General Public License
13+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14+ *
15+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16+ */
17+
18+if(searx.autocompleter) {
19+ searx.searchResults = new Bloodhound({
20+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
21+ queryTokenizer: Bloodhound.tokenizers.whitespace,
22+ remote: './autocompleter?q=%QUERY'
23+ });
24+ searx.searchResults.initialize();
25+}
26+
27+$(document).ready(function(){
28+ if(searx.autocompleter) {
29+ $('#q').typeahead(null, {
30+ name: 'search-results',
31+ displayKey: function(result) {
32+ return result;
33+ },
34+ source: searx.searchResults.ttAdapter()
35+ });
36+ }
37+});
searx/static/themes/oscar/js/searx_src/element_modifiers.js+99−99
@@ -1,99 +1,99 @@
1−/**
2− * searx is free software: you can redistribute it and/or modify
3− * it under the terms of the GNU Affero General Public License as published by
4− * the Free Software Foundation, either version 3 of the License, or
5− * (at your option) any later version.
6− *
7− * searx is distributed in the hope that it will be useful,
8− * but WITHOUT ANY WARRANTY; without even the implied warranty of
9− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10− * GNU Affero General Public License for more details.
11− *
12− * You should have received a copy of the GNU Affero General Public License
13− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14− *
15− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16− */
17−
18−$(document).ready(function(){
19− /**
20− * focus element if class="autofocus" and id="q"
21− */
22− $('#q.autofocus').focus();
23−
24− /**
25− * select full content on click if class="select-all-on-click"
26− */
27− $(".select-all-on-click").click(function () {
28− $(this).select();
29− });
30−
31− /**
32− * change text during btn-collapse click if possible
33− */
34− $('.btn-collapse').click(function() {
35− var btnTextCollapsed = $(this).data('btn-text-collapsed');
36− var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed');
37−
38− if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') {
39− if($(this).hasClass('collapsed')) {
40− new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed);
41− } else {
42− new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed);
43− }
44− $(this).html(new_html);
45− }
46− });
47−
48− /**
49− * change text during btn-toggle click if possible
50− */
51− $('.btn-toggle .btn').click(function() {
52− var btnClass = 'btn-' + $(this).data('btn-class');
53− var btnLabelDefault = $(this).data('btn-label-default');
54− var btnLabelToggled = $(this).data('btn-label-toggled');
55− if(btnLabelToggled !== '') {
56− if($(this).hasClass('btn-default')) {
57− new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled);
58− } else {
59− new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault);
60− }
61− $(this).html(new_html);
62− }
63− $(this).toggleClass(btnClass);
64− $(this).toggleClass('btn-default');
65− });
66−
67− /**
68− * change text during btn-toggle click if possible
69− */
70− $('.media-loader').click(function() {
71− var target = $(this).data('target');
72− var iframe_load = $(target + ' > iframe');
73− var srctest = iframe_load.attr('src');
74− if(srctest === undefined || srctest === false){
75− iframe_load.attr('src', iframe_load.data('src'));
76− }
77− });
78−
79− /**
80− * Select or deselect every categories on double clic
81− */
82− $(".btn-sm").dblclick(function() {
83− var btnClass = 'btn-' + $(this).data('btn-class'); // primary
84− if($(this).hasClass('btn-default')) {
85− $(".btn-sm > input").attr('checked', 'checked');
86− $(".btn-sm > input").prop("checked", true);
87− $(".btn-sm").addClass(btnClass);
88− $(".btn-sm").addClass('active');
89− $(".btn-sm").removeClass('btn-default');
90− } else {
91− $(".btn-sm > input").attr('checked', '');
92− $(".btn-sm > input").removeAttr('checked');
93− $(".btn-sm > input").checked = false;
94− $(".btn-sm").removeClass(btnClass);
95− $(".btn-sm").removeClass('active');
96− $(".btn-sm").addClass('btn-default');
97− }
98− });
99−});
1+/**
2+ * searx is free software: you can redistribute it and/or modify
3+ * it under the terms of the GNU Affero General Public License as published by
4+ * the Free Software Foundation, either version 3 of the License, or
5+ * (at your option) any later version.
6+ *
7+ * searx is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU Affero General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU Affero General Public License
13+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14+ *
15+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16+ */
17+
18+$(document).ready(function(){
19+ /**
20+ * focus element if class="autofocus" and id="q"
21+ */
22+ $('#q.autofocus').focus();
23+
24+ /**
25+ * select full content on click if class="select-all-on-click"
26+ */
27+ $(".select-all-on-click").click(function () {
28+ $(this).select();
29+ });
30+
31+ /**
32+ * change text during btn-collapse click if possible
33+ */
34+ $('.btn-collapse').click(function() {
35+ var btnTextCollapsed = $(this).data('btn-text-collapsed');
36+ var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed');
37+
38+ if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') {
39+ if($(this).hasClass('collapsed')) {
40+ new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed);
41+ } else {
42+ new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed);
43+ }
44+ $(this).html(new_html);
45+ }
46+ });
47+
48+ /**
49+ * change text during btn-toggle click if possible
50+ */
51+ $('.btn-toggle .btn').click(function() {
52+ var btnClass = 'btn-' + $(this).data('btn-class');
53+ var btnLabelDefault = $(this).data('btn-label-default');
54+ var btnLabelToggled = $(this).data('btn-label-toggled');
55+ if(btnLabelToggled !== '') {
56+ if($(this).hasClass('btn-default')) {
57+ new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled);
58+ } else {
59+ new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault);
60+ }
61+ $(this).html(new_html);
62+ }
63+ $(this).toggleClass(btnClass);
64+ $(this).toggleClass('btn-default');
65+ });
66+
67+ /**
68+ * change text during btn-toggle click if possible
69+ */
70+ $('.media-loader').click(function() {
71+ var target = $(this).data('target');
72+ var iframe_load = $(target + ' > iframe');
73+ var srctest = iframe_load.attr('src');
74+ if(srctest === undefined || srctest === false){
75+ iframe_load.attr('src', iframe_load.data('src'));
76+ }
77+ });
78+
79+ /**
80+ * Select or deselect every categories on double clic
81+ */
82+ $(".btn-sm").dblclick(function() {
83+ var btnClass = 'btn-' + $(this).data('btn-class'); // primary
84+ if($(this).hasClass('btn-default')) {
85+ $(".btn-sm > input").attr('checked', 'checked');
86+ $(".btn-sm > input").prop("checked", true);
87+ $(".btn-sm").addClass(btnClass);
88+ $(".btn-sm").addClass('active');
89+ $(".btn-sm").removeClass('btn-default');
90+ } else {
91+ $(".btn-sm > input").attr('checked', '');
92+ $(".btn-sm > input").removeAttr('checked');
93+ $(".btn-sm > input").checked = false;
94+ $(".btn-sm").removeClass(btnClass);
95+ $(".btn-sm").removeClass('active');
96+ $(".btn-sm").addClass('btn-default');
97+ }
98+ });
99+});
searx/static/themes/oscar/js/searx_src/leaflet_map.js+167−167
@@ -1,167 +1,167 @@
1−/**
2− * searx is free software: you can redistribute it and/or modify
3− * it under the terms of the GNU Affero General Public License as published by
4− * the Free Software Foundation, either version 3 of the License, or
5− * (at your option) any later version.
6− *
7− * searx is distributed in the hope that it will be useful,
8− * but WITHOUT ANY WARRANTY; without even the implied warranty of
9− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10− * GNU Affero General Public License for more details.
11− *
12− * You should have received a copy of the GNU Affero General Public License
13− * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14− *
15− * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16− */
17−
18−$(document).ready(function(){
19− $(".searx_overpass_request").on( "click", function( event ) {
20− var overpass_url = "https://overpass-api.de/api/interpreter?data=";
21− var query_start = overpass_url + "[out:json][timeout:25];(";
22− var query_end = ");out meta;";
23−
24− var osm_id = $(this).data('osm-id');
25− var osm_type = $(this).data('osm-type');
26− var result_table = $(this).data('result-table');
27− var result_table_loadicon = "#" + $(this).data('result-table-loadicon');
28−
29− // tags which can be ignored
30− var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ];
31−
32− if(osm_id && osm_type && result_table) {
33− result_table = "#" + result_table;
34− var query = null;
35− switch(osm_type) {
36− case 'node':
37− query = query_start + "node(" + osm_id + ");" + query_end;
38− break;
39− case 'way':
40− query = query_start + "way(" + osm_id + ");" + query_end;
41− break;
42− case 'relation':
43− query = query_start + "relation(" + osm_id + ");" + query_end;
44− break;
45− default:
46− break;
47− }
48− if(query) {
49− //alert(query);
50− var ajaxRequest = $.ajax( query )
51− .done(function( html) {
52− if(html && html.elements && html.elements[0]) {
53− var element = html.elements[0];
54− var newHtml = $(result_table).html();
55− for (var row in element.tags) {
56− if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) {
57− newHtml += "<tr><td>" + row + "</td><td>";
58− switch(row) {
59− case "phone":
60− case "fax":
61− newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>";
62− break;
63− case "email":
64− newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>";
65− break;
66− case "website":
67− case "url":
68− newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>";
69− break;
70− case "wikidata":
71− newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>";
72− break;
73− case "wikipedia":
74− if(element.tags[row].indexOf(":") != -1) {
75− newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>";
76− break;
77− }
78− /* jshint ignore:start */
79− default:
80− /* jshint ignore:end */
81− newHtml += element.tags[row];
82− break;
83− }
84− newHtml += "</td></tr>";
85− }
86− }
87− $(result_table).html(newHtml);
88− $(result_table).removeClass('hidden');
89− $(result_table_loadicon).addClass('hidden');
90− }
91− })
92− .fail(function() {
93− $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>");
94− });
95− }
96− }
97−
98− // this event occour only once per element
99− $( this ).off( event );
100− });
101−
102− $(".searx_init_map").on( "click", function( event ) {
103− var leaflet_target = $(this).data('leaflet-target');
104− var map_lon = $(this).data('map-lon');
105− var map_lat = $(this).data('map-lat');
106− var map_zoom = $(this).data('map-zoom');
107− var map_boundingbox = $(this).data('map-boundingbox');
108− var map_geojson = $(this).data('map-geojson');
109−
110− require(['leaflet-0.7.3.min'], function(leaflet) {
111− if(map_boundingbox) {
112− southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
113− northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
114− map_bounds = L.latLngBounds(southWest, northEast);
115− }
116−
117− // TODO hack
118− // change default imagePath
119− L.Icon.Default.imagePath = "./static/themes/oscar/img/map";
120−
121− // init map
122− var map = L.map(leaflet_target);
123−
124− // create the tile layer with correct attribution
125− var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
126− var osmMapnikAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
127− var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib});
128−
129− var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png';
130− var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
131− var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
132−
133− // init map view
134− if(map_bounds) {
135− // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021
136− setTimeout(function () {
137− map.fitBounds(map_bounds, {
138− maxZoom:17
139− });
140− }, 0);
141− } else if (map_lon && map_lat) {
142− if(map_zoom)
143− map.setView(new L.LatLng(map_lat, map_lon),map_zoom);
144− else
145− map.setView(new L.LatLng(map_lat, map_lon),8);
146− }
147−
148− map.addLayer(osmMapnik);
149−
150− var baseLayers = {
151− "OSM Mapnik": osmMapnik/*,
152− "OSM Wikimedia": osmWikimedia*/
153− };
154−
155− L.control.layers(baseLayers).addTo(map);
156−
157−
158− if(map_geojson)
159− L.geoJson(map_geojson).addTo(map);
160− /*else if(map_bounds)
161− L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/
162− });
163−
164− // this event occour only once per element
165− $( this ).off( event );
166− });
167−});
1+/**
2+ * searx is free software: you can redistribute it and/or modify
3+ * it under the terms of the GNU Affero General Public License as published by
4+ * the Free Software Foundation, either version 3 of the License, or
5+ * (at your option) any later version.
6+ *
7+ * searx is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU Affero General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU Affero General Public License
13+ * along with searx. If not, see < http://www.gnu.org/licenses/ >.
14+ *
15+ * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
16+ */
17+
18+$(document).ready(function(){
19+ $(".searx_overpass_request").on( "click", function( event ) {
20+ var overpass_url = "https://overpass-api.de/api/interpreter?data=";
21+ var query_start = overpass_url + "[out:json][timeout:25];(";
22+ var query_end = ");out meta;";
23+
24+ var osm_id = $(this).data('osm-id');
25+ var osm_type = $(this).data('osm-type');
26+ var result_table = $(this).data('result-table');
27+ var result_table_loadicon = "#" + $(this).data('result-table-loadicon');
28+
29+ // tags which can be ignored
30+ var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ];
31+
32+ if(osm_id && osm_type && result_table) {
33+ result_table = "#" + result_table;
34+ var query = null;
35+ switch(osm_type) {
36+ case 'node':
37+ query = query_start + "node(" + osm_id + ");" + query_end;
38+ break;
39+ case 'way':
40+ query = query_start + "way(" + osm_id + ");" + query_end;
41+ break;
42+ case 'relation':
43+ query = query_start + "relation(" + osm_id + ");" + query_end;
44+ break;
45+ default:
46+ break;
47+ }
48+ if(query) {
49+ //alert(query);
50+ var ajaxRequest = $.ajax( query )
51+ .done(function( html) {
52+ if(html && html.elements && html.elements[0]) {
53+ var element = html.elements[0];
54+ var newHtml = $(result_table).html();
55+ for (var row in element.tags) {
56+ if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) {
57+ newHtml += "<tr><td>" + row + "</td><td>";
58+ switch(row) {
59+ case "phone":
60+ case "fax":
61+ newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>";
62+ break;
63+ case "email":
64+ newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>";
65+ break;
66+ case "website":
67+ case "url":
68+ newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>";
69+ break;
70+ case "wikidata":
71+ newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>";
72+ break;
73+ case "wikipedia":
74+ if(element.tags[row].indexOf(":") != -1) {
75+ newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>";
76+ break;
77+ }
78+ /* jshint ignore:start */
79+ default:
80+ /* jshint ignore:end */
81+ newHtml += element.tags[row];
82+ break;
83+ }
84+ newHtml += "</td></tr>";
85+ }
86+ }
87+ $(result_table).html(newHtml);
88+ $(result_table).removeClass('hidden');
89+ $(result_table_loadicon).addClass('hidden');
90+ }
91+ })
92+ .fail(function() {
93+ $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>");
94+ });
95+ }
96+ }
97+
98+ // this event occour only once per element
99+ $( this ).off( event );
100+ });
101+
102+ $(".searx_init_map").on( "click", function( event ) {
103+ var leaflet_target = $(this).data('leaflet-target');
104+ var map_lon = $(this).data('map-lon');
105+ var map_lat = $(this).data('map-lat');
106+ var map_zoom = $(this).data('map-zoom');
107+ var map_boundingbox = $(this).data('map-boundingbox');
108+ var map_geojson = $(this).data('map-geojson');
109+
110+ require(['leaflet-0.7.3.min'], function(leaflet) {
111+ if(map_boundingbox) {
112+ southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
113+ northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
114+ map_bounds = L.latLngBounds(southWest, northEast);
115+ }
116+
117+ // TODO hack
118+ // change default imagePath
119+ L.Icon.Default.imagePath = "./static/themes/oscar/img/map";
120+
121+ // init map
122+ var map = L.map(leaflet_target);
123+
124+ // create the tile layer with correct attribution
125+ var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
126+ var osmMapnikAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
127+ var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib});
128+
129+ var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png';
130+ var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
131+ var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
132+
133+ // init map view
134+ if(map_bounds) {
135+ // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021
136+ setTimeout(function () {
137+ map.fitBounds(map_bounds, {
138+ maxZoom:17
139+ });
140+ }, 0);
141+ } else if (map_lon && map_lat) {
142+ if(map_zoom)
143+ map.setView(new L.LatLng(map_lat, map_lon),map_zoom);
144+ else
145+ map.setView(new L.LatLng(map_lat, map_lon),8);
146+ }
147+
148+ map.addLayer(osmMapnik);
149+
150+ var baseLayers = {
151+ "OSM Mapnik": osmMapnik/*,
152+ "OSM Wikimedia": osmWikimedia*/
153+ };
154+
155+ L.control.layers(baseLayers).addTo(map);
156+
157+
158+ if(map_geojson)
159+ L.geoJson(map_geojson).addTo(map);
160+ /*else if(map_bounds)
161+ L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/
162+ });
163+
164+ // this event occour only once per element
165+ $( this ).off( event );
166+ });
167+});
searx/static/themes/oscar/less/logicodev-dark/oscar.less+2−2
@@ -109,7 +109,7 @@ ul.nav li a {
109109
110110.btn:hover {
111111 color:#444 !important;
112− background-color: #BBB !important;
112+ background-color: #BBB !important;
113113}
114114
115115.btn-primary.active {
@@ -221,7 +221,7 @@ p.btn.btn-default{
221221}
222222
223223.table-hover > tbody > tr:hover > td, .table-hover > tbody > tr:hover > th {
224− background: rgb(102, 105, 110) !important;
224+ background: rgb(102, 105, 110) !important;
225225}
226226
227227.btn-success {
searx/static/themes/oscar/less/logicodev/code.less+1−3
@@ -78,7 +78,7 @@ pre, code{
7878 user-select: none;
7979 cursor: default;
8080 color: #556366;
81−
81+
8282 &::selection {
8383 background: transparent; /* WebKit/Blink Browsers */
8484 }
@@ -99,5 +99,3 @@ pre, code{
9999.highlight {
100100 font-weight: 700;
101101}
102−
103−
searx/static/themes/oscar/less/logicodev/infobox.less+1−1
@@ -30,7 +30,7 @@
3030 table-layout: fixed;
3131
3232 }
33−
33+
3434 .infobox_part:last-child {
3535 margin-bottom: 0;
3636 }
searx/static/themes/oscar/less/logicodev/navbar.less−1
@@ -28,4 +28,3 @@
2828 width: 80%;
2929 }
3030}
31−
searx/static/themes/oscar/less/pointhi/code.less+1−1
@@ -69,7 +69,7 @@
6969 -ms-user-select: none;
7070 user-select: none;
7171 cursor: default;
72−
72+
7373 &::selection {
7474 background: transparent; /* WebKit/Blink Browsers */
7575 }
searx/static/themes/oscar/less/pointhi/infobox.less+1−1
@@ -4,7 +4,7 @@
44 word-wrap: break-word;
55 table-layout: fixed;
66 }
7−
7+
88 .infobox_part:last-child {
99 margin-bottom: 0;
1010 }
searx/static/themes/simple/leaflet/leaflet.css+636−636
@@ -1,636 +1,636 @@
1−/* required styles */
2−
3−.leaflet-pane,
4−.leaflet-tile,
5−.leaflet-marker-icon,
6−.leaflet-marker-shadow,
7−.leaflet-tile-container,
8−.leaflet-pane > svg,
9−.leaflet-pane > canvas,
10−.leaflet-zoom-box,
11−.leaflet-image-layer,
12−.leaflet-layer {
13− position: absolute;
14− left: 0;
15− top: 0;
16− }
17−.leaflet-container {
18− overflow: hidden;
19− }
20−.leaflet-tile,
21−.leaflet-marker-icon,
22−.leaflet-marker-shadow {
23− -webkit-user-select: none;
24− -moz-user-select: none;
25− user-select: none;
26− -webkit-user-drag: none;
27− }
28−/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
29−.leaflet-safari .leaflet-tile {
30− image-rendering: -webkit-optimize-contrast;
31− }
32−/* hack that prevents hw layers "stretching" when loading new tiles */
33−.leaflet-safari .leaflet-tile-container {
34− width: 1600px;
35− height: 1600px;
36− -webkit-transform-origin: 0 0;
37− }
38−.leaflet-marker-icon,
39−.leaflet-marker-shadow {
40− display: block;
41− }
42−/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
43−/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
44−.leaflet-container .leaflet-overlay-pane svg,
45−.leaflet-container .leaflet-marker-pane img,
46−.leaflet-container .leaflet-shadow-pane img,
47−.leaflet-container .leaflet-tile-pane img,
48−.leaflet-container img.leaflet-image-layer {
49− max-width: none !important;
50− max-height: none !important;
51− }
52−
53−.leaflet-container.leaflet-touch-zoom {
54− -ms-touch-action: pan-x pan-y;
55− touch-action: pan-x pan-y;
56− }
57−.leaflet-container.leaflet-touch-drag {
58− -ms-touch-action: pinch-zoom;
59− /* Fallback for FF which doesn't support pinch-zoom */
60− touch-action: none;
61− touch-action: pinch-zoom;
62−}
63−.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
64− -ms-touch-action: none;
65− touch-action: none;
66−}
67−.leaflet-container {
68− -webkit-tap-highlight-color: transparent;
69−}
70−.leaflet-container a {
71− -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
72−}
73−.leaflet-tile {
74− filter: inherit;
75− visibility: hidden;
76− }
77−.leaflet-tile-loaded {
78− visibility: inherit;
79− }
80−.leaflet-zoom-box {
81− width: 0;
82− height: 0;
83− -moz-box-sizing: border-box;
84− box-sizing: border-box;
85− z-index: 800;
86− }
87−/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
88−.leaflet-overlay-pane svg {
89− -moz-user-select: none;
90− }
91−
92−.leaflet-pane { z-index: 400; }
93−
94−.leaflet-tile-pane { z-index: 200; }
95−.leaflet-overlay-pane { z-index: 400; }
96−.leaflet-shadow-pane { z-index: 500; }
97−.leaflet-marker-pane { z-index: 600; }
98−.leaflet-tooltip-pane { z-index: 650; }
99−.leaflet-popup-pane { z-index: 700; }
100−
101−.leaflet-map-pane canvas { z-index: 100; }
102−.leaflet-map-pane svg { z-index: 200; }
103−
104−.leaflet-vml-shape {
105− width: 1px;
106− height: 1px;
107− }
108−.lvml {
109− behavior: url(#default#VML);
110− display: inline-block;
111− position: absolute;
112− }
113−
114−
115−/* control positioning */
116−
117−.leaflet-control {
118− position: relative;
119− z-index: 800;
120− pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
121− pointer-events: auto;
122− }
123−.leaflet-top,
124−.leaflet-bottom {
125− position: absolute;
126− z-index: 1000;
127− pointer-events: none;
128− }
129−.leaflet-top {
130− top: 0;
131− }
132−.leaflet-right {
133− right: 0;
134− }
135−.leaflet-bottom {
136− bottom: 0;
137− }
138−.leaflet-left {
139− left: 0;
140− }
141−.leaflet-control {
142− float: left;
143− clear: both;
144− }
145−.leaflet-right .leaflet-control {
146− float: right;
147− }
148−.leaflet-top .leaflet-control {
149− margin-top: 10px;
150− }
151−.leaflet-bottom .leaflet-control {
152− margin-bottom: 10px;
153− }
154−.leaflet-left .leaflet-control {
155− margin-left: 10px;
156− }
157−.leaflet-right .leaflet-control {
158− margin-right: 10px;
159− }
160−
161−
162−/* zoom and fade animations */
163−
164−.leaflet-fade-anim .leaflet-tile {
165− will-change: opacity;
166− }
167−.leaflet-fade-anim .leaflet-popup {
168− opacity: 0;
169− -webkit-transition: opacity 0.2s linear;
170− -moz-transition: opacity 0.2s linear;
171− -o-transition: opacity 0.2s linear;
172− transition: opacity 0.2s linear;
173− }
174−.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
175− opacity: 1;
176− }
177−.leaflet-zoom-animated {
178− -webkit-transform-origin: 0 0;
179− -ms-transform-origin: 0 0;
180− transform-origin: 0 0;
181− }
182−.leaflet-zoom-anim .leaflet-zoom-animated {
183− will-change: transform;
184− }
185−.leaflet-zoom-anim .leaflet-zoom-animated {
186− -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
187− -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
188− -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
189− transition: transform 0.25s cubic-bezier(0,0,0.25,1);
190− }
191−.leaflet-zoom-anim .leaflet-tile,
192−.leaflet-pan-anim .leaflet-tile {
193− -webkit-transition: none;
194− -moz-transition: none;
195− -o-transition: none;
196− transition: none;
197− }
198−
199−.leaflet-zoom-anim .leaflet-zoom-hide {
200− visibility: hidden;
201− }
202−
203−
204−/* cursors */
205−
206−.leaflet-interactive {
207− cursor: pointer;
208− }
209−.leaflet-grab {
210− cursor: -webkit-grab;
211− cursor: -moz-grab;
212− }
213−.leaflet-crosshair,
214−.leaflet-crosshair .leaflet-interactive {
215− cursor: crosshair;
216− }
217−.leaflet-popup-pane,
218−.leaflet-control {
219− cursor: auto;
220− }
221−.leaflet-dragging .leaflet-grab,
222−.leaflet-dragging .leaflet-grab .leaflet-interactive,
223−.leaflet-dragging .leaflet-marker-draggable {
224− cursor: move;
225− cursor: -webkit-grabbing;
226− cursor: -moz-grabbing;
227− }
228−
229−/* marker & overlays interactivity */
230−.leaflet-marker-icon,
231−.leaflet-marker-shadow,
232−.leaflet-image-layer,
233−.leaflet-pane > svg path,
234−.leaflet-tile-container {
235− pointer-events: none;
236− }
237−
238−.leaflet-marker-icon.leaflet-interactive,
239−.leaflet-image-layer.leaflet-interactive,
240−.leaflet-pane > svg path.leaflet-interactive {
241− pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
242− pointer-events: auto;
243− }
244−
245−/* visual tweaks */
246−
247−.leaflet-container {
248− background: #ddd;
249− outline: 0;
250− }
251−.leaflet-container a {
252− color: #0078A8;
253− }
254−.leaflet-container a.leaflet-active {
255− outline: 2px solid orange;
256− }
257−.leaflet-zoom-box {
258− border: 2px dotted #38f;
259− background: rgba(255,255,255,0.5);
260− }
261−
262−
263−/* general typography */
264−.leaflet-container {
265− font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
266− }
267−
268−
269−/* general toolbar styles */
270−
271−.leaflet-bar {
272− box-shadow: 0 1px 5px rgba(0,0,0,0.65);
273− border-radius: 4px;
274− }
275−.leaflet-bar a,
276−.leaflet-bar a:hover {
277− background-color: #fff;
278− border-bottom: 1px solid #ccc;
279− width: 26px;
280− height: 26px;
281− line-height: 26px;
282− display: block;
283− text-align: center;
284− text-decoration: none;
285− color: black;
286− }
287−.leaflet-bar a,
288−.leaflet-control-layers-toggle {
289− background-position: 50% 50%;
290− background-repeat: no-repeat;
291− display: block;
292− }
293−.leaflet-bar a:hover {
294− background-color: #f4f4f4;
295− }
296−.leaflet-bar a:first-child {
297− border-top-left-radius: 4px;
298− border-top-right-radius: 4px;
299− }
300−.leaflet-bar a:last-child {
301− border-bottom-left-radius: 4px;
302− border-bottom-right-radius: 4px;
303− border-bottom: none;
304− }
305−.leaflet-bar a.leaflet-disabled {
306− cursor: default;
307− background-color: #f4f4f4;
308− color: #bbb;
309− }
310−
311−.leaflet-touch .leaflet-bar a {
312− width: 30px;
313− height: 30px;
314− line-height: 30px;
315− }
316−.leaflet-touch .leaflet-bar a:first-child {
317− border-top-left-radius: 2px;
318− border-top-right-radius: 2px;
319− }
320−.leaflet-touch .leaflet-bar a:last-child {
321− border-bottom-left-radius: 2px;
322− border-bottom-right-radius: 2px;
323− }
324−
325−/* zoom control */
326−
327−.leaflet-control-zoom-in,
328−.leaflet-control-zoom-out {
329− font: bold 18px 'Lucida Console', Monaco, monospace;
330− text-indent: 1px;
331− }
332−
333−.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
334− font-size: 22px;
335− }
336−
337−
338−/* layers control */
339−
340−.leaflet-control-layers {
341− box-shadow: 0 1px 5px rgba(0,0,0,0.4);
342− background: #fff;
343− border-radius: 5px;
344− }
345−.leaflet-control-layers-toggle {
346− background-image: url(images/layers.png);
347− width: 36px;
348− height: 36px;
349− }
350−.leaflet-retina .leaflet-control-layers-toggle {
351− background-image: url(images/layers-2x.png);
352− background-size: 26px 26px;
353− }
354−.leaflet-touch .leaflet-control-layers-toggle {
355− width: 44px;
356− height: 44px;
357− }
358−.leaflet-control-layers .leaflet-control-layers-list,
359−.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
360− display: none;
361− }
362−.leaflet-control-layers-expanded .leaflet-control-layers-list {
363− display: block;
364− position: relative;
365− }
366−.leaflet-control-layers-expanded {
367− padding: 6px 10px 6px 6px;
368− color: #333;
369− background: #fff;
370− }
371−.leaflet-control-layers-scrollbar {
372− overflow-y: scroll;
373− overflow-x: hidden;
374− padding-right: 5px;
375− }
376−.leaflet-control-layers-selector {
377− margin-top: 2px;
378− position: relative;
379− top: 1px;
380− }
381−.leaflet-control-layers label {
382− display: block;
383− }
384−.leaflet-control-layers-separator {
385− height: 0;
386− border-top: 1px solid #ddd;
387− margin: 5px -10px 5px -6px;
388− }
389−
390−/* Default icon URLs */
391−.leaflet-default-icon-path {
392− background-image: url(images/marker-icon.png);
393− }
394−
395−
396−/* attribution and scale controls */
397−
398−.leaflet-container .leaflet-control-attribution {
399− background: #fff;
400− background: rgba(255, 255, 255, 0.7);
401− margin: 0;
402− }
403−.leaflet-control-attribution,
404−.leaflet-control-scale-line {
405− padding: 0 5px;
406− color: #333;
407− }
408−.leaflet-control-attribution a {
409− text-decoration: none;
410− }
411−.leaflet-control-attribution a:hover {
412− text-decoration: underline;
413− }
414−.leaflet-container .leaflet-control-attribution,
415−.leaflet-container .leaflet-control-scale {
416− font-size: 11px;
417− }
418−.leaflet-left .leaflet-control-scale {
419− margin-left: 5px;
420− }
421−.leaflet-bottom .leaflet-control-scale {
422− margin-bottom: 5px;
423− }
424−.leaflet-control-scale-line {
425− border: 2px solid #777;
426− border-top: none;
427− line-height: 1.1;
428− padding: 2px 5px 1px;
429− font-size: 11px;
430− white-space: nowrap;
431− overflow: hidden;
432− -moz-box-sizing: border-box;
433− box-sizing: border-box;
434−
435− background: #fff;
436− background: rgba(255, 255, 255, 0.5);
437− }
438−.leaflet-control-scale-line:not(:first-child) {
439− border-top: 2px solid #777;
440− border-bottom: none;
441− margin-top: -2px;
442− }
443−.leaflet-control-scale-line:not(:first-child):not(:last-child) {
444− border-bottom: 2px solid #777;
445− }
446−
447−.leaflet-touch .leaflet-control-attribution,
448−.leaflet-touch .leaflet-control-layers,
449−.leaflet-touch .leaflet-bar {
450− box-shadow: none;
451− }
452−.leaflet-touch .leaflet-control-layers,
453−.leaflet-touch .leaflet-bar {
454− border: 2px solid rgba(0,0,0,0.2);
455− background-clip: padding-box;
456− }
457−
458−
459−/* popup */
460−
461−.leaflet-popup {
462− position: absolute;
463− text-align: center;
464− margin-bottom: 20px;
465− }
466−.leaflet-popup-content-wrapper {
467− padding: 1px;
468− text-align: left;
469− border-radius: 12px;
470− }
471−.leaflet-popup-content {
472− margin: 13px 19px;
473− line-height: 1.4;
474− }
475−.leaflet-popup-content p {
476− margin: 18px 0;
477− }
478−.leaflet-popup-tip-container {
479− width: 40px;
480− height: 20px;
481− position: absolute;
482− left: 50%;
483− margin-left: -20px;
484− overflow: hidden;
485− pointer-events: none;
486− }
487−.leaflet-popup-tip {
488− width: 17px;
489− height: 17px;
490− padding: 1px;
491−
492− margin: -10px auto 0;
493−
494− -webkit-transform: rotate(45deg);
495− -moz-transform: rotate(45deg);
496− -ms-transform: rotate(45deg);
497− -o-transform: rotate(45deg);
498− transform: rotate(45deg);
499− }
500−.leaflet-popup-content-wrapper,
501−.leaflet-popup-tip {
502− background: white;
503− color: #333;
504− box-shadow: 0 3px 14px rgba(0,0,0,0.4);
505− }
506−.leaflet-container a.leaflet-popup-close-button {
507− position: absolute;
508− top: 0;
509− right: 0;
510− padding: 4px 4px 0 0;
511− border: none;
512− text-align: center;
513− width: 18px;
514− height: 14px;
515− font: 16px/14px Tahoma, Verdana, sans-serif;
516− color: #c3c3c3;
517− text-decoration: none;
518− font-weight: bold;
519− background: transparent;
520− }
521−.leaflet-container a.leaflet-popup-close-button:hover {
522− color: #999;
523− }
524−.leaflet-popup-scrolled {
525− overflow: auto;
526− border-bottom: 1px solid #ddd;
527− border-top: 1px solid #ddd;
528− }
529−
530−.leaflet-oldie .leaflet-popup-content-wrapper {
531− zoom: 1;
532− }
533−.leaflet-oldie .leaflet-popup-tip {
534− width: 24px;
535− margin: 0 auto;
536−
537− -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
538− filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
539− }
540−.leaflet-oldie .leaflet-popup-tip-container {
541− margin-top: -1px;
542− }
543−
544−.leaflet-oldie .leaflet-control-zoom,
545−.leaflet-oldie .leaflet-control-layers,
546−.leaflet-oldie .leaflet-popup-content-wrapper,
547−.leaflet-oldie .leaflet-popup-tip {
548− border: 1px solid #999;
549− }
550−
551−
552−/* div icon */
553−
554−.leaflet-div-icon {
555− background: #fff;
556− border: 1px solid #666;
557− }
558−
559−
560−/* Tooltip */
561−/* Base styles for the element that has a tooltip */
562−.leaflet-tooltip {
563− position: absolute;
564− padding: 6px;
565− background-color: #fff;
566− border: 1px solid #fff;
567− border-radius: 3px;
568− color: #222;
569− white-space: nowrap;
570− -webkit-user-select: none;
571− -moz-user-select: none;
572− -ms-user-select: none;
573− user-select: none;
574− pointer-events: none;
575− box-shadow: 0 1px 3px rgba(0,0,0,0.4);
576− }
577−.leaflet-tooltip.leaflet-clickable {
578− cursor: pointer;
579− pointer-events: auto;
580− }
581−.leaflet-tooltip-top:before,
582−.leaflet-tooltip-bottom:before,
583−.leaflet-tooltip-left:before,
584−.leaflet-tooltip-right:before {
585− position: absolute;
586− pointer-events: none;
587− border: 6px solid transparent;
588− background: transparent;
589− content: "";
590− }
591−
592−/* Directions */
593−
594−.leaflet-tooltip-bottom {
595− margin-top: 6px;
596−}
597−.leaflet-tooltip-top {
598− margin-top: -6px;
599−}
600−.leaflet-tooltip-bottom:before,
601−.leaflet-tooltip-top:before {
602− left: 50%;
603− margin-left: -6px;
604− }
605−.leaflet-tooltip-top:before {
606− bottom: 0;
607− margin-bottom: -12px;
608− border-top-color: #fff;
609− }
610−.leaflet-tooltip-bottom:before {
611− top: 0;
612− margin-top: -12px;
613− margin-left: -6px;
614− border-bottom-color: #fff;
615− }
616−.leaflet-tooltip-left {
617− margin-left: -6px;
618−}
619−.leaflet-tooltip-right {
620− margin-left: 6px;
621−}
622−.leaflet-tooltip-left:before,
623−.leaflet-tooltip-right:before {
624− top: 50%;
625− margin-top: -6px;
626− }
627−.leaflet-tooltip-left:before {
628− right: 0;
629− margin-right: -12px;
630− border-left-color: #fff;
631− }
632−.leaflet-tooltip-right:before {
633− left: 0;
634− margin-left: -12px;
635− border-right-color: #fff;
636− }
1+/* required styles */
2+
3+.leaflet-pane,
4+.leaflet-tile,
5+.leaflet-marker-icon,
6+.leaflet-marker-shadow,
7+.leaflet-tile-container,
8+.leaflet-pane > svg,
9+.leaflet-pane > canvas,
10+.leaflet-zoom-box,
11+.leaflet-image-layer,
12+.leaflet-layer {
13+ position: absolute;
14+ left: 0;
15+ top: 0;
16+ }
17+.leaflet-container {
18+ overflow: hidden;
19+ }
20+.leaflet-tile,
21+.leaflet-marker-icon,
22+.leaflet-marker-shadow {
23+ -webkit-user-select: none;
24+ -moz-user-select: none;
25+ user-select: none;
26+ -webkit-user-drag: none;
27+ }
28+/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
29+.leaflet-safari .leaflet-tile {
30+ image-rendering: -webkit-optimize-contrast;
31+ }
32+/* hack that prevents hw layers "stretching" when loading new tiles */
33+.leaflet-safari .leaflet-tile-container {
34+ width: 1600px;
35+ height: 1600px;
36+ -webkit-transform-origin: 0 0;
37+ }
38+.leaflet-marker-icon,
39+.leaflet-marker-shadow {
40+ display: block;
41+ }
42+/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
43+/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
44+.leaflet-container .leaflet-overlay-pane svg,
45+.leaflet-container .leaflet-marker-pane img,
46+.leaflet-container .leaflet-shadow-pane img,
47+.leaflet-container .leaflet-tile-pane img,
48+.leaflet-container img.leaflet-image-layer {
49+ max-width: none !important;
50+ max-height: none !important;
51+ }
52+
53+.leaflet-container.leaflet-touch-zoom {
54+ -ms-touch-action: pan-x pan-y;
55+ touch-action: pan-x pan-y;
56+ }
57+.leaflet-container.leaflet-touch-drag {
58+ -ms-touch-action: pinch-zoom;
59+ /* Fallback for FF which doesn't support pinch-zoom */
60+ touch-action: none;
61+ touch-action: pinch-zoom;
62+}
63+.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
64+ -ms-touch-action: none;
65+ touch-action: none;
66+}
67+.leaflet-container {
68+ -webkit-tap-highlight-color: transparent;
69+}
70+.leaflet-container a {
71+ -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
72+}
73+.leaflet-tile {
74+ filter: inherit;
75+ visibility: hidden;
76+ }
77+.leaflet-tile-loaded {
78+ visibility: inherit;
79+ }
80+.leaflet-zoom-box {
81+ width: 0;
82+ height: 0;
83+ -moz-box-sizing: border-box;
84+ box-sizing: border-box;
85+ z-index: 800;
86+ }
87+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
88+.leaflet-overlay-pane svg {
89+ -moz-user-select: none;
90+ }
91+
92+.leaflet-pane { z-index: 400; }
93+
94+.leaflet-tile-pane { z-index: 200; }
95+.leaflet-overlay-pane { z-index: 400; }
96+.leaflet-shadow-pane { z-index: 500; }
97+.leaflet-marker-pane { z-index: 600; }
98+.leaflet-tooltip-pane { z-index: 650; }
99+.leaflet-popup-pane { z-index: 700; }
100+
101+.leaflet-map-pane canvas { z-index: 100; }
102+.leaflet-map-pane svg { z-index: 200; }
103+
104+.leaflet-vml-shape {
105+ width: 1px;
106+ height: 1px;
107+ }
108+.lvml {
109+ behavior: url(#default#VML);
110+ display: inline-block;
111+ position: absolute;
112+ }
113+
114+
115+/* control positioning */
116+
117+.leaflet-control {
118+ position: relative;
119+ z-index: 800;
120+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
121+ pointer-events: auto;
122+ }
123+.leaflet-top,
124+.leaflet-bottom {
125+ position: absolute;
126+ z-index: 1000;
127+ pointer-events: none;
128+ }
129+.leaflet-top {
130+ top: 0;
131+ }
132+.leaflet-right {
133+ right: 0;
134+ }
135+.leaflet-bottom {
136+ bottom: 0;
137+ }
138+.leaflet-left {
139+ left: 0;
140+ }
141+.leaflet-control {
142+ float: left;
143+ clear: both;
144+ }
145+.leaflet-right .leaflet-control {
146+ float: right;
147+ }
148+.leaflet-top .leaflet-control {
149+ margin-top: 10px;
150+ }
151+.leaflet-bottom .leaflet-control {
152+ margin-bottom: 10px;
153+ }
154+.leaflet-left .leaflet-control {
155+ margin-left: 10px;
156+ }
157+.leaflet-right .leaflet-control {
158+ margin-right: 10px;
159+ }
160+
161+
162+/* zoom and fade animations */
163+
164+.leaflet-fade-anim .leaflet-tile {
165+ will-change: opacity;
166+ }
167+.leaflet-fade-anim .leaflet-popup {
168+ opacity: 0;
169+ -webkit-transition: opacity 0.2s linear;
170+ -moz-transition: opacity 0.2s linear;
171+ -o-transition: opacity 0.2s linear;
172+ transition: opacity 0.2s linear;
173+ }
174+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
175+ opacity: 1;
176+ }
177+.leaflet-zoom-animated {
178+ -webkit-transform-origin: 0 0;
179+ -ms-transform-origin: 0 0;
180+ transform-origin: 0 0;
181+ }
182+.leaflet-zoom-anim .leaflet-zoom-animated {
183+ will-change: transform;
184+ }
185+.leaflet-zoom-anim .leaflet-zoom-animated {
186+ -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
187+ -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
188+ -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
189+ transition: transform 0.25s cubic-bezier(0,0,0.25,1);
190+ }
191+.leaflet-zoom-anim .leaflet-tile,
192+.leaflet-pan-anim .leaflet-tile {
193+ -webkit-transition: none;
194+ -moz-transition: none;
195+ -o-transition: none;
196+ transition: none;
197+ }
198+
199+.leaflet-zoom-anim .leaflet-zoom-hide {
200+ visibility: hidden;
201+ }
202+
203+
204+/* cursors */
205+
206+.leaflet-interactive {
207+ cursor: pointer;
208+ }
209+.leaflet-grab {
210+ cursor: -webkit-grab;
211+ cursor: -moz-grab;
212+ }
213+.leaflet-crosshair,
214+.leaflet-crosshair .leaflet-interactive {
215+ cursor: crosshair;
216+ }
217+.leaflet-popup-pane,
218+.leaflet-control {
219+ cursor: auto;
220+ }
221+.leaflet-dragging .leaflet-grab,
222+.leaflet-dragging .leaflet-grab .leaflet-interactive,
223+.leaflet-dragging .leaflet-marker-draggable {
224+ cursor: move;
225+ cursor: -webkit-grabbing;
226+ cursor: -moz-grabbing;
227+ }
228+
229+/* marker & overlays interactivity */
230+.leaflet-marker-icon,
231+.leaflet-marker-shadow,
232+.leaflet-image-layer,
233+.leaflet-pane > svg path,
234+.leaflet-tile-container {
235+ pointer-events: none;
236+ }
237+
238+.leaflet-marker-icon.leaflet-interactive,
239+.leaflet-image-layer.leaflet-interactive,
240+.leaflet-pane > svg path.leaflet-interactive {
241+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
242+ pointer-events: auto;
243+ }
244+
245+/* visual tweaks */
246+
247+.leaflet-container {
248+ background: #ddd;
249+ outline: 0;
250+ }
251+.leaflet-container a {
252+ color: #0078A8;
253+ }
254+.leaflet-container a.leaflet-active {
255+ outline: 2px solid orange;
256+ }
257+.leaflet-zoom-box {
258+ border: 2px dotted #38f;
259+ background: rgba(255,255,255,0.5);
260+ }
261+
262+
263+/* general typography */
264+.leaflet-container {
265+ font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
266+ }
267+
268+
269+/* general toolbar styles */
270+
271+.leaflet-bar {
272+ box-shadow: 0 1px 5px rgba(0,0,0,0.65);
273+ border-radius: 4px;
274+ }
275+.leaflet-bar a,
276+.leaflet-bar a:hover {
277+ background-color: #fff;
278+ border-bottom: 1px solid #ccc;
279+ width: 26px;
280+ height: 26px;
281+ line-height: 26px;
282+ display: block;
283+ text-align: center;
284+ text-decoration: none;
285+ color: black;
286+ }
287+.leaflet-bar a,
288+.leaflet-control-layers-toggle {
289+ background-position: 50% 50%;
290+ background-repeat: no-repeat;
291+ display: block;
292+ }
293+.leaflet-bar a:hover {
294+ background-color: #f4f4f4;
295+ }
296+.leaflet-bar a:first-child {
297+ border-top-left-radius: 4px;
298+ border-top-right-radius: 4px;
299+ }
300+.leaflet-bar a:last-child {
301+ border-bottom-left-radius: 4px;
302+ border-bottom-right-radius: 4px;
303+ border-bottom: none;
304+ }
305+.leaflet-bar a.leaflet-disabled {
306+ cursor: default;
307+ background-color: #f4f4f4;
308+ color: #bbb;
309+ }
310+
311+.leaflet-touch .leaflet-bar a {
312+ width: 30px;
313+ height: 30px;
314+ line-height: 30px;
315+ }
316+.leaflet-touch .leaflet-bar a:first-child {
317+ border-top-left-radius: 2px;
318+ border-top-right-radius: 2px;
319+ }
320+.leaflet-touch .leaflet-bar a:last-child {
321+ border-bottom-left-radius: 2px;
322+ border-bottom-right-radius: 2px;
323+ }
324+
325+/* zoom control */
326+
327+.leaflet-control-zoom-in,
328+.leaflet-control-zoom-out {
329+ font: bold 18px 'Lucida Console', Monaco, monospace;
330+ text-indent: 1px;
331+ }
332+
333+.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
334+ font-size: 22px;
335+ }
336+
337+
338+/* layers control */
339+
340+.leaflet-control-layers {
341+ box-shadow: 0 1px 5px rgba(0,0,0,0.4);
342+ background: #fff;
343+ border-radius: 5px;
344+ }
345+.leaflet-control-layers-toggle {
346+ background-image: url(images/layers.png);
347+ width: 36px;
348+ height: 36px;
349+ }
350+.leaflet-retina .leaflet-control-layers-toggle {
351+ background-image: url(images/layers-2x.png);
352+ background-size: 26px 26px;
353+ }
354+.leaflet-touch .leaflet-control-layers-toggle {
355+ width: 44px;
356+ height: 44px;
357+ }
358+.leaflet-control-layers .leaflet-control-layers-list,
359+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
360+ display: none;
361+ }
362+.leaflet-control-layers-expanded .leaflet-control-layers-list {
363+ display: block;
364+ position: relative;
365+ }
366+.leaflet-control-layers-expanded {
367+ padding: 6px 10px 6px 6px;
368+ color: #333;
369+ background: #fff;
370+ }
371+.leaflet-control-layers-scrollbar {
372+ overflow-y: scroll;
373+ overflow-x: hidden;
374+ padding-right: 5px;
375+ }
376+.leaflet-control-layers-selector {
377+ margin-top: 2px;
378+ position: relative;
379+ top: 1px;
380+ }
381+.leaflet-control-layers label {
382+ display: block;
383+ }
384+.leaflet-control-layers-separator {
385+ height: 0;
386+ border-top: 1px solid #ddd;
387+ margin: 5px -10px 5px -6px;
388+ }
389+
390+/* Default icon URLs */
391+.leaflet-default-icon-path {
392+ background-image: url(images/marker-icon.png);
393+ }
394+
395+
396+/* attribution and scale controls */
397+
398+.leaflet-container .leaflet-control-attribution {
399+ background: #fff;
400+ background: rgba(255, 255, 255, 0.7);
401+ margin: 0;
402+ }
403+.leaflet-control-attribution,
404+.leaflet-control-scale-line {
405+ padding: 0 5px;
406+ color: #333;
407+ }
408+.leaflet-control-attribution a {
409+ text-decoration: none;
410+ }
411+.leaflet-control-attribution a:hover {
412+ text-decoration: underline;
413+ }
414+.leaflet-container .leaflet-control-attribution,
415+.leaflet-container .leaflet-control-scale {
416+ font-size: 11px;
417+ }
418+.leaflet-left .leaflet-control-scale {
419+ margin-left: 5px;
420+ }
421+.leaflet-bottom .leaflet-control-scale {
422+ margin-bottom: 5px;
423+ }
424+.leaflet-control-scale-line {
425+ border: 2px solid #777;
426+ border-top: none;
427+ line-height: 1.1;
428+ padding: 2px 5px 1px;
429+ font-size: 11px;
430+ white-space: nowrap;
431+ overflow: hidden;
432+ -moz-box-sizing: border-box;
433+ box-sizing: border-box;
434+
435+ background: #fff;
436+ background: rgba(255, 255, 255, 0.5);
437+ }
438+.leaflet-control-scale-line:not(:first-child) {
439+ border-top: 2px solid #777;
440+ border-bottom: none;
441+ margin-top: -2px;
442+ }
443+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
444+ border-bottom: 2px solid #777;
445+ }
446+
447+.leaflet-touch .leaflet-control-attribution,
448+.leaflet-touch .leaflet-control-layers,
449+.leaflet-touch .leaflet-bar {
450+ box-shadow: none;
451+ }
452+.leaflet-touch .leaflet-control-layers,
453+.leaflet-touch .leaflet-bar {
454+ border: 2px solid rgba(0,0,0,0.2);
455+ background-clip: padding-box;
456+ }
457+
458+
459+/* popup */
460+
461+.leaflet-popup {
462+ position: absolute;
463+ text-align: center;
464+ margin-bottom: 20px;
465+ }
466+.leaflet-popup-content-wrapper {
467+ padding: 1px;
468+ text-align: left;
469+ border-radius: 12px;
470+ }
471+.leaflet-popup-content {
472+ margin: 13px 19px;
473+ line-height: 1.4;
474+ }
475+.leaflet-popup-content p {
476+ margin: 18px 0;
477+ }
478+.leaflet-popup-tip-container {
479+ width: 40px;
480+ height: 20px;
481+ position: absolute;
482+ left: 50%;
483+ margin-left: -20px;
484+ overflow: hidden;
485+ pointer-events: none;
486+ }
487+.leaflet-popup-tip {
488+ width: 17px;
489+ height: 17px;
490+ padding: 1px;
491+
492+ margin: -10px auto 0;
493+
494+ -webkit-transform: rotate(45deg);
495+ -moz-transform: rotate(45deg);
496+ -ms-transform: rotate(45deg);
497+ -o-transform: rotate(45deg);
498+ transform: rotate(45deg);
499+ }
500+.leaflet-popup-content-wrapper,
501+.leaflet-popup-tip {
502+ background: white;
503+ color: #333;
504+ box-shadow: 0 3px 14px rgba(0,0,0,0.4);
505+ }
506+.leaflet-container a.leaflet-popup-close-button {
507+ position: absolute;
508+ top: 0;
509+ right: 0;
510+ padding: 4px 4px 0 0;
511+ border: none;
512+ text-align: center;
513+ width: 18px;
514+ height: 14px;
515+ font: 16px/14px Tahoma, Verdana, sans-serif;
516+ color: #c3c3c3;
517+ text-decoration: none;
518+ font-weight: bold;
519+ background: transparent;
520+ }
521+.leaflet-container a.leaflet-popup-close-button:hover {
522+ color: #999;
523+ }
524+.leaflet-popup-scrolled {
525+ overflow: auto;
526+ border-bottom: 1px solid #ddd;
527+ border-top: 1px solid #ddd;
528+ }
529+
530+.leaflet-oldie .leaflet-popup-content-wrapper {
531+ zoom: 1;
532+ }
533+.leaflet-oldie .leaflet-popup-tip {
534+ width: 24px;
535+ margin: 0 auto;
536+
537+ -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
538+ filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
539+ }
540+.leaflet-oldie .leaflet-popup-tip-container {
541+ margin-top: -1px;
542+ }
543+
544+.leaflet-oldie .leaflet-control-zoom,
545+.leaflet-oldie .leaflet-control-layers,
546+.leaflet-oldie .leaflet-popup-content-wrapper,
547+.leaflet-oldie .leaflet-popup-tip {
548+ border: 1px solid #999;
549+ }
550+
551+
552+/* div icon */
553+
554+.leaflet-div-icon {
555+ background: #fff;
556+ border: 1px solid #666;
557+ }
558+
559+
560+/* Tooltip */
561+/* Base styles for the element that has a tooltip */
562+.leaflet-tooltip {
563+ position: absolute;
564+ padding: 6px;
565+ background-color: #fff;
566+ border: 1px solid #fff;
567+ border-radius: 3px;
568+ color: #222;
569+ white-space: nowrap;
570+ -webkit-user-select: none;
571+ -moz-user-select: none;
572+ -ms-user-select: none;
573+ user-select: none;
574+ pointer-events: none;
575+ box-shadow: 0 1px 3px rgba(0,0,0,0.4);
576+ }
577+.leaflet-tooltip.leaflet-clickable {
578+ cursor: pointer;
579+ pointer-events: auto;
580+ }
581+.leaflet-tooltip-top:before,
582+.leaflet-tooltip-bottom:before,
583+.leaflet-tooltip-left:before,
584+.leaflet-tooltip-right:before {
585+ position: absolute;
586+ pointer-events: none;
587+ border: 6px solid transparent;
588+ background: transparent;
589+ content: "";
590+ }
591+
592+/* Directions */
593+
594+.leaflet-tooltip-bottom {
595+ margin-top: 6px;
596+}
597+.leaflet-tooltip-top {
598+ margin-top: -6px;
599+}
600+.leaflet-tooltip-bottom:before,
601+.leaflet-tooltip-top:before {
602+ left: 50%;
603+ margin-left: -6px;
604+ }
605+.leaflet-tooltip-top:before {
606+ bottom: 0;
607+ margin-bottom: -12px;
608+ border-top-color: #fff;
609+ }
610+.leaflet-tooltip-bottom:before {
611+ top: 0;
612+ margin-top: -12px;
613+ margin-left: -6px;
614+ border-bottom-color: #fff;
615+ }
616+.leaflet-tooltip-left {
617+ margin-left: -6px;
618+}
619+.leaflet-tooltip-right {
620+ margin-left: 6px;
621+}
622+.leaflet-tooltip-left:before,
623+.leaflet-tooltip-right:before {
624+ top: 50%;
625+ margin-top: -6px;
626+ }
627+.leaflet-tooltip-left:before {
628+ right: 0;
629+ margin-right: -12px;
630+ border-left-color: #fff;
631+ }
632+.leaflet-tooltip-right:before {
633+ left: 0;
634+ margin-left: -12px;
635+ border-right-color: #fff;
636+ }
searx/templates/oscar/infobox.html+1−1
@@ -6,7 +6,7 @@
66 <div class="panel-body">
77 {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
88
9− {% if infobox.content %}<bdi><p class="infobox_part">{{ infobox.content }}</p></bdi>{% endif %}
9+ {% if infobox.content %}<bdi><p class="infobox_part">{{ infobox.content | safe }}</p></bdi>{% endif %}
1010
1111 {% if infobox.attributes -%}
1212 <table class="table table-striped infobox_part">
searx/templates/oscar/languages.html+8−12
@@ -1,12 +1,8 @@
1−{% if preferences -%}
2−<select class="custom-select form-control" name='language'>
3−{%- else -%}
4−<select class="time_range custom-select form-control" id='language' name='language'>
5−{%- endif -%}
6− <option value="all" {% if current_language == 'all' %}selected="selected"{% endif %}>{{ _('Default language') }}</option>
7− {%- for lang_id,lang_name,country_name,english_name in language_codes | sort(attribute=1) -%}
8− <option value="{{ lang_id }}" {% if lang_id == current_language %}selected="selected"{% endif %}>
9− {{- lang_name }} {% if country_name %}({{ country_name }}) {% endif %}- {{ lang_id -}}
10− </option>
11− {%- endfor -%}
12−</select>
1+<select class="language custom-select form-control" id="language" name="language" accesskey="l">
2+ <option value="all" {% if current_language == 'all' %}selected="selected"{% endif %}>{{ _('Default language') }}</option>
3+{%- for lang_id,lang_name,country_name,english_name in language_codes | sort(attribute=1) -%}
4+ <option value="{{ lang_id }}" {% if lang_id == current_language %}selected="selected"{% endif %}>
5+ {{- lang_name }} {% if country_name %}({{ country_name }}) {% endif %}- {{ lang_id -}}
6+ </option>
7+{%- endfor -%}
8+</select>
searx/templates/oscar/preferences.html+33−33
@@ -41,7 +41,7 @@
4141 {% set language_label = _('Search language') %}
4242 {% set language_info = _('What language do you prefer for search?') %}
4343 {{ preferences_item_header(language_info, language_label, rtl) }}
44− {% include 'oscar/languages.html' %}
44+ {% include 'oscar/languages.html' %}
4545 {{ preferences_item_footer(language_info, language_label, rtl) }}
4646
4747 {% set locale_label = _('Interface language') %}
@@ -156,26 +156,26 @@
156156 <div class="container-fluid">
157157 <fieldset>
158158 <div class="table-responsive">
159− <table class="table table-hover table-condensed table-striped">
160− <tr>
159+ <table class="table table-hover table-condensed table-striped">
160+ <tr>
161161 {% if not rtl %}
162− <th>{{ _("Allow") }}</th>
163− <th>{{ _("Engine name") }}</th>
164− <th>{{ _("Shortcut") }}</th>
165− <th>{{ _("Selected language") }}</th>
166− <th>{{ _("SafeSearch") }}</th>
167− <th>{{ _("Time range") }}</th>
168− <th>{{ _("Avg. time") }}</th>
169− <th>{{ _("Max time") }}</th>
162+ <th>{{ _("Allow") }}</th>
163+ <th>{{ _("Engine name") }}</th>
164+ <th>{{ _("Shortcut") }}</th>
165+ <th>{{ _("Selected language") }}</th>
166+ <th>{{ _("SafeSearch") }}</th>
167+ <th>{{ _("Time range") }}</th>
168+ <th>{{ _("Avg. time") }}</th>
169+ <th>{{ _("Max time") }}</th>
170170 {% else %}
171− <th>{{ _("Max time") }}</th>
172− <th>{{ _("Avg. time") }}</th>
173− <th>{{ _("Time range") }}</th>
174− <th>{{ _("SafeSearch") }}</th>
175− <th>{{ _("Selected language") }}</th>
176− <th>{{ _("Shortcut") }}</th>
177− <th>{{ _("Engine name") }}</th>
178− <th>{{ _("Allow") }}</th>
171+ <th>{{ _("Max time") }}</th>
172+ <th>{{ _("Avg. time") }}</th>
173+ <th>{{ _("Time range") }}</th>
174+ <th>{{ _("SafeSearch") }}</th>
175+ <th>{{ _("Selected language") }}</th>
176+ <th>{{ _("Shortcut") }}</th>
177+ <th>{{ _("Engine name") }}</th>
178+ <th>{{ _("Allow") }}</th>
179179 {% endif %}
180180 </tr>
181181 {% for search_engine in engines_by_category[categ] %}
@@ -186,19 +186,19 @@
186186 {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }}
187187 </td>
188188 <th>{{ search_engine.name }}</th>
189− <td class="name">{{ shortcuts[search_engine.name] }}</td>
190− <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td>
191− <td>{{ support_toggle(search_engine.safesearch==True) }}</td>
192− <td>{{ support_toggle(search_engine.time_range_support==True) }}</td>
193− <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td>
194− <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td>
195− {% else %}
196− <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td>
197− <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td>
198− <td>{{ support_toggle(search_engine.time_range_support==True) }}</td>
199− <td>{{ support_toggle(search_engine.safesearch==True) }}</td>
200− <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td>
201− <td>{{ shortcuts[search_engine.name] }}</td>
189+ <td class="name">{{ shortcuts[search_engine.name] }}</td>
190+ <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td>
191+ <td>{{ support_toggle(search_engine.safesearch==True) }}</td>
192+ <td>{{ support_toggle(search_engine.time_range_support==True) }}</td>
193+ <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td>
194+ <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td>
195+ {% else %}
196+ <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td>
197+ <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td>
198+ <td>{{ support_toggle(search_engine.time_range_support==True) }}</td>
199+ <td>{{ support_toggle(search_engine.safesearch==True) }}</td>
200+ <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td>
201+ <td>{{ shortcuts[search_engine.name] }}</td>
202202 <th>{{ search_engine.name }}</th>
203203 <td class="onoff-checkbox">
204204 {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }}
@@ -207,7 +207,7 @@
207207 </tr>
208208 {% endif %}
209209 {% endfor %}
210− </table>
210+ </table>
211211 </div>
212212 </fieldset>
213213 </div>
searx/templates/oscar/result_templates/code.html+18−18
@@ -1,18 +1,18 @@
1−{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
2−
3−{{ result_header(result, favicons) }}
4−{{ result_sub_header(result) }}
5−
6−{% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %}
7−
8−{% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %}
9−
10−<div dir="ltr">
11−{{ result.codelines|code_highlighter(result.code_language)|safe }}
12−</div>
13−
14−{% if rtl %}
15−{{ result_footer_rtl(result) }}
16−{% else %}
17−{{ result_footer(result) }}
18−{% endif %}
1+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
2+
3+{{ result_header(result, favicons) }}
4+{{ result_sub_header(result) }}
5+
6+{% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %}
7+
8+{% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %}
9+
10+<div dir="ltr">
11+{{ result.codelines|code_highlighter(result.code_language)|safe }}
12+</div>
13+
14+{% if rtl %}
15+{{ result_footer_rtl(result) }}
16+{% else %}
17+{{ result_footer(result) }}
18+{% endif %}
searx/templates/oscar/result_templates/images.html+1−1
@@ -22,7 +22,7 @@
2222 <span class="label label-default pull-right">{{ result.engine }}</span>{{- "" -}}
2323 <p class="text-muted pull-left">{{ result.pretty_url }}</p>{{- "" -}}
2424 <div class="clearfix"></div>{{- "" -}}
25− <div class="row">{{- "" -}}
25+ <div class="row">{{- "" -}}
2626 <div class="col-md-6">{{- "" -}}
2727 <a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('Get image') }}</a>{{- "" -}}
2828 </div>{{- "" -}}
searx/templates/oscar/result_templates/map.html+72−72
@@ -1,72 +1,72 @@
1−{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
2−
3−{{ result_header(result, favicons) }}
4−{{ result_sub_header(result) }}
5−
6−{% if (result.latitude and result.longitude) or result.boundingbox %}
7− <small> &bull; <a class="text-info btn-collapse collapsed searx_init_map cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-map-{{ index }}" data-leaflet-target="osm-map-{{ index }}" data-map-lon="{{ result.longitude }}" data-map-lat="{{ result.latitude }}" {% if result.boundingbox %}data-map-boundingbox='{{ result.boundingbox|tojson|safe }}'{% endif %} {% if result.geojson %}data-map-geojson='{{ result.geojson|tojson|safe }}'{% endif %} data-btn-text-collapsed="{{ _('show map') }}" data-btn-text-not-collapsed="{{ _('hide map') }}">{{ icon('globe') }} {{ _('show map') }}</a></small>
8−{% endif %}
9−
10−{% if result.osm and (result.osm.type and result.osm.id) %}
11− <small> &bull; <a class="text-info btn-collapse collapsed cursor-pointer searx_overpass_request disabled_if_nojs" data-toggle="collapse" data-target="#result-overpass-{{ index }}" data-osm-type="{{ result.osm.type }}" data-osm-id="{{ result.osm.id }}" data-result-table="result-overpass-table-{{ index }}" data-result-table-loadicon="result-overpass-table-loading-{{ index }}" data-btn-text-collapsed="{{ _('show details') }}" data-btn-text-not-collapsed="{{ _('hide details') }}">{{ icon('map-marker') }} {{ _('show details') }}</a></small>
12−{% endif %}
13−
14−{# {% if (result.latitude and result.longitude) %}
15− <small> &bull; <a class="text-info btn-collapse collapsed cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-geodata-{{ index }}" data-btn-text-collapsed="{{ _('show geodata') }}" data-btn-text-not-collapsed="{{ _('hide geodata') }}">{{ icon('map-marker') }} {{ _('show geodata') }}</a></small>
16−{% endif %} #}
17−
18−<div class="container-fluid">
19−
20−{% if result.address %}
21−<p class="row result-content result-adress col-xs-12 col-sm-5 col-md-4" itemscope itemtype="http://schema.org/PostalAddress">
22− {% if result.address.name %}
23− <strong itemprop="name">{{ result.address.name }}</strong><br/>
24− {% endif %}
25− {% if result.address.road %}
26− <span itemprop="streetAddress">
27− {% if result.address.house_number %}{{ result.address.house_number }}, {% endif %}
28− {{ result.address.road }}
29− </span><br/>
30− {% endif %}
31− {% if result.address.locality %}
32− <span itemprop="addressLocality">{{ result.address.locality }}</span>
33− {% if result.address.postcode %}, <span itemprop="postalCode">{{ result.address.postcode }}</span>{% endif %}
34− <br/>
35− {% endif %}
36− {% if result.address.country %}
37− <span itemprop="addressCountry">{{ result.address.country }}</span>
38− {% endif %}
39−</p>
40−{% endif %}
41−
42−{% if result.osm and (result.osm.type and result.osm.id) %}
43− <div class="row result-content collapse col-xs-12 col-sm-7 col-md-8" id="result-overpass-{{ index }}"{% if rtl %} dir="ltr"{% endif %}>
44− <div class="text-center" id="result-overpass-table-loading-{{ index }}"><img src="{{ url_for('static', filename='img/loader.gif') }}" alt="Loading ..."/></div>
45− <table class="table table-striped table-condensed hidden" id="result-overpass-table-{{ index }}">
46− <tr><th>key</th><th>value</th></tr>
47− </table>
48− </div>
49−{% endif %}
50−
51−{# {% if (result.latitude and result.longitude) %}
52− <div class="row collapse col-xs-12 col-sm-5 col-md-4" id="result-geodata-{{ index }}">
53− <strong>Longitude:</strong> {{ result.longitude }} <br/>
54− <strong>Latitude:</strong> {{ result.latitude }}
55− </div>
56−{% endif %} #}
57−
58−{% if result.content %}<p class="row result-content col-xs-12 col-sm-12 col-md-12">{{ result.content|safe }}</p>{% endif %}
59−
60−</div>
61−
62−{% if (result.latitude and result.longitude) or result.boundingbox %}
63− <div class="collapse" id="result-map-{{ index }}">
64− <div style="height:300px; width:100%; margin: 10px 0;" id="osm-map-{{ index }}"></div>
65− </div>
66−{% endif %}
67−
68−{% if rtl %}
69−{{ result_footer_rtl(result) }}
70−{% else %}
71−{{ result_footer(result) }}
72−{% endif %}
1+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
2+
3+{{ result_header(result, favicons) }}
4+{{ result_sub_header(result) }}
5+
6+{% if (result.latitude and result.longitude) or result.boundingbox %}
7+ <small> &bull; <a class="text-info btn-collapse collapsed searx_init_map cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-map-{{ index }}" data-leaflet-target="osm-map-{{ index }}" data-map-lon="{{ result.longitude }}" data-map-lat="{{ result.latitude }}" {% if result.boundingbox %}data-map-boundingbox='{{ result.boundingbox|tojson|safe }}'{% endif %} {% if result.geojson %}data-map-geojson='{{ result.geojson|tojson|safe }}'{% endif %} data-btn-text-collapsed="{{ _('show map') }}" data-btn-text-not-collapsed="{{ _('hide map') }}">{{ icon('globe') }} {{ _('show map') }}</a></small>
8+{% endif %}
9+
10+{% if result.osm and (result.osm.type and result.osm.id) %}
11+ <small> &bull; <a class="text-info btn-collapse collapsed cursor-pointer searx_overpass_request disabled_if_nojs" data-toggle="collapse" data-target="#result-overpass-{{ index }}" data-osm-type="{{ result.osm.type }}" data-osm-id="{{ result.osm.id }}" data-result-table="result-overpass-table-{{ index }}" data-result-table-loadicon="result-overpass-table-loading-{{ index }}" data-btn-text-collapsed="{{ _('show details') }}" data-btn-text-not-collapsed="{{ _('hide details') }}">{{ icon('map-marker') }} {{ _('show details') }}</a></small>
12+{% endif %}
13+
14+{# {% if (result.latitude and result.longitude) %}
15+ <small> &bull; <a class="text-info btn-collapse collapsed cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-geodata-{{ index }}" data-btn-text-collapsed="{{ _('show geodata') }}" data-btn-text-not-collapsed="{{ _('hide geodata') }}">{{ icon('map-marker') }} {{ _('show geodata') }}</a></small>
16+{% endif %} #}
17+
18+<div class="container-fluid">
19+
20+{% if result.address %}
21+<p class="row result-content result-adress col-xs-12 col-sm-5 col-md-4" itemscope itemtype="http://schema.org/PostalAddress">
22+ {% if result.address.name %}
23+ <strong itemprop="name">{{ result.address.name }}</strong><br/>
24+ {% endif %}
25+ {% if result.address.road %}
26+ <span itemprop="streetAddress">
27+ {% if result.address.house_number %}{{ result.address.house_number }}, {% endif %}
28+ {{ result.address.road }}
29+ </span><br/>
30+ {% endif %}
31+ {% if result.address.locality %}
32+ <span itemprop="addressLocality">{{ result.address.locality }}</span>
33+ {% if result.address.postcode %}, <span itemprop="postalCode">{{ result.address.postcode }}</span>{% endif %}
34+ <br/>
35+ {% endif %}
36+ {% if result.address.country %}
37+ <span itemprop="addressCountry">{{ result.address.country }}</span>
38+ {% endif %}
39+</p>
40+{% endif %}
41+
42+{% if result.osm and (result.osm.type and result.osm.id) %}
43+ <div class="row result-content collapse col-xs-12 col-sm-7 col-md-8" id="result-overpass-{{ index }}"{% if rtl %} dir="ltr"{% endif %}>
44+ <div class="text-center" id="result-overpass-table-loading-{{ index }}"><img src="{{ url_for('static', filename='img/loader.gif') }}" alt="Loading ..."/></div>
45+ <table class="table table-striped table-condensed hidden" id="result-overpass-table-{{ index }}">
46+ <tr><th>key</th><th>value</th></tr>
47+ </table>
48+ </div>
49+{% endif %}
50+
51+{# {% if (result.latitude and result.longitude) %}
52+ <div class="row collapse col-xs-12 col-sm-5 col-md-4" id="result-geodata-{{ index }}">
53+ <strong>Longitude:</strong> {{ result.longitude }} <br/>
54+ <strong>Latitude:</strong> {{ result.latitude }}
55+ </div>
56+{% endif %} #}
57+
58+{% if result.content %}<p class="row result-content col-xs-12 col-sm-12 col-md-12">{{ result.content|safe }}</p>{% endif %}
59+
60+</div>
61+
62+{% if (result.latitude and result.longitude) or result.boundingbox %}
63+ <div class="collapse" id="result-map-{{ index }}">
64+ <div style="height:300px; width:100%; margin: 10px 0;" id="osm-map-{{ index }}"></div>
65+ </div>
66+{% endif %}
67+
68+{% if rtl %}
69+{{ result_footer_rtl(result) }}
70+{% else %}
71+{{ result_footer(result) }}
72+{% endif %}
searx/templates/oscar/result_templates/videos.html+27−27
@@ -1,27 +1,27 @@
1−{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
2−
3−{{ result_header(result, favicons) }}
4−{{ result_sub_header(result) }}
5−
6−{% if result.embedded %}
7− <small> &bull; <a class="text-info btn-collapse collapsed cursor-pointer media-loader disabled_if_nojs" data-toggle="collapse" data-target="#result-video-{{ index }}" data-btn-text-collapsed="{{ _('show video') }}" data-btn-text-not-collapsed="{{ _('hide video') }}">{{ icon('film') }} {{ _('show video') }}</a></small>
8−{% endif %}
9−
10−{% if result.embedded %}
11−<div id="result-video-{{ index }}" class="collapse">
12− {{ result.embedded|safe }}
13−</div>
14−{% endif %}
15−
16−<div class="container-fluid">
17− <div class="row">
18− <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><img class="thumbnail col-xs-6 col-sm-4 col-md-4 result-content" src="{{ image_proxify(result.thumbnail) }}" alt="{{ result.title|striptags }} {{ result.engine }}" /></a>
19− {% if result.content %}<p class="col-xs-12 col-sm-8 col-md-8 result-content">{{ result.content|safe }}</p>{% endif %}
20− </div>
21−</div>
22−
23−{% if rtl %}
24−{{ result_footer_rtl(result) }}
25−{% else %}
26−{{ result_footer(result) }}
27−{% endif %}
1+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
2+
3+{{ result_header(result, favicons) }}
4+{{ result_sub_header(result) }}
5+
6+{% if result.embedded %}
7+ <small> &bull; <a class="text-info btn-collapse collapsed cursor-pointer media-loader disabled_if_nojs" data-toggle="collapse" data-target="#result-video-{{ index }}" data-btn-text-collapsed="{{ _('show video') }}" data-btn-text-not-collapsed="{{ _('hide video') }}">{{ icon('film') }} {{ _('show video') }}</a></small>
8+{% endif %}
9+
10+{% if result.embedded %}
11+<div id="result-video-{{ index }}" class="collapse">
12+ {{ result.embedded|safe }}
13+</div>
14+{% endif %}
15+
16+<div class="container-fluid">
17+ <div class="row">
18+ <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><img class="thumbnail col-xs-6 col-sm-4 col-md-4 result-content" src="{{ image_proxify(result.thumbnail) }}" alt="{{ result.title|striptags }} {{ result.engine }}" /></a>
19+ {% if result.content %}<p class="col-xs-12 col-sm-8 col-md-8 result-content">{{ result.content|safe }}</p>{% endif %}
20+ </div>
21+</div>
22+
23+{% if rtl %}
24+{{ result_footer_rtl(result) }}
25+{% else %}
26+{{ result_footer(result) }}
27+{% endif %}
searx/templates/oscar/results.html+62−62
@@ -15,7 +15,68 @@
1515 {% include 'oscar/search.html' %}
1616
1717 <div class="row">
18− <div class="col-sm-8" id="main_results">
18+ <div class="col-sm-4 col-sm-push-8" id="sidebar_results">
19+ {% if number_of_results != '0' -%}
20+ <p><small>{{ _('Number of results') }}: {{ number_of_results }}</small></p>
21+ {%- endif %}
22+
23+ {% if unresponsive_engines and results|length >= 1 -%}
24+ <div class="alert alert-danger fade in" role="alert">
25+ <p>{{ _('Engines cannot retrieve results') }}:</p>
26+ {%- for engine_name, error_type in unresponsive_engines -%}
27+ {{- engine_name }} ({{ error_type }}){% if not loop.last %}, {% endif %}{{- "" -}}
28+ {%- endfor -%}
29+ </div>
30+ {%- endif %}
31+
32+ {% if infoboxes -%}
33+ {% for infobox in infoboxes %}
34+ {% include 'oscar/infobox.html' %}{{- "\n\n" -}}
35+ {% endfor %}
36+ {%- endif %}
37+
38+ {% if suggestions %}
39+ <div class="panel panel-default">
40+ <div class="panel-heading">
41+ <h4 class="panel-title">{{ _('Suggestions') }}</h4>
42+ </div>
43+ <div class="panel-body">
44+ {% for suggestion in suggestions %}
45+ <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} suggestion_item">
46+ <input type="hidden" name="q" value="{{ suggestion.url }}">
47+ <button type="submit" class="btn btn-default btn-xs">{{ suggestion.title }}</button>
48+ </form>
49+ {% endfor %}
50+ </div>
51+ </div>
52+ {%- endif %}
53+
54+ <div class="panel panel-default">
55+ <div class="panel-heading">{{- "" -}}
56+ <h4 class="panel-title">{{ _('Links') }}</h4>{{- "" -}}
57+ </div>
58+ <div class="panel-body">
59+ <form role="form">{{- "" -}}
60+ <div class="form-group">{{- "" -}}
61+ <label for="search_url">{{ _('Search URL') }}</label>{{- "" -}}
62+ <input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ search_url() }}" readonly>{{- "" -}}
63+ </div>{{- "" -}}
64+ </form>
65+ <label>{{ _('Download results') }}</label>
66+ <div class="clearfix"></div>
67+ {% for output_type in ('csv', 'json', 'rss') %}
68+ <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} result_download">
69+ {{- search_form_attrs(pageno) -}}
70+ <input type="hidden" name="format" value="{{ output_type }}">{{- "" -}}
71+ <button type="submit" class="btn btn-default">{{ output_type }}</button>{{- "" -}}
72+ </form>
73+ {% endfor %}
74+ <div class="clearfix"></div>
75+ </div>
76+ </div>
77+ </div><!-- /#sidebar_results -->
78+
79+ <div class="col-sm-8 col-sm-pull-4" id="main_results">
1980 <h1 class="sr-only">{{ _('Search results') }}</h1>
2081
2182 {% if corrections -%}
@@ -91,66 +152,5 @@
91152 {% endif %}
92153 {% endif %}
93154 </div><!-- /#main_results -->
94−
95− <div class="col-sm-4" id="sidebar_results">
96− {% if number_of_results != '0' -%}
97− <p><small>{{ _('Number of results') }}: {{ number_of_results }}</small></p>
98− {%- endif %}
99−
100− {% if unresponsive_engines and results|length >= 1 -%}
101− <div class="alert alert-danger fade in" role="alert">
102− <p>{{ _('Engines cannot retrieve results') }}:</p>
103− {%- for engine_name, error_type in unresponsive_engines -%}AAAA
104− {{- engine_name }} ({{ error_type }}){% if not loop.last %}, {% endif %}{{- "" -}}
105− {%- endfor -%}
106− </div>
107− {%- endif %}
108−
109− {% if infoboxes -%}
110− {% for infobox in infoboxes %}
111− {% include 'oscar/infobox.html' %}{{- "\n\n" -}}
112− {% endfor %}
113− {%- endif %}
114−
115− {% if suggestions %}
116− <div class="panel panel-default">
117− <div class="panel-heading">
118− <h4 class="panel-title">{{ _('Suggestions') }}</h4>
119− </div>
120− <div class="panel-body">
121− {% for suggestion in suggestions %}
122− <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} suggestion_item">
123− <input type="hidden" name="q" value="{{ suggestion.url }}">
124− <button type="submit" class="btn btn-default btn-xs">{{ suggestion.title }}</button>
125− </form>
126− {% endfor %}
127− </div>
128− </div>
129− {%- endif %}
130−
131− <div class="panel panel-default">
132− <div class="panel-heading">{{- "" -}}
133− <h4 class="panel-title">{{ _('Links') }}</h4>{{- "" -}}
134− </div>
135− <div class="panel-body">
136− <form role="form">{{- "" -}}
137− <div class="form-group">{{- "" -}}
138− <label for="search_url">{{ _('Search URL') }}</label>{{- "" -}}
139− <input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ search_url() }}" readonly>{{- "" -}}
140− </div>{{- "" -}}
141− </form>
142− <label>{{ _('Download results') }}</label>
143− <div class="clearfix"></div>
144− {% for output_type in ('csv', 'json', 'rss') %}
145− <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} result_download">
146− {{- search_form_attrs(pageno) -}}
147− <input type="hidden" name="format" value="{{ output_type }}">{{- "" -}}
148− <button type="submit" class="btn btn-default">{{ output_type }}</button>{{- "" -}}
149− </form>
150− {% endfor %}
151− <div class="clearfix"></div>
152− </div>
153− </div>
154− </div><!-- /#sidebar_results -->
155155 </div>
156156{% endblock %}
searx/templates/oscar/search.html+1−1
@@ -3,7 +3,7 @@
33 <div class="row">
44 <div class="col-xs-12 col-md-8">
55 <div class="input-group search-margin">
6− <input type="search" name="q" class="form-control" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}">
6+ <input type="search" name="q" class="form-control" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}" accesskey="s">
77 <span class="input-group-btn">
88 <button type="submit" class="btn btn-default" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button>
99 </span>
searx/templates/oscar/search_full.html+18−18
@@ -1,18 +1,18 @@
1−{% from 'oscar/macros.html' import icon %}
2−
3−<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search">
4− {% if rtl %}
5− <div class="input-group">
6− {% else %}
7− <div class="input-group col-md-8 col-md-offset-2">
8− {% endif %}
9− <input type="search" name="q" class="form-control input-lg autofocus" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}">
10− <span class="input-group-btn">
11− <button type="submit" class="btn btn-default input-lg" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button>
12− </span>
13− </div>
14− <div class="col-md-8 col-md-offset-2 advanced">
15− {% include 'oscar/advanced.html' %}
16− </div>
17−
18−</form><!-- / #search_form_full -->
1+{% from 'oscar/macros.html' import icon %}
2+
3+<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search">
4+ {% if rtl %}
5+ <div class="input-group">
6+ {% else %}
7+ <div class="input-group col-md-8 col-md-offset-2">
8+ {% endif %}
9+ <input type="search" name="q" class="form-control input-lg autofocus" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}" accesskey="s">
10+ <span class="input-group-btn">
11+ <button type="submit" class="btn btn-default input-lg" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button>
12+ </span>
13+ </div>
14+ <div class="col-md-8 col-md-offset-2 advanced">
15+ {% include 'oscar/advanced.html' %}
16+ </div>
17+
18+</form><!-- / #search_form_full -->
searx/templates/oscar/time-range.html+1−1
@@ -1,4 +1,4 @@
1−<select name="time_range" id="time-range" class="custom-select form-control">{{- "" -}}
1+<select name="time_range" id="time-range" class="custom-select form-control" accesskey="t">{{- "" -}}
22 <option id="time-range-anytime" value="" {{ "selected" if time_range=="" or not time_range else ""}}>
33 {{- _('Anytime') -}}
44 </option>{{- "" -}}
searx/webapp.py+5−5
@@ -606,11 +606,11 @@ def index():
606606 # HTML output format
607607
608608 # suggestions: use RawTextQuery to get the suggestion URLs with the same bang
609− suggestion_urls = map(lambda suggestion: {
610− 'url': raw_text_query.changeSearchQuery(suggestion).getFullQuery(),
611− 'title': suggestion
612− },
613− result_container.suggestions)
609+ suggestion_urls = list(map(lambda suggestion: {
610+ 'url': raw_text_query.changeSearchQuery(suggestion).getFullQuery(),
611+ 'title': suggestion
612+ },
613+ result_container.suggestions))
614614
615615 correction_urls = list(map(lambda correction: {
616616 'url': raw_text_query.changeSearchQuery(correction).getFullQuery(),
tests/unit/engines/__init__.pydeleted

No changes to the content.

tests/unit/engines/pubmed.pydeleted−37
@@ -1,37 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import pubmed
5−from searx.testing import SearxTestCase
6−
7−
8−class TestPubmedEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = pubmed.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn('eutils.ncbi.nlm.nih.gov/', params['url'])
17− self.assertIn('term', params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, pubmed.response, None)
21− self.assertRaises(AttributeError, pubmed.response, [])
22− self.assertRaises(AttributeError, pubmed.response, '')
23− self.assertRaises(AttributeError, pubmed.response, '[]')
24−
25− response = mock.Mock(text='<PubmedArticleSet></PubmedArticleSet>')
26− self.assertEqual(pubmed.response(response), [])
27−
28− xml_mock = """<eSearchResult><Count>1</Count><RetMax>1</RetMax><RetStart>0</RetStart><IdList>
29−<Id>1</Id>
30−</IdList></eSearchResult>
31−"""
32−
33− response = mock.Mock(text=xml_mock.encode('utf-8'))
34− results = pubmed.response(response)
35− self.assertEqual(type(results), list)
36− self.assertEqual(len(results), 1)
37− self.assertEqual(results[0]['content'], 'No abstract is available for this publication.')
tests/unit/engines/seedpeer_fixture.htmldeleted−110
@@ -1,110 +0,0 @@
1−<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2− <html xmlns="http://www.w3.org/1999/xhtml">
3− <head>
4− </head>
5− <body>
6− <div id="header">
7− <div id="whoIsYou">
8− <a href="/lang.php"><small>SeedPeer in your own language?</small></a>&nbsp;&nbsp;&nbsp;<a href="http://www.seedpeer.eu"><img src="/images/flags/uk.gif" width="16px" alt="Torrents EN" /></a> <a href="http://spanish.seedpeer.eu"><img src="/images/flags/es.gif" width="16px" alt="Torrents ES" /></a> <a href="http://german.seedpeer.eu"><img src="/images/flags/de.gif" width="16px" alt="Torrents DE" /></a> <a href="http://french.seedpeer.eu"><img src="/images/flags/fr.gif" width="16px" alt="Torrents FR" /></a> <a href="http://portuguese.seedpeer.eu"><img src="/images/flags/pt.gif" width="16px" alt="Torrents Portuguese" /></a> <a href="http://swedish.seedpeer.eu"><img src="/images/flags/se.gif" width="16px" alt="Torrents Sweden" /></a>
9− </div>
10−
11− <script type="text/javascript">
12− whoIsYou();
13− </script>
14− <div id="search">
15− <form action="/search.php" method="get">
16− <input id="topsearchbar" name="search" value="narcos season 2" />
17− <input type="submit" class="searchbutton" value="Torrents" />
18− <input style="color:#000" type="submit" class="searchbutton" name="usenet" value="Usenet Binaries" />
19− </form>
20− <div id="suggestion"></div>
21− </div>
22− <div id="logo"><a href="/"><img src="/images/logo2.gif" alt="Seedpeer homepage" width="415" height="143" /></a></div>
23− <div id="subtext"><a href="/">Home</a> &gt; <a href="/search.html">Torrent search</a> &gt; Narcos season 2 | page 1</div>
24− </div>
25− <div id="nav">
26− <ul>
27− <!--
28− <li><font style="color:red;font-size:9px;font-weight:bold;">NEW</font><a title="Download TOP Games for FREE" rel="nofollow" href="http://www.bigrebelads.com/affiliate/index?ref=9301" target="_blank">FREE Games</a></li>
29−
30− -->
31− <li style="border-left:none" id="categories"><a title="Browse Torrent Categories" href="/browse.html">Categories</a>
32− <ul>
33− <li><a title="Browse Anime Torrents" href="/browse.html#6">Anime</a></li>
34− <li><a title="Browse Game Torrents" href="/browse.html#4">Games</a></li>
35− <li><a title="Browse Movie Torrents" href="/browse.html#1">Movies</a></li>
36− <li><a title="Browse Music Torrents" href="/browse.html#3">Music</a></li>
37− <li><a title="Browse Software Torrents" href="/browse.html#5">Software</a></li>
38− <li><a title="Browse TV Torrents" href="/browse.html#2">TV Shows</a></li>
39− <li><a title="Browse Other Torrents" href="/browse.html#7">Others</a></li>
40− </ul>
41− </li>
42− <li><a title="Upload A Torrents" href="/upload.html">Upload torrent</a></li>
43− <li id="verified"><a title="Verified Torrents" href="/verified.html">Verified</a></li>
44− <li id="searchoptions"><a title="Search Torrents" href="/search.html">Torrent search</a></li>
45− <li id="newsgroups"><a style="color:#212b3e" title="News Groups" href="/usenet.html">Usenet Binaries</a></li>
46− <li id="about" style="border-right:none"><a rel="nofollow" href="/faq.html">About Us</a>
47− <ul>
48− <li><a title="SeedPeer Statistics" href="/stats.html">Statistics</a></li>
49− <li><a title="Contact Us" href="/contact.html">Contact</a></li>
50− <li><a title="Frequently Asked Questions" href="/faq.html">FAQ</a></li>
51− <li><a title="SeedPeer API" href="http://api.seedpeer.eu">Our API</a></li>
52− <li><a title="SeedPeer Blog" href="/blog">Blog</a></li>
53− </ul>
54− </li>
55− <!--<li><a href="/toolbar.php">Our Toolbar</a></li>-->
56− </ul>
57− <div class="clear"></div>
58− </div>
59− <div id="body"><div id="pageTop"></div>
60− <div id="headerbox"><h1>Verified <font class="colored">Narcos season 2</font> torrents</h1></div><table width="100%"><tr><th>
61− <span style="float:right">
62− <a href="/search/narcos-season-2/8/1.html"><img style="vertical-align:middle" src="/images/comments.gif" alt="comments" /></a> |
63− <a href="/search/narcos-season-2/7/1.html"><img style="vertical-align:middle" src="/images/ver.gif" alt="verified" /></a>
64− </span>
65− <a href="/search/narcos-season-2/1/1.html">Torrent name</a></th><th class="right"><a href="/search/narcos-season-2/2/1.html">Age</a></th><th class="right"><a href="/search/narcos-season-2/3/1.html">Size</a></th><th class="right"><a href="/search/narcos-season-2/4/1.html">Seeds</a></th><th class="right"><a href="/search/narcos-season-2/5/1.html">Peers</a></th><th class="center"><a href="/search/narcos-season-2/6/1.html">Health</a></th><td class="tableAd" rowspan="6"><iframe src="http://creative.wwwpromoter.com/13689?d=300x250" width="300" height="250" style="border: none;" frameborder="0" scrolling="no"></iframe></td></tr><tr class=""><td><a class="pblink" id="pblink_table_item_1" href="" data-tad="431726" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> Full Version</a></td><td class="right">20 hours</td><td class="right">681.3 MB</td><td class="right"><font color="green">28</font> </td><td class="right"><font color="navy">654</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class="tdark"><td><a class="pblink" id="pblink_table_item_2" href="" data-tad="431727" data-url="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> Trusted Source</a></td><td class="right">12 hours</td><td class="right">787.1 MB</td><td class="right"><font color="green">64</font> </td><td class="right"><font color="navy">220</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class=""><td><a class="pblink" id="pblink_table_item_3" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Full Narcos season 2 Download</strong></a> <small><a class="pblink" id="pblink_table_item_4" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow">Usenet</a></small></td><td class="right">24 hours</td><td class="right">775.5 MB</td><td class="right"><font color="green">60</font> </td><td class="right"><font color="navy">236</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class="tdark"><td><a class="pblink" id="pblink_table_item_5" href="" data-tad="431730" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> 2014 - DIRECT STREAMING</a> <small><a class="pblink" id="pblink_table_item_6" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow">Movies</a></small></td><td class="right">17 hours</td><td class="right">654.1 MB</td><td class="right"><font color="green">2</font> </td><td class="right"><font color="navy">391</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class=""><td><a class="pblink" id="pblink_table_item_7" href="" data-tad="431731" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> 2014</a> <small><a class="pblink" id="pblink_table_item_8" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow">Movies</a></small></td><td class="right">20 hours</td><td class="right">754.5 MB</td><td class="right"><font color="green">21</font> </td><td class="right"><font color="navy">919</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr></table><br /><br /><center><iframe src='http://creative.wwwpromoter.com/13689?d=728x90' width='728' height='90' style='border: none;' frameborder='0' scrolling='no'></iframe><center><span style="float:right;margin:1em .2em 0 0"><a title="Download at the speed of your connection" href="/usenet.php?search=narcos+season+2"><img src="/images/dlf.gif" alt="Search Binaries" /></a></span><div style="margin-bottom:1em;margin-right:290px" id="headerbox"><h1><a href="/searchfeed/narcos+season+2.xml" target="_blank" title="SeedPeer RSS Torrent Search Feed fornarcos season 2"><img src="/images/feedIcon.png" border="0" /></a>&nbsp;2 <font class="colored">Narcos season 2</font> Torrents were found</h1></div><table width="100%"><tr><th>
66− <span style="float:right">
67− <a href="/search/narcos-season-2/8/1.html"><img style="vertical-align:middle" src="/images/comments.gif" alt="comments" /></a> |
68− <a href="/search/narcos-season-2/7/1.html"><img style="vertical-align:middle" src="/images/ver.gif" alt="verified" /></a>
69− </span>
70− <a href="/search/narcos-season-2/1/1.html">Torrent name</a></th><th class="right"><a href="/search/narcos-season-2/2/1.html">Age</a></th><th class="right"><a href="/search/narcos-season-2/3/1.html">Size</a></th><th class="right"><a href="/search/narcos-season-2/4/1.html">Seeds</a></th><th class="right"><a href="/search/narcos-season-2/5/1.html">Peers</a></th><th class="center"><a href="/search/narcos-season-2/6/1.html">Health</a></th></tr><tr class=""><td><small class="comments"><a href="http://www.facebook.com/sharer.php?t=Download%20<strong class='colored'>Narcos</strong> <strong class='colored'>Season</strong> <strong class='colored'>2</strong> Complete 7<strong class='colored'>2</strong>0p WebRip EN-SUB x<strong class='colored'>2</strong>64-[MULVAcoded] S0<strong class='colored'>2</strong>%20 torrent&u=http://seedpeer.seedpeer.eu/details/11686840/Narcos-Season-2-Complete-720p-WebRip-EN-SUB-x264-[MULVAcoded]-S02.html"><img src="/images/facebook.png" alt="Add to Facebook" width="14" height="14" /></a></small><a href="/details/11686840/Narcos-Season-2-Complete-720p-WebRip-EN-SUB-x264-[MULVAcoded]-S02.html"><strong class='colored'>Narcos</strong> <strong class='colored'>Season</strong> <strong class='colored'>2</strong> Complete 7<strong class='colored'>2</strong>0p WebRip EN-SUB x<strong class='colored'>2</strong>64-[MULVAcoded] S0<strong class='colored'>2</strong> <small><a href="/browse.html#11686840"></a></small></a></td><td class="right">19 hours</td><td class="right">4.39 GB</td><td class="right"><font color="green">715</font> </td><td class="right"><font color="navy">183</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" width="40" height="11" /></td></tr><tr class="tdark"><td><small class="comments"><a href="http://www.facebook.com/sharer.php?t=Download%20<strong class='colored'>Narcos</strong> - <strong class='colored'>Season</strong> <strong class='colored'>2</strong> - 7<strong class='colored'>2</strong>0p WEBRiP - x<strong class='colored'>2</strong>65 HEVC - ShAaNiG%20 torrent&u=http://seedpeer.seedpeer.eu/details/11685972/Narcos---Season-2---720p-WEBRiP---x265-HEVC---ShAaNiG.html"><img src="/images/facebook.png" alt="Add to Facebook" width="14" height="14" /></a></small><a href="/details/11685972/Narcos---Season-2---720p-WEBRiP---x265-HEVC---ShAaNiG.html"><strong class='colored'>Narcos</strong> - <strong class='colored'>Season</strong> <strong class='colored'>2</strong> - 7<strong class='colored'>2</strong>0p WEBRiP - x<strong class='colored'>2</strong>65 HEVC - ShAaNiG <small><a href="/browse.html#11685972"></a></small></a></td><td class="right">1 day</td><td class="right">2.48 GB</td><td class="right"><font color="green">861</font> </td><td class="right"><font color="navy">332</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" width="40" height="11" /></td></tr></table><div id="headerbox"><h1>Related searches for: <font class="colored">Narcos season 2</font></h1></div><div id="search_suggestions"><br />Other suggested searches: </div><br /><a href="http://torrentz2.eu/search?f=narcos-season-2">Search for "narcos-season-2" on Torrentz2.eu</a><br /><a href="http://torrent-finder.info/show.php?q=narcos-season-2">Search for "narcos-season-2" on Torrent-Finder</a><br /><center><iframe src='http://creative.wwwpromoter.com/13689?d=300x250' width='300' height='250' style='border: none;' frameborder='0' scrolling='no'></iframe>&nbsp;<iframe src='http://creative.wwwpromoter.com/13689?d=300x250' width='300' height='250' style='border: none;' frameborder='0' scrolling='no'></iframe>&nbsp;<iframe src='http://creative.wwwpromoter.com/13689?d=300x250' width='300' height='250' style='border: none;' frameborder='0' scrolling='no'></iframe></center><div id="footer">
71− <table width="100%">
72− <tr>
73− <td width="30%">
74− <h2>Torrents Download</h2>
75− <a href="/">Torrent search</a><br />
76− <a href="/browse.html">Browse categories</a><br />
77− <a href="/verified.html">Verified Torrents</a><br />
78− <a href="/order-date.html">Today's torrents</a><br />
79− <a href="/yesterday.html">Yesterday's torrents</a><br />
80− <a href="/stats.html">Statistics</a><br />
81− <br />
82− <a href="/faq.html#copyright"><strong>Copyright & Removal</strong></a>
83− </td>
84− <td width="30%"><h2>Cool Stuff</h2>
85− <a href="/promotional.php">Promotional</a><br />
86− <a href="/contact.html">Advertising Information</a><br />
87− <strong><a href="/plugins.php" title="Add a search plugin to Firefox or Internet Explorer">Search Plugin <span style="color:red">*</span></a></strong><br />
88− <a href="http://www.utorrent.com">&micro;Torrent Client</a><br />
89− <a href="/blog">Seedpeer Blog</a><br />
90− </td>
91− <td width="30%"><h2>Links</h2>
92− <a href="http://www.sumotorrent.com" target="_blank"><strong>SumoTorrent</strong></a><br />
93− <a href="http://www.torrent-finder.info" target="_blank"><strong>Torrent Finder</strong></a><br />
94− <a href="http://www.torrentpond.com" target="_blank"><strong>TorrentPond</strong></a><br />
95− <a href="https://www.limetorrents.cc" target="_blank">LimeTorrents.cc</a><br />
96− <a href="http://www.torrents.to/" target="_blank">Torrents.to</a><br />
97− <a href="http://www.torrentfunk.com" target="_blank">TorrentFunk</a><br />
98− <a href="https://monova.org" target="_blank">Monova</a><br />
99− <a href="http://www.torrentroom.com" target="_blank">TorrentRoom</a><br />
100− <a href="http://www.katcr.co/" target="_blank">Kickass Torrents Community</a><br />
101− </td>
102− <td width="10%"><div id="bottomlogo"></div></td>
103− </tr>
104− </table>
105− <br />
106− <br />
107− </div>
108− </div>
109− </body>
110− </html>
tests/unit/engines/test_acgsou.pydeleted−78
@@ -1,78 +0,0 @@
1−# coding=utf-8
2−from collections import defaultdict
3−import mock
4−from searx.engines import acgsou
5−from searx.testing import SearxTestCase
6−
7−
8−class TestAcgsouEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dic = defaultdict(dict)
13− dic['pageno'] = 1
14− params = acgsou.request(query, dic)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('acgsou.com' in params['url'])
18−
19− def test_response(self):
20− resp = mock.Mock(text='<html></html>')
21− self.assertEqual(acgsou.response(resp), [])
22−
23− html = u"""
24− <html>
25−<table id="listTable" class="list_style table_fixed">
26− <thead class="tcat">
27− <tr>
28− <th axis="string" class="l1 tableHeaderOver">test</th>
29− <th axis="string" class="l2 tableHeaderOver">test</th>
30− <th axis="string" class="l3 tableHeaderOver">test</th>
31− <th axis="size" class="l4 tableHeaderOver">test</th>
32− <th axis="number" class="l5 tableHeaderOver">test</th>
33− <th axis="number" class="l6 tableHeaderOver">test</th>
34− <th axis="number" class="l7 tableHeaderOver">test</th>
35− <th axis="string" class="l8 tableHeaderOver">test</th>
36− </tr>
37− </thead>
38− <tbody class="tbody" id="data_list">
39− <tr class="alt1 ">
40− <td nowrap="nowrap">date</td>
41− <td><a href="category.html">testcategory テスト</a></td>
42− <td style="text-align:left;">
43− <a href="show-torrentid.html" target="_blank">torrentname テスト</a>
44− </td>
45− <td>1MB</td>
46− <td nowrap="nowrap">
47− <span class="bts_1">
48− 29
49− </span>
50− </td>
51− <td nowrap="nowrap">
52− <span class="btl_1">
53− 211
54− </span>
55− </td>
56− <td nowrap="nowrap">
57− <span class="btc_">
58− 168
59− </span>
60− </td>
61− <td><a href="random.html">user</a></td>
62− </tr>
63− </tbody>
64−</table>
65−</html>
66− """
67−
68− resp = mock.Mock(text=html)
69− results = acgsou.response(resp)
70−
71− self.assertEqual(type(results), list)
72− self.assertEqual(len(results), 1)
73−
74− r = results[0]
75− self.assertEqual(r['url'], 'http://www.acgsou.com/show-torrentid.html')
76− self.assertEqual(r['content'], u'Category: "testcategory テスト".')
77− self.assertEqual(r['title'], u'torrentname テスト')
78− self.assertEqual(r['filesize'], 1048576)
tests/unit/engines/test_archlinux.pydeleted−111
@@ -1,111 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import archlinux
4−from searx.testing import SearxTestCase
5−
6−domains = {
7− 'all': 'https://wiki.archlinux.org',
8− 'de': 'https://wiki.archlinux.de',
9− 'fr': 'https://wiki.archlinux.fr',
10− 'ja': 'https://wiki.archlinuxjp.org',
11− 'ro': 'http://wiki.archlinux.ro',
12− 'tr': 'http://archtr.org/wiki'
13−}
14−
15−
16−class TestArchLinuxEngine(SearxTestCase):
17−
18− def test_request(self):
19− query = 'test_query'
20− dic = defaultdict(dict)
21− dic['pageno'] = 1
22− dic['language'] = 'en-US'
23− params = archlinux.request(query, dic)
24− self.assertTrue('url' in params)
25− self.assertTrue(query in params['url'])
26− self.assertTrue('wiki.archlinux.org' in params['url'])
27−
28− for lang, name in archlinux.main_langs:
29− dic['language'] = lang
30− params = archlinux.request(query, dic)
31− self.assertTrue(name in params['url'])
32−
33− for lang, domain in domains.items():
34− dic['language'] = lang
35− params = archlinux.request(query, dic)
36− self.assertTrue(domain in params['url'])
37−
38− def test_response(self):
39− response = mock.Mock(text='<html></html>',
40− search_params={'language': 'en_US'})
41− self.assertEqual(archlinux.response(response), [])
42−
43− html = """
44− <ul class="mw-search-results">
45− <li>
46− <div class="mw-search-result-heading">
47− <a href="/index.php/ATI" title="ATI">ATI</a>
48− </div>
49− <div class="searchresult">
50− Lorem ipsum dolor sit amet
51− </div>
52− <div class="mw-search-result-data">
53− 30 KB (4,630 words) - 19:04, 17 March 2016</div>
54− </li>
55− <li>
56− <div class="mw-search-result-heading">
57− <a href="/index.php/Frequently_asked_questions" title="Frequently asked questions">
58− Frequently asked questions
59− </a>
60− </div>
61− <div class="searchresult">
62− CPUs with AMDs instruction set "AMD64"
63− </div>
64− <div class="mw-search-result-data">
65− 17 KB (2,722 words) - 20:13, 21 March 2016
66− </div>
67− </li>
68− <li>
69− <div class="mw-search-result-heading">
70− <a href="/index.php/CPU_frequency_scaling" title="CPU frequency scaling">CPU frequency scaling</a>
71− </div>
72− <div class="searchresult">
73− ondemand for AMD and older Intel CPU
74− </div>
75− <div class="mw-search-result-data">
76− 15 KB (2,319 words) - 23:46, 16 March 2016
77− </div>
78− </li>
79− </ul>
80− """
81−
82− expected = [
83− {
84− 'title': 'ATI',
85− 'url': 'https://wiki.archlinux.org/index.php/ATI'
86− },
87− {
88− 'title': 'Frequently asked questions',
89− 'url': 'https://wiki.archlinux.org/index.php/Frequently_asked_questions'
90− },
91− {
92− 'title': 'CPU frequency scaling',
93− 'url': 'https://wiki.archlinux.org/index.php/CPU_frequency_scaling'
94− }
95− ]
96−
97− response = mock.Mock(text=html)
98− response.search_params = {
99− 'language': 'en_US'
100− }
101− results = archlinux.response(response)
102−
103− self.assertEqual(type(results), list)
104− self.assertEqual(len(results), len(expected))
105−
106− i = 0
107− for exp in expected:
108− res = results[i]
109− i += 1
110− for key, value in exp.items():
111− self.assertEqual(res[key], value)
tests/unit/engines/test_arxiv.pydeleted−58
@@ -1,58 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import arxiv
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBaseEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'.encode('utf-8')
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = arxiv.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn('export.arxiv.org/api/', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, arxiv.response, None)
20− self.assertRaises(AttributeError, arxiv.response, [])
21− self.assertRaises(AttributeError, arxiv.response, '')
22− self.assertRaises(AttributeError, arxiv.response, '[]')
23−
24− response = mock.Mock(content=b'''<?xml version="1.0" encoding="UTF-8"?>
25−<feed xmlns="http://www.w3.org/2005/Atom"></feed>''')
26− self.assertEqual(arxiv.response(response), [])
27−
28− xml_mock = b'''<?xml version="1.0" encoding="UTF-8"?>
29−<feed xmlns="http://www.w3.org/2005/Atom">
30− <title type="html">ArXiv Query: search_query=all:test_query&amp;id_list=&amp;start=0&amp;max_results=1</title>
31− <id>http://arxiv.org/api/1</id>
32− <updated>2000-01-21T00:00:00-01:00</updated>
33− <opensearch:totalResults xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">1</opensearch:totalResults>
34− <opensearch:startIndex xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">0</opensearch:startIndex>
35− <opensearch:itemsPerPage xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">1</opensearch:itemsPerPage>
36− <entry>
37− <id>http://arxiv.org/1</id>
38− <updated>2000-01-01T00:00:01Z</updated>
39− <published>2000-01-01T00:00:01Z</published>
40− <title>Mathematical proof.</title>
41− <summary>Mathematical formula.</summary>
42− <author>
43− <name>A. B.</name>
44− </author>
45− <link href="http://arxiv.org/1" rel="alternate" type="text/html"/>
46− <link title="pdf" href="http://arxiv.org/1" rel="related" type="application/pdf"/>
47− <category term="math.QA" scheme="http://arxiv.org/schemas/atom"/>
48− <category term="1" scheme="http://arxiv.org/schemas/atom"/>
49− </entry>
50−</feed>
51−'''
52−
53− response = mock.Mock(content=xml_mock)
54− results = arxiv.response(response)
55− self.assertEqual(type(results), list)
56− self.assertEqual(len(results), 1)
57− self.assertEqual(results[0]['title'], 'Mathematical proof.')
58− self.assertEqual(results[0]['content'], 'Mathematical formula.')
tests/unit/engines/test_base.pydeleted−91
@@ -1,91 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import base
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBaseEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = base.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn('base-search.net', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, base.response, None)
20− self.assertRaises(AttributeError, base.response, [])
21− self.assertRaises(AttributeError, base.response, '')
22− self.assertRaises(AttributeError, base.response, '[]')
23−
24− response = mock.Mock(content=b'<response></response>')
25− self.assertEqual(base.response(response), [])
26−
27− xml_mock = b"""<?xml version="1.0"?>
28−<response>
29− <lst name="responseHeader">
30− <int name="status">0</int>
31− <int name="QTime">1</int>
32− </lst>
33− <result name="response" numFound="1" start="0">
34− <doc>
35− <date name="dchdate">2000-01-01T01:01:01Z</date>
36− <str name="dcdocid">1</str>
37− <str name="dccontinent">cna</str>
38− <str name="dccountry">us</str>
39− <str name="dccollection">ftciteseerx</str>
40− <str name="dcprovider">CiteSeerX</str>
41− <str name="dctitle">Science and more</str>
42− <arr name="dccreator">
43− <str>Someone</str>
44− </arr>
45− <arr name="dcperson">
46− <str>Someone</str>
47− </arr>
48− <arr name="dcsubject">
49− <str>Science and more</str>
50− </arr>
51− <str name="dcdescription">Science, and even more.</str>
52− <arr name="dccontributor">
53− <str>The neighbour</str>
54− </arr>
55− <str name="dcdate">2001</str>
56− <int name="dcyear">2001</int>
57− <arr name="dctype">
58− <str>text</str>
59− </arr>
60− <arr name="dctypenorm">
61− <str>1</str>
62− </arr>
63− <arr name="dcformat">
64− <str>application/pdf</str>
65− </arr>
66− <arr name="dccontenttype">
67− <str>application/pdf</str>
68− </arr>
69− <arr name="dcidentifier">
70− <str>http://example.org/</str>
71− </arr>
72− <str name="dclink">http://example.org</str>
73− <str name="dcsource">http://example.org</str>
74− <arr name="dclanguage">
75− <str>en</str>
76− </arr>
77− <str name="dcrights">Under the example.org licence</str>
78− <int name="dcoa">1</int>
79− <arr name="dclang">
80− <str>eng</str>
81− </arr>
82− </doc>
83− </result>
84−</response>"""
85−
86− response = mock.Mock(content=xml_mock)
87− results = base.response(response)
88− self.assertEqual(type(results), list)
89− self.assertEqual(len(results), 1)
90− self.assertEqual(results[0]['title'], 'Science and more')
91− self.assertEqual(results[0]['content'], 'Science, and even more.')
tests/unit/engines/test_bing.pydeleted−178
@@ -1,178 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import bing
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBingEngine(SearxTestCase):
9−
10− def test_request(self):
11− bing.supported_languages = ['en', 'fr', 'zh-CHS', 'zh-CHT', 'pt-PT', 'pt-BR']
12− query = u'test_query'
13− dicto = defaultdict(dict)
14− dicto['pageno'] = 1
15− dicto['language'] = 'fr-FR'
16− params = bing.request(query.encode('utf-8'), dicto)
17− self.assertTrue('url' in params)
18− self.assertTrue(query in params['url'])
19− self.assertTrue('language%3AFR' in params['url'])
20− self.assertTrue('bing.com' in params['url'])
21−
22− dicto['language'] = 'all'
23− params = bing.request(query.encode('utf-8'), dicto)
24− self.assertTrue('language' in params['url'])
25−
26− def test_response(self):
27− dicto = defaultdict(dict)
28− dicto['pageno'] = 1
29− dicto['language'] = 'fr-FR'
30− self.assertRaises(AttributeError, bing.response, None)
31− self.assertRaises(AttributeError, bing.response, [])
32− self.assertRaises(AttributeError, bing.response, '')
33− self.assertRaises(AttributeError, bing.response, '[]')
34−
35− response = mock.Mock(text='<html></html>')
36− response.search_params = dicto
37− self.assertEqual(bing.response(response), [])
38−
39− response = mock.Mock(text='<html></html>')
40− response.search_params = dicto
41− self.assertEqual(bing.response(response), [])
42−
43− html = """
44− <div>
45− <div id="b_tween">
46− <span class="sb_count" data-bm="4">23 900 000 résultats</span>
47− </div>
48− <ol id="b_results" role="main">
49− <div class="sa_cc" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
50− <div Class="sa_mc">
51− <div class="sb_tlst">
52− <h3>
53− <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
54− <strong>This</strong> should be the title</a>
55− </h3>
56− </div>
57− <div class="sb_meta"><cite><strong>this</strong>.meta.com</cite>
58− <span class="c_tlbxTrg">
59− <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
60− </span>
61− </span>
62− </div>
63− <p><strong>This</strong> should be the content.</p>
64− </div>
65− </div>
66− </ol>
67− </div>
68− """
69− response = mock.Mock(text=html)
70− response.search_params = dicto
71− results = bing.response(response)
72− self.assertEqual(type(results), list)
73− self.assertEqual(len(results), 2)
74− self.assertEqual(results[0]['title'], 'This should be the title')
75− self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
76− self.assertEqual(results[0]['content'], 'This should be the content.')
77− self.assertEqual(results[-1]['number_of_results'], 23900000)
78−
79− html = """
80− <div>
81− <div id="b_tween">
82− <span class="sb_count" data-bm="4">9-18 résultats sur 23 900 000</span>
83− </div>
84− <ol id="b_results" role="main">
85− <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
86− <div Class="sa_mc">
87− <div class="sb_tlst">
88− <h2>
89− <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
90− <strong>This</strong> should be the title</a>
91− </h2>
92− </div>
93− <div class="sb_meta"><cite><strong>this</strong>.meta.com</cite>
94− <span class="c_tlbxTrg">
95− <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
96− </span>
97− </span>
98− </div>
99− <p><strong>This</strong> should be the content.</p>
100− </div>
101− </li>
102− </ol>
103− </div>
104− """
105− dicto['pageno'] = 2
106− response = mock.Mock(text=html)
107− response.search_params = dicto
108− results = bing.response(response)
109− self.assertEqual(type(results), list)
110− self.assertEqual(len(results), 2)
111− self.assertEqual(results[0]['title'], 'This should be the title')
112− self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
113− self.assertEqual(results[0]['content'], 'This should be the content.')
114− self.assertEqual(results[-1]['number_of_results'], 23900000)
115−
116− html = """
117− <div>
118− <div id="b_tween">
119− <span class="sb_count" data-bm="4">23 900 000 résultats</span>
120− </div>
121− <ol id="b_results" role="main">
122− <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
123− <div Class="sa_mc">
124− <div class="sb_tlst">
125− <h2>
126− <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
127− <strong>This</strong> should be the title</a>
128− </h2>
129− </div>
130− <div class="sb_meta"><cite><strong>this</strong>.meta.com</cite>
131− <span class="c_tlbxTrg">
132− <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
133− </span>
134− </span>
135− </div>
136− <p><strong>This</strong> should be the content.</p>
137− </div>
138− </li>
139− </ol>
140− </div>
141− """
142− dicto['pageno'] = 33900000
143− response = mock.Mock(text=html)
144− response.search_params = dicto
145− results = bing.response(response)
146− self.assertEqual(bing.response(response), [])
147−
148− def test_fetch_supported_languages(self):
149− html = """<html></html>"""
150− response = mock.Mock(text=html)
151− results = bing._fetch_supported_languages(response)
152− self.assertEqual(type(results), list)
153− self.assertEqual(len(results), 0)
154−
155− html = """
156− <html>
157− <body>
158− <form>
159− <div id="limit-languages">
160− <div>
161− <div><input id="es" value="es"></input></div>
162− </div>
163− <div>
164− <div><input id="pt_BR" value="pt_BR"></input></div>
165− <div><input id="pt_PT" value="pt_PT"></input></div>
166− </div>
167− </div>
168− </form>
169− </body>
170− </html>
171− """
172− response = mock.Mock(text=html)
173− languages = bing._fetch_supported_languages(response)
174− self.assertEqual(type(languages), list)
175− self.assertEqual(len(languages), 3)
176− self.assertIn('es', languages)
177− self.assertIn('pt-BR', languages)
178− self.assertIn('pt-PT', languages)
tests/unit/engines/test_bing_images.pydeleted−132
@@ -1,132 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import bing_images
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBingImagesEngine(SearxTestCase):
9−
10− def test_request(self):
11− bing_images.supported_languages = ['fr-FR', 'en-US']
12− bing_images.language_aliases = {}
13− query = 'test_query'
14− dicto = defaultdict(dict)
15− dicto['pageno'] = 1
16− dicto['language'] = 'fr-FR'
17− dicto['safesearch'] = 1
18− dicto['time_range'] = ''
19− params = bing_images.request(query, dicto)
20− self.assertTrue('url' in params)
21− self.assertTrue(query in params['url'])
22− self.assertTrue('bing.com' in params['url'])
23− self.assertTrue('SRCHHPGUSR' in params['cookies'])
24− self.assertTrue('DEMOTE' in params['cookies']['SRCHHPGUSR'])
25− self.assertTrue('_EDGE_S' in params['cookies'])
26− self.assertTrue('fr-fr' in params['cookies']['_EDGE_S'])
27−
28− dicto['language'] = 'fr'
29− params = bing_images.request(query, dicto)
30− self.assertTrue('_EDGE_S' in params['cookies'])
31− self.assertTrue('fr-fr' in params['cookies']['_EDGE_S'])
32−
33− dicto['language'] = 'all'
34− params = bing_images.request(query, dicto)
35− self.assertTrue('_EDGE_S' in params['cookies'])
36− self.assertTrue('en-us' in params['cookies']['_EDGE_S'])
37−
38− def test_response(self):
39− self.assertRaises(AttributeError, bing_images.response, None)
40− self.assertRaises(AttributeError, bing_images.response, [])
41− self.assertRaises(AttributeError, bing_images.response, '')
42− self.assertRaises(AttributeError, bing_images.response, '[]')
43−
44− response = mock.Mock(text='<html></html>')
45− self.assertEqual(bing_images.response(response), [])
46−
47− response = mock.Mock(text='<html></html>')
48− self.assertEqual(bing_images.response(response), [])
49−
50− html = """
51− <div id="mmComponent_images_1">
52− <ul>
53− <li>
54− <div>
55− <div class="imgpt">
56− <a m='{"purl":"page_url","murl":"img_url","turl":"thumb_url","t":"Page 1 title"}'>
57− <img src="" alt="alt text" />
58− </a>
59− <div class="img_info">
60− <span>1 x 1 - jpeg</span>
61− <a>1.example.org</a>
62− </div>
63− </div>
64− <div></div>
65− </div>
66− <div>
67− <div class="imgpt">
68− <a m='{"purl":"page_url2","murl":"img_url2","turl":"thumb_url2","t":"Page 2 title"}'>
69− <img src="" alt="alt text 2" />
70− </a>
71− <div class="img_info">
72− <span>2 x 2 - jpeg</span>
73− <a>2.example.org</a>
74− </div>
75− </div>
76− </div>
77− </li>
78− </ul>
79− <ul>
80− <li>
81− <div>
82− <div class="imgpt">
83− <a m='{"purl":"page_url3","murl":"img_url3","turl":"thumb_url3","t":"Page 3 title"}'>
84− <img src="" alt="alt text 3" />
85− </a>
86− <div class="img_info">
87− <span>3 x 3 - jpeg</span>
88− <a>3.example.org</a>
89− </div>
90− </div>
91− </div>
92− </li>
93− </ul>
94− </div>
95− """
96− html = html.replace('\r\n', '').replace('\n', '').replace('\r', '')
97− response = mock.Mock(text=html)
98− results = bing_images.response(response)
99− self.assertEqual(type(results), list)
100− self.assertEqual(len(results), 3)
101− self.assertEqual(results[0]['title'], 'Page 1 title')
102− self.assertEqual(results[0]['url'], 'page_url')
103− self.assertEqual(results[0]['content'], '')
104− self.assertEqual(results[0]['thumbnail_src'], 'thumb_url')
105− self.assertEqual(results[0]['img_src'], 'img_url')
106− self.assertEqual(results[0]['img_format'], '1 x 1 - jpeg')
107− self.assertEqual(results[0]['source'], '1.example.org')
108−
109− def test_fetch_supported_languages(self):
110− html = """
111− <div>
112− <div id="region-section-content">
113− <ul class="b_vList">
114− <li>
115− <a href="https://bing...&setmkt=de-DE&s...">Germany</a>
116− <a href="https://bing...&setmkt=nb-NO&s...">Norway</a>
117− </li>
118− </ul>
119− <ul class="b_vList">
120− <li>
121− <a href="https://bing...&setmkt=es-AR&s...">Argentina</a>
122− </li>
123− </ul>
124− </div>
125− </div>
126− """
127− response = mock.Mock(text=html)
128− languages = list(bing_images._fetch_supported_languages(response))
129− self.assertEqual(len(languages), 3)
130− self.assertIn('de-DE', languages)
131− self.assertIn('no-NO', languages)
132− self.assertIn('es-AR', languages)
tests/unit/engines/test_bing_news.pydeleted−147
@@ -1,147 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import bing_news
4−from searx.testing import SearxTestCase
5−import lxml
6−
7−
8−class TestBingNewsEngine(SearxTestCase):
9−
10− def test_request(self):
11− bing_news.supported_languages = ['en', 'fr']
12− query = 'test_query'
13− dicto = defaultdict(dict)
14− dicto['pageno'] = 1
15− dicto['language'] = 'fr-FR'
16− dicto['time_range'] = ''
17− params = bing_news.request(query, dicto)
18− self.assertIn('url', params)
19− self.assertIn(query, params['url'])
20− self.assertIn('bing.com', params['url'])
21− self.assertIn('fr', params['url'])
22−
23− dicto['language'] = 'all'
24− params = bing_news.request(query, dicto)
25− self.assertIn('en', params['url'])
26−
27− def test_no_url_in_request_year_time_range(self):
28− dicto = defaultdict(dict)
29− query = 'test_query'
30− dicto['time_range'] = 'year'
31− params = bing_news.request(query, dicto)
32− self.assertEqual({}, params['url'])
33−
34− def test_response(self):
35− self.assertRaises(AttributeError, bing_news.response, None)
36− self.assertRaises(AttributeError, bing_news.response, [])
37− self.assertRaises(AttributeError, bing_news.response, '')
38− self.assertRaises(AttributeError, bing_news.response, '[]')
39−
40− response = mock.Mock(content='<html></html>')
41− self.assertEqual(bing_news.response(response), [])
42−
43− response = mock.Mock(content='<html></html>')
44− self.assertEqual(bing_news.response(response), [])
45−
46− html = """<?xml version="1.0" encoding="utf-8" ?>
47−<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
48− <channel>
49− <title>python - Bing News</title>
50− <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
51− <description>Search results</description>
52− <image>
53− <url>http://10.53.64.9/rsslogo.gif</url>
54− <title>test</title>
55− <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
56− </image>
57− <copyright>Copyright</copyright>
58− <item>
59− <title>Title</title>
60− <link>https://www.bing.com/news/apiclick.aspx?ref=FexRss&amp;aid=&amp;tid=c237eccc50bd4758b106a5e3c94fce09&amp;url=http%3a%2f%2furl.of.article%2f&amp;c=xxxxxxxxx&amp;mkt=en-us</link>
61− <description>Article Content</description>
62− <pubDate>Tue, 02 Jun 2015 13:37:00 GMT</pubDate>
63− <News:Source>Infoworld</News:Source>
64− <News:Image>http://a1.bing4.com/th?id=ON.13371337133713371337133713371337&amp;pid=News</News:Image>
65− <News:ImageSize>w={0}&amp;h={1}&amp;c=7</News:ImageSize>
66− <News:ImageKeepOriginalRatio></News:ImageKeepOriginalRatio>
67− <News:ImageMaxWidth>620</News:ImageMaxWidth>
68− <News:ImageMaxHeight>413</News:ImageMaxHeight>
69− </item>
70− <item>
71− <title>Another Title</title>
72− <link>https://www.bing.com/news/apiclick.aspx?ref=FexRss&amp;aid=&amp;tid=c237eccc50bd4758b106a5e3c94fce09&amp;url=http%3a%2f%2fanother.url.of.article%2f&amp;c=xxxxxxxxx&amp;mkt=en-us</link>
73− <description>Another Article Content</description>
74− <pubDate>Tue, 02 Jun 2015 13:37:00 GMT</pubDate>
75− </item>
76− </channel>
77−</rss>""" # noqa
78− response = mock.Mock(content=html.encode('utf-8'))
79− results = bing_news.response(response)
80− self.assertEqual(type(results), list)
81− self.assertEqual(len(results), 2)
82− self.assertEqual(results[0]['title'], 'Title')
83− self.assertEqual(results[0]['url'], 'http://url.of.article/')
84− self.assertEqual(results[0]['content'], 'Article Content')
85− self.assertEqual(results[0]['img_src'], 'https://www.bing.com/th?id=ON.13371337133713371337133713371337')
86− self.assertEqual(results[1]['title'], 'Another Title')
87− self.assertEqual(results[1]['url'], 'http://another.url.of.article/')
88− self.assertEqual(results[1]['content'], 'Another Article Content')
89− self.assertNotIn('img_src', results[1])
90−
91− html = """<?xml version="1.0" encoding="utf-8" ?>
92−<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
93− <channel>
94− <title>python - Bing News</title>
95− <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
96− <description>Search results</description>
97− <image>
98− <url>http://10.53.64.9/rsslogo.gif</url>
99− <title>test</title>
100− <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
101− </image>
102− <copyright>Copyright</copyright>
103− <item>
104− <title>Title</title>
105− <link>http://another.url.of.article/</link>
106− <description>Article Content</description>
107− <pubDate>garbage</pubDate>
108− <News:Source>Infoworld</News:Source>
109− <News:Image>http://another.bing.com/image</News:Image>
110− <News:ImageSize>w={0}&amp;h={1}&amp;c=7</News:ImageSize>
111− <News:ImageKeepOriginalRatio></News:ImageKeepOriginalRatio>
112− <News:ImageMaxWidth>620</News:ImageMaxWidth>
113− <News:ImageMaxHeight>413</News:ImageMaxHeight>
114− </item>
115− </channel>
116−</rss>""" # noqa
117− response = mock.Mock(content=html.encode('utf-8'))
118− results = bing_news.response(response)
119− self.assertEqual(type(results), list)
120− self.assertEqual(len(results), 1)
121− self.assertEqual(results[0]['title'], 'Title')
122− self.assertEqual(results[0]['url'], 'http://another.url.of.article/')
123− self.assertEqual(results[0]['content'], 'Article Content')
124− self.assertEqual(results[0]['img_src'], 'http://another.bing.com/image')
125−
126− html = """<?xml version="1.0" encoding="utf-8" ?>
127−<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
128− <channel>
129− <title>python - Bing News</title>
130− <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
131− <description>Search results</description>
132− <image>
133− <url>http://10.53.64.9/rsslogo.gif</url>
134− <title>test</title>
135− <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
136− </image>
137− </channel>
138−</rss>""" # noqa
139−
140− response = mock.Mock(content=html.encode('utf-8'))
141− results = bing_news.response(response)
142− self.assertEqual(type(results), list)
143− self.assertEqual(len(results), 0)
144−
145− html = """<?xml version="1.0" encoding="utf-8" ?>gabarge"""
146− response = mock.Mock(content=html.encode('utf-8'))
147− self.assertRaises(lxml.etree.XMLSyntaxError, bing_news.response, response)
tests/unit/engines/test_bing_videos.pydeleted−72
@@ -1,72 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import bing_videos
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBingVideosEngine(SearxTestCase):
9−
10− def test_request(self):
11− bing_videos.supported_languages = ['fr-FR', 'en-US']
12− bing_videos.language_aliases = {}
13− query = 'test_query'
14− dicto = defaultdict(dict)
15− dicto['pageno'] = 1
16− dicto['language'] = 'fr-FR'
17− dicto['safesearch'] = 0
18− dicto['time_range'] = ''
19− params = bing_videos.request(query, dicto)
20− self.assertTrue('url' in params)
21− self.assertTrue(query in params['url'])
22− self.assertTrue('bing.com' in params['url'])
23− self.assertTrue('SRCHHPGUSR' in params['cookies'])
24− self.assertTrue('OFF' in params['cookies']['SRCHHPGUSR'])
25− self.assertTrue('_EDGE_S' in params['cookies'])
26− self.assertTrue('fr-fr' in params['cookies']['_EDGE_S'])
27−
28− dicto['pageno'] = 2
29− dicto['time_range'] = 'day'
30− dicto['safesearch'] = 2
31− params = bing_videos.request(query, dicto)
32− self.assertTrue('first=29' in params['url'])
33− self.assertTrue('1440' in params['url'])
34− self.assertIn('SRCHHPGUSR', params['cookies'])
35− self.assertTrue('STRICT' in params['cookies']['SRCHHPGUSR'])
36−
37− def test_response(self):
38− self.assertRaises(AttributeError, bing_videos.response, None)
39− self.assertRaises(AttributeError, bing_videos.response, [])
40− self.assertRaises(AttributeError, bing_videos.response, '')
41− self.assertRaises(AttributeError, bing_videos.response, '[]')
42−
43− response = mock.Mock(text='<html></html>')
44− self.assertEqual(bing_videos.response(response), [])
45−
46− response = mock.Mock(text='<html></html>')
47− self.assertEqual(bing_videos.response(response), [])
48−
49− html = """
50− <div class="dg_u">
51− <div>
52− <a>
53− <div>
54− <div>
55− <div class="mc_vtvc_meta_block">
56− <div><span>100 views</span><span>1 year ago</span></div><div><span>ExampleTube</span><span>Channel 1<span></div> #noqa
57− </div>
58− </div>
59− <div class="vrhdata" vrhm='{"du":"01:11","murl":"https://www.example.com/watch?v=DEADBEEF","thid":"OVP.BINGTHUMB1","vt":"Title 1"}'></div> # noqa
60− </div>
61− </a>
62− </div>
63− </div>
64− """
65− response = mock.Mock(text=html)
66− results = bing_videos.response(response)
67− self.assertEqual(type(results), list)
68− self.assertEqual(len(results), 1)
69− self.assertEqual(results[0]['title'], 'Title 1')
70− self.assertEqual(results[0]['url'], 'https://www.example.com/watch?v=DEADBEEF')
71− self.assertEqual(results[0]['content'], '01:11 - 100 views - 1 year ago - ExampleTube - Channel 1')
72− self.assertEqual(results[0]['thumbnail'], 'https://www.bing.com/th?id=OVP.BINGTHUMB1')
tests/unit/engines/test_btdigg.pydeleted−112
@@ -1,112 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import btdigg
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBtdiggEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 0
14− params = btdigg.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn(query, params['url'])
17− self.assertIn('btdig.com', params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, btdigg.response, None)
21− self.assertRaises(AttributeError, btdigg.response, [])
22− self.assertRaises(AttributeError, btdigg.response, '')
23− self.assertRaises(AttributeError, btdigg.response, '[]')
24−
25− response = mock.Mock(text='<html></html>')
26− self.assertEqual(btdigg.response(response), [])
27−
28− html = u"""
29− <div class="one_result" style="display:table-row;background-color:#e8e8e8">
30− <div style="display:table-cell;color:rgb(0, 0, 0)">
31− <div style="display:table">
32− <div style="display:table-row">
33− <div class="torrent_name" style="display:table-cell">
34− <a style="color:rgb(0, 0, 204);text-decoration:underline;font-size:150%"
35− href="http://btdig.com/a72f35b7ee3a10928f02bb799e40ae5db701ed1c/pdf?q=pdf&amp;p=1&amp;order=0"
36− >3.9GBdeLibrosByHuasoFromHell(3de4)</a>
37− </div>
38− </div>
39− </div>
40− <div style="display:table">
41− <div style="display:table-row">
42− <div style="display:table-cell">
43− <span class="torrent_files" style="color:#666;padding-left:10px">4217</span> files <span
44− class="torrent_size" style="color:#666;padding-left:10px">1 GB</span><span
45− class="torrent_age" style="color:rgb(0, 102, 0);padding-left:10px;margin: 0px 4px"
46− >found 3 years ago</span>
47− </div>
48− </div>
49− </div>
50− <div style="display:table;width:100%;padding:10px">
51− <div style="display:table-row">
52− <div class="torrent_magnet" style="display:table-cell">
53− <div class="fa fa-magnet" style="color:#cc0000">
54− <a href="magnet:?xt=urn:btih:a72f35b7ee3a10928f02bb799e40ae5db701ed1c&amp;dn=3.9GBdeLibrosBy..."
55− title="Download via magnet-link"> magnet:?xt=urn:btih:a72f35b7ee...</a>
56− </div>
57− </div>
58− <div style="display:table-cell;color:rgb(0, 0, 0);text-align:right">
59− <span style="color:rgb(136, 136, 136);margin: 0px 0px 0px 4px"></span><span
60− style="color:rgb(0, 102, 0);margin: 0px 4px">found 3 years ago</span>
61− </div>
62− </div>
63− </div>
64− <div class="torrent_excerpt" style="display:table;padding:10px;white-space:nowrap">
65− <div class="fa fa-folder-open" style="padding-left:0em"> 3.9GBdeLibrosByHuasoFromHell(3de4)</div><br/>
66− <div class="fa fa-folder-open" style="padding-left:1em"> Libros H-Z</div><br/>
67− <div class="fa fa-folder-open" style="padding-left:2em"> H</div><br/><div class="fa fa-file-archive-o"
68− style="padding-left:3em"> H.H. Hollis - El truco de la espada-<b
69− style="color:red; background-color:yellow">pdf</b>.zip</div><span
70− style="color:#666;padding-left:10px">17 KB</span><br/>
71− <div class="fa fa-file-archive-o" style="padding-left:3em"> Hagakure - El Libro del Samurai-<b
72− style="color:red; background-color:yellow">pdf</b>.zip</div><span
73− style="color:#666;padding-left:10px">95 KB</span><br/>
74− <div class="fa fa-folder-open" style="padding-left:3em"> Hamsun, Knut (1859-1952)</div><br/>
75− <div class="fa fa-file-archive-o" style="padding-left:4em"> Hamsun, Knut - Hambre-<b
76− style="color:red; background-color:yellow">pdf</b>.zip</div><span
77− style="color:#666;padding-left:10px">786 KB</span><br/>
78− <div class="fa fa-plus-circle"><a
79− href="http://btdig.com/a72f35b7ee3a10928f02bb799e40ae5db701ed1c/pdf?q=pdf&amp;p=1&amp;order=0"
80− > 4214 hidden files<span style="color:#666;padding-left:10px">1 GB</span></a></div>
81− </div>
82− </div>
83− </div>
84− """
85− response = mock.Mock(text=html.encode('utf-8'))
86− results = btdigg.response(response)
87− self.assertEqual(type(results), list)
88− self.assertEqual(len(results), 1)
89− self.assertEqual(results[0]['title'], '3.9GBdeLibrosByHuasoFromHell(3de4)')
90− self.assertEqual(results[0]['url'],
91− 'http://btdig.com/a72f35b7ee3a10928f02bb799e40ae5db701ed1c/pdf?q=pdf&p=1&order=0')
92− self.assertEqual(results[0]['content'],
93− '3.9GBdeLibrosByHuasoFromHell(3de4) | ' +
94− 'Libros H-Z | ' +
95− 'H H.H. Hollis - El truco de la espada-pdf.zip17 KB | ' +
96− 'Hagakure - El Libro del Samurai-pdf.zip95 KB | ' +
97− 'Hamsun, Knut (1859-1952) | Hamsun, Knut - Hambre-pdf.zip786 KB | ' +
98− '4214 hidden files1 GB')
99− self.assertEqual(results[0]['filesize'], 1 * 1024 * 1024 * 1024)
100− self.assertEqual(results[0]['files'], 4217)
101− self.assertEqual(results[0]['magnetlink'],
102− 'magnet:?xt=urn:btih:a72f35b7ee3a10928f02bb799e40ae5db701ed1c&dn=3.9GBdeLibrosBy...')
103−
104− html = """
105− <div style="display:table-row;background-color:#e8e8e8">
106−
107− </div>
108− """
109− response = mock.Mock(text=html.encode('utf-8'))
110− results = btdigg.response(response)
111− self.assertEqual(type(results), list)
112− self.assertEqual(len(results), 0)
tests/unit/engines/test_currency_convert.pydeleted−56
@@ -1,56 +0,0 @@
1−from collections import defaultdict
2−from datetime import datetime
3−import mock
4−from searx.engines import currency_convert
5−from searx.testing import SearxTestCase
6−
7−
8−class TestCurrencyConvertEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = b'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = currency_convert.request(query, dicto)
15− self.assertNotIn('url', params)
16−
17− query = b'convert 10 Pound Sterlings to United States Dollars'
18− params = currency_convert.request(query, dicto)
19− self.assertIn('url', params)
20− self.assertIn('duckduckgo.com', params['url'])
21− self.assertIn('GBP', params['url'])
22− self.assertIn('USD', params['url'])
23−
24− def test_response(self):
25− dicto = defaultdict(dict)
26− dicto['amount'] = float(10)
27− dicto['from'] = "GBP"
28− dicto['to'] = "USD"
29− dicto['from_name'] = "pound sterling"
30− dicto['to_name'] = "United States dollar"
31− response = mock.Mock(text='a,b,c,d', search_params=dicto)
32− self.assertEqual(currency_convert.response(response), [])
33− body = """ddg_spice_currency(
34− {
35− "conversion":{
36− "converted-amount": "0.5"
37− },
38− "topConversions":[
39− {
40− },
41− {
42− }
43− ]
44− }
45− );
46− """
47− response = mock.Mock(text=body, search_params=dicto)
48− results = currency_convert.response(response)
49− self.assertEqual(type(results), list)
50− self.assertEqual(len(results), 1)
51− self.assertEqual(results[0]['answer'], '10.0 GBP = 5.0 USD, 1 GBP (pound sterling)' +
52− ' = 0.5 USD (United States dollar)')
53−
54− target_url = 'https://duckduckgo.com/js/spice/currency/1/{}/{}'.format(
55− dicto['from'], dicto['to'])
56− self.assertEqual(results[0]['url'], target_url)
tests/unit/engines/test_dailymotion.pydeleted−112
@@ -1,112 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import dailymotion
5−from searx.testing import SearxTestCase
6−
7−
8−class TestDailymotionEngine(SearxTestCase):
9−
10− def test_request(self):
11− dailymotion.supported_languages = ['en', 'fr']
12− query = 'test_query'
13− dicto = defaultdict(dict)
14− dicto['pageno'] = 0
15− dicto['language'] = 'fr-FR'
16− params = dailymotion.request(query, dicto)
17− self.assertTrue('url' in params)
18− self.assertTrue(query in params['url'])
19− self.assertTrue('dailymotion.com' in params['url'])
20− self.assertTrue('fr' in params['url'])
21−
22− dicto['language'] = 'all'
23− params = dailymotion.request(query, dicto)
24− self.assertTrue('en' in params['url'])
25−
26− def test_response(self):
27− self.assertRaises(AttributeError, dailymotion.response, None)
28− self.assertRaises(AttributeError, dailymotion.response, [])
29− self.assertRaises(AttributeError, dailymotion.response, '')
30− self.assertRaises(AttributeError, dailymotion.response, '[]')
31−
32− response = mock.Mock(text='{}')
33− self.assertEqual(dailymotion.response(response), [])
34−
35− response = mock.Mock(text='{"data": []}')
36− self.assertEqual(dailymotion.response(response), [])
37−
38− json = """
39− {
40− "page": 1,
41− "limit": 5,
42− "explicit": false,
43− "total": 289487,
44− "has_more": true,
45− "list": [
46− {
47− "created_time": 1422173451,
48− "title": "Title",
49− "description": "Description",
50− "duration": 81,
51− "url": "http://www.url",
52− "thumbnail_360_url": "http://thumbnail",
53− "id": "x2fit7q"
54− }
55− ]
56− }
57− """
58− response = mock.Mock(text=json)
59− results = dailymotion.response(response)
60− self.assertEqual(type(results), list)
61− self.assertEqual(len(results), 1)
62− self.assertEqual(results[0]['title'], 'Title')
63− self.assertEqual(results[0]['url'], 'http://www.url')
64− self.assertEqual(results[0]['content'], 'Description')
65− self.assertIn('x2fit7q', results[0]['embedded'])
66−
67− json = r"""
68− {"toto":[
69− {"id":200,"name":"Artist Name",
70− "link":"http:\/\/www.dailymotion.com\/artist\/1217","type":"artist"}
71− ]}
72− """
73− response = mock.Mock(text=json)
74− results = dailymotion.response(response)
75− self.assertEqual(type(results), list)
76− self.assertEqual(len(results), 0)
77−
78− def test_fetch_supported_languages(self):
79− json = r"""
80− {"list":[{"code":"af","name":"Afrikaans","native_name":"Afrikaans",
81− "localized_name":"Afrikaans","display_name":"Afrikaans"},
82− {"code":"ar","name":"Arabic","native_name":"\u0627\u0644\u0639\u0631\u0628\u064a\u0629",
83− "localized_name":"Arabic","display_name":"Arabic"},
84− {"code":"la","name":"Latin","native_name":null,
85− "localized_name":"Latin","display_name":"Latin"}
86− ]}
87− """
88− response = mock.Mock(text=json)
89− languages = dailymotion._fetch_supported_languages(response)
90− self.assertEqual(type(languages), dict)
91− self.assertEqual(len(languages), 3)
92− self.assertIn('af', languages)
93− self.assertIn('ar', languages)
94− self.assertIn('la', languages)
95−
96− self.assertEqual(type(languages['af']), dict)
97− self.assertEqual(type(languages['ar']), dict)
98− self.assertEqual(type(languages['la']), dict)
99−
100− self.assertIn('name', languages['af'])
101− self.assertIn('name', languages['ar'])
102− self.assertNotIn('name', languages['la'])
103−
104− self.assertIn('english_name', languages['af'])
105− self.assertIn('english_name', languages['ar'])
106− self.assertIn('english_name', languages['la'])
107−
108− self.assertEqual(languages['af']['name'], 'Afrikaans')
109− self.assertEqual(languages['af']['english_name'], 'Afrikaans')
110− self.assertEqual(languages['ar']['name'], u'العربية')
111− self.assertEqual(languages['ar']['english_name'], 'Arabic')
112− self.assertEqual(languages['la']['english_name'], 'Latin')
tests/unit/engines/test_deezer.pydeleted−57
@@ -1,57 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import deezer
4−from searx.testing import SearxTestCase
5−
6−
7−class TestDeezerEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = deezer.request(query, dicto)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('deezer.com' in params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, deezer.response, None)
20− self.assertRaises(AttributeError, deezer.response, [])
21− self.assertRaises(AttributeError, deezer.response, '')
22− self.assertRaises(AttributeError, deezer.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(deezer.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(deezer.response(response), [])
29−
30− json = r"""
31− {"data":[
32− {"id":100, "title":"Title of track",
33− "link":"https:\/\/www.deezer.com\/track\/1094042","duration":232,
34− "artist":{"id":200,"name":"Artist Name",
35− "link":"https:\/\/www.deezer.com\/artist\/1217","type":"artist"},
36− "album":{"id":118106,"title":"Album Title","type":"album"},"type":"track"}
37− ]}
38− """
39− response = mock.Mock(text=json)
40− results = deezer.response(response)
41− self.assertEqual(type(results), list)
42− self.assertEqual(len(results), 1)
43− self.assertEqual(results[0]['title'], 'Title of track')
44− self.assertEqual(results[0]['url'], 'https://www.deezer.com/track/1094042')
45− self.assertEqual(results[0]['content'], 'Artist Name - Album Title - Title of track')
46− self.assertTrue('100' in results[0]['embedded'])
47−
48− json = r"""
49− {"data":[
50− {"id":200,"name":"Artist Name",
51− "link":"https:\/\/www.deezer.com\/artist\/1217","type":"artist"}
52− ]}
53− """
54− response = mock.Mock(text=json)
55− results = deezer.response(response)
56− self.assertEqual(type(results), list)
57− self.assertEqual(len(results), 0)
tests/unit/engines/test_deviantart.pydeleted−24
@@ -1,24 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import deviantart
4−from searx.testing import SearxTestCase
5−
6−
7−class TestDeviantartEngine(SearxTestCase):
8−
9− def test_request(self):
10− dicto = defaultdict(dict)
11− query = 'test_query'
12− dicto['pageno'] = 0
13− dicto['time_range'] = ''
14− params = deviantart.request(query, dicto)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('deviantart.com' in params['url'])
18−
19− def test_no_url_in_request_year_time_range(self):
20− dicto = defaultdict(dict)
21− query = 'test_query'
22− dicto['time_range'] = 'year'
23− params = deviantart.request(query, dicto)
24− self.assertEqual({}, params['url'])
tests/unit/engines/test_digbt.pydeleted−61
@@ -1,61 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import digbt
4−from searx.testing import SearxTestCase
5−
6−
7−class TestDigBTEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = digbt.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['url'])
16− self.assertIn('digbt.org', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, digbt.response, None)
20− self.assertRaises(AttributeError, digbt.response, [])
21− self.assertRaises(AttributeError, digbt.response, '')
22− self.assertRaises(AttributeError, digbt.response, '[]')
23−
24− response = mock.Mock(text='<html></html>')
25− self.assertEqual(digbt.response(response), [])
26−
27− html = """
28− <table class="table">
29− <tr><td class="x-item">
30− <div>
31− <a title="The Big Bang Theory" class="title" href="/The-Big-Bang-Theory-d2.html">
32− The Big <span class="highlight">Bang</span> Theory
33− </a>
34− <span class="ctime"><span style="color:red;">4 hours ago</span></span>
35− </div>
36− <div class="files">
37− <ul>
38− <li>The Big Bang Theory 2.9 GB</li>
39− <li>....</li>
40− </ul>
41− </div>
42− <div class="tail">
43− Files: 1 Size: 2.9 GB Downloads: 1 Updated: <span style="color:red;">4 hours ago</span>
44− &nbsp; &nbsp;
45− <a class="title" href="magnet:?xt=urn:btih:a&amp;dn=The+Big+Bang+Theory">
46− <span class="glyphicon glyphicon-magnet"></span> magnet-link
47− </a>
48− &nbsp; &nbsp;
49− </div>
50− </td></tr>
51− </table>
52− """
53− response = mock.Mock(text=html.encode('utf-8'))
54− results = digbt.response(response)
55− self.assertEqual(type(results), list)
56− self.assertEqual(len(results), 1)
57− self.assertEqual(results[0]['title'], 'The Big Bang Theory')
58− self.assertEqual(results[0]['url'], 'https://digbt.org/The-Big-Bang-Theory-d2.html')
59− self.assertEqual(results[0]['content'], 'The Big Bang Theory 2.9 GB ....')
60− self.assertEqual(results[0]['filesize'], 3113851289)
61− self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:a&dn=The+Big+Bang+Theory')
tests/unit/engines/test_digg.pydeleted−16
@@ -1,16 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import digg
4−from searx.testing import SearxTestCase
5−
6−
7−class TestDiggEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 1
13− params = digg.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['url'])
16− self.assertIn('digg.com', params['url'])
tests/unit/engines/test_doku.pydeleted−79
@@ -1,79 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import doku
5−from searx.testing import SearxTestCase
6−
7−
8−class TestDokuEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− params = doku.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['url'])
16−
17− def test_response(self):
18− self.assertRaises(AttributeError, doku.response, None)
19− self.assertRaises(AttributeError, doku.response, [])
20− self.assertRaises(AttributeError, doku.response, '')
21− self.assertRaises(AttributeError, doku.response, '[]')
22−
23− response = mock.Mock(text='<html></html>')
24− self.assertEqual(doku.response(response), [])
25−
26− html = u"""
27− <div class="search_quickresult">
28− <h3>Pages trouvées :</h3>
29− <ul class="search_quickhits">
30− <li> <a href="/xfconf-query" class="wikilink1" title="xfconf-query">xfconf-query</a></li>
31− </ul>
32− <div class="clearer"></div>
33− </div>
34− """
35− response = mock.Mock(text=html)
36− results = doku.response(response)
37− expected = [{'content': '', 'title': 'xfconf-query', 'url': 'http://localhost:8090/xfconf-query'}]
38− self.assertEqual(doku.response(response), expected)
39−
40− html = u"""
41− <dl class="search_results">
42− <dt><a href="/xvnc?s[]=query" class="wikilink1" title="xvnc">xvnc</a>: 40 Occurrences trouvées</dt>
43− <dd>er = /usr/bin/Xvnc
44− server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 640x480 ... er = /usr/bin/Xvnc
45− server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 800x600 ... er = /usr/bin/Xvnc
46− server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 1024x768 ... er = /usr/bin/Xvnc
47− server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 1280x1024 -depth 8 -Sec</dd>
48− <dt><a href="/postfix_mysql_tls_sasl_1404?s[]=query"
49− class="wikilink1"
50− title="postfix_mysql_tls_sasl_1404">postfix_mysql_tls_sasl_1404</a>: 14 Occurrences trouvées</dt>
51− <dd>tdepasse
52− hosts = 127.0.0.1
53− dbname = postfix
54− <strong class="search_hit">query</strong> = SELECT goto FROM alias WHERE address='%s' AND a... tdepasse
55− hosts = 127.0.0.1
56− dbname = postfix
57− <strong class="search_hit">query</strong> = SELECT domain FROM domain WHERE domain='%s'
58− #optional <strong class="search_hit">query</strong> to use when relaying for backup MX
59− #<strong class="search_hit">query</strong> = SELECT domain FROM domain WHERE domain='%s' and backupmx =</dd>
60− <dt><a href="/bind9?s[]=query" class="wikilink1" title="bind9">bind9</a>: 12 Occurrences trouvées</dt>
61− <dd> printcmd
62−;; Got answer:
63−;; -&gt;&gt;HEADER&lt;&lt;- opcode: <strong class="search_hit">QUERY</strong>, status: NOERROR, id: 13427
64−;; flags: qr aa rd ra; <strong class="search_hit">QUERY</strong>: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
65−
66−[...]
67−
68−;; <strong class="search_hit">Query</strong> time: 1 msec
69−;; SERVER: 127.0.0.1#53(127.0.0.1)
70−;... par la requête (<strong class="search_hit">Query</strong> time) , entre la première et la deuxième requête.</dd>
71− </dl>
72− """
73− response = mock.Mock(text=html)
74− results = doku.response(response)
75− self.assertEqual(type(results), list)
76− self.assertEqual(len(results), 3)
77− self.assertEqual(results[0]['title'], 'xvnc')
78−# FIXME self.assertEqual(results[0]['url'], u'http://this.should.be.the.link/ű')
79−# FIXME self.assertEqual(results[0]['content'], 'This should be the content.')
tests/unit/engines/test_duckduckgo.pydeleted−106
@@ -1,106 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import load_engine, duckduckgo
5−from searx.testing import SearxTestCase
6−
7−
8−class TestDuckduckgoEngine(SearxTestCase):
9−
10− def test_request(self):
11− duckduckgo = load_engine({'engine': 'duckduckgo', 'name': 'duckduckgo'})
12−
13− query = 'test_query'
14− dicto = defaultdict(dict)
15− dicto['pageno'] = 1
16− dicto['time_range'] = ''
17−
18− dicto['language'] = 'de-CH'
19− params = duckduckgo.request(query, dicto)
20− self.assertIn('url', params)
21− self.assertIn(query, params['url'])
22− self.assertIn('duckduckgo.com', params['url'])
23− self.assertIn('ch-de', params['url'])
24− self.assertIn('s=0', params['url'])
25−
26− # when ddg uses non standard codes
27− dicto['language'] = 'zh-HK'
28− params = duckduckgo.request(query, dicto)
29− self.assertIn('hk-tzh', params['url'])
30−
31− dicto['language'] = 'en-GB'
32− params = duckduckgo.request(query, dicto)
33− self.assertIn('uk-en', params['url'])
34−
35− # no country given
36− dicto['language'] = 'en'
37− params = duckduckgo.request(query, dicto)
38− self.assertIn('us-en', params['url'])
39−
40− def test_no_url_in_request_year_time_range(self):
41− dicto = defaultdict(dict)
42− query = 'test_query'
43− dicto['time_range'] = 'year'
44− params = duckduckgo.request(query, dicto)
45− self.assertEqual({}, params['url'])
46−
47− def test_response(self):
48− self.assertRaises(AttributeError, duckduckgo.response, None)
49− self.assertRaises(AttributeError, duckduckgo.response, [])
50− self.assertRaises(AttributeError, duckduckgo.response, '')
51− self.assertRaises(AttributeError, duckduckgo.response, '[]')
52−
53− response = mock.Mock(text='<html></html>')
54− self.assertEqual(duckduckgo.response(response), [])
55−
56− html = u"""
57− <div class="result results_links results_links_deep web-result result--no-result">
58− <div class="links_main links_deep result__body">
59− <h2 class="result__title">
60− </h2>
61− <div class="no-results">No results</div>
62− <div class="result__extras">
63− </div>
64− </div>
65− </div>
66− """
67− response = mock.Mock(text=html)
68− results = duckduckgo.response(response)
69− self.assertEqual(duckduckgo.response(response), [])
70−
71− html = u"""
72− <div class="result results_links results_links_deep web-result ">
73− <div class="links_main links_deep result__body">
74− <h2 class="result__title">
75− <a rel="nofollow" class="result__a" href="http://this.should.be.the.link/ű">
76− This <b>is</b> <b>the</b> title
77− </a>
78− </h2>
79− <a class="result__snippet" href="http://this.should.be.the.link/ű">
80− <b>This</b> should be the content.
81− </a>
82− <div class="result__extras">
83− </div>
84− </div>
85− </div>
86− """
87− response = mock.Mock(text=html)
88− results = duckduckgo.response(response)
89− self.assertEqual(type(results), list)
90− self.assertEqual(len(results), 1)
91− self.assertEqual(results[0]['title'], 'This is the title')
92− self.assertEqual(results[0]['url'], u'http://this.should.be.the.link/ű')
93− self.assertEqual(results[0]['content'], 'This should be the content.')
94−
95− def test_fetch_supported_languages(self):
96− js = """some code...regions:{
97− "wt-wt":"All Results","ar-es":"Argentina","au-en":"Australia","at-de":"Austria","be-fr":"Belgium (fr)"
98− }some more code..."""
99− response = mock.Mock(text=js)
100− languages = list(duckduckgo._fetch_supported_languages(response))
101− self.assertEqual(len(languages), 5)
102− self.assertIn('wt-WT', languages)
103− self.assertIn('es-AR', languages)
104− self.assertIn('en-AU', languages)
105− self.assertIn('de-AT', languages)
106− self.assertIn('fr-BE', languages)
tests/unit/engines/test_duckduckgo_definitions.pydeleted−255
@@ -1,255 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import duckduckgo_definitions
4−from searx.testing import SearxTestCase
5−
6−
7−class TestDDGDefinitionsEngine(SearxTestCase):
8−
9− def test_result_to_text(self):
10− url = ''
11− text = 'Text'
12− html_result = 'Html'
13− result = duckduckgo_definitions.result_to_text(url, text, html_result)
14− self.assertEqual(result, text)
15−
16− html_result = '<a href="url">Text in link</a>'
17− result = duckduckgo_definitions.result_to_text(url, text, html_result)
18− self.assertEqual(result, 'Text in link')
19−
20− def test_request(self):
21− duckduckgo_definitions.supported_languages = ['en-US', 'es-ES']
22− query = 'test_query'
23− dicto = defaultdict(dict)
24− dicto['pageno'] = 1
25− dicto['language'] = 'es'
26− params = duckduckgo_definitions.request(query, dicto)
27− self.assertIn('url', params)
28− self.assertIn(query, params['url'])
29− self.assertIn('duckduckgo.com', params['url'])
30− self.assertIn('headers', params)
31− self.assertIn('Accept-Language', params['headers'])
32− self.assertIn('es', params['headers']['Accept-Language'])
33−
34− def test_response(self):
35− self.assertRaises(AttributeError, duckduckgo_definitions.response, None)
36− self.assertRaises(AttributeError, duckduckgo_definitions.response, [])
37− self.assertRaises(AttributeError, duckduckgo_definitions.response, '')
38− self.assertRaises(AttributeError, duckduckgo_definitions.response, '[]')
39−
40− response = mock.Mock(text='{}')
41− self.assertEqual(duckduckgo_definitions.response(response), [])
42−
43− response = mock.Mock(text='{"data": []}')
44− self.assertEqual(duckduckgo_definitions.response(response), [])
45−
46− json = """
47− {
48− "DefinitionSource": "definition source",
49− "Heading": "heading",
50− "ImageWidth": 0,
51− "RelatedTopics": [
52− {
53− "Result": "Top-level domains",
54− "Icon": {
55− "URL": "",
56− "Height": "",
57− "Width": ""
58− },
59− "FirstURL": "https://first.url",
60− "Text": "text"
61− },
62− {
63− "Topics": [
64− {
65− "Result": "result topic",
66− "Icon": {
67− "URL": "",
68− "Height": "",
69− "Width": ""
70− },
71− "FirstURL": "https://duckduckgo.com/?q=2%2F2",
72− "Text": "result topic text"
73− }
74− ],
75− "Name": "name"
76− }
77− ],
78− "Entity": "Entity",
79− "Type": "A",
80− "Redirect": "",
81− "DefinitionURL": "http://definition.url",
82− "AbstractURL": "https://abstract.url",
83− "Definition": "this is the definition",
84− "AbstractSource": "abstract source",
85− "Infobox": {
86− "content": [
87− {
88− "data_type": "string",
89− "value": "1999",
90− "label": "Introduced",
91− "wiki_order": 0
92− }
93− ],
94− "meta": [
95− {
96− "data_type": "string",
97− "value": ".test",
98− "label": "article_title"
99− }
100− ]
101− },
102− "Image": "image.png",
103− "ImageIsLogo": 0,
104− "Abstract": "abstract",
105− "AbstractText": "abstract text",
106− "AnswerType": "",
107− "ImageHeight": 0,
108− "Results": [{
109− "Result" : "result title",
110− "Icon" : {
111− "URL" : "result url",
112− "Height" : 16,
113− "Width" : 16
114− },
115− "FirstURL" : "result first url",
116− "Text" : "result text"
117− }
118− ],
119− "Answer": "answer"
120− }
121− """
122− response = mock.Mock(text=json)
123− results = duckduckgo_definitions.response(response)
124− self.assertEqual(type(results), list)
125− self.assertEqual(len(results), 4)
126− self.assertEqual(results[0]['answer'], 'answer')
127− self.assertEqual(results[1]['title'], 'heading')
128− self.assertEqual(results[1]['url'], 'result first url')
129− self.assertEqual(results[2]['suggestion'], 'text')
130− self.assertEqual(results[3]['infobox'], 'heading')
131− self.assertEqual(results[3]['id'], 'https://definition.url')
132− self.assertEqual(results[3]['entity'], 'Entity')
133− self.assertIn('abstract', results[3]['content'])
134− self.assertIn('this is the definition', results[3]['content'])
135− self.assertEqual(results[3]['img_src'], 'image.png')
136− self.assertIn('Introduced', results[3]['attributes'][0]['label'])
137− self.assertIn('1999', results[3]['attributes'][0]['value'])
138− self.assertIn({'url': 'https://abstract.url', 'title': 'abstract source'}, results[3]['urls'])
139− self.assertIn({'url': 'http://definition.url', 'title': 'definition source'}, results[3]['urls'])
140− self.assertIn({'name': 'name', 'suggestions': ['result topic text']}, results[3]['relatedTopics'])
141−
142− json = """
143− {
144− "DefinitionSource": "definition source",
145− "Heading": "heading",
146− "ImageWidth": 0,
147− "RelatedTopics": [],
148− "Entity": "Entity",
149− "Type": "A",
150− "Redirect": "",
151− "DefinitionURL": "",
152− "AbstractURL": "https://abstract.url",
153− "Definition": "",
154− "AbstractSource": "abstract source",
155− "Image": "",
156− "ImageIsLogo": 0,
157− "Abstract": "",
158− "AbstractText": "abstract text",
159− "AnswerType": "",
160− "ImageHeight": 0,
161− "Results": [],
162− "Answer": ""
163− }
164− """
165− response = mock.Mock(text=json)
166− results = duckduckgo_definitions.response(response)
167− self.assertEqual(type(results), list)
168− self.assertEqual(len(results), 1)
169− self.assertEqual(results[0]['url'], 'https://abstract.url')
170− self.assertEqual(results[0]['title'], 'heading')
171− self.assertEqual(results[0]['content'], '')
172−
173− json = """
174− {
175− "DefinitionSource": "definition source",
176− "Heading": "heading",
177− "ImageWidth": 0,
178− "RelatedTopics": [
179− {
180− "Result": "Top-level domains",
181− "Icon": {
182− "URL": "",
183− "Height": "",
184− "Width": ""
185− },
186− "FirstURL": "https://first.url",
187− "Text": "heading"
188− },
189− {
190− "Name": "name"
191− },
192− {
193− "Topics": [
194− {
195− "Result": "result topic",
196− "Icon": {
197− "URL": "",
198− "Height": "",
199− "Width": ""
200− },
201− "FirstURL": "https://duckduckgo.com/?q=2%2F2",
202− "Text": "heading"
203− }
204− ],
205− "Name": "name"
206− }
207− ],
208− "Entity": "Entity",
209− "Type": "A",
210− "Redirect": "",
211− "DefinitionURL": "http://definition.url",
212− "AbstractURL": "https://abstract.url",
213− "Definition": "this is the definition",
214− "AbstractSource": "abstract source",
215− "Infobox": {
216− "meta": [
217− {
218− "data_type": "string",
219− "value": ".test",
220− "label": "article_title"
221− }
222− ]
223− },
224− "Image": "image.png",
225− "ImageIsLogo": 0,
226− "Abstract": "abstract",
227− "AbstractText": "abstract text",
228− "AnswerType": "",
229− "ImageHeight": 0,
230− "Results": [{
231− "Result" : "result title",
232− "Icon" : {
233− "URL" : "result url",
234− "Height" : 16,
235− "Width" : 16
236− },
237− "Text" : "result text"
238− }
239− ],
240− "Answer": ""
241− }
242− """
243− response = mock.Mock(text=json)
244− results = duckduckgo_definitions.response(response)
245− self.assertEqual(type(results), list)
246− self.assertEqual(len(results), 1)
247− self.assertEqual(results[0]['infobox'], 'heading')
248− self.assertEqual(results[0]['id'], 'https://definition.url')
249− self.assertEqual(results[0]['entity'], 'Entity')
250− self.assertIn('abstract', results[0]['content'])
251− self.assertIn('this is the definition', results[0]['content'])
252− self.assertEqual(results[0]['img_src'], 'image.png')
253− self.assertIn({'url': 'https://abstract.url', 'title': 'abstract source'}, results[0]['urls'])
254− self.assertIn({'url': 'http://definition.url', 'title': 'definition source'}, results[0]['urls'])
255− self.assertIn({'name': 'name', 'suggestions': []}, results[0]['relatedTopics'])
tests/unit/engines/test_duckduckgo_images.pydeleted−75
@@ -1,75 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import duckduckgo_images
5−from searx.testing import SearxTestCase
6−
7−
8−class TestDuckduckgoImagesEngine(SearxTestCase):
9−
10− def test_request(self):
11− duckduckgo_images.supported_languages = ['de-CH', 'en-US']
12− query = 'test_query'
13− dicto = defaultdict(dict)
14− dicto['is_test'] = True
15− dicto['pageno'] = 1
16− dicto['safesearch'] = 0
17− dicto['language'] = 'all'
18− params = duckduckgo_images.request(query, dicto)
19− self.assertIn('url', params)
20− self.assertIn(query, params['url'])
21− self.assertIn('duckduckgo.com', params['url'])
22− self.assertIn('s=0', params['url'])
23− self.assertIn('p=-1', params['url'])
24− self.assertIn('vqd=12345', params['url'])
25−
26− # test paging, safe search and language
27− dicto['pageno'] = 2
28− dicto['safesearch'] = 2
29− dicto['language'] = 'de'
30− params = duckduckgo_images.request(query, dicto)
31− self.assertIn('url', params)
32− self.assertIn(query, params['url'])
33− self.assertIn('s=50', params['url'])
34− self.assertIn('p=1', params['url'])
35− self.assertIn('ch-de', params['url'])
36−
37− def test_response(self):
38− self.assertRaises(AttributeError, duckduckgo_images.response, None)
39− self.assertRaises(AttributeError, duckduckgo_images.response, [])
40− self.assertRaises(AttributeError, duckduckgo_images.response, '')
41− self.assertRaises(AttributeError, duckduckgo_images.response, '[]')
42−
43− response = mock.Mock(text='If this error persists, please let us know: ops@duckduckgo.com')
44− self.assertRaises(Exception, duckduckgo_images.response, response)
45−
46− json = u"""
47− {
48− "query": "test_query",
49− "results": [
50− {
51− "title": "Result 1",
52− "url": "https://site1.url",
53− "thumbnail": "https://thumb1.nail",
54− "image": "https://image1"
55− },
56− {
57− "title": "Result 2",
58− "url": "https://site2.url",
59− "thumbnail": "https://thumb2.nail",
60− "image": "https://image2"
61− }
62− ]
63− }
64− """
65− response = mock.Mock(text=json)
66− results = duckduckgo_images.response(response)
67− self.assertEqual(len(results), 2)
68− self.assertEqual(results[0]['title'], 'Result 1')
69− self.assertEqual(results[0]['url'], 'https://site1.url')
70− self.assertEqual(results[0]['thumbnail_src'], 'https://thumb1.nail')
71− self.assertEqual(results[0]['img_src'], 'https://image1')
72− self.assertEqual(results[1]['title'], 'Result 2')
73− self.assertEqual(results[1]['url'], 'https://site2.url')
74− self.assertEqual(results[1]['thumbnail_src'], 'https://thumb2.nail')
75− self.assertEqual(results[1]['img_src'], 'https://image2')
tests/unit/engines/test_duden.pydeleted−47
@@ -1,47 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import duden
4−from searx.testing import SearxTestCase
5−from datetime import datetime
6−
7−
8−class TestDudenEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'Haus'
12− dic = defaultdict(dict)
13− data = [
14− [1, 'https://www.duden.de/suchen/dudenonline/Haus'],
15− [2, 'https://www.duden.de/suchen/dudenonline/Haus?search_api_fulltext=&page=1']
16− ]
17− for page_no, exp_res in data:
18− dic['pageno'] = page_no
19− params = duden.request(query, dic)
20− self.assertTrue('url' in params)
21− self.assertTrue(query in params['url'])
22− self.assertTrue('duden.de' in params['url'])
23− self.assertEqual(params['url'], exp_res)
24−
25− def test_response(self):
26− resp = mock.Mock(text='<html></html>')
27− self.assertEqual(duden.response(resp), [])
28−
29− html = """
30− <section class="vignette">
31− <h2"> <a href="/rechtschreibung/Haus">
32− <strong>This is the title also here</strong>
33− </a> </h2>
34− <p>This is the content</p>
35− </section>
36− """
37− resp = mock.Mock(text=html)
38− results = duden.response(resp)
39−
40− self.assertEqual(len(results), 1)
41− self.assertEqual(type(results), list)
42−
43− # testing result (dictionary entry)
44− r = results[0]
45− self.assertEqual(r['url'], 'https://www.duden.de/rechtschreibung/Haus')
46− self.assertEqual(r['title'], 'This is the title also here')
47− self.assertEqual(r['content'], 'This is the content')
tests/unit/engines/test_dummy.pydeleted−26
@@ -1,26 +0,0 @@
1−from searx.engines import dummy
2−from searx.testing import SearxTestCase
3−
4−
5−class TestDummyEngine(SearxTestCase):
6−
7− def test_request(self):
8− test_params = [
9− [1, 2, 3],
10− ['a'],
11− [],
12− 1
13− ]
14− for params in test_params:
15− self.assertEqual(dummy.request(None, params), params)
16−
17− def test_response(self):
18− responses = [
19− None,
20− [],
21− True,
22− dict(),
23− tuple()
24− ]
25− for response in responses:
26− self.assertEqual(dummy.response(response), [])
tests/unit/engines/test_faroo.pydeleted−113
@@ -1,113 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import faroo
5−from searx.testing import SearxTestCase
6−
7−
8−class TestFarooEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− dicto['language'] = 'fr-FR'
15− dicto['category'] = 'general'
16− params = faroo.request(query, dicto)
17− self.assertIn('url', params)
18− self.assertIn(query, params['url'])
19− self.assertIn('faroo.com', params['url'])
20− self.assertIn('en', params['url'])
21− self.assertIn('web', params['url'])
22−
23− dicto['language'] = 'all'
24− params = faroo.request(query, dicto)
25− self.assertIn('en', params['url'])
26−
27− dicto['language'] = 'de-DE'
28− params = faroo.request(query, dicto)
29− self.assertIn('de', params['url'])
30−
31− def test_response(self):
32− self.assertRaises(AttributeError, faroo.response, None)
33− self.assertRaises(AttributeError, faroo.response, [])
34− self.assertRaises(AttributeError, faroo.response, '')
35− self.assertRaises(AttributeError, faroo.response, '[]')
36−
37− response = mock.Mock(text='{}')
38− self.assertEqual(faroo.response(response), [])
39−
40− response = mock.Mock(text='{"data": []}')
41− self.assertEqual(faroo.response(response), [])
42−
43− response = mock.Mock(text='{"data": []}', status_code=429)
44− self.assertRaises(Exception, faroo.response, response)
45−
46− json = """
47− {
48− "results": [
49− {
50− "title": "This is the title",
51− "kwic": "This is the content",
52− "content": "",
53− "url": "http://this.is.the.url/",
54− "iurl": "",
55− "domain": "css3test.com",
56− "author": "Jim Dalrymple",
57− "news": true,
58− "votes": "10",
59− "date": 1360622563000,
60− "related": []
61− },
62− {
63− "title": "This is the title2",
64− "kwic": "This is the content2",
65− "content": "",
66− "url": "http://this.is.the.url2/",
67− "iurl": "",
68− "domain": "css3test.com",
69− "author": "Jim Dalrymple",
70− "news": false,
71− "votes": "10",
72− "related": []
73− },
74− {
75− "title": "This is the title3",
76− "kwic": "This is the content3",
77− "content": "",
78− "url": "http://this.is.the.url3/",
79− "iurl": "http://upload.wikimedia.org/optimized.jpg",
80− "domain": "css3test.com",
81− "author": "Jim Dalrymple",
82− "news": false,
83− "votes": "10",
84− "related": []
85− }
86− ],
87− "query": "test",
88− "suggestions": [],
89− "count": 100,
90− "start": 1,
91− "length": 10,
92− "time": "15"
93− }
94− """
95− response = mock.Mock(text=json)
96− results = faroo.response(response)
97− self.assertEqual(type(results), list)
98− self.assertEqual(len(results), 3)
99− self.assertEqual(results[0]['title'], 'This is the title')
100− self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
101− self.assertEqual(results[0]['content'], 'This is the content')
102− self.assertEqual(results[1]['title'], 'This is the title2')
103− self.assertEqual(results[1]['url'], 'http://this.is.the.url2/')
104− self.assertEqual(results[1]['content'], 'This is the content2')
105− self.assertEqual(results[2]['thumbnail'], 'http://upload.wikimedia.org/optimized.jpg')
106−
107− json = """
108− {}
109− """
110− response = mock.Mock(text=json)
111− results = faroo.response(response)
112− self.assertEqual(type(results), list)
113− self.assertEqual(len(results), 0)
tests/unit/engines/test_fdroid.pydeleted−60
@@ -1,60 +0,0 @@
1−import mock
2−from collections import defaultdict
3−from searx.engines import fdroid
4−from searx.testing import SearxTestCase
5−
6−
7−class TestFdroidEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dic = defaultdict(dict)
12− dic['pageno'] = 1
13− params = fdroid.request(query, dic)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('search.f-droid.org' in params['url'])
17−
18− def test_response_empty(self):
19− resp = mock.Mock(text='<html></html>')
20− self.assertEqual(fdroid.response(resp), [])
21−
22− def test_response_oneresult(self):
23− html = """
24−<!DOCTYPE html>
25−<html>
26−<head>
27− <title>test</title>
28−</head>
29−<body>
30− <div class="site-wrapper">
31− <div class="main-content">
32− <a class="package-header" href="https://example.com/app.url">
33− <img class="package-icon" src="https://example.com/appexample.logo.png" />
34−
35− <div class="package-info">
36− <h4 class="package-name">
37− App Example 1
38− </h4>
39−
40− <div class="package-desc">
41− <span class="package-summary">Description App Example 1</span>
42− <span class="package-license">GPL-3.0-only</span>
43− </div>
44− </div>
45− </a>
46− </div>
47− </div>
48−</body>
49−</html>
50− """
51−
52− resp = mock.Mock(text=html)
53− results = fdroid.response(resp)
54−
55− self.assertEqual(type(results), list)
56− self.assertEqual(len(results), 1)
57− self.assertEqual(results[0]['url'], 'https://example.com/app.url')
58− self.assertEqual(results[0]['title'], 'App Example 1')
59− self.assertEqual(results[0]['content'], 'Description App Example 1 - GPL-3.0-only')
60− self.assertEqual(results[0]['img_src'], 'https://example.com/appexample.logo.png')
tests/unit/engines/test_flickr.pydeleted−142
@@ -1,142 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import flickr
4−from searx.testing import SearxTestCase
5−
6−
7−class TestFlickrEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = flickr.request(query, dicto)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('flickr.com' in params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, flickr.response, None)
20− self.assertRaises(AttributeError, flickr.response, [])
21− self.assertRaises(AttributeError, flickr.response, '')
22− self.assertRaises(AttributeError, flickr.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(flickr.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(flickr.response(response), [])
29−
30− json = r"""
31− { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
32− "photo": [
33− { "id": "15751017054", "owner": "66847915@N08",
34− "secret": "69c22afc40", "server": "7285", "farm": 8,
35− "title": "Photo title", "ispublic": 1,
36− "isfriend": 0, "isfamily": 0,
37− "description": { "_content": "Description" },
38− "ownername": "Owner",
39− "url_o": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_9178e0f963_o.jpg",
40− "height_o": "2100", "width_o": "2653",
41− "url_n": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_n.jpg",
42− "height_n": "253", "width_n": "320",
43− "url_z": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_z.jpg",
44− "height_z": "507", "width_z": "640" }
45− ] }, "stat": "ok" }
46− """
47− response = mock.Mock(text=json)
48− results = flickr.response(response)
49− self.assertEqual(type(results), list)
50− self.assertEqual(len(results), 1)
51− self.assertEqual(results[0]['title'], 'Photo title')
52− self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/66847915@N08/15751017054')
53− self.assertTrue('o.jpg' in results[0]['img_src'])
54− self.assertTrue('n.jpg' in results[0]['thumbnail_src'])
55− self.assertTrue('Owner' in results[0]['author'])
56− self.assertTrue('Description' in results[0]['content'])
57−
58− json = r"""
59− { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
60− "photo": [
61− { "id": "15751017054", "owner": "66847915@N08",
62− "secret": "69c22afc40", "server": "7285", "farm": 8,
63− "title": "Photo title", "ispublic": 1,
64− "isfriend": 0, "isfamily": 0,
65− "description": { "_content": "Description" },
66− "ownername": "Owner",
67− "url_z": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_z.jpg",
68− "height_z": "507", "width_z": "640" }
69− ] }, "stat": "ok" }
70− """
71− response = mock.Mock(text=json)
72− results = flickr.response(response)
73− self.assertEqual(type(results), list)
74− self.assertEqual(len(results), 1)
75− self.assertEqual(results[0]['title'], 'Photo title')
76− self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/66847915@N08/15751017054')
77− self.assertTrue('z.jpg' in results[0]['img_src'])
78− self.assertTrue('z.jpg' in results[0]['thumbnail_src'])
79− self.assertTrue('Owner' in results[0]['author'])
80− self.assertTrue('Description' in results[0]['content'])
81−
82− json = r"""
83− { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
84− "photo": [
85− { "id": "15751017054", "owner": "66847915@N08",
86− "secret": "69c22afc40", "server": "7285", "farm": 8,
87− "title": "Photo title", "ispublic": 1,
88− "isfriend": 0, "isfamily": 0,
89− "description": { "_content": "Description" },
90− "ownername": "Owner",
91− "url_o": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_9178e0f963_o.jpg",
92− "height_o": "2100", "width_o": "2653" }
93− ] }, "stat": "ok" }
94− """
95− response = mock.Mock(text=json)
96− results = flickr.response(response)
97− self.assertEqual(type(results), list)
98− self.assertEqual(len(results), 1)
99− self.assertEqual(results[0]['title'], 'Photo title')
100− self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/66847915@N08/15751017054')
101− self.assertTrue('o.jpg' in results[0]['img_src'])
102− self.assertTrue('o.jpg' in results[0]['thumbnail_src'])
103− self.assertTrue('Owner' in results[0]['author'])
104− self.assertTrue('Description' in results[0]['content'])
105−
106− json = r"""
107− { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
108− "photo": [
109− { "id": "15751017054", "owner": "66847915@N08",
110− "secret": "69c22afc40", "server": "7285", "farm": 8,
111− "title": "Photo title", "ispublic": 1,
112− "isfriend": 0, "isfamily": 0,
113− "description": { "_content": "Description" },
114− "ownername": "Owner",
115− "url_n": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_n.jpg",
116− "height_n": "253", "width_n": "320" }
117− ] }, "stat": "ok" }
118− """
119− response = mock.Mock(text=json)
120− results = flickr.response(response)
121− self.assertEqual(type(results), list)
122− self.assertEqual(len(results), 0)
123−
124− json = """
125− { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
126− "toto": [] }, "stat": "ok" }
127− """
128− response = mock.Mock(text=json)
129− results = flickr.response(response)
130− self.assertEqual(type(results), list)
131− self.assertEqual(len(results), 0)
132−
133− json = r"""
134− {"toto":[
135− {"id":200,"name":"Artist Name",
136− "link":"http:\/\/www.flickr.com\/artist\/1217","type":"artist"}
137− ]}
138− """
139− response = mock.Mock(text=json)
140− results = flickr.response(response)
141− self.assertEqual(type(results), list)
142− self.assertEqual(len(results), 0)
tests/unit/engines/test_flickr_noapi.pydeleted−357
@@ -1,357 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import flickr_noapi
4−from searx.testing import SearxTestCase
5−
6−
7−class TestFlickrNoapiEngine(SearxTestCase):
8−
9− def test_build_flickr_url(self):
10− url = flickr_noapi.build_flickr_url("uid", "pid")
11− self.assertIn("uid", url)
12− self.assertIn("pid", url)
13−
14− def test_request(self):
15− query = 'test_query'
16− dicto = defaultdict(dict)
17− dicto['pageno'] = 1
18− dicto['time_range'] = ''
19− params = flickr_noapi.request(query, dicto)
20− self.assertIn('url', params)
21− self.assertIn(query, params['url'])
22− self.assertIn('flickr.com', params['url'])
23−
24− def test_response(self):
25− self.assertRaises(AttributeError, flickr_noapi.response, None)
26− self.assertRaises(AttributeError, flickr_noapi.response, [])
27− self.assertRaises(AttributeError, flickr_noapi.response, '')
28− self.assertRaises(AttributeError, flickr_noapi.response, '[]')
29−
30− response = mock.Mock(text='"modelExport:{"legend":[],"main":{"search-photos-lite-models":[{"photos":{}}]}}')
31− self.assertEqual(flickr_noapi.response(response), [])
32−
33− response = \
34− mock.Mock(text='"modelExport:{"legend":[],"main":{"search-photos-lite-models":[{"photos":{"_data":[]}}]}}')
35− self.assertEqual(flickr_noapi.response(response), [])
36−
37− # everthing is ok test
38− json = """
39− modelExport: {
40− "legend": [
41− [
42− "search-photos-lite-models",
43− "0",
44− "photos",
45− "_data",
46− "0"
47− ]
48− ],
49− "main": {
50− "search-photos-lite-models": [
51− {
52− "photos": {
53− "_data": [
54− {
55− "_flickrModelRegistry": "photo-lite-models",
56− "title": "This%20is%20the%20title",
57− "username": "Owner",
58− "pathAlias": "klink692",
59− "realname": "Owner",
60− "license": 0,
61− "ownerNsid": "59729010@N00",
62− "canComment": false,
63− "commentCount": 14,
64− "faveCount": 21,
65− "id": "14001294434",
66− "sizes": {
67− "c": {
68− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_c.jpg",
69− "width": 541,
70− "height": 800,
71− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_c.jpg",
72− "key": "c"
73− },
74− "h": {
75− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_761d32237a_h.jpg",
76− "width": 1081,
77− "height": 1600,
78− "url": "//c4.staticflickr.com/8/7246/14001294434_761d32237a_h.jpg",
79− "key": "h"
80− },
81− "k": {
82− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_f145a2c11a_k.jpg",
83− "width": 1383,
84− "height": 2048,
85− "url": "//c4.staticflickr.com/8/7246/14001294434_f145a2c11a_k.jpg",
86− "key": "k"
87− },
88− "l": {
89− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_b.jpg",
90− "width": 692,
91− "height": 1024,
92− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_b.jpg",
93− "key": "l"
94− },
95− "m": {
96− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777.jpg",
97− "width": 338,
98− "height": 500,
99− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777.jpg",
100− "key": "m"
101− },
102− "n": {
103− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_n.jpg",
104− "width": 216,
105− "height": 320,
106− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_n.jpg",
107− "key": "n"
108− },
109− "q": {
110− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_q.jpg",
111− "width": 150,
112− "height": 150,
113− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_q.jpg",
114− "key": "q"
115− },
116− "s": {
117− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_m.jpg",
118− "width": 162,
119− "height": 240,
120− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_m.jpg",
121− "key": "s"
122− },
123− "sq": {
124− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_s.jpg",
125− "width": 75,
126− "height": 75,
127− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_s.jpg",
128− "key": "sq"
129− },
130− "t": {
131− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_t.jpg",
132− "width": 68,
133− "height": 100,
134− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_t.jpg",
135− "key": "t"
136− },
137− "z": {
138− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_z.jpg",
139− "width": 433,
140− "height": 640,
141− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_z.jpg",
142− "key": "z"
143− }
144− }
145− }
146− ]
147− }
148− }
149− ]
150− }
151− }
152− """
153− # Flickr serves search results in a json block named 'modelExport' buried inside a script tag,
154− # this json is served as a single line terminating with a comma.
155− json = ''.join(json.split()) + ',\n'
156− response = mock.Mock(text=json)
157− results = flickr_noapi.response(response)
158− self.assertEqual(type(results), list)
159− self.assertEqual(len(results), 1)
160− self.assertEqual(results[0]['title'], 'This is the title')
161− self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/59729010@N00/14001294434')
162− self.assertIn('k.jpg', results[0]['img_src'])
163− self.assertIn('n.jpg', results[0]['thumbnail_src'])
164− self.assertIn('Owner', results[0]['author'])
165−
166− # no n size, only the z size
167− json = """
168− modelExport: {
169− "legend": [
170− [
171− "search-photos-lite-models",
172− "0",
173− "photos",
174− "_data",
175− "0"
176− ]
177− ],
178− "main": {
179− "search-photos-lite-models": [
180− {
181− "photos": {
182− "_data": [
183− {
184− "_flickrModelRegistry": "photo-lite-models",
185− "title": "This%20is%20the%20title",
186− "username": "Owner",
187− "pathAlias": "klink692",
188− "realname": "Owner",
189− "license": 0,
190− "ownerNsid": "59729010@N00",
191− "canComment": false,
192− "commentCount": 14,
193− "faveCount": 21,
194− "id": "14001294434",
195− "sizes": {
196− "z": {
197− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_z.jpg",
198− "width": 433,
199− "height": 640,
200− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_z.jpg",
201− "key": "z"
202− }
203− }
204− }
205− ]
206− }
207− }
208− ]
209− }
210− }
211− """
212− json = ''.join(json.split()) + ',\n'
213− response = mock.Mock(text=json)
214− results = flickr_noapi.response(response)
215− self.assertEqual(type(results), list)
216− self.assertEqual(len(results), 1)
217− self.assertEqual(results[0]['title'], 'This is the title')
218− self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/59729010@N00/14001294434')
219− self.assertIn('z.jpg', results[0]['img_src'])
220− self.assertIn('z.jpg', results[0]['thumbnail_src'])
221− self.assertIn('Owner', results[0]['author'])
222−
223− # no z or n size
224− json = """
225− modelExport: {
226− "legend": [
227− [
228− "search-photos-lite-models",
229− "0",
230− "photos",
231− "_data",
232− "0"
233− ]
234− ],
235− "main": {
236− "search-photos-lite-models": [
237− {
238− "photos": {
239− "_data": [
240− {
241− "_flickrModelRegistry": "photo-lite-models",
242− "title": "This%20is%20the%20title",
243− "username": "Owner",
244− "pathAlias": "klink692",
245− "realname": "Owner",
246− "license": 0,
247− "ownerNsid": "59729010@N00",
248− "canComment": false,
249− "commentCount": 14,
250− "faveCount": 21,
251− "id": "14001294434",
252− "sizes": {
253− "o": {
254− "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_o.jpg",
255− "width": 433,
256− "height": 640,
257− "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_o.jpg",
258− "key": "o"
259− }
260− }
261− }
262− ]
263− }
264− }
265− ]
266− }
267− }
268− """
269− json = ''.join(json.split()) + ',\n'
270− response = mock.Mock(text=json)
271− results = flickr_noapi.response(response)
272− self.assertEqual(type(results), list)
273− self.assertEqual(len(results), 1)
274− self.assertEqual(results[0]['title'], 'This is the title')
275− self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/59729010@N00/14001294434')
276− self.assertIn('o.jpg', results[0]['img_src'])
277− self.assertIn('o.jpg', results[0]['thumbnail_src'])
278− self.assertIn('Owner', results[0]['author'])
279−
280− # no image test
281− json = """
282− modelExport: {
283− "legend": [
284− [
285− "search-photos-lite-models",
286− "0",
287− "photos",
288− "_data",
289− "0"
290− ]
291− ],
292− "main": {
293− "search-photos-lite-models": [
294− {
295− "photos": {
296− "_data": [
297− {
298− "_flickrModelRegistry": "photo-lite-models",
299− "title": "This is the title",
300− "username": "Owner",
301− "pathAlias": "klink692",
302− "realname": "Owner",
303− "license": 0,
304− "ownerNsid": "59729010@N00",
305− "canComment": false,
306− "commentCount": 14,
307− "faveCount": 21,
308− "id": "14001294434",
309− "sizes": {
310− }
311− }
312− ]
313− }
314− }
315− ]
316− }
317− }
318− """
319− json = ''.join(json.split()) + ',\n'
320− response = mock.Mock(text=json)
321− results = flickr_noapi.response(response)
322− self.assertEqual(type(results), list)
323− self.assertEqual(len(results), 0)
324−
325− # null test
326− json = """
327− modelExport: {
328− "legend": [null],
329− "main": {
330− "search-photos-lite-models": [
331− {
332− "photos": {
333− "_data": [null]
334− }
335− }
336− ]
337− }
338− }
339− """
340− json = ''.join(json.split()) + ',\n'
341− response = mock.Mock(text=json)
342− results = flickr_noapi.response(response)
343− self.assertEqual(type(results), list)
344− self.assertEqual(len(results), 0)
345−
346− # garbage test
347− json = r"""
348− {"toto":[
349− {"id":200,"name":"Artist Name",
350− "link":"http:\/\/www.flickr.com\/artist\/1217","type":"artist"}
351− ]}
352− """
353− json = ''.join(json.split()) + ',\n'
354− response = mock.Mock(text=json)
355− results = flickr_noapi.response(response)
356− self.assertEqual(type(results), list)
357− self.assertEqual(len(results), 0)
tests/unit/engines/test_framalibre.pydeleted−103
@@ -1,103 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import framalibre
5−from searx.testing import SearxTestCase
6−
7−
8−class TestFramalibreEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 0
14− params = framalibre.request(query, dicto)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('framalibre.org' in params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, framalibre.response, None)
21− self.assertRaises(AttributeError, framalibre.response, [])
22− self.assertRaises(AttributeError, framalibre.response, '')
23− self.assertRaises(AttributeError, framalibre.response, '[]')
24−
25− response = mock.Mock(text='{}')
26− self.assertEqual(framalibre.response(response), [])
27−
28− response = mock.Mock(text='{"data": []}')
29− self.assertEqual(framalibre.response(response), [])
30−
31− html = u"""
32− <div class="nodes-list-row">
33− <div id="node-431"
34− class="node node-logiciel-annuaires node-promoted node-teaser node-teaser node-sheet clearfix nodes-list"
35− about="/content/gogs" typeof="sioc:Item foaf:Document">
36− <header class="media">
37− <div class="media-left">
38− <div class="field field-name-field-logo field-type-image field-label-hidden">
39− <div class="field-items">
40− <div class="field-item even">
41− <a href="/content/gogs">
42− <img class="media-object img-responsive" typeof="foaf:Image"
43− src="https://framalibre.org/sites/default/files/styles/teaser_logo/public/leslogos/gogs-lg.png?itok=rrCxKKBy"
44− width="70" height="70" alt="" />
45− </a>
46− </div>
47− </div>
48− </div>
49− </div>
50− <div class="media-body">
51− <h3 class="node-title"><a href="/content/gogs">Gogs</a></h3>
52− <span property="dc:title" content="Gogs" class="rdf-meta element-hidden"></span>
53− <div class="field field-name-field-annuaires field-type-taxonomy-term-reference field-label-hidden">
54− <div class="field-items">
55− <div class="field-item even">
56− <a href="/annuaires/cloudwebapps"
57− typeof="skos:Concept" property="rdfs:label skos:prefLabel"
58− datatype="" class="label label-primary">Cloud/webApps</a>
59− </div>
60− </div>
61− </div>
62− </div>
63− </header>
64− <div class="content">
65− <div class="field field-name-field-votre-appr-ciation field-type-fivestar field-label-hidden">
66− <div class="field-items">
67− <div class="field-item even">
68− </div>
69− </div>
70− </div>
71− <div class="field field-name-body field-type-text-with-summary field-label-hidden">
72− <div class="field-items">
73− <div class="field-item even" property="content:encoded">
74− <p>Gogs est une interface web basée sur git et une bonne alternative à GitHub.</p>
75− </div>
76− </div>
77− </div>
78− </div>
79− <footer>
80− <a href="/content/gogs" class="read-more btn btn-default btn-sm">Voir la notice</a>
81− <div class="field field-name-field-lien-officiel field-type-link-field field-label-hidden">
82− <div class="field-items">
83− <div class="field-item even">
84− <a href="https://gogs.io/" target="_blank" title="Voir le site officiel">
85− <span class="glyphicon glyphicon-globe"></span>
86− <span class="sr-only">Lien officiel</span>
87− </a>
88− </div>
89− </div>
90− </div>
91− </footer>
92− </div>
93− </div>
94− """
95− response = mock.Mock(text=html)
96− results = framalibre.response(response)
97− self.assertEqual(type(results), list)
98− self.assertEqual(len(results), 1)
99− self.assertEqual(results[0]['title'], 'Gogs')
100− self.assertEqual(results[0]['url'],
101− 'https://framalibre.org/content/gogs')
102− self.assertEqual(results[0]['content'],
103− u"Gogs est une interface web basée sur git et une bonne alternative à GitHub.")
tests/unit/engines/test_frinkiac.pydeleted−50
@@ -1,50 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import frinkiac
5−from searx.testing import SearxTestCase
6−
7−
8−class TestFrinkiacEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− request_dict = defaultdict(dict)
13− params = frinkiac.request(query, request_dict)
14− self.assertTrue('url' in params)
15−
16− def test_response(self):
17− self.assertRaises(AttributeError, frinkiac.response, None)
18− self.assertRaises(AttributeError, frinkiac.response, [])
19− self.assertRaises(AttributeError, frinkiac.response, '')
20− self.assertRaises(AttributeError, frinkiac.response, '[]')
21−
22− text = """
23−[{"Id":770931,
24− "Episode":"S06E18",
25− "Timestamp":534616,
26− "Filename":""},
27− {"Id":1657080,
28− "Episode":"S12E14",
29− "Timestamp":910868,
30− "Filename":""},
31− {"Id":1943753,
32− "Episode":"S14E21",
33− "Timestamp":773439,
34− "Filename":""},
35− {"Id":107835,
36− "Episode":"S02E03",
37− "Timestamp":531709,
38− "Filename":""}]
39− """
40−
41− response = mock.Mock(text=text)
42− results = frinkiac.response(response)
43− self.assertEqual(type(results), list)
44− self.assertEqual(len(results), 4)
45− self.assertEqual(results[0]['title'], u'S06E18')
46− self.assertIn('p=caption', results[0]['url'])
47− self.assertIn('e=S06E18', results[0]['url'])
48− self.assertIn('t=534616', results[0]['url'])
49− self.assertEqual(results[0]['thumbnail_src'], 'https://frinkiac.com/img/S06E18/534616/medium.jpg')
50− self.assertEqual(results[0]['img_src'], 'https://frinkiac.com/img/S06E18/534616.jpg')
tests/unit/engines/test_genius.pydeleted−231
@@ -1,231 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from datetime import datetime
4−from searx.engines import genius
5−from searx.testing import SearxTestCase
6−
7−
8−class TestGeniusEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = genius.request(query, dicto)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('genius.com' in params['url'])
18−
19− def test_response(self):
20−
21− json_empty = """
22− {
23− "meta": {
24− "status": 200
25− },
26− "response": {
27− "sections": [
28− {
29− "type": "top_hit",
30− "hits": []
31− },
32− {
33− "type": "song",
34− "hits": []
35− },
36− {
37− "type": "lyric",
38− "hits": []
39− },
40− {
41− "type": "artist",
42− "hits": []
43− },
44− {
45− "type": "album",
46− "hits": []
47− },
48− {
49− "type": "tag",
50− "hits": []
51− },
52− {
53− "type": "video",
54− "hits": []
55− },
56− {
57− "type": "article",
58− "hits": []
59− },
60− {
61− "type": "user",
62− "hits": []
63− }
64− ]
65− }
66− }
67− """
68−
69− resp = mock.Mock(text=json_empty)
70− self.assertEqual(genius.response(resp), [])
71−
72− json = """
73− {
74− "meta": {
75− "status": 200
76− },
77− "response": {
78− "sections": [
79− {
80− "type": "lyric",
81− "hits": [
82− {
83− "highlights": [
84− {
85− "property": "lyrics",
86− "value": "Sample lyrics",
87− "snippet": true,
88− "ranges": []
89− }
90− ],
91− "index": "lyric",
92− "type": "song",
93− "result": {
94− "_type": "song",
95− "annotation_count": 45,
96− "api_path": "/songs/52916",
97− "full_title": "J't'emmerde by MC Jean Gab'1",
98− "header_image_thumbnail_url": "https://images.genius.com/xxx.300x300x1.jpg",
99− "header_image_url": "https://images.genius.com/ef9f736a86df3c3b1772f3fb7fbdb21c.1000x1000x1.jpg",
100− "id": 52916,
101− "instrumental": false,
102− "lyrics_owner_id": 15586,
103− "lyrics_state": "complete",
104− "lyrics_updated_at": 1498744545,
105− "path": "/Mc-jean-gab1-jtemmerde-lyrics",
106− "pyongs_count": 4,
107− "song_art_image_thumbnail_url": "https://images.genius.com/xxx.300x300x1.jpg",
108− "stats": {
109− "hot": false,
110− "unreviewed_annotations": 0,
111− "pageviews": 62490
112− },
113− "title": "J't'emmerde",
114− "title_with_featured": "J't'emmerde",
115− "updated_by_human_at": 1498744546,
116− "url": "https://genius.com/Mc-jean-gab1-jtemmerde-lyrics",
117− "primary_artist": {
118− "_type": "artist",
119− "api_path": "/artists/12691",
120− "header_image_url": "https://images.genius.com/c7847662a58f8c2b0f02a6e217d60907.960x657x1.jpg",
121− "id": 12691,
122− "image_url": "https://s3.amazonaws.com/rapgenius/Mc-jean-gab1.jpg",
123− "index_character": "m",
124− "is_meme_verified": false,
125− "is_verified": false,
126− "name": "MC Jean Gab'1",
127− "slug": "Mc-jean-gab1",
128− "url": "https://genius.com/artists/Mc-jean-gab1"
129− }
130− }
131− }
132− ]
133− },
134− {
135− "type": "artist",
136− "hits": [
137− {
138− "highlights": [],
139− "index": "artist",
140− "type": "artist",
141− "result": {
142− "_type": "artist",
143− "api_path": "/artists/191580",
144− "header_image_url": "https://assets.genius.com/images/default_avatar_300.png?1503090542",
145− "id": 191580,
146− "image_url": "https://assets.genius.com/images/default_avatar_300.png?1503090542",
147− "index_character": "a",
148− "is_meme_verified": false,
149− "is_verified": false,
150− "name": "ASDF Guy",
151− "slug": "Asdf-guy",
152− "url": "https://genius.com/artists/Asdf-guy"
153− }
154− }
155− ]
156− },
157− {
158− "type": "album",
159− "hits": [
160− {
161− "highlights": [],
162− "index": "album",
163− "type": "album",
164− "result": {
165− "_type": "album",
166− "api_path": "/albums/132332",
167− "cover_art_thumbnail_url": "https://images.genius.com/xxx.300x300x1.jpg",
168− "cover_art_url": "https://images.genius.com/xxx.600x600x1.jpg",
169− "full_title": "ASD by A Skylit Drive",
170− "id": 132332,
171− "name": "ASD",
172− "name_with_artist": "ASD (artist: A Skylit Drive)",
173− "release_date_components": {
174− "year": 2015,
175− "month": null,
176− "day": null
177− },
178− "url": "https://genius.com/albums/A-skylit-drive/Asd",
179− "artist": {
180− "_type": "artist",
181− "api_path": "/artists/48712",
182− "header_image_url": "https://images.genius.com/814c1551293172c56306d0e310c6aa89.620x400x1.jpg",
183− "id": 48712,
184− "image_url": "https://images.genius.com/814c1551293172c56306d0e310c6aa89.620x400x1.jpg",
185− "index_character": "s",
186− "is_meme_verified": false,
187− "is_verified": false,
188− "name": "A Skylit Drive",
189− "slug": "A-skylit-drive",
190− "url": "https://genius.com/artists/A-skylit-drive"
191− }
192− }
193− }
194− ]
195− }
196− ]
197− }
198− }
199− """
200−
201− resp = mock.Mock(text=json)
202− results = genius.response(resp)
203−
204− self.assertEqual(len(results), 3)
205− self.assertEqual(type(results), list)
206−
207− # check lyric parsing
208− r = results[0]
209− self.assertEqual(r['url'], 'https://genius.com/Mc-jean-gab1-jtemmerde-lyrics')
210− self.assertEqual(r['title'], "J't'emmerde by MC Jean Gab'1")
211− self.assertEqual(r['content'], "Sample lyrics")
212− self.assertEqual(r['template'], 'videos.html')
213− self.assertEqual(r['thumbnail'], 'https://images.genius.com/xxx.300x300x1.jpg')
214− created = datetime.fromtimestamp(1498744545)
215− self.assertEqual(r['publishedDate'], created)
216−
217− # check artist parsing
218− r = results[1]
219− self.assertEqual(r['url'], 'https://genius.com/artists/Asdf-guy')
220− self.assertEqual(r['title'], "ASDF Guy")
221− self.assertEqual(r['content'], None)
222− self.assertEqual(r['template'], 'videos.html')
223− self.assertEqual(r['thumbnail'], 'https://assets.genius.com/images/default_avatar_300.png?1503090542')
224−
225− # check album parsing
226− r = results[2]
227− self.assertEqual(r['url'], 'https://genius.com/albums/A-skylit-drive/Asd')
228− self.assertEqual(r['title'], "ASD by A Skylit Drive")
229− self.assertEqual(r['content'], "Released: 2015")
230− self.assertEqual(r['template'], 'videos.html')
231− self.assertEqual(r['thumbnail'], 'https://images.genius.com/xxx.600x600x1.jpg')
tests/unit/engines/test_gigablast.pydeleted−119
@@ -1,119 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import gigablast
4−from searx.testing import SearxTestCase
5−
6−
7−class TestGigablastEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− dicto['safesearch'] = 0
14− dicto['language'] = 'all'
15− params = gigablast.request(query, dicto)
16− self.assertTrue('url' in params)
17− self.assertTrue(query in params['url'])
18− self.assertTrue('gigablast.com' in params['url'])
19− self.assertTrue('xx' in params['url'])
20−
21− dicto['language'] = 'en-US'
22− params = gigablast.request(query, dicto)
23− self.assertTrue('en' in params['url'])
24− self.assertFalse('en-US' in params['url'])
25−
26− def test_response(self):
27− self.assertRaises(AttributeError, gigablast.response, None)
28− self.assertRaises(AttributeError, gigablast.response, [])
29− self.assertRaises(AttributeError, gigablast.response, '')
30− self.assertRaises(AttributeError, gigablast.response, '[]')
31−
32− response = mock.Mock(text='{"results": []}')
33− self.assertEqual(gigablast.response(response), [])
34−
35− json = """{"results": [
36− {
37− "title":"South by Southwest 2016",
38− "dmozEntry":{
39− "dmozCatId":1041152,
40− "directCatId":1,
41− "dmozCatStr":"Top: Regional: North America: United States",
42− "dmozTitle":"South by Southwest (SXSW)",
43− "dmozSum":"Annual music, film, and interactive conference.",
44− "dmozAnchor":""
45− },
46− "dmozEntry":{
47− "dmozCatId":763945,
48− "directCatId":1,
49− "dmozCatStr":"Top: Regional: North America: United States",
50− "dmozTitle":"South by Southwest (SXSW)",
51− "dmozSum":"",
52− "dmozAnchor":"www.sxsw.com"
53− },
54− "dmozEntry":{
55− "dmozCatId":761446,
56− "directCatId":1,
57− "dmozCatStr":"Top: Regional: North America: United States",
58− "dmozTitle":"South by Southwest (SXSW)",
59− "dmozSum":"Music, film, and interactive conference and festival.",
60− "dmozAnchor":""
61− },
62− "indirectDmozCatId":1041152,
63− "indirectDmozCatId":763945,
64− "indirectDmozCatId":761446,
65− "contentType":"html",
66− "sum":"This should be the content.",
67− "url":"www.sxsw.com",
68− "hopCount":0,
69− "size":" 102k",
70− "sizeInBytes":104306,
71− "bytesUsedToComputeSummary":70000,
72− "docId":269411794364,
73− "docScore":586571136.000000,
74− "summaryGenTimeMS":12,
75− "summaryTagdbLookupTimeMS":0,
76− "summaryTitleRecLoadTimeMS":1,
77− "site":"www.sxsw.com",
78− "spidered":1452203608,
79− "firstIndexedDateUTC":1444167123,
80− "contentHash32":2170650347,
81− "language":"English",
82− "langAbbr":"en"
83− }
84−]}
85− """
86− response = mock.Mock(text=json)
87− results = gigablast.response(response)
88− self.assertEqual(type(results), list)
89− self.assertEqual(len(results), 1)
90− self.assertEqual(results[0]['title'], 'South by Southwest 2016')
91− self.assertEqual(results[0]['url'], 'www.sxsw.com')
92− self.assertEqual(results[0]['content'], 'This should be the content.')
93−
94− def test_fetch_supported_languages(self):
95− html = """<html></html>"""
96− response = mock.Mock(text=html)
97− results = gigablast._fetch_supported_languages(response)
98− self.assertEqual(type(results), list)
99− self.assertEqual(len(results), 0)
100−
101− html = """
102− <html>
103− <body>
104− <span id="menu2">
105− <a href="/search?&rxikd=1&qlang=xx"></a>
106− <a href="/search?&rxikd=1&qlang=en"></a>
107− <a href="/search?&rxikd=1&prepend=gblang%3Aen"></a>
108− <a href="/search?&rxikd=1&qlang=zh_"></a>
109− <a href="/search?&rxikd=1&prepend=gblang%3Azh_tw"></a>
110− </span>
111− </body>
112− </html>
113− """
114− response = mock.Mock(text=html)
115− languages = gigablast._fetch_supported_languages(response)
116− self.assertEqual(type(languages), list)
117− self.assertEqual(len(languages), 2)
118− self.assertIn('en', languages)
119− self.assertIn('zh-TW', languages)
tests/unit/engines/test_github.pydeleted−61
@@ -1,61 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import github
4−from searx.testing import SearxTestCase
5−
6−
7−class TestGitHubEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− params = github.request(query, defaultdict(dict))
12− self.assertTrue('url' in params)
13− self.assertTrue(query in params['url'])
14− self.assertTrue('github.com' in params['url'])
15− self.assertEqual(params['headers']['Accept'], github.accept_header)
16−
17− def test_response(self):
18− self.assertRaises(AttributeError, github.response, None)
19− self.assertRaises(AttributeError, github.response, [])
20− self.assertRaises(AttributeError, github.response, '')
21− self.assertRaises(AttributeError, github.response, '[]')
22−
23− response = mock.Mock(text='{}')
24− self.assertEqual(github.response(response), [])
25−
26− response = mock.Mock(text='{"items": []}')
27− self.assertEqual(github.response(response), [])
28−
29− json = """
30− {
31− "items": [
32− {
33− "name": "title",
34− "html_url": "url",
35− "description": ""
36− }
37− ]
38− }
39− """
40− response = mock.Mock(text=json)
41− results = github.response(response)
42− self.assertEqual(type(results), list)
43− self.assertEqual(len(results), 1)
44− self.assertEqual(results[0]['title'], 'title')
45− self.assertEqual(results[0]['url'], 'url')
46− self.assertEqual(results[0]['content'], '')
47−
48− json = """
49− {
50− "items": [
51− {
52− "name": "title",
53− "html_url": "url",
54− "description": "desc"
55− }
56− ]
57− }
58− """
59− response = mock.Mock(text=json)
60− results = github.response(response)
61− self.assertEqual(results[0]['content'], "desc")
tests/unit/engines/test_google.pydeleted−194
@@ -1,194 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−import lxml
5−from searx.engines import google
6−from searx.testing import SearxTestCase
7−
8−
9−class TestGoogleEngine(SearxTestCase):
10−
11− def mock_response(self, text):
12− response = mock.Mock(text=text, url='https://www.google.com/search?q=test&start=0&gbv=1&gws_rd=cr')
13− response.search_params = mock.Mock()
14− response.search_params.get = mock.Mock(return_value='www.google.com')
15− return response
16−
17− def test_request(self):
18− google.supported_languages = ['en', 'fr', 'zh-CN', 'iw']
19− google.language_aliases = {'he': 'iw'}
20−
21− query = 'test_query'
22− dicto = defaultdict(dict)
23− dicto['pageno'] = 1
24− dicto['language'] = 'fr-FR'
25− dicto['time_range'] = ''
26− params = google.request(query, dicto)
27− self.assertIn('url', params)
28− self.assertIn(query, params['url'])
29− self.assertIn('google.fr', params['url'])
30− self.assertIn('fr', params['url'])
31− self.assertIn('fr', params['headers']['Accept-Language'])
32−
33− dicto['language'] = 'en-US'
34− params = google.request(query, dicto)
35− self.assertIn('google.com', params['url'])
36− self.assertIn('en', params['url'])
37− self.assertIn('en', params['headers']['Accept-Language'])
38−
39− dicto['language'] = 'zh'
40− params = google.request(query, dicto)
41− self.assertIn('google.com', params['url'])
42− self.assertIn('zh-CN', params['url'])
43− self.assertIn('zh-CN', params['headers']['Accept-Language'])
44−
45− dicto['language'] = 'he'
46− params = google.request(query, dicto)
47− self.assertIn('google.com', params['url'])
48− self.assertIn('iw', params['url'])
49− self.assertIn('iw', params['headers']['Accept-Language'])
50−
51− def test_response(self):
52− self.assertRaises(AttributeError, google.response, None)
53− self.assertRaises(AttributeError, google.response, [])
54− self.assertRaises(AttributeError, google.response, '')
55− self.assertRaises(AttributeError, google.response, '[]')
56−
57− response = self.mock_response('<html></html>')
58− self.assertEqual(google.response(response), [])
59−
60− html = """
61− <div class="ZINbbc xpd O9g5cc uUPGi">
62− <div>
63− <div class="kCrYT">
64− <a href="/url?q=http://this.should.be.the.link/">
65− <div class="BNeawe">
66− <b>This</b> is <b>the</b> title
67− </div>
68− <div class="BNeawe">
69− http://website
70− </div>
71− </a>
72− </div>
73− <div class="kCrYT">
74− <div>
75− <div class="BNeawe">
76− <div>
77− <div class="BNeawe">
78− This should be the content.
79− </div>
80− </div>
81− </div>
82− </div>
83− </div>
84− </div>
85− </p>
86− <div class="ZINbbc xpd O9g5cc uUPGi">
87− <div>
88− <div class="kCrYT">
89− <span>
90− <div class="BNeawe">
91− Related searches
92− </div>
93− </span>
94− </div>
95− <div class="rVLSBd">
96− <a>
97− <div>
98− <div class="BNeawe">
99− suggestion title
100− </div>
101− </div>
102− </a>
103− </div>
104− </div>
105− </p>
106− """
107− response = self.mock_response(html)
108− results = google.response(response)
109− self.assertEqual(type(results), list)
110− self.assertEqual(len(results), 2)
111− self.assertEqual(results[0]['title'], 'This is the title')
112− self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
113− self.assertEqual(results[0]['content'], 'This should be the content.')
114− self.assertEqual(results[1]['suggestion'], 'suggestion title')
115−
116− html = """
117− <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
118− </li>
119− """
120− response = self.mock_response(html)
121− results = google.response(response)
122− self.assertEqual(type(results), list)
123− self.assertEqual(len(results), 0)
124−
125− response = mock.Mock(text='<html></html>', url='https://sorry.google.com')
126− response.search_params = mock.Mock()
127− response.search_params.get = mock.Mock(return_value='www.google.com')
128− self.assertRaises(RuntimeWarning, google.response, response)
129−
130− response = mock.Mock(text='<html></html>', url='https://www.google.com/sorry/IndexRedirect')
131− response.search_params = mock.Mock()
132− response.search_params.get = mock.Mock(return_value='www.google.com')
133− self.assertRaises(RuntimeWarning, google.response, response)
134−
135− def test_parse_images(self):
136− html = """
137− <li>
138− <div>
139− <a href="http://www.google.com/url?q=http://this.is.the.url/">
140− <img style="margin:3px 0;margin-right:6px;padding:0" height="90"
141− src="https://this.is.the.image/image.jpg" width="60" align="middle" alt="" border="0">
142− </a>
143− </div>
144− </li>
145− """
146− dom = lxml.html.fromstring(html)
147− results = google.parse_images(dom, 'www.google.com')
148− self.assertEqual(type(results), list)
149− self.assertEqual(len(results), 1)
150− self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
151− self.assertEqual(results[0]['title'], '')
152− self.assertEqual(results[0]['content'], '')
153− self.assertEqual(results[0]['img_src'], 'https://this.is.the.image/image.jpg')
154−
155− def test_fetch_supported_languages(self):
156− html = """<html></html>"""
157− response = mock.Mock(text=html)
158− languages = google._fetch_supported_languages(response)
159− self.assertEqual(type(languages), dict)
160− self.assertEqual(len(languages), 0)
161−
162− html = u"""
163− <html>
164− <body>
165− <div id="langSec">
166− <div>
167− <input name="lr" data-name="english" value="lang_en" />
168− <input name="lr" data-name="中文 (简体)" value="lang_zh-CN" />
169− <input name="lr" data-name="中文 (繁體)" value="lang_zh-TW" />
170− </div>
171− </div>
172− </body>
173− </html>
174− """
175− response = mock.Mock(text=html)
176− languages = google._fetch_supported_languages(response)
177− self.assertEqual(type(languages), dict)
178− self.assertEqual(len(languages), 3)
179−
180− self.assertIn('en', languages)
181− self.assertIn('zh-CN', languages)
182− self.assertIn('zh-TW', languages)
183−
184− self.assertEquals(type(languages['en']), dict)
185− self.assertEquals(type(languages['zh-CN']), dict)
186− self.assertEquals(type(languages['zh-TW']), dict)
187−
188− self.assertIn('name', languages['en'])
189− self.assertIn('name', languages['zh-CN'])
190− self.assertIn('name', languages['zh-TW'])
191−
192− self.assertEquals(languages['en']['name'], 'English')
193− self.assertEquals(languages['zh-CN']['name'], u'中文 (简体)')
194− self.assertEquals(languages['zh-TW']['name'], u'中文 (繁體)')
tests/unit/engines/test_google_images.pydeleted−27
@@ -1,27 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import google_images
4−from searx.testing import SearxTestCase
5−
6−
7−class TestGoogleImagesEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 1
13− dicto['safesearch'] = 1
14− dicto['time_range'] = ''
15− params = google_images.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18−
19− dicto['safesearch'] = 0
20− params = google_images.request(query, dicto)
21− self.assertNotIn('safe', params['url'])
22−
23− def test_response(self):
24− self.assertRaises(AttributeError, google_images.response, None)
25− self.assertRaises(AttributeError, google_images.response, [])
26− self.assertRaises(AttributeError, google_images.response, '')
27− self.assertRaises(AttributeError, google_images.response, '[]')
tests/unit/engines/test_google_news.pydeleted−102
@@ -1,102 +0,0 @@
1−# -*- coding: utf-8 -*-
2−
3−from collections import defaultdict
4−import mock
5−from searx.engines import google_news
6−from searx.testing import SearxTestCase
7−
8−
9−class TestGoogleNewsEngine(SearxTestCase):
10−
11− def test_request(self):
12− google_news.supported_languages = ['en-US', 'fr-FR']
13− google_news.language_aliases = {}
14− query = 'test_query'
15− dicto = defaultdict(dict)
16− dicto['pageno'] = 1
17− dicto['language'] = 'fr-FR'
18− dicto['time_range'] = 'w'
19− params = google_news.request(query, dicto)
20− self.assertIn('url', params)
21− self.assertIn(query, params['url'])
22− self.assertIn('fr', params['url'])
23−
24− dicto['language'] = 'all'
25− params = google_news.request(query, dicto)
26− self.assertIn('url', params)
27− self.assertNotIn('fr', params['url'])
28−
29− def test_response(self):
30− self.assertRaises(AttributeError, google_news.response, None)
31− self.assertRaises(AttributeError, google_news.response, [])
32− self.assertRaises(AttributeError, google_news.response, '')
33− self.assertRaises(AttributeError, google_news.response, '[]')
34−
35− response = mock.Mock(text='{}')
36− self.assertEqual(google_news.response(response), [])
37−
38− response = mock.Mock(text='{"data": []}')
39− self.assertEqual(google_news.response(response), [])
40−
41− html = u"""
42−<h2 class="hd">Search Results</h2>
43−<div data-async-context="query:searx" id="ires">
44− <div eid="oC2oWcGXCafR6ASkwoCwDA" id="rso">
45− <div class="_NId">
46− <!--m-->
47− <div class="g _cy">
48− <div class="ts _JGs _JHs _tJs _KGs _jHs">
49− <div class="_hJs">
50− <h3 class="r _gJs">
51− <a class="l lLrAF" href="https://example.com/" onmousedown="return rwt(this,'','','','11','AFQjCNEyehpzD5cJK1KUfXBx9RmsbqqG9g','','0ahUKEwjB58OR54HWAhWnKJoKHSQhAMY4ChCpAggiKAAwAA','','',event)">Example title</a>
52− </h3>
53− <div class="slp">
54− <span class="_OHs _PHs">
55− Mac &amp; i</span>
56− <span class="_QGs">
57− -</span>
58− <span class="f nsa _QHs">
59− Mar 21, 2016</span>
60− </div>
61− <div class="st">Example description</div>
62− </div>
63− </div>
64− </div>
65− <div class="g _cy">
66− <div class="ts _JGs _JHs _oGs _KGs _jHs">
67− <a class="top _xGs _SHs" href="https://example2.com/" onmousedown="return rwt(this,'','','','12','AFQjCNHObfH7sYmLWI1SC-YhWXKZFRzRjw','','0ahUKEwjB58OR54HWAhWnKJoKHSQhAMY4ChC8iAEIJDAB','','',event)">
68− <img class="th _RGs" src="https://example2.com/image.jpg" alt="Story image for searx from Golem.de" onload="typeof google==='object'&&google.aft&&google.aft(this)">
69− </a>
70− <div class="_hJs">
71− <h3 class="r _gJs">
72− <a class="l lLrAF" href="https://example2.com/" onmousedown="return rwt(this,'','','','12','AFQjCNHObfH7sYmLWI1SC-YhWXKZFRzRjw','','0ahUKEwjB58OR54HWAhWnKJoKHSQhAMY4ChCpAgglKAAwAQ','','',event)">Example title 2</a>
73− </h3>
74− <div class="slp">
75− <span class="_OHs _PHs">
76− Golem.de</span>
77− <span class="_QGs">
78− -</span>
79− <span class="f nsa _QHs">
80− Oct 4, 2016</span>
81− </div>
82− <div class="st">Example description 2</div>
83− </div>
84− </div>
85− </div>
86− </div>
87− </div>
88−</div>
89−
90−
91− """ # noqa
92− response = mock.Mock(text=html)
93− results = google_news.response(response)
94− self.assertEqual(type(results), list)
95− self.assertEqual(len(results), 2)
96− self.assertEqual(results[0]['title'], u'Example title')
97− self.assertEqual(results[0]['url'], 'https://example.com/')
98− self.assertEqual(results[0]['content'], 'Example description')
99− self.assertEqual(results[1]['title'], u'Example title 2')
100− self.assertEqual(results[1]['url'], 'https://example2.com/')
101− self.assertEqual(results[1]['content'], 'Example description 2')
102− self.assertEqual(results[1]['img_src'], 'https://example2.com/image.jpg')
tests/unit/engines/test_google_videos.pydeleted−79
@@ -1,79 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import google_videos
4−from searx.testing import SearxTestCase
5−
6−
7−class TestGoogleVideosEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 1
13− dicto['safesearch'] = 1
14− dicto['time_range'] = ''
15− params = google_videos.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18−
19− dicto['safesearch'] = 0
20− params = google_videos.request(query, dicto)
21− self.assertNotIn('safe', params['url'])
22−
23− def test_response(self):
24− self.assertRaises(AttributeError, google_videos.response, None)
25− self.assertRaises(AttributeError, google_videos.response, [])
26− self.assertRaises(AttributeError, google_videos.response, '')
27− self.assertRaises(AttributeError, google_videos.response, '[]')
28−
29− html = r"""
30− <div>
31− <div>
32− <div class="g">
33− <div class="r">
34− <a href="url_1"><h3>Title 1</h3></a>
35− </div>
36− <div class="s">
37− <div>
38− <a>
39− <g-img>
40− <img id="vidthumb1">
41− </g-img>
42− </a>
43− </div>
44− </div>
45− <div>
46− <span class="st">Content 1</span>
47− </div>
48− </div>
49− <div class="g">
50− <div class="r">
51− <a href="url_2"><h3>Title 2</h3></a>
52− </div>
53− <div class="s">
54− <div>
55− <a>
56− <g-img>
57− <img id="vidthumb2">
58− </g-img>
59− </a>
60− </div>
61− </div>
62− <div>
63− <span class="st">Content 2</span>
64− </div>
65− </div>
66− </div>
67− </div>
68− <script>function _setImagesSrc(c,d,e){}</script>
69− """
70− response = mock.Mock(text=html)
71− results = google_videos.response(response)
72− self.assertEqual(type(results), list)
73− self.assertEqual(len(results), 2)
74− self.assertEqual(results[0]['url'], u'url_1')
75− self.assertEqual(results[0]['title'], u'Title 1')
76− self.assertEqual(results[0]['content'], u'Content 1')
77− self.assertEqual(results[1]['url'], u'url_2')
78− self.assertEqual(results[1]['title'], u'Title 2')
79− self.assertEqual(results[1]['content'], u'Content 2')
tests/unit/engines/test_ina.pydeleted−64
@@ -1,64 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import ina
4−from searx.testing import SearxTestCase
5−
6−
7−class TestInaEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = ina.request(query, dicto)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('ina.fr' in params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, ina.response, None)
20− self.assertRaises(AttributeError, ina.response, [])
21− self.assertRaises(AttributeError, ina.response, '')
22− self.assertRaises(AttributeError, ina.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(ina.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(ina.response(response), [])
29−
30− json = """
31− {"content":"\\t<div class=\\"container\\">\\n\\t\\n\
32− <!-- DEBUT CONTENU PRINCIPAL -->\\n<div class=\\"row\\">\\n\
33− <div class=\\"search-results--list\\"><div class=\\"media\\">\\n\
34− \\t\\t\\t\\t<a class=\\"media-left media-video premium xiti_click_action\\" \
35− data-xiti-params=\\"recherche_v4::resultats_conference_de_presse_du_general_de_gaulle::N\\" \
36− href=\\"\\/video\\/CAF89035682\\/conference-de-presse-du-general-de-gaulle-video.html\\">\\n\
37− <img src=\\"https:\\/\\/www.ina.fr\\/images_v2\\/140x105\\/CAF89035682.jpeg\\" \
38− alt=\\"Conf\\u00e9rence de presse du G\\u00e9n\\u00e9ral de Gaulle \\">\\n\
39− \\t\\t\\t\\t\\t<\\/a>\\n\
40− \\t\\t\\t\\t\\t<div class=\\"media-body\\">\\n\\t\\t\\t\\t\\t\\t<h3 class=\\"h3--title media-heading\\">\\n\
41− \\t\\t\\t\\t\\t\\t\\t<a class=\\"xiti_click_action\\" \
42− data-xiti-params=\\"recherche_v4::resultats_conference_de_presse_du_general_de_gaulle::N\\" \
43− href=\\"\\/video\\/CAF89035682\\/conference-de-presse-du-general-de-gaulle-video.html\\">\
44− Conf\\u00e9rence de presse du G\\u00e9n\\u00e9ral de Gaulle <\\/a>\\n\
45− <\\/h3>\\n\
46− <div class=\\"media-body__info\\">\\n<span class=\\"broadcast\\">27\\/11\\/1967<\\/span>\\n\
47− <span class=\\"views\\">29321 vues<\\/span>\\n\
48− <span class=\\"duration\\">01h 33m 07s<\\/span>\\n\
49− <\\/div>\\n\
50− <p class=\\"media-body__summary\\">VERSION INTEGRALE DE LA CONFERENCE DE PRESSE DU GENERAL DE GAULLE . \
51− - PA le Pr\\u00e9sident DE GAULLE : il ouvre les bras et s'assied. DP journalis...<\\/p>\\n\
52− <\\/div>\\n<\\/div><!-- \\/.media -->\\n"
53− }
54− """
55− response = mock.Mock(text=json)
56− results = ina.response(response)
57− self.assertEqual(type(results), list)
58− self.assertEqual(len(results), 1)
59− self.assertEqual(results[0]['title'], u'Conf\xe9rence de presse du G\xe9n\xe9ral de Gaulle')
60− self.assertEqual(results[0]['url'],
61− 'https://www.ina.fr/video/CAF89035682/conference-de-presse-du-general-de-gaulle-video.html')
62− self.assertEqual(results[0]['content'],
63− u"VERSION INTEGRALE DE LA CONFERENCE DE PRESSE DU GENERAL DE GAULLE ."
64− u" - PA le Pr\u00e9sident DE GAULLE : il ouvre les bras et s'assied. DP journalis...")
tests/unit/engines/test_kickass.pydeleted−397
@@ -1,397 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import kickass
5−from searx.testing import SearxTestCase
6−
7−
8−class TestKickassEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = kickass.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn(query, params['url'])
17− self.assertIn('kickass.cd', params['url'])
18− self.assertFalse(params['verify'])
19−
20− def test_response(self):
21− self.assertRaises(AttributeError, kickass.response, None)
22− self.assertRaises(AttributeError, kickass.response, [])
23− self.assertRaises(AttributeError, kickass.response, '')
24− self.assertRaises(AttributeError, kickass.response, '[]')
25−
26− response = mock.Mock(text='<html></html>')
27− self.assertEqual(kickass.response(response), [])
28−
29− html = """
30− <table cellpadding="0" cellspacing="0" class="data" style="width: 100%">
31− <tr class="firstr">
32− <th class="width100perc nopad">torrent name</th>
33− <th class="center">
34− <a href="/search/test/?field=size&sorder=desc" rel="nofollow">size</a>
35− </th>
36− <th class="center"><span class="files">
37− <a href="/search/test/?field=files_count&sorder=desc" rel="nofollow">files</a></span>
38− </th>
39− <th class="center"><span>
40− <a href="/search/test/?field=time_add&sorder=desc" rel="nofollow">age</a></span>
41− </th>
42− <th class="center"><span class="seed">
43− <a href="/search/test/?field=seeders&sorder=desc" rel="nofollow">seed</a></span>
44− </th>
45− <th class="lasttd nobr center">
46− <a href="/search/test/?field=leechers&sorder=desc" rel="nofollow">leech</a>
47− </th>
48− </tr>
49− <tr class="even" id="torrent_test6478745">
50− <td>
51− <div class="iaconbox center floatright">
52− <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
53− <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
54− <i class="ka ka-comment"></i>
55− </a>
56− <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
57− <i class="ka ka16 ka-verify ka-green"></i>
58− </a>
59− <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
60− <i class="ka ka16 ka-arrow-down partner1Button"></i>
61− </a>
62− <a title="Torrent magnet link"
63− href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
64− <i class="ka ka16 ka-magnet"></i>
65− </a>
66− <a title="Download torrent file"
67− href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
68− <i class="ka ka16 ka-arrow-down"></i>
69− </a>
70− </div>
71− <div class="torrentname">
72− <a href="/test-t6478745.html" class="torType txtType"></a>
73− <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
74− <div class="markeredBlock torType txtType">
75− <a href="/url.html" class="cellMainLink">
76− <strong class="red">This should be the title</strong>
77− </a>
78− <span class="font11px lightgrey block">
79− Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
80− <a class="plain" href="/user/riri/">riri</a> in
81− <span id="cat_6478745">
82− <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
83− </span>
84− </span>
85− </div>
86− </td>
87− <td class="nobr center">449 bytes</td>
88− <td class="center">4</td>
89− <td class="center">2&nbsp;years</td>
90− <td class="green center">10</td>
91− <td class="red lasttd center">1</td>
92− </tr>
93− </table>
94− """
95− response = mock.Mock(text=html)
96− results = kickass.response(response)
97− self.assertEqual(type(results), list)
98− self.assertEqual(len(results), 1)
99− self.assertEqual(results[0]['title'], 'This should be the title')
100− self.assertEqual(results[0]['url'], 'https://kickass.cd/url.html')
101− self.assertEqual(results[0]['content'], 'Posted by riri in Other > Unsorted')
102− self.assertEqual(results[0]['seed'], 10)
103− self.assertEqual(results[0]['leech'], 1)
104− self.assertEqual(results[0]['filesize'], 449)
105− self.assertEqual(results[0]['files'], 4)
106− self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETURL&dn=test')
107− self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/53917.torrent?title=test')
108−
109− html = """
110− <table cellpadding="0" cellspacing="0" class="data" style="width: 100%">
111− <tr class="firstr">
112− <th class="width100perc nopad">torrent name</th>
113− <th class="center">
114− <a href="/search/test/?field=size&sorder=desc" rel="nofollow">size</a>
115− </th>
116− <th class="center"><span class="files">
117− <a href="/search/test/?field=files_count&sorder=desc" rel="nofollow">files</a></span>
118− </th>
119− <th class="center"><span>
120− <a href="/search/test/?field=time_add&sorder=desc" rel="nofollow">age</a></span>
121− </th>
122− <th class="center"><span class="seed">
123− <a href="/search/test/?field=seeders&sorder=desc" rel="nofollow">seed</a></span>
124− </th>
125− <th class="lasttd nobr center">
126− <a href="/search/test/?field=leechers&sorder=desc" rel="nofollow">leech</a>
127− </th>
128− </tr>
129− </table>
130− """
131− response = mock.Mock(text=html)
132− results = kickass.response(response)
133− self.assertEqual(type(results), list)
134− self.assertEqual(len(results), 0)
135−
136− html = """
137− <table cellpadding="0" cellspacing="0" class="data" style="width: 100%">
138− <tr class="firstr">
139− <th class="width100perc nopad">torrent name</th>
140− <th class="center">
141− <a href="/search/test/?field=size&sorder=desc" rel="nofollow">size</a>
142− </th>
143− <th class="center"><span class="files">
144− <a href="/search/test/?field=files_count&sorder=desc" rel="nofollow">files</a></span>
145− </th>
146− <th class="center"><span>
147− <a href="/search/test/?field=time_add&sorder=desc" rel="nofollow">age</a></span>
148− </th>
149− <th class="center"><span class="seed">
150− <a href="/search/test/?field=seeders&sorder=desc" rel="nofollow">seed</a></span>
151− </th>
152− <th class="lasttd nobr center">
153− <a href="/search/test/?field=leechers&sorder=desc" rel="nofollow">leech</a>
154− </th>
155− </tr>
156− <tr class="even" id="torrent_test6478745">
157− <td>
158− <div class="iaconbox center floatright">
159− <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
160− <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
161− <i class="ka ka-comment"></i>
162− </a>
163− <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
164− <i class="ka ka16 ka-verify ka-green"></i>
165− </a>
166− <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
167− <i class="ka ka16 ka-arrow-down partner1Button"></i>
168− </a>
169− <a title="Torrent magnet link"
170− href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
171− <i class="ka ka16 ka-magnet"></i>
172− </a>
173− <a title="Download torrent file"
174− href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
175− <i class="ka ka16 ka-arrow-down"></i>
176− </a>
177− </div>
178− <div class="torrentname">
179− <a href="/test-t6478745.html" class="torType txtType"></a>
180− <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
181− <div class="markeredBlock torType txtType">
182− <a href="/url.html" class="cellMainLink">
183− <strong class="red">This should be the title</strong>
184− </a>
185− <span class="font11px lightgrey block">
186− Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
187− <a class="plain" href="/user/riri/">riri</a> in
188− <span id="cat_6478745">
189− <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
190− </span>
191− </span>
192− </div>
193− </td>
194− <td class="nobr center">1 KiB</td>
195− <td class="center">4</td>
196− <td class="center">2&nbsp;years</td>
197− <td class="green center">10</td>
198− <td class="red lasttd center">1</td>
199− </tr>
200− <tr class="even" id="torrent_test6478745">
201− <td>
202− <div class="iaconbox center floatright">
203− <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
204− <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
205− <i class="ka ka-comment"></i>
206− </a>
207− <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
208− <i class="ka ka16 ka-verify ka-green"></i>
209− </a>
210− <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
211− <i class="ka ka16 ka-arrow-down partner1Button"></i>
212− </a>
213− <a title="Torrent magnet link"
214− href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
215− <i class="ka ka16 ka-magnet"></i>
216− </a>
217− <a title="Download torrent file"
218− href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
219− <i class="ka ka16 ka-arrow-down"></i>
220− </a>
221− </div>
222− <div class="torrentname">
223− <a href="/test-t6478745.html" class="torType txtType"></a>
224− <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
225− <div class="markeredBlock torType txtType">
226− <a href="/url.html" class="cellMainLink">
227− <strong class="red">This should be the title</strong>
228− </a>
229− <span class="font11px lightgrey block">
230− Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
231− <a class="plain" href="/user/riri/">riri</a> in
232− <span id="cat_6478745">
233− <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
234− </span>
235− </span>
236− </div>
237− </td>
238− <td class="nobr center">1 MiB</td>
239− <td class="center">4</td>
240− <td class="center">2&nbsp;years</td>
241− <td class="green center">9</td>
242− <td class="red lasttd center">1</td>
243− </tr>
244− <tr class="even" id="torrent_test6478745">
245− <td>
246− <div class="iaconbox center floatright">
247− <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
248− <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
249− <i class="ka ka-comment"></i>
250− </a>
251− <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
252− <i class="ka ka16 ka-verify ka-green"></i>
253− </a>
254− <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
255− <i class="ka ka16 ka-arrow-down partner1Button"></i>
256− </a>
257− <a title="Torrent magnet link"
258− href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
259− <i class="ka ka16 ka-magnet"></i>
260− </a>
261− <a title="Download torrent file"
262− href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
263− <i class="ka ka16 ka-arrow-down"></i>
264− </a>
265− </div>
266− <div class="torrentname">
267− <a href="/test-t6478745.html" class="torType txtType"></a>
268− <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
269− <div class="markeredBlock torType txtType">
270− <a href="/url.html" class="cellMainLink">
271− <strong class="red">This should be the title</strong>
272− </a>
273− <span class="font11px lightgrey block">
274− Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
275− <a class="plain" href="/user/riri/">riri</a> in
276− <span id="cat_6478745">
277− <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
278− </span>
279− </span>
280− </div>
281− </td>
282− <td class="nobr center">1 GiB</td>
283− <td class="center">4</td>
284− <td class="center">2&nbsp;years</td>
285− <td class="green center">8</td>
286− <td class="red lasttd center">1</td>
287− </tr>
288− <tr class="even" id="torrent_test6478745">
289− <td>
290− <div class="iaconbox center floatright">
291− <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
292− <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
293− <i class="ka ka-comment"></i>
294− </a>
295− <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
296− <i class="ka ka16 ka-verify ka-green"></i>
297− </a>
298− <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
299− <i class="ka ka16 ka-arrow-down partner1Button"></i>
300− </a>
301− <a title="Torrent magnet link"
302− href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
303− <i class="ka ka16 ka-magnet"></i>
304− </a>
305− <a title="Download torrent file"
306− href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
307− <i class="ka ka16 ka-arrow-down"></i>
308− </a>
309− </div>
310− <div class="torrentname">
311− <a href="/test-t6478745.html" class="torType txtType"></a>
312− <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
313− <div class="markeredBlock torType txtType">
314− <a href="/url.html" class="cellMainLink">
315− <strong class="red">This should be the title</strong>
316− </a>
317− <span class="font11px lightgrey block">
318− Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
319− <a class="plain" href="/user/riri/">riri</a> in
320− <span id="cat_6478745">
321− <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
322− </span>
323− </span>
324− </div>
325− </td>
326− <td class="nobr center">1 TiB</td>
327− <td class="center">4</td>
328− <td class="center">2&nbsp;years</td>
329− <td class="green center">7</td>
330− <td class="red lasttd center">1</td>
331− </tr>
332− <tr class="even" id="torrent_test6478745">
333− <td>
334− <div class="iaconbox center floatright">
335− <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
336− <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
337− <i class="ka ka-comment"></i>
338− </a>
339− <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
340− <i class="ka ka16 ka-verify ka-green"></i>
341− </a>
342− <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
343− <i class="ka ka16 ka-arrow-down partner1Button"></i>
344− </a>
345− <a title="Torrent magnet link"
346− href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
347− <i class="ka ka16 ka-magnet"></i>
348− </a>
349− <a title="Download torrent file"
350− href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
351− <i class="ka ka16 ka-arrow-down"></i>
352− </a>
353− </div>
354− <div class="torrentname">
355− <a href="/test-t6478745.html" class="torType txtType"></a>
356− <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
357− <div class="markeredBlock torType txtType">
358− <a href="/url.html" class="cellMainLink">
359− <strong class="red">This should be the title</strong>
360− </a>
361− <span class="font11px lightgrey block">
362− Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
363− <a class="plain" href="/user/riri/">riri</a> in
364− <span id="cat_6478745">
365− <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
366− </span>
367− </span>
368− </div>
369− </td>
370− <td class="nobr center">z bytes</td>
371− <td class="center">r</td>
372− <td class="center">2&nbsp;years</td>
373− <td class="green center">a</td>
374− <td class="red lasttd center">t</td>
375− </tr>
376− </table>
377− """
378− response = mock.Mock(text=html)
379− results = kickass.response(response)
380− self.assertEqual(type(results), list)
381− self.assertEqual(len(results), 5)
382− self.assertEqual(results[0]['title'], 'This should be the title')
383− self.assertEqual(results[0]['url'], 'https://kickass.cd/url.html')
384− self.assertEqual(results[0]['content'], 'Posted by riri in Other > Unsorted')
385− self.assertEqual(results[0]['seed'], 10)
386− self.assertEqual(results[0]['leech'], 1)
387− self.assertEqual(results[0]['files'], 4)
388− self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETURL&dn=test')
389− self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/53917.torrent?title=test')
390− self.assertEqual(results[0]['filesize'], 1000)
391− self.assertEqual(results[1]['filesize'], 1000000)
392− self.assertEqual(results[2]['filesize'], 1000000000)
393− self.assertEqual(results[3]['filesize'], 1000000000000)
394− self.assertEqual(results[4]['seed'], 0)
395− self.assertEqual(results[4]['leech'], 0)
396− self.assertEqual(results[4]['files'], None)
397− self.assertEqual(results[4]['filesize'], None)
tests/unit/engines/test_mediawiki.pydeleted−130
@@ -1,130 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import mediawiki
5−from searx.testing import SearxTestCase
6−
7−
8−class TestMediawikiEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− dicto['language'] = 'fr_FR'
15− params = mediawiki.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18− self.assertIn('wikipedia.org', params['url'])
19− self.assertIn('fr', params['url'])
20−
21− dicto['language'] = 'all'
22− params = mediawiki.request(query, dicto)
23− self.assertIn('en', params['url'])
24−
25− mediawiki.base_url = "http://test.url/"
26− mediawiki.search_url = mediawiki.base_url +\
27− 'w/api.php?action=query'\
28− '&list=search'\
29− '&{query}'\
30− '&srprop=timestamp'\
31− '&format=json'\
32− '&sroffset={offset}'\
33− '&srlimit={limit}' # noqa
34− params = mediawiki.request(query, dicto)
35− self.assertIn('test.url', params['url'])
36−
37− def test_response(self):
38− dicto = defaultdict(dict)
39− dicto['language'] = 'fr'
40− mediawiki.base_url = "https://{language}.wikipedia.org/"
41−
42− self.assertRaises(AttributeError, mediawiki.response, None)
43− self.assertRaises(AttributeError, mediawiki.response, [])
44− self.assertRaises(AttributeError, mediawiki.response, '')
45− self.assertRaises(AttributeError, mediawiki.response, '[]')
46−
47− response = mock.Mock(text='{}', search_params=dicto)
48− self.assertEqual(mediawiki.response(response), [])
49−
50− response = mock.Mock(text='{"data": []}', search_params=dicto)
51− self.assertEqual(mediawiki.response(response), [])
52−
53− json = """
54− {
55− "query-continue": {
56− "search": {
57− "sroffset": 1
58− }
59− },
60− "query": {
61− "searchinfo": {
62− "totalhits": 29721
63− },
64− "search": [
65− {
66− "ns": 0,
67− "title": "This is the title étude",
68− "timestamp": "2014-12-19T17:42:52Z"
69− }
70− ]
71− }
72− }
73− """
74− response = mock.Mock(text=json, search_params=dicto)
75− results = mediawiki.response(response)
76− self.assertEqual(type(results), list)
77− self.assertEqual(len(results), 1)
78− self.assertEqual(results[0]['title'], u'This is the title étude')
79− self.assertIn('fr.wikipedia.org', results[0]['url'])
80− self.assertIn('This_is_the_title', results[0]['url'])
81− self.assertIn('%C3%A9tude', results[0]['url'])
82− self.assertEqual(results[0]['content'], '')
83−
84− json = """
85− {
86− "query-continue": {
87− "search": {
88− "sroffset": 1
89− }
90− },
91− "query": {
92− "searchinfo": {
93− "totalhits": 29721
94− },
95− "search": [
96− ]
97− }
98− }
99− """
100− response = mock.Mock(text=json, search_params=dicto)
101− results = mediawiki.response(response)
102− self.assertEqual(type(results), list)
103− self.assertEqual(len(results), 0)
104−
105− json = """
106− {
107− "query-continue": {
108− "search": {
109− "sroffset": 1
110− }
111− },
112− "query": {
113− }
114− }
115− """
116− response = mock.Mock(text=json, search_params=dicto)
117− results = mediawiki.response(response)
118− self.assertEqual(type(results), list)
119− self.assertEqual(len(results), 0)
120−
121− json = r"""
122− {"toto":[
123− {"id":200,"name":"Artist Name",
124− "link":"http:\/\/www.mediawiki.com\/artist\/1217","type":"artist"}
125− ]}
126− """
127− response = mock.Mock(text=json, search_params=dicto)
128− results = mediawiki.response(response)
129− self.assertEqual(type(results), list)
130− self.assertEqual(len(results), 0)
tests/unit/engines/test_mixcloud.pydeleted−67
@@ -1,67 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import mixcloud
4−from searx.testing import SearxTestCase
5−
6−
7−class TestMixcloudEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = mixcloud.request(query, dicto)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('mixcloud.com' in params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, mixcloud.response, None)
20− self.assertRaises(AttributeError, mixcloud.response, [])
21− self.assertRaises(AttributeError, mixcloud.response, '')
22− self.assertRaises(AttributeError, mixcloud.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(mixcloud.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(mixcloud.response(response), [])
29−
30− json = """
31− {"data":[
32− {
33− "user": {
34− "url": "http://www.mixcloud.com/user/",
35− "username": "user",
36− "name": "User",
37− "key": "/user/"
38− },
39− "key": "/user/this-is-the-url/",
40− "created_time": "2014-11-14T13:30:02Z",
41− "audio_length": 3728,
42− "slug": "this-is-the-url",
43− "name": "Title of track",
44− "url": "http://www.mixcloud.com/user/this-is-the-url/",
45− "updated_time": "2014-11-14T13:14:10Z"
46− }
47− ]}
48− """
49− response = mock.Mock(text=json)
50− results = mixcloud.response(response)
51− self.assertEqual(type(results), list)
52− self.assertEqual(len(results), 1)
53− self.assertEqual(results[0]['title'], 'Title of track')
54− self.assertEqual(results[0]['url'], 'http://www.mixcloud.com/user/this-is-the-url/')
55− self.assertEqual(results[0]['content'], 'User')
56− self.assertTrue('http://www.mixcloud.com/user/this-is-the-url/' in results[0]['embedded'])
57−
58− json = r"""
59− {"toto":[
60− {"id":200,"name":"Artist Name",
61− "link":"http:\/\/www.mixcloud.com\/artist\/1217","type":"artist"}
62− ]}
63− """
64− response = mock.Mock(text=json)
65− results = mixcloud.response(response)
66− self.assertEqual(type(results), list)
67− self.assertEqual(len(results), 0)
tests/unit/engines/test_nyaa.pydeleted−124
@@ -1,124 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import nyaa
4−from searx.testing import SearxTestCase
5−
6−
7−class TestNyaaEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dic = defaultdict(dict)
12− dic['pageno'] = 1
13− params = nyaa.request(query, dic)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('nyaa.si' in params['url'])
17−
18− def test_response(self):
19− resp = mock.Mock(text='<html></html>')
20− self.assertEqual(nyaa.response(resp), [])
21−
22− html = """
23− <table class="table table-bordered table-hover table-striped torrent-list">
24− <thead>
25− <tr>
26− <th class="hdr-category text-center" style="width:80px;">
27− <div>Category</div>
28− </th>
29− <th class="hdr-name" style="width:auto;">
30− <div>Name</div>
31− </th>
32− <th class="hdr-comments sorting text-center" title="Comments" style="width:50px;">
33− <a href="/?f=0&amp;c=0_0&amp;q=Death+Parade&amp;s=comments&amp;o=desc"></a>
34− <i class="fa fa-comments-o"></i>
35− </th>
36− <th class="hdr-link text-center" style="width:70px;">
37− <div>Link</div>
38− </th>
39− <th class="hdr-size sorting text-center" style="width:100px;">
40− <a href="/?f=0&amp;c=0_0&amp;q=Death+Parade&amp;s=size&amp;o=desc"></a>
41− <div>Size</div>
42− </th>
43− <th class="hdr-date sorting_desc text-center" title="In local time" style="width:140px;">
44− <a href="/?f=0&amp;c=0_0&amp;q=Death+Parade&amp;s=id&amp;o=asc"></a>
45− <div>Date</div>
46− </th>
47− <th class="hdr-seeders sorting text-center" title="Seeders" style="width:50px;">
48− <a href="/?f=0&amp;c=0_0&amp;q=Death+Parade&amp;s=seeders&amp;o=desc"></a>
49− <i class="fa fa-arrow-up" aria-hidden="true"></i>
50− </th>
51− <th class="hdr-leechers sorting text-center" title="Leechers" style="width:50px;">
52− <a href="/?f=0&amp;c=0_0&amp;q=Death+Parade&amp;s=leechers&amp;o=desc"></a>
53− <i class="fa fa-arrow-down" aria-hidden="true"></i>
54− </th>
55− <th class="hdr-downloads sorting text-center" title="Completed downloads" style="width:50px;">
56− <a href="/?f=0&amp;c=0_0&amp;q=Death+Parade&amp;s=downloads&amp;o=desc"></a>
57− <i class="fa fa-check" aria-hidden="true"></i>
58− </th>
59− </tr>
60− </thead>
61− <tbody>
62− <tr class="default">
63− <td style="padding:0 4px;">
64− <a href="/?c=1_2" title="Anime - English-translated">
65− <img src="/static/img/icons/nyaa/1_2.png" alt="Anime - English-translated">
66− </a>
67− </td>
68− <td colspan="2">
69− <a href="/view/1" title="Sample title 1">Sample title 1</a>
70− </td>
71− <td class="text-center" style="white-space: nowrap;">
72− <a href="/download/1.torrent"><i class="fa fa-fw fa-download"></i></a>
73− <a href="magnet:?xt=urn:btih:2"><i class="fa fa-fw fa-magnet"></i></a>
74− </td>
75− <td class="text-center">723.7 MiB</td>
76− <td class="text-center" data-timestamp="1503307456" title="1 week 3
77− days 9 hours 44 minutes 39 seconds ago">2017-08-21 11:24</td>
78− <td class="text-center" style="color: green;">1</td>
79− <td class="text-center" style="color: red;">3</td>
80− <td class="text-center">12</td>
81− </tr>
82− <tr class="default">
83− <td style="padding:0 4px;">
84− <a href="/?c=1_2" title="Anime - English-translated">
85− <img src="/static/img/icons/nyaa/1_2.png" alt="Anime - English-translated">
86− </a>
87− </td>
88− <td colspan="2">
89− <a href="/view/2" title="Sample title 2">Sample title 2</a>
90− </td>
91− <td class="text-center" style="white-space: nowrap;">
92− <a href="magnet:?xt=urn:btih:2"><i class="fa fa-fw fa-magnet"></i></a>
93− </td>
94− <td class="text-center">8.2 GiB</td>
95− <td class="text-center" data-timestamp="1491608400" title="4 months 3
96− weeks 4 days 19 hours 28 minutes 55 seconds ago">2017-04-08 01:40</td>
97− <td class="text-center" style="color: green;">10</td>
98− <td class="text-center" style="color: red;">1</td>
99− <td class="text-center">206</td>
100− </tr>
101− </tbody>
102− </table>
103− """
104−
105− resp = mock.Mock(text=html)
106− results = nyaa.response(resp)
107−
108− self.assertEqual(type(results), list)
109− self.assertEqual(len(results), 2)
110−
111− r = results[0]
112− self.assertTrue(r['url'].find('1') >= 0)
113− self.assertTrue(r['torrentfile'].find('1.torrent') >= 0)
114− self.assertTrue(r['content'].find('Anime - English-translated') >= 0)
115− self.assertTrue(r['content'].find('Downloaded 12 times.') >= 0)
116−
117− self.assertEqual(r['title'], 'Sample title 1')
118− self.assertEqual(r['seed'], 1)
119− self.assertEqual(r['leech'], 3)
120− self.assertEqual(r['filesize'], 723700000)
121−
122− r = results[1]
123− self.assertTrue(r['url'].find('2') >= 0)
124− self.assertTrue(r['magnetlink'].find('magnet:') >= 0)
tests/unit/engines/test_openstreetmap.pydeleted−199
@@ -1,199 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import openstreetmap
5−from searx.testing import SearxTestCase
6−
7−
8−class TestOpenstreetmapEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = openstreetmap.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn(query, params['url'])
17− self.assertIn('openstreetmap.org', params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, openstreetmap.response, None)
21− self.assertRaises(AttributeError, openstreetmap.response, [])
22− self.assertRaises(AttributeError, openstreetmap.response, '')
23− self.assertRaises(AttributeError, openstreetmap.response, '[]')
24−
25− response = mock.Mock(text='{}')
26− self.assertEqual(openstreetmap.response(response), [])
27−
28− response = mock.Mock(text='{"data": []}')
29− self.assertEqual(openstreetmap.response(response), [])
30−
31− json = """
32− [
33− {
34− "place_id": "127732055",
35− "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
36− "osm_type": "relation",
37− "osm_id": "7444",
38− "boundingbox": [
39− "48.8155755",
40− "48.902156",
41− "2.224122",
42− "2.4697602"
43− ],
44− "lat": "48.8565056",
45− "lon": "2.3521334",
46− "display_name": "This is the title",
47− "class": "place",
48− "type": "city",
49− "importance": 0.96893459932191,
50− "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
51− "address": {
52− "city": "Paris",
53− "county": "Paris",
54− "state": "Île-de-France",
55− "country": "France",
56− "country_code": "fr"
57− },
58− "geojson": {
59− "type": "Polygon",
60− "coordinates": [
61− [
62− [
63− 2.224122,
64− 48.854199
65− ]
66− ]
67− ]
68− }
69− }
70− ]
71− """
72− response = mock.Mock(text=json)
73− results = openstreetmap.response(response)
74− self.assertEqual(type(results), list)
75− self.assertEqual(len(results), 1)
76− self.assertEqual(results[0]['title'], 'This is the title')
77− self.assertEqual(results[0]['url'], 'https://openstreetmap.org/relation/7444')
78− self.assertIn('coordinates', results[0]['geojson'])
79− self.assertEqual(results[0]['geojson']['coordinates'][0][0][0], 2.224122)
80− self.assertEqual(results[0]['geojson']['coordinates'][0][0][1], 48.854199)
81− self.assertEqual(results[0]['address'], None)
82− self.assertIn('48.8155755', results[0]['boundingbox'])
83− self.assertIn('48.902156', results[0]['boundingbox'])
84− self.assertIn('2.224122', results[0]['boundingbox'])
85− self.assertIn('2.4697602', results[0]['boundingbox'])
86−
87− json = """
88− [
89− {
90− "place_id": "127732055",
91− "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
92− "osm_type": "relation",
93− "osm_id": "7444",
94− "boundingbox": [
95− "48.8155755",
96− "48.902156",
97− "2.224122",
98− "2.4697602"
99− ],
100− "lat": "48.8565056",
101− "lon": "2.3521334",
102− "display_name": "This is the title",
103− "class": "tourism",
104− "type": "city",
105− "importance": 0.96893459932191,
106− "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
107− "address": {
108− "city": "Paris",
109− "county": "Paris",
110− "state": "Île-de-France",
111− "country": "France",
112− "country_code": "fr",
113− "address29": "Address"
114− },
115− "geojson": {
116− "type": "Polygon",
117− "coordinates": [
118− [
119− [
120− 2.224122,
121− 48.854199
122− ]
123− ]
124− ]
125− }
126− },
127− {
128− "place_id": "127732055",
129− "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
130− "osm_type": "relation",
131− "osm_id": "7444",
132− "boundingbox": [
133− "48.8155755",
134− "48.902156",
135− "2.224122",
136− "2.4697602"
137− ],
138− "lat": "48.8565056",
139− "lon": "2.3521334",
140− "display_name": "This is the title",
141− "class": "tourism",
142− "type": "city",
143− "importance": 0.96893459932191,
144− "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
145− "address": {
146− "city": "Paris",
147− "county": "Paris",
148− "state": "Île-de-France",
149− "country": "France",
150− "postcode": 75000,
151− "country_code": "fr"
152− },
153− "geojson": {
154− "type": "Polygon",
155− "coordinates": [
156− [
157− [
158− 2.224122,
159− 48.854199
160− ]
161− ]
162− ]
163− }
164− },
165− {
166− "place_id": "127732055",
167− "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
168− "osm_type": "node",
169− "osm_id": "7444",
170− "boundingbox": [
171− "48.8155755",
172− "48.902156",
173− "2.224122",
174− "2.4697602"
175− ],
176− "lat": "48.8565056",
177− "lon": "2.3521334",
178− "display_name": "This is the title",
179− "class": "tourism",
180− "type": "city",
181− "importance": 0.96893459932191,
182− "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
183− "address": {
184− "city": "Paris",
185− "county": "Paris",
186− "state": "Île-de-France",
187− "country": "France",
188− "country_code": "fr",
189− "address29": "Address"
190− }
191− }
192− ]
193− """
194− response = mock.Mock(text=json)
195− results = openstreetmap.response(response)
196− self.assertEqual(type(results), list)
197− self.assertEqual(len(results), 3)
198− self.assertIn('48.8565056', results[2]['geojson']['coordinates'])
199− self.assertIn('2.3521334', results[2]['geojson']['coordinates'])
tests/unit/engines/test_pdbe.pydeleted−109
@@ -1,109 +0,0 @@
1−import mock
2−from collections import defaultdict
3−from searx.engines import pdbe
4−from searx.testing import SearxTestCase
5−
6−
7−class TestPdbeEngine(SearxTestCase):
8− def test_request(self):
9− query = 'test_query'
10− dicto = defaultdict(dict)
11− params = pdbe.request(query, dicto)
12− self.assertTrue('url' in params)
13− self.assertTrue('ebi.ac.uk' in params['url'])
14− self.assertTrue('data' in params)
15− self.assertTrue('q' in params['data'])
16− self.assertTrue(query in params['data']['q'])
17− self.assertTrue('wt' in params['data'])
18− self.assertTrue('json' in params['data']['wt'])
19− self.assertTrue('method' in params)
20− self.assertTrue(params['method'] == 'POST')
21−
22− def test_response(self):
23− self.assertRaises(AttributeError, pdbe.response, None)
24− self.assertRaises(AttributeError, pdbe.response, [])
25− self.assertRaises(AttributeError, pdbe.response, '')
26− self.assertRaises(AttributeError, pdbe.response, '[]')
27−
28− json = """
29−{
30− "response": {
31− "docs": [
32− {
33− "citation_title": "X-ray crystal structure of ferric Aplysia limacina myoglobin in different liganded states.",
34− "citation_year": 1993,
35− "entry_author_list": [
36− "Conti E, Moser C, Rizzi M, Mattevi A, Lionetti C, Coda A, Ascenzi P, Brunori M, Bolognesi M"
37− ],
38− "journal": "J. Mol. Biol.",
39− "journal_page": "498-508",
40− "journal_volume": "233",
41− "pdb_id": "2fal",
42− "status": "REL",
43− "title": "X-RAY CRYSTAL STRUCTURE OF FERRIC APLYSIA LIMACINA MYOGLOBIN IN DIFFERENT LIGANDED STATES"
44− }
45− ],
46− "numFound": 1,
47− "start": 0
48− },
49− "responseHeader": {
50− "QTime": 0,
51− "params": {
52− "q": "2fal",
53− "wt": "json"
54− },
55− "status": 0
56− }
57−}
58−"""
59−
60− response = mock.Mock(text=json)
61− results = pdbe.response(response)
62− self.assertEqual(type(results), list)
63− self.assertEqual(len(results), 1)
64− self.assertEqual(results[0]['title'],
65− 'X-RAY CRYSTAL STRUCTURE OF FERRIC APLYSIA LIMACINA MYOGLOBIN IN DIFFERENT LIGANDED STATES')
66− self.assertEqual(results[0]['url'], pdbe.pdbe_entry_url.format(pdb_id='2fal'))
67− self.assertEqual(results[0]['img_src'], pdbe.pdbe_preview_url.format(pdb_id='2fal'))
68− self.assertTrue('Conti E' in results[0]['content'])
69− self.assertTrue('X-ray crystal structure of ferric Aplysia limacina myoglobin in different liganded states.' in
70− results[0]['content'])
71− self.assertTrue('1993' in results[0]['content'])
72−
73− # Testing proper handling of PDB entries marked as obsolete
74− json = """
75−{
76− "response": {
77− "docs": [
78− {
79− "citation_title": "Obsolete entry test",
80− "citation_year": 2016,
81− "entry_author_list": ["Doe J"],
82− "journal": "J. Obs.",
83− "journal_page": "1-2",
84− "journal_volume": "1",
85− "pdb_id": "xxxx",
86− "status": "OBS",
87− "title": "OBSOLETE ENTRY TEST",
88− "superseded_by": "yyyy"
89− }
90− ],
91− "numFound": 1,
92− "start": 0
93− },
94− "responseHeader": {
95− "QTime": 0,
96− "params": {
97− "q": "xxxx",
98− "wt": "json"
99− },
100− "status": 0
101− }
102−}
103−"""
104− response = mock.Mock(text=json)
105− results = pdbe.response(response)
106− self.assertEqual(type(results), list)
107− self.assertEqual(len(results), 1)
108− self.assertEqual(results[0]['title'], 'OBSOLETE ENTRY TEST (OBSOLETE)')
109− self.assertTrue(results[0]['content'].startswith('This entry has been superseded by'))
tests/unit/engines/test_photon.pydeleted−166
@@ -1,166 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import photon
5−from searx.testing import SearxTestCase
6−
7−
8−class TestPhotonEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− dicto['language'] = 'all'
15− params = photon.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18− self.assertIn('photon.komoot.de', params['url'])
19−
20− dicto['language'] = 'all'
21− params = photon.request(query, dicto)
22− self.assertNotIn('lang', params['url'])
23−
24− dicto['language'] = 'al'
25− params = photon.request(query, dicto)
26− self.assertNotIn('lang', params['url'])
27−
28− dicto['language'] = 'fr'
29− params = photon.request(query, dicto)
30− self.assertIn('fr', params['url'])
31−
32− def test_response(self):
33− self.assertRaises(AttributeError, photon.response, None)
34− self.assertRaises(AttributeError, photon.response, [])
35− self.assertRaises(AttributeError, photon.response, '')
36− self.assertRaises(AttributeError, photon.response, '[]')
37−
38− response = mock.Mock(text='{}')
39− self.assertEqual(photon.response(response), [])
40−
41− response = mock.Mock(text='{"data": []}')
42− self.assertEqual(photon.response(response), [])
43−
44− json = """
45− {
46− "features": [
47− {
48− "properties": {
49− "osm_key": "waterway",
50− "extent": [
51− -1.4508446,
52− 51.1614997,
53− -1.4408036,
54− 51.1525635
55− ],
56− "name": "This is the title",
57− "state": "England",
58− "osm_id": 114823817,
59− "osm_type": "W",
60− "osm_value": "river",
61− "city": "Test Valley",
62− "country": "United Kingdom"
63− },
64− "type": "Feature",
65− "geometry": {
66− "type": "Point",
67− "coordinates": [
68− -1.4458571,
69− 51.1576661
70− ]
71− }
72− },
73− {
74− "properties": {
75− "osm_key": "place",
76− "street": "Rue",
77− "state": "Ile-de-France",
78− "osm_id": 129211377,
79− "osm_type": "R",
80− "housenumber": "10",
81− "postcode": "75011",
82− "osm_value": "house",
83− "city": "Paris",
84− "country": "France"
85− },
86− "type": "Feature",
87− "geometry": {
88− "type": "Point",
89− "coordinates": [
90− 2.3725025,
91− 48.8654481
92− ]
93− }
94− },
95− {
96− "properties": {
97− "osm_key": "amenity",
98− "street": "Allée",
99− "name": "Bibliothèque",
100− "state": "Ile-de-France",
101− "osm_id": 1028573132,
102− "osm_type": "N",
103− "postcode": "75001",
104− "osm_value": "library",
105− "city": "Paris",
106− "country": "France"
107− },
108− "type": "Feature",
109− "geometry": {
110− "type": "Point",
111− "coordinates": [
112− 2.3445634,
113− 48.862494
114− ]
115− }
116− },
117− {
118− "properties": {
119− "osm_key": "amenity",
120− "osm_id": 1028573132,
121− "osm_type": "Y",
122− "postcode": "75001",
123− "osm_value": "library",
124− "city": "Paris",
125− "country": "France"
126− },
127− "type": "Feature",
128− "geometry": {
129− "type": "Point",
130− "coordinates": [
131− 2.3445634,
132− 48.862494
133− ]
134− }
135− },
136− {
137− }
138− ],
139− "type": "FeatureCollection"
140− }
141− """
142− response = mock.Mock(text=json)
143− results = photon.response(response)
144− self.assertEqual(type(results), list)
145− self.assertEqual(len(results), 3)
146− self.assertEqual(results[0]['title'], 'This is the title')
147− self.assertEqual(results[0]['content'], '')
148− self.assertEqual(results[0]['longitude'], -1.4458571)
149− self.assertEqual(results[0]['latitude'], 51.1576661)
150− self.assertIn(-1.4508446, results[0]['boundingbox'])
151− self.assertIn(51.1614997, results[0]['boundingbox'])
152− self.assertIn(-1.4408036, results[0]['boundingbox'])
153− self.assertIn(51.1525635, results[0]['boundingbox'])
154− self.assertIn('type', results[0]['geojson'])
155− self.assertEqual(results[0]['geojson']['type'], 'Point')
156− self.assertEqual(results[0]['address'], None)
157− self.assertEqual(results[0]['osm']['type'], 'way')
158− self.assertEqual(results[0]['osm']['id'], 114823817)
159− self.assertEqual(results[0]['url'], 'https://openstreetmap.org/way/114823817')
160− self.assertEqual(results[1]['osm']['type'], 'relation')
161− self.assertEqual(results[2]['address']['name'], u'Bibliothèque')
162− self.assertEqual(results[2]['address']['house_number'], None)
163− self.assertEqual(results[2]['address']['locality'], 'Paris')
164− self.assertEqual(results[2]['address']['postcode'], '75001')
165− self.assertEqual(results[2]['address']['country'], 'France')
166− self.assertEqual(results[2]['osm']['type'], 'node')
tests/unit/engines/test_piratebay.pydeleted−166
@@ -1,166 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import piratebay
5−from searx.testing import SearxTestCase
6−
7−
8−class TestPiratebayEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− dicto['category'] = 'Toto'
15− params = piratebay.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18− self.assertIn('piratebay.org', params['url'])
19− self.assertIn('0', params['url'])
20−
21− dicto['category'] = 'music'
22− params = piratebay.request(query, dicto)
23− self.assertIn('100', params['url'])
24−
25− def test_response(self):
26− self.assertRaises(AttributeError, piratebay.response, None)
27− self.assertRaises(AttributeError, piratebay.response, [])
28− self.assertRaises(AttributeError, piratebay.response, '')
29− self.assertRaises(AttributeError, piratebay.response, '[]')
30−
31− response = mock.Mock(text='<html></html>')
32− self.assertEqual(piratebay.response(response), [])
33−
34− html = """
35− <table id="searchResult">
36− <tr>
37− </tr>
38− <tr>
39− <td class="vertTh">
40− <center>
41− <a href="#" title="More from this category">Anime</a><br/>
42− (<a href="#" title="More from this category">Anime</a>)
43− </center>
44− </td>
45− <td>
46− <div class="detName">
47− <a href="/this.is.the.link" class="detLink" title="Title">
48− This is the title
49− </a>
50− </div>
51− <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet">
52− <img src="/static/img/icon-magnet.gif" alt="Magnet link"/>
53− </a>
54− <a href="http://torcache.net/torrent/TORRENTFILE.torrent" title="Download this torrent">
55− <img src="/static/img/dl.gif" class="dl" alt="Download"/>
56− </a>
57− <a href="/user/HorribleSubs">
58− <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/>
59− </a>
60− <img src="/static/img/11x11p.png"/>
61− <font class="detDesc">
62− This is the content <span>and should be</span> OK
63− </font>
64− </td>
65− <td align="right">13</td>
66− <td align="right">334</td>
67− </tr>
68− <tr>
69− <td class="vertTh">
70− <center>
71− <a href="#" title="More from this category">Anime</a><br/>
72− (<a href="#" title="More from this category">Anime</a>)
73− </center>
74− </td>
75− <td>
76− <div class="detName">
77− <a href="/this.is.the.link" class="detLink" title="Title">
78− This is the title
79− </a>
80− </div>
81− <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet">
82− <img src="/static/img/icon-magnet.gif" alt="Magnet link"/>
83− </a>
84− <a href="/user/HorribleSubs">
85− <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/>
86− </a>
87− <img src="/static/img/11x11p.png"/>
88− <font class="detDesc">
89− This is the content <span>and should be</span> OK
90− </font>
91− </td>
92− <td align="right">13</td>
93− <td align="right">334</td>
94− </tr>
95− </table>
96− """
97− response = mock.Mock(text=html)
98− results = piratebay.response(response)
99− self.assertEqual(type(results), list)
100− self.assertEqual(len(results), 2)
101− self.assertEqual(results[0]['title'], 'This is the title')
102− self.assertEqual(results[0]['url'], 'https://thepiratebay.org/this.is.the.link')
103− self.assertEqual(results[0]['content'], 'This is the content and should be OK')
104− self.assertEqual(results[0]['seed'], 13)
105− self.assertEqual(results[0]['leech'], 334)
106− self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETLINK')
107− self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/TORRENTFILE.torrent')
108−
109− self.assertEqual(results[1]['torrentfile'], None)
110−
111− html = """
112− <table id="searchResult">
113− <tr>
114− </tr>
115− <tr>
116− <td class="vertTh">
117− <center>
118− <a href="#" title="More from this category">Anime</a><br/>
119− (<a href="#" title="More from this category">Anime</a>)
120− </center>
121− </td>
122− <td>
123− <div class="detName">
124− <a href="/this.is.the.link" class="detLink" title="Title">
125− This is the title
126− </a>
127− </div>
128− <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet">
129− <img src="/static/img/icon-magnet.gif" alt="Magnet link"/>
130− </a>
131− <a href="http://torcache.net/torrent/TORRENTFILE.torrent" title="Download this torrent">
132− <img src="/static/img/dl.gif" class="dl" alt="Download"/>
133− </a>
134− <a href="/user/HorribleSubs">
135− <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/>
136− </a>
137− <img src="/static/img/11x11p.png"/>
138− <font class="detDesc">
139− This is the content <span>and should be</span> OK
140− </font>
141− </td>
142− <td align="right">s</td>
143− <td align="right">d</td>
144− </tr>
145− </table>
146− """
147− response = mock.Mock(text=html)
148− results = piratebay.response(response)
149− self.assertEqual(type(results), list)
150− self.assertEqual(len(results), 1)
151− self.assertEqual(results[0]['title'], 'This is the title')
152− self.assertEqual(results[0]['url'], 'https://thepiratebay.org/this.is.the.link')
153− self.assertEqual(results[0]['content'], 'This is the content and should be OK')
154− self.assertEqual(results[0]['seed'], 0)
155− self.assertEqual(results[0]['leech'], 0)
156− self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETLINK')
157− self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/TORRENTFILE.torrent')
158−
159− html = """
160− <table id="searchResult">
161− </table>
162− """
163− response = mock.Mock(text=html)
164− results = piratebay.response(response)
165− self.assertEqual(type(results), list)
166− self.assertEqual(len(results), 0)
tests/unit/engines/test_qwant.pydeleted−339
@@ -1,339 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import qwant
4−from searx.testing import SearxTestCase
5−
6−
7−class TestQwantEngine(SearxTestCase):
8−
9− def test_request(self):
10− qwant.supported_languages = ['en-US', 'fr-CA', 'fr-FR']
11− qwant.language_aliases = {}
12− query = 'test_query'
13− dicto = defaultdict(dict)
14− dicto['pageno'] = 0
15− dicto['language'] = 'fr-FR'
16− qwant.categories = ['']
17− params = qwant.request(query, dicto)
18− self.assertIn('url', params)
19− self.assertIn(query, params['url'])
20− self.assertIn('web', params['url'])
21− self.assertIn('qwant.com', params['url'])
22− self.assertIn('fr_fr', params['url'])
23−
24− dicto['language'] = 'all'
25− qwant.categories = ['news']
26− params = qwant.request(query, dicto)
27− self.assertFalse('fr' in params['url'])
28− self.assertIn('news', params['url'])
29−
30− dicto['language'] = 'fr'
31− params = qwant.request(query, dicto)
32− self.assertIn('fr_fr', params['url'])
33−
34− def test_response(self):
35− self.assertRaises(AttributeError, qwant.response, None)
36− self.assertRaises(AttributeError, qwant.response, [])
37− self.assertRaises(AttributeError, qwant.response, '')
38− self.assertRaises(AttributeError, qwant.response, '[]')
39−
40− response = mock.Mock(text='{}')
41− self.assertEqual(qwant.response(response), [])
42−
43− response = mock.Mock(text='{"data": {}}')
44− self.assertEqual(qwant.response(response), [])
45−
46− json = """
47− {
48− "status": "success",
49− "data": {
50− "query": {
51− "locale": "en_us",
52− "query": "Test",
53− "offset": 10
54− },
55− "result": {
56− "items": [
57− {
58− "title": "Title",
59− "score": 9999,
60− "url": "http://www.url.xyz",
61− "source": "...",
62− "desc": "Description",
63− "date": "",
64− "_id": "db0aadd62c2a8565567ffc382f5c61fa",
65− "favicon": "https://s.qwant.com/fav.ico"
66− }
67− ],
68− "filters": []
69− },
70− "cache": {
71− "key": "e66aa864c00147a0e3a16ff7a5efafde",
72− "created": 1433092754,
73− "expiration": 259200,
74− "status": "miss",
75− "age": 0
76− }
77− }
78− }
79− """
80− response = mock.Mock(text=json)
81− qwant.categories = ['general']
82− results = qwant.response(response)
83− self.assertEqual(type(results), list)
84− self.assertEqual(len(results), 1)
85− self.assertEqual(results[0]['title'], 'Title')
86− self.assertEqual(results[0]['url'], 'http://www.url.xyz')
87− self.assertEqual(results[0]['content'], 'Description')
88−
89− json = """
90− {
91− "status": "success",
92− "data": {
93− "query": {
94− "locale": "en_us",
95− "query": "Test",
96− "offset": 10
97− },
98− "result": {
99− "items": [
100− {
101− "title": "Title",
102− "score": 9999,
103− "url": "http://www.url.xyz",
104− "source": "...",
105− "media": "http://image.jpg",
106− "desc": "",
107− "thumbnail": "http://thumbnail.jpg",
108− "date": "",
109− "_id": "db0aadd62c2a8565567ffc382f5c61fa",
110− "favicon": "https://s.qwant.com/fav.ico"
111− }
112− ],
113− "filters": []
114− },
115− "cache": {
116− "key": "e66aa864c00147a0e3a16ff7a5efafde",
117− "created": 1433092754,
118− "expiration": 259200,
119− "status": "miss",
120− "age": 0
121− }
122− }
123− }
124− """
125− response = mock.Mock(text=json)
126− qwant.categories = ['images']
127− results = qwant.response(response)
128− self.assertEqual(type(results), list)
129− self.assertEqual(len(results), 1)
130− self.assertEqual(results[0]['title'], 'Title')
131− self.assertEqual(results[0]['url'], 'http://www.url.xyz')
132− self.assertEqual(results[0]['content'], '')
133− self.assertEqual(results[0]['thumbnail_src'], 'http://thumbnail.jpg')
134− self.assertEqual(results[0]['img_src'], 'http://image.jpg')
135−
136− json = """
137− {
138− "status": "success",
139− "data": {
140− "query": {
141− "locale": "en_us",
142− "query": "Test",
143− "offset": 10
144− },
145− "result": {
146− "items": [
147− {
148− "title": "Title",
149− "score": 9999,
150− "url": "http://www.url.xyz",
151− "source": "...",
152− "desc": "Description",
153− "date": 1433260920,
154− "_id": "db0aadd62c2a8565567ffc382f5c61fa",
155− "favicon": "https://s.qwant.com/fav.ico"
156− }
157− ],
158− "filters": []
159− },
160− "cache": {
161− "key": "e66aa864c00147a0e3a16ff7a5efafde",
162− "created": 1433092754,
163− "expiration": 259200,
164− "status": "miss",
165− "age": 0
166− }
167− }
168− }
169− """
170− response = mock.Mock(text=json)
171− qwant.categories = ['news']
172− results = qwant.response(response)
173− self.assertEqual(type(results), list)
174− self.assertEqual(len(results), 1)
175− self.assertEqual(results[0]['title'], 'Title')
176− self.assertEqual(results[0]['url'], 'http://www.url.xyz')
177− self.assertEqual(results[0]['content'], 'Description')
178− self.assertIn('publishedDate', results[0])
179−
180− json = """
181− {
182− "status": "success",
183− "data": {
184− "query": {
185− "locale": "en_us",
186− "query": "Test",
187− "offset": 10
188− },
189− "result": {
190− "items": [
191− {
192− "title": "Title",
193− "score": 9999,
194− "url": "http://www.url.xyz",
195− "source": "...",
196− "desc": "Description",
197− "date": 1433260920,
198− "_id": "db0aadd62c2a8565567ffc382f5c61fa",
199− "favicon": "https://s.qwant.com/fav.ico"
200− }
201− ],
202− "filters": []
203− },
204− "cache": {
205− "key": "e66aa864c00147a0e3a16ff7a5efafde",
206− "created": 1433092754,
207− "expiration": 259200,
208− "status": "miss",
209− "age": 0
210− }
211− }
212− }
213− """
214− response = mock.Mock(text=json)
215− qwant.categories = ['social media']
216− results = qwant.response(response)
217− self.assertEqual(type(results), list)
218− self.assertEqual(len(results), 1)
219− self.assertEqual(results[0]['title'], 'Title')
220− self.assertEqual(results[0]['url'], 'http://www.url.xyz')
221− self.assertEqual(results[0]['content'], 'Description')
222− self.assertIn('publishedDate', results[0])
223−
224− json = """
225− {
226− "status": "success",
227− "data": {
228− "query": {
229− "locale": "en_us",
230− "query": "Test",
231− "offset": 10
232− },
233− "result": {
234− "items": [
235− {
236− "title": "Title",
237− "score": 9999,
238− "url": "http://www.url.xyz",
239− "source": "...",
240− "desc": "Description",
241− "date": 1433260920,
242− "_id": "db0aadd62c2a8565567ffc382f5c61fa",
243− "favicon": "https://s.qwant.com/fav.ico"
244− }
245− ],
246− "filters": []
247− },
248− "cache": {
249− "key": "e66aa864c00147a0e3a16ff7a5efafde",
250− "created": 1433092754,
251− "expiration": 259200,
252− "status": "miss",
253− "age": 0
254− }
255− }
256− }
257− """
258− response = mock.Mock(text=json)
259− qwant.categories = ['']
260− results = qwant.response(response)
261− self.assertEqual(type(results), list)
262− self.assertEqual(len(results), 0)
263−
264− json = """
265− {
266− "status": "success",
267− "data": {
268− "query": {
269− "locale": "en_us",
270− "query": "Test",
271− "offset": 10
272− },
273− "result": {
274− "filters": []
275− },
276− "cache": {
277− "key": "e66aa864c00147a0e3a16ff7a5efafde",
278− "created": 1433092754,
279− "expiration": 259200,
280− "status": "miss",
281− "age": 0
282− }
283− }
284− }
285− """
286− response = mock.Mock(text=json)
287− results = qwant.response(response)
288− self.assertEqual(type(results), list)
289− self.assertEqual(len(results), 0)
290−
291− json = """
292− {
293− "status": "success",
294− "data": {
295− "query": {
296− "locale": "en_us",
297− "query": "Test",
298− "offset": 10
299− },
300− "cache": {
301− "key": "e66aa864c00147a0e3a16ff7a5efafde",
302− "created": 1433092754,
303− "expiration": 259200,
304− "status": "miss",
305− "age": 0
306− }
307− }
308− }
309− """
310− response = mock.Mock(text=json)
311− results = qwant.response(response)
312− self.assertEqual(type(results), list)
313− self.assertEqual(len(results), 0)
314−
315− json = """
316− {
317− "status": "success"
318− }
319− """
320− response = mock.Mock(text=json)
321− results = qwant.response(response)
322− self.assertEqual(type(results), list)
323− self.assertEqual(len(results), 0)
324−
325− def test_fetch_supported_languages(self):
326− page = """some code...
327− config_set('project.regionalisation', {"continents":{},"languages":
328− {"de":{"code":"de","name":"Deutsch","countries":["DE","CH","AT"]},
329− "it":{"code":"it","name":"Italiano","countries":["IT","CH"]}}});
330− some more code..."""
331− response = mock.Mock(text=page)
332− languages = qwant._fetch_supported_languages(response)
333− self.assertEqual(type(languages), list)
334− self.assertEqual(len(languages), 5)
335− self.assertIn('de-DE', languages)
336− self.assertIn('de-CH', languages)
337− self.assertIn('de-AT', languages)
338− self.assertIn('it-IT', languages)
339− self.assertIn('it-CH', languages)
tests/unit/engines/test_reddit.pydeleted−71
@@ -1,71 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import reddit
4−from searx.testing import SearxTestCase
5−from datetime import datetime
6−
7−
8−class TestRedditEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dic = defaultdict(dict)
13− params = reddit.request(query, dic)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('reddit.com' in params['url'])
17−
18− def test_response(self):
19− resp = mock.Mock(text='{}')
20− self.assertEqual(reddit.response(resp), [])
21−
22− json = """
23− {
24− "kind": "Listing",
25− "data": {
26− "children": [{
27− "data": {
28− "url": "http://google2.com/",
29− "permalink": "http://google.com/",
30− "title": "Title number one",
31− "selftext": "Sample",
32− "created_utc": 1401219957.0,
33− "thumbnail": "http://image.com/picture.jpg"
34− }
35− }, {
36− "data": {
37− "url": "https://reddit2.com/",
38− "permalink": "https://reddit.com/",
39− "title": "Title number two",
40− "selftext": "Dominus vobiscum",
41− "created_utc": 1438792533.0,
42− "thumbnail": "self"
43− }
44− }]
45− }
46− }
47− """
48−
49− resp = mock.Mock(text=json)
50− results = reddit.response(resp)
51−
52− self.assertEqual(len(results), 2)
53− self.assertEqual(type(results), list)
54−
55− # testing first result (picture)
56− r = results[0]
57− self.assertEqual(r['url'], 'http://google.com/')
58− self.assertEqual(r['title'], 'Title number one')
59− self.assertEqual(r['template'], 'images.html')
60− self.assertEqual(r['img_src'], 'http://google2.com/')
61− self.assertEqual(r['thumbnail_src'], 'http://image.com/picture.jpg')
62−
63− # testing second result (self-post)
64− r = results[1]
65− self.assertEqual(r['url'], 'https://reddit.com/')
66− self.assertEqual(r['title'], 'Title number two')
67− self.assertEqual(r['content'], 'Dominus vobiscum')
68− created = datetime.fromtimestamp(1438792533.0)
69− self.assertEqual(r['publishedDate'], created)
70− self.assertTrue('thumbnail_src' not in r)
71− self.assertTrue('img_src' not in r)
tests/unit/engines/test_scanr_structures.pydeleted−175
@@ -1,175 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import scanr_structures
4−from searx.testing import SearxTestCase
5−
6−
7−class TestScanrStructuresEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 1
13− params = scanr_structures.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['data'])
16− self.assertIn('scanr.enseignementsup-recherche.gouv.fr', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, scanr_structures.response, None)
20− self.assertRaises(AttributeError, scanr_structures.response, [])
21− self.assertRaises(AttributeError, scanr_structures.response, '')
22− self.assertRaises(AttributeError, scanr_structures.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(scanr_structures.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(scanr_structures.response(response), [])
29−
30− json = u"""
31− {
32− "request":
33− {
34− "query":"test_query",
35− "page":1,
36− "pageSize":20,
37− "sortOrder":"RELEVANCY",
38− "sortDirection":"ASC",
39− "searchField":"ALL",
40− "from":0
41− },
42− "total":2471,
43− "results":[
44− {
45− "id":"200711886U",
46− "label":"Laboratoire d'Informatique de Grenoble",
47− "kind":"RNSR",
48− "publicEntity":true,
49− "address":{"city":"Grenoble","departement":"38"},
50− "logo":"/static/logos/200711886U.png",
51− "acronym":"LIG",
52− "type":{"code":"UR","label":"Unit\xe9 de recherche"},
53− "level":2,
54− "institutions":[
55− {
56− "id":"193819125",
57− "label":"Grenoble INP",
58− "acronym":"IPG",
59− "code":"UMR 5217"
60− },
61− {
62− "id":"130021397",
63− "label":"Universit\xe9 de Grenoble Alpes",
64− "acronym":"UGA",
65− "code":"UMR 5217"
66− },
67− {
68− "id":"180089013",
69− "label":"Centre national de la recherche scientifique",
70− "acronym":"CNRS",
71− "code":"UMR 5217"
72− },
73− {
74− "id":"180089047",
75− "label":"Institut national de recherche en informatique et en automatique",
76− "acronym":"Inria",
77− "code":"UMR 5217"
78− }
79− ],
80− "highlights":[
81− {
82− "type":"projects",
83− "value":"linguicielles d\xe9velopp\xe9s jusqu'ici par le GETALP\
84− du <strong>LIG</strong> en tant que prototypes op\xe9rationnels.\
85−\\r\\nDans le contexte"
86− },
87− {
88− "type":"acronym",
89− "value":"<strong>LIG</strong>"
90− },
91− {
92− "type":"websiteContents",
93− "value":"S\xe9lection\\nListe structures\\nD\xe9tail\\n\
94− Accueil\\n200711886U : <strong>LIG</strong>\
95− Laboratoire d'Informatique de Grenoble Unit\xe9 de recherche"},
96− {
97− "type":"publications",
98− "value":"de noms. Nous avons d'abord d\xe9velopp\xe9 LOOV \
99− (pour <strong>Lig</strong> Overlaid OCR in Vid\xe9o), \
100− un outil d'extraction des"
101− }
102− ]
103− },
104− {
105− "id":"199511665F",
106− "label":"Laboratoire Bordelais de Recherche en Informatique",
107− "kind":"RNSR",
108− "publicEntity":true,
109− "address":{"city":"Talence","departement":"33"},
110− "logo":"/static/logos/199511665F.png",
111− "acronym":"LaBRI",
112− "type":{"code":"UR","label":"Unit\xe9 de recherche"},
113− "level":2,
114− "institutions":[
115− {
116− "id":"130006356",
117− "label":"Institut polytechnique de Bordeaux",
118− "acronym":"IPB",
119− "code":"UMR 5800"
120− },
121− {
122− "id":"130018351",
123− "label":"Universit\xe9 de Bordeaux",
124− "acronym":null,
125− "code":"UMR 5800"
126− },
127− {
128− "id":"180089013",
129− "label":"Centre national de la recherche scientifique",
130− "acronym":"CNRS",
131− "code":"UMR 5800"
132− },
133− {
134− "id":"180089047",
135− "label":"Institut national de recherche en informatique et en automatique",
136− "acronym":"Inria",
137− "code":"UMR 5800"
138− }
139− ],
140− "highlights":[
141− {
142− "type":"websiteContents",
143− "value":"Samia Kerdjoudj\\n2016-07-05\\nDouble-exponential\
144− and <strong>triple</strong>-exponential bounds for\
145− choosability problems parameterized"
146− },
147− {
148− "type":"publications",
149− "value":"de cam\xe9ras install\xe9es dans les lieux publiques \
150− a <strong>tripl\xe9</strong> en 2009, passant de 20 000 \
151− \xe0 60 000. Malgr\xe9 le"
152− }
153− ]
154− }
155− ]
156− }
157− """
158− response = mock.Mock(text=json)
159− results = scanr_structures.response(response)
160− self.assertEqual(type(results), list)
161− self.assertEqual(len(results), 2)
162− self.assertEqual(results[0]['title'], u"Laboratoire d'Informatique de Grenoble")
163− self.assertEqual(results[0]['url'], 'https://scanr.enseignementsup-recherche.gouv.fr/structure/200711886U')
164− self.assertEqual(results[0]['content'],
165− u"linguicielles d\xe9velopp\xe9s jusqu'ici par le GETALP "
166− u"du LIG en tant que prototypes "
167− u"op\xe9rationnels. Dans le contexte")
168− self.assertEqual(results[1]['img_src'],
169− 'https://scanr.enseignementsup-recherche.gouv.fr//static/logos/199511665F.png')
170− self.assertEqual(results[1]['content'],
171− "Samia Kerdjoudj 2016-07-05 Double-exponential and"
172− " triple-exponential bounds for "
173− "choosability problems parameterized")
174− self.assertEqual(results[1]['url'], 'https://scanr.enseignementsup-recherche.gouv.fr/structure/199511665F')
175− self.assertEqual(results[1]['title'], u"Laboratoire Bordelais de Recherche en Informatique")
tests/unit/engines/test_searchcode_code.pydeleted−75
@@ -1,75 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import searchcode_code
4−from searx.testing import SearxTestCase
5−
6−
7−class TestSearchcodeCodeEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = searchcode_code.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['url'])
16− self.assertIn('searchcode.com', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, searchcode_code.response, None)
20− self.assertRaises(AttributeError, searchcode_code.response, [])
21− self.assertRaises(AttributeError, searchcode_code.response, '')
22− self.assertRaises(AttributeError, searchcode_code.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(searchcode_code.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(searchcode_code.response(response), [])
29−
30− json = """
31− {
32− "matchterm": "test",
33− "previouspage": null,
34− "searchterm": "test",
35− "query": "test",
36− "total": 1000,
37− "page": 0,
38− "nextpage": 1,
39− "results": [
40− {
41− "repo": "https://repo",
42− "linescount": 1044,
43− "location": "/tests",
44− "name": "Name",
45− "url": "https://url",
46− "md5hash": "ecac6e479edd2b9406c9e08603cec655",
47− "lines": {
48− "1": "// Test 011",
49− "2": "// Source: "
50− },
51− "id": 51223527,
52− "filename": "File.CPP"
53− }
54− ]
55− }
56− """
57− response = mock.Mock(text=json)
58− results = searchcode_code.response(response)
59− self.assertEqual(type(results), list)
60− self.assertEqual(len(results), 1)
61− self.assertEqual(results[0]['title'], 'Name - File.CPP')
62− self.assertEqual(results[0]['url'], 'https://url')
63− self.assertEqual(results[0]['repository'], 'https://repo')
64− self.assertEqual(results[0]['code_language'], 'cpp')
65−
66− json = r"""
67− {"toto":[
68− {"id":200,"name":"Artist Name",
69− "link":"http:\/\/www.searchcode_code.com\/artist\/1217","type":"artist"}
70− ]}
71− """
72− response = mock.Mock(text=json)
73− results = searchcode_code.response(response)
74− self.assertEqual(type(results), list)
75− self.assertEqual(len(results), 0)
tests/unit/engines/test_searchcode_doc.pydeleted−70
@@ -1,70 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import searchcode_doc
4−from searx.testing import SearxTestCase
5−
6−
7−class TestSearchcodeDocEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = searchcode_doc.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['url'])
16− self.assertIn('searchcode.com', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, searchcode_doc.response, None)
20− self.assertRaises(AttributeError, searchcode_doc.response, [])
21− self.assertRaises(AttributeError, searchcode_doc.response, '')
22− self.assertRaises(AttributeError, searchcode_doc.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(searchcode_doc.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(searchcode_doc.response(response), [])
29−
30− json = """
31− {
32− "matchterm": "test",
33− "previouspage": null,
34− "searchterm": "test",
35− "query": "test",
36− "total": 60,
37− "page": 0,
38− "nextpage": 1,
39− "results": [
40− {
41− "synopsis": "Synopsis",
42− "displayname": null,
43− "name": "test",
44− "url": "http://url",
45− "type": "Type",
46− "icon": null,
47− "namespace": "Namespace",
48− "description": "Description"
49− }
50− ]
51− }
52− """
53− response = mock.Mock(text=json)
54− results = searchcode_doc.response(response)
55− self.assertEqual(type(results), list)
56− self.assertEqual(len(results), 1)
57− self.assertEqual(results[0]['title'], '[Type] Namespace test')
58− self.assertEqual(results[0]['url'], 'http://url')
59− self.assertIn('Description', results[0]['content'])
60−
61− json = r"""
62− {"toto":[
63− {"id":200,"name":"Artist Name",
64− "link":"http:\/\/www.searchcode_doc.com\/artist\/1217","type":"artist"}
65− ]}
66− """
67− response = mock.Mock(text=json)
68− results = searchcode_doc.response(response)
69− self.assertEqual(type(results), list)
70− self.assertEqual(len(results), 0)
tests/unit/engines/test_seedpeer.pydeleted−66
@@ -1,66 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import seedpeer
5−from searx.testing import SearxTestCase
6−
7−
8−class TestBtdiggEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = seedpeer.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn(query, params['url'])
17− self.assertIn('seedpeer', params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, seedpeer.response, None)
21− self.assertRaises(AttributeError, seedpeer.response, [])
22− self.assertRaises(AttributeError, seedpeer.response, '')
23− self.assertRaises(AttributeError, seedpeer.response, '[]')
24−
25− response = mock.Mock(text='<html></html>')
26− self.assertEqual(seedpeer.response(response), [])
27−
28− html = u"""
29− <html>
30− <head>
31− <script></script>
32− <script type="text/javascript" src="not_here.js"></script>
33− <script type="text/javascript">
34− window.initialData=
35− {"data": {"list": [{"name": "Title", "seeds": "10", "peers": "20", "size": "1024", "hash": "abc123"}]}}
36− </script>
37− </head>
38− <body>
39− <table></table>
40− <table>
41− <thead><tr></tr></thead>
42− <tbody>
43− <tr>
44− <td><a href="link">Title</a></td>
45− <td>1 year</td>
46− <td>1 KB</td>
47− <td>10</td>
48− <td>20</td>
49− <td></td>
50− </tr>
51− </tbody>
52− </table>
53− </body>
54− </html>
55− """
56− response = mock.Mock(text=html)
57− results = seedpeer.response(response)
58− self.assertEqual(type(results), list)
59− self.assertEqual(len(results), 1)
60− self.assertEqual(results[0]['title'], 'Title')
61− self.assertEqual(results[0]['url'], 'https://seedpeer.me/link')
62− self.assertEqual(results[0]['seed'], 10)
63− self.assertEqual(results[0]['leech'], 20)
64− self.assertEqual(results[0]['filesize'], 1024)
65− self.assertEqual(results[0]['torrentfile'], 'https://seedpeer.me/torrent/abc123')
66− self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:abc123')
tests/unit/engines/test_soundcloud.pydeleted−192
@@ -1,192 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import soundcloud
4−from searx.testing import SearxTestCase
5−from searx.url_utils import quote_plus
6−
7−
8−class TestSoundcloudEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− params = soundcloud.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn(query, params['url'])
17− self.assertIn('soundcloud.com', params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, soundcloud.response, None)
21− self.assertRaises(AttributeError, soundcloud.response, [])
22− self.assertRaises(AttributeError, soundcloud.response, '')
23− self.assertRaises(AttributeError, soundcloud.response, '[]')
24−
25− response = mock.Mock(text='{}')
26− self.assertEqual(soundcloud.response(response), [])
27−
28− response = mock.Mock(text='{"data": []}')
29− self.assertEqual(soundcloud.response(response), [])
30−
31− json = """
32− {
33− "collection": [
34− {
35− "kind": "track",
36− "id": 159723640,
37− "created_at": "2014/07/22 00:51:21 +0000",
38− "user_id": 2976616,
39− "duration": 303780,
40− "commentable": true,
41− "state": "finished",
42− "original_content_size": 13236349,
43− "last_modified": "2015/01/31 15:14:50 +0000",
44− "sharing": "public",
45− "tag_list": "seekae flume",
46− "permalink": "seekae-test-recognise-flume-re-work",
47− "streamable": true,
48− "embeddable_by": "all",
49− "downloadable": true,
50− "purchase_url": "http://www.facebook.com/seekaemusic",
51− "label_id": null,
52− "purchase_title": "Seekae",
53− "genre": "freedownload",
54− "title": "This is the title",
55− "description": "This is the content",
56− "label_name": "Future Classic",
57− "release": "",
58− "track_type": "remix",
59− "key_signature": "",
60− "isrc": "",
61− "video_url": null,
62− "bpm": null,
63− "release_year": 2014,
64− "release_month": 7,
65− "release_day": 22,
66− "original_format": "mp3",
67− "license": "all-rights-reserved",
68− "uri": "https://api.soundcloud.com/tracks/159723640",
69− "user": {
70− "id": 2976616,
71− "kind": "user",
72− "permalink": "flume",
73− "username": "Flume",
74− "last_modified": "2014/11/24 19:21:29 +0000",
75− "uri": "https://api.soundcloud.com/users/2976616",
76− "permalink_url": "http://soundcloud.com/flume",
77− "avatar_url": "https://i1.sndcdn.com/avatars-000044475439-4zi7ii-large.jpg"
78− },
79− "permalink_url": "http://soundcloud.com/this.is.the.url",
80− "artwork_url": "https://i1.sndcdn.com/artworks-000085857162-xdxy5c-large.jpg",
81− "waveform_url": "https://w1.sndcdn.com/DWrL1lAN8BkP_m.png",
82− "stream_url": "https://api.soundcloud.com/tracks/159723640/stream",
83− "download_url": "https://api.soundcloud.com/tracks/159723640/download",
84− "playback_count": 2190687,
85− "download_count": 54856,
86− "favoritings_count": 49061,
87− "comment_count": 826,
88− "likes_count": 49061,
89− "reposts_count": 15910,
90− "attachments_uri": "https://api.soundcloud.com/tracks/159723640/attachments",
91− "policy": "ALLOW"
92− }
93− ],
94− "total_results": 375750,
95− "next_href": "https://api.soundcloud.com/search?&q=test",
96− "tx_id": ""
97− }
98− """
99− response = mock.Mock(text=json)
100− results = soundcloud.response(response)
101− self.assertEqual(type(results), list)
102− self.assertEqual(len(results), 1)
103− self.assertEqual(results[0]['title'], 'This is the title')
104− self.assertEqual(results[0]['url'], 'http://soundcloud.com/this.is.the.url')
105− self.assertEqual(results[0]['content'], 'This is the content')
106− self.assertIn(quote_plus('https://api.soundcloud.com/tracks/159723640'), results[0]['embedded'])
107−
108− json = """
109− {
110− "collection": [
111− {
112− "kind": "user",
113− "id": 159723640,
114− "created_at": "2014/07/22 00:51:21 +0000",
115− "user_id": 2976616,
116− "duration": 303780,
117− "commentable": true,
118− "state": "finished",
119− "original_content_size": 13236349,
120− "last_modified": "2015/01/31 15:14:50 +0000",
121− "sharing": "public",
122− "tag_list": "seekae flume",
123− "permalink": "seekae-test-recognise-flume-re-work",
124− "streamable": true,
125− "embeddable_by": "all",
126− "downloadable": true,
127− "purchase_url": "http://www.facebook.com/seekaemusic",
128− "label_id": null,
129− "purchase_title": "Seekae",
130− "genre": "freedownload",
131− "title": "This is the title",
132− "description": "This is the content",
133− "label_name": "Future Classic",
134− "release": "",
135− "track_type": "remix",
136− "key_signature": "",
137− "isrc": "",
138− "video_url": null,
139− "bpm": null,
140− "release_year": 2014,
141− "release_month": 7,
142− "release_day": 22,
143− "original_format": "mp3",
144− "license": "all-rights-reserved",
145− "uri": "https://api.soundcloud.com/tracks/159723640",
146− "user": {
147− "id": 2976616,
148− "kind": "user",
149− "permalink": "flume",
150− "username": "Flume",
151− "last_modified": "2014/11/24 19:21:29 +0000",
152− "uri": "https://api.soundcloud.com/users/2976616",
153− "permalink_url": "http://soundcloud.com/flume",
154− "avatar_url": "https://i1.sndcdn.com/avatars-000044475439-4zi7ii-large.jpg"
155− },
156− "permalink_url": "http://soundcloud.com/this.is.the.url",
157− "artwork_url": "https://i1.sndcdn.com/artworks-000085857162-xdxy5c-large.jpg",
158− "waveform_url": "https://w1.sndcdn.com/DWrL1lAN8BkP_m.png",
159− "stream_url": "https://api.soundcloud.com/tracks/159723640/stream",
160− "download_url": "https://api.soundcloud.com/tracks/159723640/download",
161− "playback_count": 2190687,
162− "download_count": 54856,
163− "favoritings_count": 49061,
164− "comment_count": 826,
165− "likes_count": 49061,
166− "reposts_count": 15910,
167− "attachments_uri": "https://api.soundcloud.com/tracks/159723640/attachments",
168− "policy": "ALLOW"
169− }
170− ],
171− "total_results": 375750,
172− "next_href": "https://api.soundcloud.com/search?&q=test",
173− "tx_id": ""
174− }
175− """
176− response = mock.Mock(text=json)
177− results = soundcloud.response(response)
178− self.assertEqual(type(results), list)
179− self.assertEqual(len(results), 0)
180−
181− json = """
182− {
183− "collection": [],
184− "total_results": 375750,
185− "next_href": "https://api.soundcloud.com/search?&q=test",
186− "tx_id": ""
187− }
188− """
189− response = mock.Mock(text=json)
190− results = soundcloud.response(response)
191− self.assertEqual(type(results), list)
192− self.assertEqual(len(results), 0)
tests/unit/engines/test_spotify.pydeleted−124
@@ -1,124 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import spotify
4−from searx.testing import SearxTestCase
5−
6−
7−class TestSpotifyEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = spotify.request(query, dicto)
14− self.assertIn('url', params)
15− self.assertIn(query, params['url'])
16− self.assertIn('spotify.com', params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, spotify.response, None)
20− self.assertRaises(AttributeError, spotify.response, [])
21− self.assertRaises(AttributeError, spotify.response, '')
22− self.assertRaises(AttributeError, spotify.response, '[]')
23−
24− response = mock.Mock(text='{}')
25− self.assertEqual(spotify.response(response), [])
26−
27− response = mock.Mock(text='{"data": []}')
28− self.assertEqual(spotify.response(response), [])
29−
30− json = """
31− {
32− "tracks": {
33− "href": "https://api.spotify.com/v1/search?query=nosfell&offset=0&limit=20&type=track",
34− "items": [
35− {
36− "album": {
37− "album_type": "album",
38− "external_urls": {
39− "spotify": "https://open.spotify.com/album/5c9ap1PBkSGLxT3J73toxA"
40− },
41− "href": "https://api.spotify.com/v1/albums/5c9ap1PBkSGLxT3J73toxA",
42− "id": "5c9ap1PBkSGLxT3J73toxA",
43− "name": "Album Title",
44− "type": "album",
45− "uri": "spotify:album:5c9ap1PBkSGLxT3J73toxA"
46− },
47− "artists": [
48− {
49− "external_urls": {
50− "spotify": "https://open.spotify.com/artist/0bMc6b75FfZEpQHG1jifKu"
51− },
52− "href": "https://api.spotify.com/v1/artists/0bMc6b75FfZEpQHG1jifKu",
53− "id": "0bMc6b75FfZEpQHG1jifKu",
54− "name": "Artist Name",
55− "type": "artist",
56− "uri": "spotify:artist:0bMc6b75FfZEpQHG1jifKu"
57− }
58− ],
59− "disc_number": 1,
60− "duration_ms": 202386,
61− "explicit": false,
62− "external_ids": {
63− "isrc": "FRV640600067"
64− },
65− "external_urls": {
66− "spotify": "https://open.spotify.com/track/2GzvFiedqW8hgqUpWcASZa"
67− },
68− "href": "https://api.spotify.com/v1/tracks/2GzvFiedqW8hgqUpWcASZa",
69− "id": "1000",
70− "is_playable": true,
71− "name": "Title of track",
72− "popularity": 6,
73− "preview_url": "https://p.scdn.co/mp3-preview/7b8ecda580965a066b768c2647f877e43f7b1a0a",
74− "track_number": 3,
75− "type": "track",
76− "uri": "spotify:track:2GzvFiedqW8hgqUpWcASZa"
77− }
78− ],
79− "limit": 20,
80− "next": "https://api.spotify.com/v1/search?query=nosfell&offset=20&limit=20&type=track",
81− "offset": 0,
82− "previous": null,
83− "total": 107
84− }
85− }
86− """
87− response = mock.Mock(text=json)
88− results = spotify.response(response)
89− self.assertEqual(type(results), list)
90− self.assertEqual(len(results), 1)
91− self.assertEqual(results[0]['title'], 'Title of track')
92− self.assertEqual(results[0]['url'], 'https://open.spotify.com/track/2GzvFiedqW8hgqUpWcASZa')
93− self.assertEqual(results[0]['content'], 'Artist Name - Album Title - Title of track')
94− self.assertIn('1000', results[0]['embedded'])
95−
96− json = """
97− {
98− "tracks": {
99− "href": "https://api.spotify.com/v1/search?query=nosfell&offset=0&limit=20&type=track",
100− "items": [
101− {
102− "href": "https://api.spotify.com/v1/tracks/2GzvFiedqW8hgqUpWcASZa",
103− "id": "1000",
104− "is_playable": true,
105− "name": "Title of track",
106− "popularity": 6,
107− "preview_url": "https://p.scdn.co/mp3-preview/7b8ecda580965a066b768c2647f877e43f7b1a0a",
108− "track_number": 3,
109− "type": "album",
110− "uri": "spotify:track:2GzvFiedqW8hgqUpWcASZa"
111− }
112− ],
113− "limit": 20,
114− "next": "https://api.spotify.com/v1/search?query=nosfell&offset=20&limit=20&type=track",
115− "offset": 0,
116− "previous": null,
117− "total": 107
118− }
119− }
120− """
121− response = mock.Mock(text=json)
122− results = spotify.response(response)
123− self.assertEqual(type(results), list)
124− self.assertEqual(len(results), 0)
tests/unit/engines/test_stackoverflow.pydeleted−106
@@ -1,106 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import stackoverflow
4−from searx.testing import SearxTestCase
5−
6−
7−class TestStackoverflowEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− params = stackoverflow.request(query, dicto)
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16− self.assertTrue('stackoverflow.com' in params['url'])
17−
18− def test_response(self):
19− self.assertRaises(AttributeError, stackoverflow.response, None)
20− self.assertRaises(AttributeError, stackoverflow.response, [])
21− self.assertRaises(AttributeError, stackoverflow.response, '')
22− self.assertRaises(AttributeError, stackoverflow.response, '[]')
23−
24− response = mock.Mock(text='<html></html>')
25− self.assertEqual(stackoverflow.response(response), [])
26−
27− html = """
28− <div class="question-summary search-result" id="answer-id-1783426">
29− <div class="statscontainer">
30− <div class="statsarrow"></div>
31− <div class="stats">
32− <div class="vote">
33− <div class="votes answered">
34− <span class="vote-count-post "><strong>2583</strong></span>
35− <div class="viewcount">votes</div>
36− </div>
37− </div>
38− </div>
39− </div>
40− <div class="summary">
41− <div class="result-link">
42− <span>
43− <a href="/questions/this.is.the.url"
44− data-searchsession="/questions"
45− title="Checkout remote Git branch">
46− This is the title
47− </a>
48− </span>
49− </div>
50− <div class="excerpt">
51− This is the content
52− </div>
53− <div class="tags user-tags t-git t-git-checkout t-remote-branch">
54− </div>
55− <div class="started fr">
56− answered <span title="2009-11-23 14:26:08Z" class="relativetime">nov 23 '09</span> by
57− <a href="/users/214090/hallski">hallski</a>
58− </div>
59− </div>
60− </div>
61− """
62− response = mock.Mock(text=html)
63− results = stackoverflow.response(response)
64− self.assertEqual(type(results), list)
65− self.assertEqual(len(results), 1)
66− self.assertEqual(results[0]['title'], 'This is the title')
67− self.assertEqual(results[0]['url'], 'https://stackoverflow.com/questions/this.is.the.url')
68− self.assertEqual(results[0]['content'], 'This is the content')
69−
70− html = """
71− <div class="statscontainer">
72− <div class="statsarrow"></div>
73− <div class="stats">
74− <div class="vote">
75− <div class="votes answered">
76− <span class="vote-count-post "><strong>2583</strong></span>
77− <div class="viewcount">votes</div>
78− </div>
79− </div>
80− </div>
81− </div>
82− <div class="summary">
83− <div class="result-link">
84− <span>
85− <a href="/questions/this.is.the.url"
86− data-searchsession="/questions"
87− title="Checkout remote Git branch">
88− This is the title
89− </a>
90− </span>
91− </div>
92− <div class="excerpt">
93− This is the content
94− </div>
95− <div class="tags user-tags t-git t-git-checkout t-remote-branch">
96− </div>
97− <div class="started fr">
98− answered <span title="2009-11-23 14:26:08Z" class="relativetime">nov 23 '09</span> by
99− <a href="/users/214090/hallski">hallski</a>
100− </div>
101− </div>
102− """
103− response = mock.Mock(text=html)
104− results = stackoverflow.response(response)
105− self.assertEqual(type(results), list)
106− self.assertEqual(len(results), 0)
tests/unit/engines/test_startpage.pydeleted−67
@@ -1,67 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import startpage
5−from searx.testing import SearxTestCase
6−
7−
8−class TestStartpageEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 1
14− dicto['language'] = 'fr_FR'
15− params = startpage.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn('startpage.com', params['url'])
18− self.assertIn('data', params)
19− self.assertIn('query', params['data'])
20− self.assertIn(query, params['data']['query'])
21−
22− dicto['language'] = 'all'
23− params = startpage.request(query, dicto)
24−
25− def test_response(self):
26− self.assertRaises(AttributeError, startpage.response, None)
27− self.assertRaises(AttributeError, startpage.response, [])
28− self.assertRaises(AttributeError, startpage.response, '')
29− self.assertRaises(AttributeError, startpage.response, '[]')
30−
31− response = mock.Mock(text='<html></html>')
32− self.assertEqual(startpage.response(response), [])
33−
34− html = """
35−<div class="w-gl__result">
36− <a
37− class="w-gl__result-title"
38− href="http://this.should.be.the.link/"
39− data-onw="1"
40− rel="noopener noreferrer"
41− target="_blank">
42−
43− <h3>This should be the title</h3>
44− </a>
45− <div class="w-gl__result-second-line-container">
46− <div class="w-gl__result-url-container">
47− <a
48− class="w-gl__result-url"
49− href="http://this.should.be.the.link/"
50− rel="noopener noreferrer"
51− target="_blank">https://www.cnbc.com/2019/10/12/dj-zedd-banned-in-china-for-liking-a-south-park-tweet.html</a>
52− </div>
53− <a
54− class="w-gl__anonymous-view-url"
55− href="https://eu-browse.startpage.com/do/proxy?ep=556b554d576b6f5054554546423167764b5445616455554d5342675441774659495246304848774f5267385453304941486b5949546c63704e33774f526b705544565647516d4a61554246304847674f4a556f6957415a4f436b455042426b6b4f7a64535a52784a56514a4f45307743446c567250445a4f4c52514e5677554e46776b4b545563704c7931554c5167465467644f42464d4f4255426f4d693152624634525741305845526c595746636b626d67494e42705743466c515252634f4267456e597a7346596b7856435134465345634f564249794b5752785643315863546769515773764a5163494c5877505246315865456f5141426b4f41774167596d6c5a4e30395758773442465251495677596c624770665a6b786344466b4151455663425249794d6a78525a55554157516f4342556766526b51314b57514e&amp;ek=4q58686o5047786n6343527259445247576p6o38&amp;ekdata=84abd523dc13cba5c65164d04d7d7263"
56− target="_blank">Anonymous View</a>
57− </div>
58− <p class="w-gl__description">This should be the content.</p>
59− </div>
60− """ # noqa
61− response = mock.Mock(text=html.encode('utf-8'))
62− results = startpage.response(response)
63− self.assertEqual(type(results), list)
64− self.assertEqual(len(results), 1)
65− self.assertEqual(results[0]['title'], 'This should be the title')
66− self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
67− self.assertEqual(results[0]['content'], 'This should be the content.')
tests/unit/engines/test_tokyotoshokan.pydeleted−110
@@ -1,110 +0,0 @@
1−import mock
2−from collections import defaultdict
3−from searx.engines import tokyotoshokan
4−from searx.testing import SearxTestCase
5−from datetime import datetime
6−
7−
8−class TestTokyotoshokanEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dic = defaultdict(dict)
13− dic['pageno'] = 1
14− params = tokyotoshokan.request(query, dic)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('tokyotosho.info' in params['url'])
18−
19− def test_response(self):
20− resp = mock.Mock(text='<html></html>')
21− self.assertEqual(tokyotoshokan.response(resp), [])
22−
23− html = """
24− <table class="listing">
25− <tbody>
26− <tr class="shade category_0">
27− <td rowspan="2">
28− <a href="/?cat=7"><span class="sprite_cat-raw"></span></a>
29− </td>
30− <td class="desc-top">
31− <a href="magnet:?xt=urn:btih:4c19eb46b5113685fbd2288ed2531b0b">
32− <span class="sprite_magnet"></span>
33− </a>
34− <a rel="nofollow" type="application/x-bittorrent" href="http://www.nyaa.se/f">
35− Koyomimonogatari
36− </a>
37− </td>
38− <td class="web"><a rel="nofollow" href="details.php?id=975700">Details</a></td>
39− </tr>
40− <tr class="shade category_0">
41− <td class="desc-bot">
42− Authorized: <span class="auth_ok">Yes</span>
43− Submitter: <a href="?username=Ohys">Ohys</a> |
44− Size: 10.5MB |
45− Date: 2016-03-26 16:41 UTC |
46− Comment: sample comment
47− </td>
48− <td style="color: #BBB; font-family: monospace" class="stats" align="right">
49− S: <span style="color: red">53</span>
50− L: <span style="color: red">18</span>
51− C: <span style="color: red">0</span>
52− ID: 975700
53− </td>
54− </tr>
55−
56− <tr class="category_0">
57− <td rowspan="2">
58− <a href="/?cat=7"><span class="sprite_cat-raw"></span></a>
59− </td>
60− <td class="desc-top">
61− <a rel="nofollow" type="application/x-bittorrent" href="http://google.com/q">
62− Owarimonogatari
63− </a>
64− </td>
65− <td class="web"><a rel="nofollow" href="details.php?id=975700">Details</a></td>
66− </tr>
67− <tr class="category_0">
68− <td class="desc-bot">
69− Submitter: <a href="?username=Ohys">Ohys</a> |
70− Size: 932.84EB |
71− Date: QWERTY-03-26 16:41 UTC
72− </td>
73− <td style="color: #BBB; font-family: monospace" class="stats" align="right">
74− S: <span style="color: red">0</span>
75− </td>
76− </tr>
77− </tbody>
78− </table>
79− """
80−
81− resp = mock.Mock(text=html)
82− results = tokyotoshokan.response(resp)
83−
84− self.assertEqual(type(results), list)
85− self.assertEqual(len(results), 2)
86−
87− # testing the first result, which has correct format
88− # and should have all information fields filled
89− r = results[0]
90− self.assertEqual(r['url'], 'http://www.nyaa.se/f')
91− self.assertEqual(r['title'], 'Koyomimonogatari')
92− self.assertEqual(r['magnetlink'], 'magnet:?xt=urn:btih:4c19eb46b5113685fbd2288ed2531b0b')
93− self.assertEqual(r['filesize'], int(1024 * 1024 * 10.5))
94− self.assertEqual(r['publishedDate'], datetime(2016, 3, 26, 16, 41))
95− self.assertEqual(r['content'], 'Comment: sample comment')
96− self.assertEqual(r['seed'], 53)
97− self.assertEqual(r['leech'], 18)
98−
99− # testing the second result, which does not include magnet link,
100− # seed & leech info, and has incorrect size & creation date
101− r = results[1]
102− self.assertEqual(r['url'], 'http://google.com/q')
103− self.assertEqual(r['title'], 'Owarimonogatari')
104−
105− self.assertFalse('magnetlink' in r)
106− self.assertFalse('filesize' in r)
107− self.assertFalse('content' in r)
108− self.assertFalse('publishedDate' in r)
109− self.assertFalse('seed' in r)
110− self.assertFalse('leech' in r)
tests/unit/engines/test_torrentz.pydeleted−87
@@ -1,87 +0,0 @@
1−import mock
2−from collections import defaultdict
3−from searx.engines import torrentz
4−from searx.testing import SearxTestCase
5−from datetime import datetime
6−
7−
8−class TestTorrentzEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dic = defaultdict(dict)
13− dic['pageno'] = 1
14− params = torrentz.request(query, dic)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('torrentz2.eu' in params['url'])
18−
19− def test_response(self):
20− resp = mock.Mock(text='<html></html>')
21− self.assertEqual(torrentz.response(resp), [])
22−
23− html = """
24− <div class="results">
25− <dl>
26− <dt>
27− <a href="/4362e08b1d80e1820fb2550b752f9f3126fe76d6">
28− Completely valid info
29− </a>
30− books ebooks
31− </dt>
32− <dd>
33− <span>1</span>
34− <span title="1503595924">5 hours</span>
35− <span>30 MB</span>
36− <span>14</span>
37− <span>1</span>
38− </dd>
39− </dl>
40−
41− <dl>
42− <dt>
43− <a href="/poaskdpokaspod">
44− Invalid hash and date and filesize
45− </a>
46− books ebooks
47− </dt>
48− <dd>
49− <span>1</span>
50− <span title="1503595924 aaa">5 hours</span>
51− <span>30MB</span>
52− <span>5,555</span>
53− <span>1,234,567</span>
54− </dd>
55− </dl>
56− </div>
57− """
58−
59− resp = mock.Mock(text=html)
60− results = torrentz.response(resp)
61−
62− self.assertEqual(type(results), list)
63− self.assertEqual(len(results), 2)
64−
65− # testing against the first result
66− r = results[0]
67− self.assertEqual(r['url'], 'https://torrentz2.eu/4362e08b1d80e1820fb2550b752f9f3126fe76d6')
68− self.assertEqual(r['title'], 'Completely valid info books ebooks')
69− # 22 Nov 2015 03:01:42
70− self.assertEqual(r['publishedDate'], datetime.fromtimestamp(1503595924))
71− self.assertEqual(r['seed'], 14)
72− self.assertEqual(r['leech'], 1)
73− self.assertEqual(r['filesize'], 30 * 1024 * 1024)
74− self.assertEqual(r['magnetlink'], 'magnet:?xt=urn:btih:4362e08b1d80e1820fb2550b752f9f3126fe76d6')
75−
76− # testing against the second result
77− r = results[1]
78− self.assertEqual(r['url'], 'https://torrentz2.eu/poaskdpokaspod')
79− self.assertEqual(r['title'], 'Invalid hash and date and filesize books ebooks')
80− self.assertEqual(r['seed'], 5555)
81− self.assertEqual(r['leech'], 1234567)
82−
83− # in the second result we have invalid hash, creation date & torrent size,
84− # so these tests should fail
85− self.assertFalse('magnetlink' in r)
86− self.assertFalse('filesize' in r)
87− self.assertFalse('publishedDate' in r)
tests/unit/engines/test_twitter.pydeleted−502
@@ -1,502 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import twitter
5−from searx.testing import SearxTestCase
6−
7−
8−class TestTwitterEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 0
14− dicto['language'] = 'fr_FR'
15− params = twitter.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18− self.assertIn('twitter.com', params['url'])
19− self.assertIn('cookies', params)
20− self.assertIn('lang', params['cookies'])
21− self.assertIn('fr', params['cookies']['lang'])
22−
23− dicto['language'] = 'all'
24− params = twitter.request(query, dicto)
25− self.assertIn('cookies', params)
26− self.assertIn('lang', params['cookies'])
27− self.assertIn('en', params['cookies']['lang'])
28−
29− def test_response(self):
30− self.assertRaises(AttributeError, twitter.response, None)
31− self.assertRaises(AttributeError, twitter.response, [])
32− self.assertRaises(AttributeError, twitter.response, '')
33− self.assertRaises(AttributeError, twitter.response, '[]')
34−
35− response = mock.Mock(text='<html></html>')
36− self.assertEqual(twitter.response(response), [])
37−
38− html = """
39− <li class="js-stream-item stream-item stream-item expanding-stream-item" data-item-id="563005573290287105"
40− id="stream-item-tweet-563005573290287105" data-item-type="tweet">
41− <div class="tweet original-tweet js-stream-tweet js-actionable-tweet js-profile-popup-actionable
42− js-original-tweet has-cards has-native-media" data-tweet-id="563005573290287105" data-disclosure-type=""
43− data-item-id="563005573290287105" data-screen-name="Jalopnik" data-name="Jalopnik"
44− data-user-id="3060631" data-has-native-media="true" data-has-cards="true" data-card-type="photo"
45− data-expanded-footer="&lt;div class=&quot;js-tweet-details-fixer
46− tweet-details-fixer&quot;&gt;&#10;&#10;&#10;
47− &lt;div class=&quot;cards-media-container js-media-container&quot;&gt;&lt;div
48− data-card-url=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot; data-card-type=&quot;
49− photo&quot; class=&quot;cards-base cards-multimedia&quot; data-element-context=&quot;platform_photo_card
50− &quot;&gt;&#10;&#10;&#10; &lt;a class=&quot;media media-thumbnail twitter-timeline-link is-preview
51− &quot; data-url=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
52− data-resolved-url-large=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
53− href=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot;&gt;&#10;
54− &lt;div class=&quot;&quot;&gt;&#10; &lt;img src=&quot;
55− https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg&quot;
56− alt=&quot;Embedded image permalink&quot; width=&quot;636&quot; height=&quot;309&quot;&gt;&#10;
57− &lt;/div&gt;&#10;&#10; &lt;/a&gt;&#10;&#10; &lt;div class=&quot;cards-content&quot;&gt;&#10;
58− &lt;div class=&quot;byline&quot;&gt;&#10; &#10; &lt;/div&gt;&#10; &#10; &lt;/div&gt;&#10;
59− &#10;&lt;/div&gt;&#10;&#10;&#10;&#10;&#10;&lt;/div&gt;&#10;&#10;&#10;&#10; &lt;div
60− class=&quot;js-machine-translated-tweet-container&quot;&gt;&lt;/div&gt;&#10; &lt;div
61− class=&quot;js-tweet-stats-container tweet-stats-container &quot;&gt;&#10; &lt;/div&gt;&#10;&#10;
62− &lt;div class=&quot;client-and-actions&quot;&gt;&#10; &lt;span class=&quot;metadata&quot;&gt;&#10;
63− &lt;span&gt;5:06 PM - 4 Feb 2015&lt;/span&gt;&#10;&#10; &amp;middot; &lt;a
64− class=&quot;permalink-link js-permalink js-nav&quot; href=&quot;/Jalopnik/status/563005573290287105
65− &quot;tabindex=&quot;-1&quot;&gt;Details&lt;/a&gt;&#10; &#10;&#10; &#10; &#10;
66− &#10;&#10; &lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;/div&gt;&#10;" data-you-follow="false"
67− data-you-block="false">
68− <div class="context">
69− </div>
70− <div class="content">
71− <div class="stream-item-header">
72− <a class="account-group js-account-group js-action-profile js-user-profile-link js-nav"
73− href="/Jalopnik" data-user-id="3060631">
74− <img class="avatar js-action-profile-avatar"
75− src="https://pbs.twimg.com/profile_images/2976430168/5cd4a59_bigger.jpeg" alt="">
76− <strong class="fullname js-action-profile-name show-popup-with-id" data-aria-label-part>
77− Jalopnik
78− </strong>
79− <span>&rlm;</span>
80− <span class="username js-action-profile-name" data-aria-label-part>
81− <s>@</s><b>TitleName</b>
82− </span>
83− </a>
84− <small class="time">
85− <a href="/this.is.the.url"
86− class="tweet-timestamp js-permalink js-nav js-tooltip" title="5:06 PM - 4 Feb 2015" >
87− <span class="u-hiddenVisually" data-aria-label-part="last">17 minutes ago</span>
88− </a>
89− </small>
90− </div>
91− <p class="js-tweet-text tweet-text" lang="en" data-aria-label-part="0">
92− This is the content étude à€
93− <a href="http://t.co/nRWsqQAwBL" rel="nofollow" dir="ltr"
94− data-expanded-url="http://jalo.ps/ReMENu4" class="twitter-timeline-link"
95− target="_blank" title="http://jalo.ps/ReMENu4" >
96− <span class="tco-ellipsis">
97− </span>
98− <span class="invisible">http://</span><span class="js-display-url">link.in.tweet</span>
99− <span class="invisible"></span>
100− <span class="tco-ellipsis">
101− <span class="invisible">&nbsp;</span>
102− </span>
103− </a>
104− <a href="http://t.co/rbFsfeE0l3" class="twitter-timeline-link u-hidden"
105− data-pre-embedded="true" dir="ltr">
106− pic.twitter.com/rbFsfeE0l3
107− </a>
108− </p>
109− <div class="expanded-content js-tweet-details-dropdown">
110− </div>
111− <div class="stream-item-footer">
112− <a class="details with-icn js-details" href="/Jalopnik/status/563005573290287105">
113− <span class="Icon Icon--photo">
114− </span>
115− <b>
116− <span class="expand-stream-item js-view-details">
117− View photo
118− </span>
119− <span class="collapse-stream-item js-hide-details">
120− Hide photo
121− </span>
122− </b>
123− </a>
124− <span class="ProfileTweet-action--reply u-hiddenVisually">
125− <span class="ProfileTweet-actionCount" aria-hidden="true" data-tweet-stat-count="0">
126− <span class="ProfileTweet-actionCountForAria" >0 replies</span>
127− </span>
128− </span>
129− <span class="ProfileTweet-action--retweet u-hiddenVisually">
130− <span class="ProfileTweet-actionCount" data-tweet-stat-count="8">
131− <span class="ProfileTweet-actionCountForAria" data-aria-label-part>8 retweets</span>
132− </span>
133− </span>
134− <span class="ProfileTweet-action--favorite u-hiddenVisually">
135− <span class="ProfileTweet-actionCount" data-tweet-stat-count="14">
136− <span class="ProfileTweet-actionCountForAria" data-aria-label-part>14 favorites</span>
137− </span>
138− </span>
139− <div role="group" aria-label="Tweet actions" class="ProfileTweet-actionList u-cf js-actions">
140− <div class="ProfileTweet-action ProfileTweet-action--reply">
141− <button class="ProfileTweet-actionButton u-textUserColorHover js-actionButton
142− js-actionReply" data-modal="ProfileTweet-reply" type="button" title="Reply">
143− <span class="Icon Icon--reply">
144− </span>
145− <span class="u-hiddenVisually">Reply</span>
146− <span class="ProfileTweet-actionCount u-textUserColorHover
147− ProfileTweet-actionCount--isZero">
148− <span class="ProfileTweet-actionCountForPresentation" aria-hidden="true">
149− </span>
150− </span>
151− </button>
152− </div>
153− <div class="ProfileTweet-action ProfileTweet-action--retweet js-toggleState js-toggleRt">
154− <button class="ProfileTweet-actionButton js-actionButton js-actionRetweet js-tooltip"
155− title="Retweet" data-modal="ProfileTweet-retweet" type="button">
156− <span class="Icon Icon--retweet">
157− </span>
158− <span class="u-hiddenVisually">Retweet</span>
159− <span class="ProfileTweet-actionCount">
160− <span class="ProfileTweet-actionCountForPresentation">8</span>
161− </span>
162− </button>
163− <button class="ProfileTweet-actionButtonUndo js-actionButton js-actionRetweet"
164− data-modal="ProfileTweet-retweet" title="Undo retweet" type="button">
165− <span class="Icon Icon--retweet">
166− </span>
167− <span class="u-hiddenVisually">Retweeted</span>
168− <span class="ProfileTweet-actionCount">
169− <span class="ProfileTweet-actionCountForPresentation">8</span>
170− </span>
171− </button>
172− </div>
173− <div class="ProfileTweet-action ProfileTweet-action--favorite js-toggleState">
174− <button class="ProfileTweet-actionButton js-actionButton js-actionFavorite js-tooltip"
175− title="Favorite" type="button">
176− <span class="Icon Icon--favorite">
177− </span>
178− <span class="u-hiddenVisually">Favorite</span>
179− <span class="ProfileTweet-actionCount">
180− <span class="ProfileTweet-actionCountForPresentation">14</span>
181− </span>
182− </button>
183− <button class="ProfileTweet-actionButtonUndo u-linkClean js-actionButton
184− js-actionFavorite" title="Undo favorite" type="button">
185− <span class="Icon Icon--favorite">
186− </span>
187− <span class="u-hiddenVisually">Favorited</span>
188− <span class="ProfileTweet-actionCount">
189− <span class="ProfileTweet-actionCountForPresentation">
190− 14
191− </span>
192− </span>
193− </button>
194− </div>
195− <div class="ProfileTweet-action ProfileTweet-action--more js-more-ProfileTweet-actions">
196− <div class="dropdown">
197− <button class="ProfileTweet-actionButton u-textUserColorHover dropdown-toggle
198− js-tooltip js-dropdown-toggle" type="button" title="More">
199− <span class="Icon Icon--dots">
200− </span>
201− <span class="u-hiddenVisually">More</span>
202− </button>
203− <div class="dropdown-menu">
204− <div class="dropdown-caret">
205− <div class="caret-outer">
206− </div>
207− <div class="caret-inner">
208− </div>
209− </div>
210− <ul>
211− <li class="share-via-dm js-actionShareViaDM" data-nav="share_tweet_dm">
212− <button type="button" class="dropdown-link">
213− Share via Direct Message
214− </button>
215− </li>
216− <li class="embed-link js-actionEmbedTweet" data-nav="embed_tweet">
217− <button type="button" class="dropdown-link">
218− Embed Tweet
219− </button>
220− </li>
221− <li class="mute-user-item pretty-link">
222− <button type="button" class="dropdown-link">
223− Mute
224− </button>
225− </li>
226− <li class="unmute-user-item pretty-link">
227− <button type="button" class="dropdown-link">
228− Unmute
229− </button>
230− </li>
231− <li class="block-or-report-link js-actionBlockOrReport"
232− data-nav="block_or_report">
233− <button type="button" class="dropdown-link">
234− Block or report
235− </button>
236− </li>
237− </ul>
238− </div>
239− </div>
240− </div>
241− </div>
242− </div>
243− </div>
244− </div>
245− </li>
246− """
247− response = mock.Mock(text=html)
248− results = twitter.response(response)
249− self.assertEqual(type(results), list)
250− self.assertEqual(len(results), 1)
251− self.assertEqual(results[0]['title'], '@TitleName')
252− self.assertEqual(results[0]['url'], 'https://twitter.com/this.is.the.url')
253− self.assertIn(u'This is the content', results[0]['content'])
254− # self.assertIn(u'This is the content étude à€', results[0]['content'])
255−
256− html = """
257− <li class="js-stream-item stream-item stream-item expanding-stream-item" data-item-id="563005573290287105"
258− id="stream-item-tweet-563005573290287105" data-item-type="tweet">
259− <div class="tweet original-tweet js-stream-tweet js-actionable-tweet js-profile-popup-actionable
260− js-original-tweet has-cards has-native-media" data-tweet-id="563005573290287105" data-disclosure-type=""
261− data-item-id="563005573290287105" data-screen-name="Jalopnik" data-name="Jalopnik"
262− data-user-id="3060631" data-has-native-media="true" data-has-cards="true" data-card-type="photo"
263− data-expanded-footer="&lt;div class=&quot;js-tweet-details-fixer
264− tweet-details-fixer&quot;&gt;&#10;&#10;&#10;
265− &lt;div class=&quot;cards-media-container js-media-container&quot;&gt;&lt;div
266− data-card-url=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot; data-card-type=&quot;
267− photo&quot; class=&quot;cards-base cards-multimedia&quot; data-element-context=&quot;platform_photo_card
268− &quot;&gt;&#10;&#10;&#10; &lt;a class=&quot;media media-thumbnail twitter-timeline-link is-preview
269− &quot; data-url=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
270− data-resolved-url-large=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
271− href=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot;&gt;&#10;
272− &lt;div class=&quot;&quot;&gt;&#10; &lt;img src=&quot;
273− https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg&quot;
274− alt=&quot;Embedded image permalink&quot; width=&quot;636&quot; height=&quot;309&quot;&gt;&#10;
275− &lt;/div&gt;&#10;&#10; &lt;/a&gt;&#10;&#10; &lt;div class=&quot;cards-content&quot;&gt;&#10;
276− &lt;div class=&quot;byline&quot;&gt;&#10; &#10; &lt;/div&gt;&#10; &#10; &lt;/div&gt;&#10;
277− &#10;&lt;/div&gt;&#10;&#10;&#10;&#10;&#10;&lt;/div&gt;&#10;&#10;&#10;&#10; &lt;div
278− class=&quot;js-machine-translated-tweet-container&quot;&gt;&lt;/div&gt;&#10; &lt;div
279− class=&quot;js-tweet-stats-container tweet-stats-container &quot;&gt;&#10; &lt;/div&gt;&#10;&#10;
280− &lt;div class=&quot;client-and-actions&quot;&gt;&#10; &lt;span class=&quot;metadata&quot;&gt;&#10;
281− &lt;span&gt;5:06 PM - 4 Feb 2015&lt;/span&gt;&#10;&#10; &amp;middot; &lt;a
282− class=&quot;permalink-link js-permalink js-nav&quot; href=&quot;/Jalopnik/status/563005573290287105
283− &quot;tabindex=&quot;-1&quot;&gt;Details&lt;/a&gt;&#10; &#10;&#10; &#10; &#10;
284− &#10;&#10; &lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;/div&gt;&#10;" data-you-follow="false"
285− data-you-block="false">
286− <div class="context">
287− </div>
288− <div class="content">
289− <div class="stream-item-header">
290− <a class="account-group js-account-group js-action-profile js-user-profile-link js-nav"
291− href="/Jalopnik" data-user-id="3060631">
292− <img class="avatar js-action-profile-avatar"
293− src="https://pbs.twimg.com/profile_images/2976430168/5cd4a59_bigger.jpeg" alt="">
294− <strong class="fullname js-action-profile-name show-popup-with-id" data-aria-label-part>
295− Jalopnik
296− </strong>
297− <span>&rlm;</span>
298− <span class="username js-action-profile-name" data-aria-label-part>
299− <s>@</s><b>TitleName</b>
300− </span>
301− </a>
302− <small class="time">
303− <a href="/this.is.the.url"
304− class="tweet-timestamp js-permalink js-nav js-tooltip" title="5:06 PM - 4 Feb 2015" >
305− <span class="_timestamp js-short-timestamp js-relative-timestamp" data-time="1423065963"
306− data-time-ms="1423065963000" data-long-form="true" aria-hidden="true">
307− 17m
308− </span>
309− <span class="u-hiddenVisually" data-aria-label-part="last">17 minutes ago</span>
310− </a>
311− </small>
312− </div>
313− <p class="js-tweet-text tweet-text" lang="en" data-aria-label-part="0">
314− This is the content étude à€
315− <a href="http://t.co/nRWsqQAwBL" rel="nofollow" dir="ltr"
316− data-expanded-url="http://jalo.ps/ReMENu4" class="twitter-timeline-link"
317− target="_blank" title="http://jalo.ps/ReMENu4" >
318− <span class="tco-ellipsis">
319− </span>
320− <span class="invisible">http://</span><span class="js-display-url">link.in.tweet</span>
321− <span class="invisible"></span>
322− <span class="tco-ellipsis">
323− <span class="invisible">&nbsp;</span>
324− </span>
325− </a>
326− <a href="http://t.co/rbFsfeE0l3" class="twitter-timeline-link u-hidden"
327− data-pre-embedded="true" dir="ltr">
328− pic.twitter.com/rbFsfeE0l3
329− </a>
330− </p>
331− <div class="expanded-content js-tweet-details-dropdown">
332− </div>
333− <div class="stream-item-footer">
334− <a class="details with-icn js-details" href="/Jalopnik/status/563005573290287105">
335− <span class="Icon Icon--photo">
336− </span>
337− <b>
338− <span class="expand-stream-item js-view-details">
339− View photo
340− </span>
341− <span class="collapse-stream-item js-hide-details">
342− Hide photo
343− </span>
344− </b>
345− </a>
346− <span class="ProfileTweet-action--reply u-hiddenVisually">
347− <span class="ProfileTweet-actionCount" aria-hidden="true" data-tweet-stat-count="0">
348− <span class="ProfileTweet-actionCountForAria" >0 replies</span>
349− </span>
350− </span>
351− <span class="ProfileTweet-action--retweet u-hiddenVisually">
352− <span class="ProfileTweet-actionCount" data-tweet-stat-count="8">
353− <span class="ProfileTweet-actionCountForAria" data-aria-label-part>8 retweets</span>
354− </span>
355− </span>
356− <span class="ProfileTweet-action--favorite u-hiddenVisually">
357− <span class="ProfileTweet-actionCount" data-tweet-stat-count="14">
358− <span class="ProfileTweet-actionCountForAria" data-aria-label-part>14 favorites</span>
359− </span>
360− </span>
361− <div role="group" aria-label="Tweet actions" class="ProfileTweet-actionList u-cf js-actions">
362− <div class="ProfileTweet-action ProfileTweet-action--reply">
363− <button class="ProfileTweet-actionButton u-textUserColorHover js-actionButton
364− js-actionReply" data-modal="ProfileTweet-reply" type="button" title="Reply">
365− <span class="Icon Icon--reply">
366− </span>
367− <span class="u-hiddenVisually">Reply</span>
368− <span class="ProfileTweet-actionCount u-textUserColorHover
369− ProfileTweet-actionCount--isZero">
370− <span class="ProfileTweet-actionCountForPresentation" aria-hidden="true">
371− </span>
372− </span>
373− </button>
374− </div>
375− <div class="ProfileTweet-action ProfileTweet-action--retweet js-toggleState js-toggleRt">
376− <button class="ProfileTweet-actionButton js-actionButton js-actionRetweet js-tooltip"
377− title="Retweet" data-modal="ProfileTweet-retweet" type="button">
378− <span class="Icon Icon--retweet">
379− </span>
380− <span class="u-hiddenVisually">Retweet</span>
381− <span class="ProfileTweet-actionCount">
382− <span class="ProfileTweet-actionCountForPresentation">8</span>
383− </span>
384− </button>
385− <button class="ProfileTweet-actionButtonUndo js-actionButton js-actionRetweet"
386− data-modal="ProfileTweet-retweet" title="Undo retweet" type="button">
387− <span class="Icon Icon--retweet">
388− </span>
389− <span class="u-hiddenVisually">Retweeted</span>
390− <span class="ProfileTweet-actionCount">
391− <span class="ProfileTweet-actionCountForPresentation">8</span>
392− </span>
393− </button>
394− </div>
395− <div class="ProfileTweet-action ProfileTweet-action--favorite js-toggleState">
396− <button class="ProfileTweet-actionButton js-actionButton js-actionFavorite js-tooltip"
397− title="Favorite" type="button">
398− <span class="Icon Icon--favorite">
399− </span>
400− <span class="u-hiddenVisually">Favorite</span>
401− <span class="ProfileTweet-actionCount">
402− <span class="ProfileTweet-actionCountForPresentation">14</span>
403− </span>
404− </button>
405− <button class="ProfileTweet-actionButtonUndo u-linkClean js-actionButton
406− js-actionFavorite" title="Undo favorite" type="button">
407− <span class="Icon Icon--favorite">
408− </span>
409− <span class="u-hiddenVisually">Favorited</span>
410− <span class="ProfileTweet-actionCount">
411− <span class="ProfileTweet-actionCountForPresentation">
412− 14
413− </span>
414− </span>
415− </button>
416− </div>
417− <div class="ProfileTweet-action ProfileTweet-action--more js-more-ProfileTweet-actions">
418− <div class="dropdown">
419− <button class="ProfileTweet-actionButton u-textUserColorHover dropdown-toggle
420− js-tooltip js-dropdown-toggle" type="button" title="More">
421− <span class="Icon Icon--dots">
422− </span>
423− <span class="u-hiddenVisually">More</span>
424− </button>
425− <div class="dropdown-menu">
426− <div class="dropdown-caret">
427− <div class="caret-outer">
428− </div>
429− <div class="caret-inner">
430− </div>
431− </div>
432− <ul>
433− <li class="share-via-dm js-actionShareViaDM" data-nav="share_tweet_dm">
434− <button type="button" class="dropdown-link">
435− Share via Direct Message
436− </button>
437− </li>
438− <li class="embed-link js-actionEmbedTweet" data-nav="embed_tweet">
439− <button type="button" class="dropdown-link">
440− Embed Tweet
441− </button>
442− </li>
443− <li class="mute-user-item pretty-link">
444− <button type="button" class="dropdown-link">
445− Mute
446− </button>
447− </li>
448− <li class="unmute-user-item pretty-link">
449− <button type="button" class="dropdown-link">
450− Unmute
451− </button>
452− </li>
453− <li class="block-or-report-link js-actionBlockOrReport"
454− data-nav="block_or_report">
455− <button type="button" class="dropdown-link">
456− Block or report
457− </button>
458− </li>
459− </ul>
460− </div>
461− </div>
462− </div>
463− </div>
464− </div>
465− </div>
466− </div>
467− </li>
468− """
469− response = mock.Mock(text=html)
470− results = twitter.response(response)
471− self.assertEqual(type(results), list)
472− self.assertEqual(len(results), 1)
473− self.assertEqual(results[0]['title'], '@TitleName')
474− self.assertEqual(results[0]['url'], 'https://twitter.com/this.is.the.url')
475− self.assertIn(u'This is the content', results[0]['content'])
476−
477− html = """
478− <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
479− <div Class="sa_mc">
480− <div class="sb_tlst">
481− <h2>
482− <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
483− <strong>This</strong> should be the title</a>
484− </h2>
485− </div>
486− <div class="sb_meta">
487− <cite>
488− <strong>this</strong>.meta.com</cite>
489− <span class="c_tlbxTrg">
490− <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
491− </span>
492− </span>
493− </div>
494− <p>
495− <strong>This</strong> should be the content.</p>
496− </div>
497− </li>
498− """
499− response = mock.Mock(text=html)
500− results = twitter.response(response)
501− self.assertEqual(type(results), list)
502− self.assertEqual(len(results), 0)
tests/unit/engines/test_unsplash.pydeleted−38
@@ -1,38 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.testing import SearxTestCase
4−from searx.engines import unsplash
5−
6−
7−class TestUnsplashEngine(SearxTestCase):
8− def test_request(self):
9− query = 'penguin'
10− _dict = defaultdict(dict)
11− _dict['pageno'] = 1
12− params = unsplash.request(query, _dict)
13−
14− self.assertTrue('url' in params)
15− self.assertTrue(query in params['url'])
16−
17− def test_response(self):
18− resp = mock.Mock(text='{}')
19− result = unsplash.response(resp)
20− self.assertEqual([], result)
21−
22− resp.text = '{"results": []}'
23− result = unsplash.response(resp)
24− self.assertEqual([], result)
25−
26− # Sourced from https://unsplash.com/napi/search/photos?query=penguin&xp=&per_page=20&page=2
27− with open('./tests/unit/engines/unsplash_fixture.json') as fixture:
28− resp.text = fixture.read()
29−
30− result = unsplash.response(resp)
31− self.assertEqual(len(result), 2)
32− self.assertEqual(result[0]['title'], 'low angle photography of swimming penguin')
33− self.assertEqual(result[0]['url'], 'https://unsplash.com/photos/FY8d721UO_4')
34− self.assertEqual(result[0]['thumbnail_src'], 'https://images.unsplash.com/photo-1523557148507-1b77641c7e7c?ixlib=rb-0.3.5&q=80\
35−&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max')
36− self.assertEqual(result[0]['img_src'], 'https://images.unsplash.com/photo-1523557148507-1b77641c7e7c\
37−?ixlib=rb-0.3.5')
38− self.assertEqual(result[0]['content'], '')
tests/unit/engines/test_vimeo.pydeleted−36
@@ -1,36 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import vimeo
5−from searx.testing import SearxTestCase
6−
7−
8−class TestVimeoEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 0
14− params = vimeo.request(query, dicto)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertTrue('vimeo.com' in params['url'])
18−
19− def test_response(self):
20− self.assertRaises(AttributeError, vimeo.response, None)
21− self.assertRaises(AttributeError, vimeo.response, [])
22− self.assertRaises(AttributeError, vimeo.response, '')
23− self.assertRaises(AttributeError, vimeo.response, '[]')
24−
25− json = u"""
26−{"filtered":{"total":274641,"page":1,"per_page":18,"paging":{"next":"?sizes=590x332&page=2","previous":null,"first":"?sizes=590x332&page=1","last":"?sizes=590x332&page=15258"},"data":[{"is_staffpick":false,"is_featured":true,"type":"clip","clip":{"uri":"\\/videos\\/106557563","name":"Hot Rod Revue: The South","link":"https:\\/\\/vimeo.com\\/106557563","duration":4069,"created_time":"2014-09-19T03:38:04+00:00","privacy":{"view":"ptv"},"pictures":{"sizes":[{"width":"590","height":"332","link":"https:\\/\\/i.vimeocdn.com\\/video\\/489717884_590x332.jpg?r=pad","link_with_play_button":"https:\\/\\/i.vimeocdn.com\\/filter\\/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F489717884_590x332.jpg&src1=http%3A%2F%2Ff.vimeocdn.com%2Fp%2Fimages%2Fcrawler_play.png"}]},"stats":{"plays":null},"metadata":{"connections":{"comments":{"total":0},"likes":{"total":5}},"interactions":[]},"user":{"name":"Cal Thorley","link":"https:\\/\\/vimeo.com\\/calthorley","pictures":{"sizes":[{"width":30,"height":30,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_30x30?r=pad"},{"width":75,"height":75,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_75x75?r=pad"},{"width":100,"height":100,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_100x100?r=pad"},{"width":300,"height":300,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_300x300?r=pad"}]}}}}]}};
27−
28−""" # noqa
29− response = mock.Mock(text=json)
30− results = vimeo.response(response)
31− self.assertEqual(type(results), list)
32− self.assertEqual(len(results), 1)
33− self.assertEqual(results[0]['title'], u'Hot Rod Revue: The South')
34− self.assertEqual(results[0]['url'], 'https://vimeo.com/106557563')
35− self.assertEqual(results[0]['content'], '')
36− self.assertEqual(results[0]['thumbnail'], 'https://i.vimeocdn.com/video/489717884_590x332.jpg?r=pad')
tests/unit/engines/test_wikidata.pydeleted−514
@@ -1,514 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from lxml.html import fromstring
3−from lxml import etree
4−from collections import defaultdict
5−import mock
6−from searx.engines import wikidata
7−from searx.testing import SearxTestCase
8−
9−
10−class TestWikidataEngine(SearxTestCase):
11−
12− def test_request(self):
13− query = 'test_query'
14− dicto = defaultdict(dict)
15− dicto['language'] = 'all'
16− params = wikidata.request(query, dicto)
17− self.assertIn('url', params)
18− self.assertIn(query, params['url'])
19− self.assertIn('wikidata.org', params['url'])
20−
21− dicto['language'] = 'es_ES'
22− params = wikidata.request(query, dicto)
23− self.assertIn(query, params['url'])
24−
25− # successful cases are not tested here to avoid sending additional requests
26− def test_response(self):
27− self.assertRaises(AttributeError, wikidata.response, None)
28− self.assertRaises(AttributeError, wikidata.response, [])
29− self.assertRaises(AttributeError, wikidata.response, '')
30− self.assertRaises(AttributeError, wikidata.response, '[]')
31−
32− wikidata.supported_languages = ['en', 'es']
33− wikidata.language_aliases = {}
34− response = mock.Mock(content='<html></html>'.encode("utf-8"), search_params={"language": "en"})
35− self.assertEqual(wikidata.response(response), [])
36−
37− def test_getDetail(self):
38− response = {}
39− results = wikidata.getDetail(response, "Q123", "en", "en-US", etree.HTMLParser())
40− self.assertEqual(results, [])
41−
42− title_html = '<div><div class="wikibase-title-label">Test</div></div>'
43− html = """
44− <div>
45− <div class="wikibase-entitytermsview-heading-description">
46− </div>
47− <div>
48− <ul class="wikibase-sitelinklistview-listview">
49− <li data-wb-siteid="enwiki"><a href="http://en.wikipedia.org/wiki/Test">Test</a></li>
50− </ul>
51− </div>
52− </div>
53− """
54− response = {"parse": {"displaytitle": title_html, "text": html}}
55−
56− results = wikidata.getDetail(response, "Q123", "en", "en-US", etree.HTMLParser())
57− self.assertEqual(len(results), 1)
58− self.assertEqual(results[0]['url'], 'https://en.wikipedia.org/wiki/Test')
59−
60− title_html = """
61− <div>
62− <div class="wikibase-title-label">
63− <span lang="en">Test</span>
64− <sup class="wb-language-fallback-indicator">English</sup>
65− </div>
66− </div>
67− """
68− html = """
69− <div>
70− <div class="wikibase-entitytermsview-heading-description">
71− <span lang="en">Description</span>
72− <sup class="wb-language-fallback-indicator">English</sup>
73− </div>
74− <div id="P856">
75− <div class="wikibase-statementgroupview-property-label">
76− <a href="/wiki/Property:P856">
77− <span lang="en">official website</span>
78− <sup class="wb-language-fallback-indicator">English</sup>
79− </a>
80− </div>
81− <div class="wikibase-statementview-mainsnak">
82− <a class="external free" href="https://officialsite.com">
83− https://officialsite.com
84− </a>
85− </div>
86− </div>
87− <div>
88− <ul class="wikibase-sitelinklistview-listview">
89− <li data-wb-siteid="enwiki"><a href="http://en.wikipedia.org/wiki/Test">Test</a></li>
90− </ul>
91− </div>
92− </div>
93− """
94− response = {"parse": {"displaytitle": title_html, "text": html}}
95−
96− results = wikidata.getDetail(response, "Q123", "yua", "yua_MX", etree.HTMLParser())
97− self.assertEqual(len(results), 2)
98− self.assertEqual(results[0]['title'], 'Official website')
99− self.assertEqual(results[0]['url'], 'https://officialsite.com')
100−
101− self.assertEqual(results[1]['infobox'], 'Test')
102− self.assertEqual(results[1]['id'], None)
103− self.assertEqual(results[1]['content'], 'Description')
104− self.assertEqual(results[1]['attributes'], [])
105− self.assertEqual(results[1]['urls'][0]['title'], 'Official website')
106− self.assertEqual(results[1]['urls'][0]['url'], 'https://officialsite.com')
107− self.assertEqual(results[1]['urls'][1]['title'], 'Wikipedia (en)')
108− self.assertEqual(results[1]['urls'][1]['url'], 'https://en.wikipedia.org/wiki/Test')
109−
110− def test_add_image(self):
111− image_src = wikidata.add_image(fromstring("<div></div>"))
112− self.assertEqual(image_src, None)
113−
114− html = u"""
115− <div>
116− <div id="P18">
117− <div class="wikibase-statementgroupview-property-label">
118− <a href="/wiki/Property:P18">
119− image
120− </a>
121− </div>
122− <div class="wikibase-statementlistview">
123− <div class="wikibase-statementview listview-item">
124− <div class="wikibase-statementview-rankselector">
125− <span class="wikibase-rankselector-normal"></span>
126− </div>
127− <div class="wikibase-statementview-mainsnak">
128− <div>
129− <div class="wikibase-snakview-value">
130− <div class="commons-media-caption">
131− <a href="https://commons.wikimedia.org/wiki/File:image.png">image.png</a>
132− <br/>2,687 &#215; 3,356; 1.22 MB
133− </div>
134− </div>
135− </div>
136− </div>
137− </div>
138− </div>
139− </div>
140− </div>
141− """
142− html_etree = fromstring(html)
143− id_cache = wikidata.get_id_cache(html_etree)
144− image_src = wikidata.add_image(id_cache)
145− self.assertEqual(image_src,
146− "https://commons.wikimedia.org/wiki/Special:FilePath/image.png?width=500&height=400")
147−
148− html = u"""
149− <div>
150− <div id="P2910">
151− <div class="wikibase-statementgroupview-property-label">
152− <a href="/wiki/Property:P2910">
153− icon
154− </a>
155− </div>
156− <div class="wikibase-statementlistview">
157− <div class="wikibase-statementview listview-item">
158− <div class="wikibase-statementview-rankselector">
159− <span class="wikibase-rankselector-normal"></span>
160− </div>
161− <div class="wikibase-statementview-mainsnak">
162− <div>
163− <div class="wikibase-snakview-value">
164− <div class="commons-media-caption">
165− <a href="https://commons.wikimedia.org/wiki/File:icon.png">icon.png</a>
166− <br/>671 &#215; 671; 18 KB</div>
167− </div>
168− </div>
169− </div>
170− </div>
171− </div>
172− </div>
173− </div>
174− <div id="P154">
175− <div class="wikibase-statementgroupview-property-label">
176− <a href="/wiki/Property:P154">
177− logo
178− </a>
179− </div>
180− <div class="wikibase-statementlistview">
181− <div class="wikibase-statementview listview-item">
182− <div class="wikibase-statementview-rankselector">
183− <span class="wikibase-rankselector-normal"></span>
184− </div>
185− <div class="wikibase-statementview-mainsnak">
186− <div>
187− <div class="wikibase-snakview-value">
188− <div class="commons-media-caption">
189− <a href="https://commons.wikimedia.org/wiki/File:logo.png">logo.png</a>
190− <br/>170 &#215; 170; 1 KB
191− </div>
192− </div>
193− </div>
194− </div>
195− </div>
196− </div>
197− </div>
198− </div>
199− """
200− html_etree = fromstring(html)
201− id_cache = wikidata.get_id_cache(html_etree)
202−
203− image_src = wikidata.add_image(id_cache)
204− self.assertEqual(image_src,
205− "https://commons.wikimedia.org/wiki/Special:FilePath/logo.png?width=500&height=400")
206−
207− def test_add_attribute(self):
208− html = u"""
209− <div>
210− <div id="P27">
211− <div class="wikibase-statementgroupview-property-label">
212− <a href="/wiki/Property:P27">
213− country of citizenship
214− </a>
215− </div>
216− <div class="wikibase-statementlistview">
217− <div class="wikibase-statementview listview-item">
218− <div class="wikibase-statementview-rankselector">
219− <span class="wikibase-rankselector-normal"></span>
220− </div>
221− <div class="wikibase-statementview-mainsnak">
222− <div>
223− <div class="wikibase-snakview-value">
224− <a href="/wiki/Q145">
225− United Kingdom
226− </a>
227− </div>
228− </div>
229− </div>
230− </div>
231− </div>
232− </div>
233− </div>
234− """
235− attributes = []
236− html_etree = fromstring(html)
237− id_cache = wikidata.get_id_cache(html_etree)
238−
239− wikidata.add_attribute(attributes, id_cache, "Fail")
240− self.assertEqual(attributes, [])
241−
242− wikidata.add_attribute(attributes, id_cache, "P27")
243− self.assertEqual(len(attributes), 1)
244− self.assertEqual(attributes[0]["label"], "Country of citizenship")
245− self.assertEqual(attributes[0]["value"], "United Kingdom")
246−
247− html = u"""
248− <div>
249− <div id="P569">
250− <div class="wikibase-statementgroupview-property-label">
251− <a href="/wiki/Property:P569">
252− date of birth
253− </a>
254− </div>
255− <div class="wikibase-statementlistview">
256− <div class="wikibase-statementview listview-item">
257− <div class="wikibase-statementview-rankselector">
258− <span class="wikibase-rankselector-normal"></span>
259− </div>
260− <div class="wikibase-statementview-mainsnak">
261− <div>
262− <div class="wikibase-snakview-value">
263− 27 January 1832
264− <sup class="wb-calendar-name">
265− Gregorian
266− </sup>
267− </div>
268− </div>
269− </div>
270− </div>
271− </div>
272− </div>
273− </div>
274− """
275− attributes = []
276− html_etree = fromstring(html)
277− id_cache = wikidata.get_id_cache(html_etree)
278− wikidata.add_attribute(attributes, id_cache, "P569", date=True)
279− self.assertEqual(len(attributes), 1)
280− self.assertEqual(attributes[0]["label"], "Date of birth")
281− self.assertEqual(attributes[0]["value"], "27 January 1832")
282−
283− html = u"""
284− <div>
285− <div id="P6">
286− <div class="wikibase-statementgroupview-property-label">
287− <a href="/wiki/Property:P27">
288− head of government
289− </a>
290− </div>
291− <div class="wikibase-statementlistview">
292− <div class="wikibase-statementview listview-item">
293− <div class="wikibase-statementview-rankselector">
294− <span class="wikibase-rankselector-normal"></span>
295− </div>
296− <div class="wikibase-statementview-mainsnak">
297− <div>
298− <div class="wikibase-snakview-value">
299− <a href="/wiki/Q206">
300− Old Prime Minister
301− </a>
302− </div>
303− </div>
304− </div>
305− </div>
306− <div class="wikibase-statementview listview-item">
307− <div class="wikibase-statementview-rankselector">
308− <span class="wikibase-rankselector-preferred"></span>
309− </div>
310− <div class="wikibase-statementview-mainsnak">
311− <div>
312− <div class="wikibase-snakview-value">
313− <a href="/wiki/Q3099714">
314− Actual Prime Minister
315− </a>
316− </div>
317− </div>
318− </div>
319− </div>
320− </div>
321− </div>
322− </div>
323− """
324− attributes = []
325− html_etree = fromstring(html)
326− id_cache = wikidata.get_id_cache(html_etree)
327− wikidata.add_attribute(attributes, id_cache, "P6")
328− self.assertEqual(len(attributes), 1)
329− self.assertEqual(attributes[0]["label"], "Head of government")
330− self.assertEqual(attributes[0]["value"], "Old Prime Minister, Actual Prime Minister")
331−
332− attributes = []
333− html_etree = fromstring(html)
334− id_cache = wikidata.get_id_cache(html_etree)
335− wikidata.add_attribute(attributes, id_cache, "P6", trim=True)
336− self.assertEqual(len(attributes), 1)
337− self.assertEqual(attributes[0]["value"], "Actual Prime Minister")
338−
339− def test_add_url(self):
340− html = u"""
341− <div>
342− <div id="P856">
343− <div class="wikibase-statementgroupview-property-label">
344− <a href="/wiki/Property:P856">
345− official website
346− </a>
347− </div>
348− <div class="wikibase-statementlistview">
349− <div class="wikibase-statementview listview-item">
350− <div class="wikibase-statementview-mainsnak">
351− <div>
352− <div class="wikibase-snakview-value">
353− <a class="external free" href="https://searx.me">
354− https://searx.me/
355− </a>
356− </div>
357− </div>
358− </div>
359− </div>
360− </div>
361− </div>
362− </div>
363− """
364− urls = []
365− html_etree = fromstring(html)
366− id_cache = wikidata.get_id_cache(html_etree)
367− wikidata.add_url(urls, html_etree, id_cache, 'P856')
368− self.assertEquals(len(urls), 1)
369− self.assertIn({'title': 'Official website', 'url': 'https://searx.me/'}, urls)
370− urls = []
371− results = []
372− wikidata.add_url(urls, html_etree, id_cache, 'P856', 'custom label', results=results)
373− self.assertEquals(len(urls), 1)
374− self.assertEquals(len(results), 1)
375− self.assertIn({'title': 'custom label', 'url': 'https://searx.me/'}, urls)
376− self.assertIn({'title': 'custom label', 'url': 'https://searx.me/'}, results)
377−
378− html = u"""
379− <div>
380− <div id="P856">
381− <div class="wikibase-statementgroupview-property-label">
382− <a href="/wiki/Property:P856">
383− official website
384− </a>
385− </div>
386− <div class="wikibase-statementlistview">
387− <div class="wikibase-statementview listview-item">
388− <div class="wikibase-statementview-mainsnak">
389− <div>
390− <div class="wikibase-snakview-value">
391− <a class="external free" href="http://www.worldofwarcraft.com">
392− http://www.worldofwarcraft.com
393− </a>
394− </div>
395− </div>
396− </div>
397− </div>
398− <div class="wikibase-statementview listview-item">
399− <div class="wikibase-statementview-mainsnak">
400− <div>
401− <div class="wikibase-snakview-value">
402− <a class="external free" href="http://eu.battle.net/wow/en/">
403− http://eu.battle.net/wow/en/
404− </a>
405− </div>
406− </div>
407− </div>
408− </div>
409− </div>
410− </div>
411− </div>
412− """
413− urls = []
414− html_etree = fromstring(html)
415− id_cache = wikidata.get_id_cache(html_etree)
416− wikidata.add_url(urls, html_etree, id_cache, 'P856')
417− self.assertEquals(len(urls), 2)
418− self.assertIn({'title': 'Official website', 'url': 'http://www.worldofwarcraft.com'}, urls)
419− self.assertIn({'title': 'Official website', 'url': 'http://eu.battle.net/wow/en/'}, urls)
420−
421− def test_get_imdblink(self):
422− html = u"""
423− <div>
424− <div class="wikibase-statementview-mainsnak">
425− <div>
426− <div class="wikibase-snakview-value">
427− <a class="wb-external-id" href="http://www.imdb.com/tt0433664">
428− tt0433664
429− </a>
430− </div>
431− </div>
432− </div>
433− </div>
434− """
435− html_etree = fromstring(html)
436− imdblink = wikidata.get_imdblink(html_etree, 'https://www.imdb.com/')
437−
438− html = u"""
439− <div>
440− <div class="wikibase-statementview-mainsnak">
441− <div>
442− <div class="wikibase-snakview-value">
443− <a class="wb-external-id"
444− href="href="http://tools.wmflabs.org/...http://www.imdb.com/&id=nm4915994"">
445− nm4915994
446− </a>
447− </div>
448− </div>
449− </div>
450− </div>
451− """
452− html_etree = fromstring(html)
453− imdblink = wikidata.get_imdblink(html_etree, 'https://www.imdb.com/')
454− self.assertIn('https://www.imdb.com/name/nm4915994', imdblink)
455−
456− def test_get_geolink(self):
457− html = u"""
458− <div>
459− <div class="wikibase-statementview-mainsnak">
460− <div>
461− <div class="wikibase-snakview-value">
462− 60°N, 40°E
463− </div>
464− </div>
465− </div>
466− </div>
467− """
468− html_etree = fromstring(html)
469− geolink = wikidata.get_geolink(html_etree)
470− self.assertIn('https://www.openstreetmap.org/', geolink)
471− self.assertIn('lat=60&lon=40', geolink)
472−
473− html = u"""
474− <div>
475− <div class="wikibase-statementview-mainsnak">
476− <div>
477− <div class="wikibase-snakview-value">
478− 34°35'59"S, 58°22'55"W
479− </div>
480− </div>
481− </div>
482− </div>
483− """
484− html_etree = fromstring(html)
485− geolink = wikidata.get_geolink(html_etree)
486− self.assertIn('https://www.openstreetmap.org/', geolink)
487− self.assertIn('lat=-34.59', geolink)
488− self.assertIn('lon=-58.38', geolink)
489−
490− def test_get_wikilink(self):
491− html = """
492− <div>
493− <div>
494− <ul class="wikibase-sitelinklistview-listview">
495− <li data-wb-siteid="arwiki"><a href="http://ar.wikipedia.org/wiki/Test">Test</a></li>
496− <li data-wb-siteid="enwiki"><a href="http://en.wikipedia.org/wiki/Test">Test</a></li>
497− </ul>
498− </div>
499− <div>
500− <ul class="wikibase-sitelinklistview-listview">
501− <li data-wb-siteid="enwikiquote"><a href="https://en.wikiquote.org/wiki/Test">Test</a></li>
502− </ul>
503− </div>
504− </div>
505− """
506− html_etree = fromstring(html)
507− wikilink = wikidata.get_wikilink(html_etree, 'nowiki')
508− self.assertEqual(wikilink, None)
509− wikilink = wikidata.get_wikilink(html_etree, 'enwiki')
510− self.assertEqual(wikilink, 'https://en.wikipedia.org/wiki/Test')
511− wikilink = wikidata.get_wikilink(html_etree, 'arwiki')
512− self.assertEqual(wikilink, 'https://ar.wikipedia.org/wiki/Test')
513− wikilink = wikidata.get_wikilink(html_etree, 'enwikiquote')
514− self.assertEqual(wikilink, 'https://en.wikiquote.org/wiki/Test')
tests/unit/engines/test_wikipedia.pydeleted−263
@@ -1,263 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import wikipedia
5−from searx.testing import SearxTestCase
6−
7−
8−class TestWikipediaEngine(SearxTestCase):
9−
10− def test_request(self):
11− wikipedia.supported_languages = ['fr', 'en', 'no']
12− wikipedia.language_aliases = {'nb': 'no'}
13−
14− query = 'test_query'
15− dicto = defaultdict(dict)
16− dicto['language'] = 'fr-FR'
17− params = wikipedia.request(query.encode('utf-8'), dicto)
18− self.assertIn('url', params)
19− self.assertIn(query, params['url'])
20− self.assertIn('test_query', params['url'])
21− self.assertIn('Test_Query', params['url'])
22− self.assertIn('fr.wikipedia.org', params['url'])
23−
24− query = u'Test_Query'
25− params = wikipedia.request(query.encode('utf-8'), dicto)
26− self.assertIn('Test_Query', params['url'])
27− self.assertNotIn('test_query', params['url'])
28−
29− dicto['language'] = 'nb'
30− params = wikipedia.request(query, dicto)
31− self.assertIn('no.wikipedia.org', params['url'])
32− dicto['language'] = 'all'
33− params = wikipedia.request(query, dicto)
34− self.assertIn('en', params['url'])
35−
36− dicto['language'] = 'xx'
37− params = wikipedia.request(query, dicto)
38− self.assertIn('en.wikipedia.org', params['url'])
39−
40− def test_response(self):
41− dicto = defaultdict(dict)
42− dicto['language'] = 'fr'
43−
44− self.assertRaises(AttributeError, wikipedia.response, None)
45− self.assertRaises(AttributeError, wikipedia.response, [])
46− self.assertRaises(AttributeError, wikipedia.response, '')
47− self.assertRaises(AttributeError, wikipedia.response, '[]')
48−
49− # page not found
50− json = """
51− {
52− "batchcomplete": "",
53− "query": {
54− "normalized": [],
55− "pages": {
56− "-1": {
57− "ns": 0,
58− "title": "",
59− "missing": ""
60− }
61− }
62− }
63− }"""
64− response = mock.Mock(text=json, search_params=dicto)
65− self.assertEqual(wikipedia.response(response), [])
66−
67− # normal case
68− json = """
69− {
70− "batchcomplete": "",
71− "query": {
72− "normalized": [],
73− "pages": {
74− "12345": {
75− "pageid": 12345,
76− "ns": 0,
77− "title": "The Title",
78− "extract": "The Title is...",
79− "thumbnail": {
80− "source": "img_src.jpg"
81− },
82− "pageimage": "img_name.jpg"
83− }
84− }
85− }
86− }"""
87− response = mock.Mock(text=json, search_params=dicto)
88− results = wikipedia.response(response)
89− self.assertEqual(type(results), list)
90− self.assertEqual(len(results), 2)
91− self.assertEqual(results[0]['title'], u'The Title')
92− self.assertIn('fr.wikipedia.org/wiki/The_Title', results[0]['url'])
93− self.assertEqual(results[1]['infobox'], u'The Title')
94− self.assertIn('fr.wikipedia.org/wiki/The_Title', results[1]['id'])
95− self.assertIn('The Title is...', results[1]['content'])
96− self.assertEqual(results[1]['img_src'], 'img_src.jpg')
97−
98− # disambiguation page
99− json = """
100− {
101− "batchcomplete": "",
102− "query": {
103− "normalized": [],
104− "pages": {
105− "12345": {
106− "pageid": 12345,
107− "ns": 0,
108− "title": "The Title",
109− "extract": "The Title can be:\\nThe Title 1\\nThe Title 2\\nThe Title 3\\nThe Title 4......................................................................................................................................." """ # noqa
110− json += """
111− }
112− }
113− }
114− }"""
115− response = mock.Mock(text=json, search_params=dicto)
116− results = wikipedia.response(response)
117− self.assertEqual(type(results), list)
118− self.assertEqual(len(results), 2)
119−
120− # no image
121− json = """
122− {
123− "batchcomplete": "",
124− "query": {
125− "normalized": [],
126− "pages": {
127− "12345": {
128− "pageid": 12345,
129− "ns": 0,
130− "title": "The Title",
131− "extract": "The Title is......................................................................................................................................................................................." """ # noqa
132− json += """
133− }
134− }
135− }
136− }"""
137− response = mock.Mock(text=json, search_params=dicto)
138− results = wikipedia.response(response)
139− self.assertEqual(type(results), list)
140− self.assertEqual(len(results), 2)
141− self.assertIn('The Title is...', results[1]['content'])
142− self.assertEqual(results[1]['img_src'], None)
143−
144− # title not in first paragraph
145− json = u"""
146− {
147− "batchcomplete": "",
148− "query": {
149− "normalized": [],
150− "pages": {
151− "12345": {
152− "pageid": 12345,
153− "ns": 0,
154− "title": "披頭四樂隊",
155− "extract": "披头士乐队....................................................................................................................................................................................................\\n披頭四樂隊...", """ # noqa
156− json += """
157− "thumbnail": {
158− "source": "img_src.jpg"
159− },
160− "pageimage": "img_name.jpg"
161− }
162− }
163− }
164− }"""
165− response = mock.Mock(text=json, search_params=dicto)
166− results = wikipedia.response(response)
167− self.assertEqual(type(results), list)
168− self.assertEqual(len(results), 2)
169− self.assertEqual(results[1]['infobox'], u'披頭四樂隊')
170− self.assertIn(u'披头士乐队...', results[1]['content'])
171−
172− def test_fetch_supported_languages(self):
173− html = u"""<html></html>"""
174− response = mock.Mock(text=html)
175− languages = wikipedia._fetch_supported_languages(response)
176− self.assertEqual(type(languages), dict)
177− self.assertEqual(len(languages), 0)
178−
179− html = u"""
180− <html>
181− <body>
182− <div>
183− <div>
184− <h3>Table header</h3>
185− <table class="sortable jquery-tablesorter">
186− <thead>
187− <tr>
188− <th>N</th>
189− <th>Language</th>
190− <th>Language (local)</th>
191− <th>Wiki</th>
192− <th>Articles</th>
193− </tr>
194− </thead>
195− <tbody>
196− <tr>
197− <td>2</td>
198− <td><a>Swedish</a></td>
199− <td><a>Svenska</a></td>
200− <td><a>sv</a></td>
201− <td><a><b>3000000</b></a></td>
202− </tr>
203− <tr>
204− <td>3</td>
205− <td><a>Cebuano</a></td>
206− <td><a>Sinugboanong Binisaya</a></td>
207− <td><a>ceb</a></td>
208− <td><a><b>3000000</b></a></td>
209− </tr>
210− </tbody>
211− </table>
212− <h3>Table header</h3>
213− <table class="sortable jquery-tablesorter">
214− <thead>
215− <tr>
216− <th>N</th>
217− <th>Language</th>
218− <th>Language (local)</th>
219− <th>Wiki</th>
220− <th>Articles</th>
221− </tr>
222− </thead>
223− <tbody>
224− <tr>
225− <td>2</td>
226− <td><a>Norwegian (Bokmål)</a></td>
227− <td><a>Norsk (Bokmål)</a></td>
228− <td><a>no</a></td>
229− <td><a><b>100000</b></a></td>
230− </tr>
231− </tbody>
232− </table>
233− </div>
234− </div>
235− </body>
236− </html>
237− """
238− response = mock.Mock(text=html)
239− languages = wikipedia._fetch_supported_languages(response)
240− self.assertEqual(type(languages), dict)
241− self.assertEqual(len(languages), 3)
242−
243− self.assertIn('sv', languages)
244− self.assertIn('ceb', languages)
245− self.assertIn('no', languages)
246−
247− self.assertEqual(type(languages['sv']), dict)
248− self.assertEqual(type(languages['ceb']), dict)
249− self.assertEqual(type(languages['no']), dict)
250−
251− self.assertIn('name', languages['sv'])
252− self.assertIn('english_name', languages['sv'])
253− self.assertIn('articles', languages['sv'])
254−
255− self.assertEqual(languages['sv']['name'], 'Svenska')
256− self.assertEqual(languages['sv']['english_name'], 'Swedish')
257− self.assertEqual(languages['sv']['articles'], 3000000)
258− self.assertEqual(languages['ceb']['name'], 'Sinugboanong Binisaya')
259− self.assertEqual(languages['ceb']['english_name'], 'Cebuano')
260− self.assertEqual(languages['ceb']['articles'], 3000000)
261− self.assertEqual(languages['no']['name'], u'Norsk (Bokmål)')
262− self.assertEqual(languages['no']['english_name'], u'Norwegian (Bokmål)')
263− self.assertEqual(languages['no']['articles'], 100000)
tests/unit/engines/test_wolframalpha_api.pydeleted−166
@@ -1,166 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from requests import Request
5−from searx.engines import wolframalpha_api
6−from searx.testing import SearxTestCase
7−
8−
9−class TestWolframAlphaAPIEngine(SearxTestCase):
10−
11− def test_request(self):
12− query = 'test_query'
13− dicto = defaultdict(dict)
14− params = wolframalpha_api.request(query, dicto)
15−
16− # TODO: test api_key
17− self.assertIn('url', params)
18− self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
19− self.assertIn(query, params['url'])
20− self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
21−
22− def test_replace_pua_chars(self):
23− self.assertEqual('i', wolframalpha_api.replace_pua_chars(u'\uf74e'))
24−
25− def test_response(self):
26− self.assertRaises(AttributeError, wolframalpha_api.response, None)
27− self.assertRaises(AttributeError, wolframalpha_api.response, [])
28− self.assertRaises(AttributeError, wolframalpha_api.response, '')
29− self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
30−
31− referer_url = 'referer_url'
32− request = Request(headers={'Referer': referer_url})
33−
34− # test failure
35− xml = '''<?xml version='1.0' encoding='UTF-8'?>
36− <queryresult success='false' error='false' />
37− '''
38− response = mock.Mock(content=xml.encode('utf-8'))
39− self.assertEqual(wolframalpha_api.response(response), [])
40−
41− # test basic case
42− xml = b"""<?xml version='1.0' encoding='UTF-8'?>
43− <queryresult success='true'
44− error='false'
45− numpods='3'
46− datatypes='Math'
47− id='queryresult_id'
48− host='http://www4c.wolframalpha.com'
49− related='related_url'
50− version='2.6'>
51− <pod title='Input'
52− scanner='Identity'
53− id='Input'
54− numsubpods='1'>
55− <subpod title=''>
56− <img src='input_img_src.gif'
57− alt='input_img_alt'
58− title='input_img_title' />
59− <plaintext>input_plaintext</plaintext>
60− </subpod>
61− </pod>
62− <pod title='Result'
63− scanner='Simplification'
64− id='Result'
65− numsubpods='1'
66− primary='true'>
67− <subpod title=''>
68− <img src='result_img_src.gif'
69− alt='result_img_alt'
70− title='result_img_title' />
71− <plaintext>result_plaintext</plaintext>
72− </subpod>
73− </pod>
74− <pod title='Manipulatives illustration'
75− scanner='Arithmetic'
76− id='Illustration'
77− numsubpods='1'>
78− <subpod title=''>
79− <img src='illustration_img_src.gif'
80− alt='illustration_img_alt' />
81− <plaintext>illustration_plaintext</plaintext>
82− </subpod>
83− </pod>
84− </queryresult>
85− """
86− response = mock.Mock(content=xml, request=request)
87− results = wolframalpha_api.response(response)
88− self.assertEqual(type(results), list)
89− self.assertEqual(len(results), 2)
90− self.assertEqual('input_plaintext', results[0]['infobox'])
91−
92− self.assertEqual(len(results[0]['attributes']), 3)
93− self.assertEqual('Input', results[0]['attributes'][0]['label'])
94− self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
95− self.assertEqual('Result', results[0]['attributes'][1]['label'])
96− self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
97− self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
98− self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
99− self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
100−
101− self.assertEqual(len(results[0]['urls']), 1)
102−
103− self.assertEqual(referer_url, results[0]['urls'][0]['url'])
104− self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
105− self.assertEqual(referer_url, results[1]['url'])
106− self.assertEqual('Wolfram|Alpha (input_plaintext)', results[1]['title'])
107− self.assertIn('result_plaintext', results[1]['content'])
108−
109− # test calc
110− xml = b"""<?xml version='1.0' encoding='UTF-8'?>
111− <queryresult success='true'
112− error='false'
113− numpods='2'
114− datatypes=''
115− parsetimedout='false'
116− id='queryresult_id'
117− host='http://www5b.wolframalpha.com'
118− related='related_url'
119− version='2.6' >
120− <pod title='Indefinite integral'
121− scanner='Integral'
122− id='IndefiniteIntegral'
123− error='false'
124− numsubpods='1'
125− primary='true'>
126− <subpod title=''>
127− <img src='integral_image.gif'
128− alt='integral_img_alt'
129− title='integral_img_title' />
130− <plaintext>integral_plaintext</plaintext>
131− </subpod>
132− </pod>
133− <pod title='Plot of the integral'
134− scanner='Integral'
135− id='Plot'
136− error='false'
137− numsubpods='1'>
138− <subpod title=''>
139− <img src='plot.gif'
140− alt='plot_alt'
141− title='' />
142− <plaintext></plaintext>
143− </subpod>
144− </pod>
145− </queryresult>
146− """
147− response = mock.Mock(content=xml, request=request)
148− results = wolframalpha_api.response(response)
149− self.assertEqual(type(results), list)
150− self.assertEqual(len(results), 2)
151− self.assertEqual('integral_plaintext', results[0]['infobox'])
152−
153− self.assertEqual(len(results[0]['attributes']), 2)
154− self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
155− self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
156− self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
157− self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
158− self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
159−
160− self.assertEqual(len(results[0]['urls']), 1)
161−
162− self.assertEqual(referer_url, results[0]['urls'][0]['url'])
163− self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
164− self.assertEqual(referer_url, results[1]['url'])
165− self.assertEqual('Wolfram|Alpha (integral_plaintext)', results[1]['title'])
166− self.assertIn('integral_plaintext', results[1]['content'])
tests/unit/engines/test_wolframalpha_noapi.pydeleted−224
@@ -1,224 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from requests import Request
5−from searx.engines import wolframalpha_noapi
6−from searx.testing import SearxTestCase
7−
8−
9−class TestWolframAlphaNoAPIEngine(SearxTestCase):
10−
11− def test_request(self):
12− query = 'test_query'
13− dicto = defaultdict(dict)
14− params = wolframalpha_noapi.request(query, dicto)
15−
16− self.assertIn('url', params)
17− self.assertIn('https://www.wolframalpha.com/input/json.jsp', params['url'])
18− self.assertIn(query, params['url'])
19− self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
20−
21− def test_response(self):
22− self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
23− self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
24− self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
25− self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
26−
27− referer_url = 'referer_url'
28− request = Request(headers={'Referer': referer_url})
29−
30− # test failure
31− json = r'''
32− {"queryresult" : {
33− "success" : false,
34− "error" : false,
35− "numpods" : 0,
36− "id" : "",
37− "host" : "https:\/\/www5a.wolframalpha.com",
38− "didyoumeans" : {}
39− }}
40− '''
41− response = mock.Mock(text=json, request=request)
42− self.assertEqual(wolframalpha_noapi.response(response), [])
43−
44− # test basic case
45− json = r'''
46− {"queryresult" : {
47− "success" : true,
48− "error" : false,
49− "numpods" : 6,
50− "datatypes" : "Math",
51− "id" : "queryresult_id",
52− "host" : "https:\/\/www5b.wolframalpha.com",
53− "related" : "related_url",
54− "version" : "2.6",
55− "pods" : [
56− {
57− "title" : "Input",
58− "scanners" : [
59− "Identity"
60− ],
61− "id" : "Input",
62− "error" : false,
63− "numsubpods" : 1,
64− "subpods" : [
65− {
66− "title" : "",
67− "img" : {
68− "src" : "input_img_src.gif",
69− "alt" : "input_img_alt",
70− "title" : "input_img_title"
71− },
72− "plaintext" : "input_plaintext",
73− "minput" : "input_minput"
74− }
75− ]
76− },
77− {
78− "title" : "Result",
79− "scanners" : [
80− "Simplification"
81− ],
82− "id" : "Result",
83− "error" : false,
84− "numsubpods" : 1,
85− "primary" : true,
86− "subpods" : [
87− {
88− "title" : "",
89− "img" : {
90− "src" : "result_img_src.gif",
91− "alt" : "result_img_alt",
92− "title" : "result_img_title"
93− },
94− "plaintext" : "result_plaintext",
95− "moutput" : "result_moutput"
96− }
97− ]
98− },
99− {
100− "title" : "Manipulatives illustration",
101− "scanners" : [
102− "Arithmetic"
103− ],
104− "id" : "Illustration",
105− "error" : false,
106− "numsubpods" : 1,
107− "subpods" : [
108− {
109− "title" : "",
110− "CDFcontent" : "Resizeable",
111− "img" : {
112− "src" : "illustration_img_src.gif",
113− "alt" : "illustration_img_alt",
114− "title" : "illustration_img_title"
115− },
116− "plaintext" : "illustration_img_plaintext"
117− }
118− ]
119− }
120− ]
121− }}
122− '''
123− response = mock.Mock(text=json, request=request)
124− results = wolframalpha_noapi.response(response)
125− self.assertEqual(type(results), list)
126− self.assertEqual(len(results), 2)
127− self.assertEqual('input_plaintext', results[0]['infobox'])
128−
129− self.assertEqual(len(results[0]['attributes']), 3)
130− self.assertEqual('Input', results[0]['attributes'][0]['label'])
131− self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
132− self.assertEqual('Result', results[0]['attributes'][1]['label'])
133− self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
134− self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
135− self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
136− self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
137−
138− self.assertEqual(len(results[0]['urls']), 1)
139−
140− self.assertEqual(referer_url, results[0]['urls'][0]['url'])
141− self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
142− self.assertEqual(referer_url, results[1]['url'])
143− self.assertEqual('Wolfram|Alpha (input_plaintext)', results[1]['title'])
144− self.assertIn('result_plaintext', results[1]['content'])
145−
146− # test calc
147− json = r"""
148− {"queryresult" : {
149− "success" : true,
150− "error" : false,
151− "numpods" : 2,
152− "datatypes" : "",
153− "id" : "queryresult_id",
154− "host" : "https:\/\/www4b.wolframalpha.com",
155− "related" : "related_url",
156− "version" : "2.6",
157− "pods" : [
158− {
159− "title" : "Indefinite integral",
160− "scanners" : [
161− "Integral"
162− ],
163− "id" : "IndefiniteIntegral",
164− "error" : false,
165− "numsubpods" : 1,
166− "primary" : true,
167− "subpods" : [
168− {
169− "title" : "",
170− "img" : {
171− "src" : "integral_img_src.gif",
172− "alt" : "integral_img_alt",
173− "title" : "integral_img_title"
174− },
175− "plaintext" : "integral_plaintext",
176− "minput" : "integral_minput",
177− "moutput" : "integral_moutput"
178− }
179− ]
180− },
181− {
182− "title" : "Plot of the integral",
183− "scanners" : [
184− "Integral"
185− ],
186− "id" : "Plot",
187− "error" : false,
188− "numsubpods" : 1,
189− "subpods" : [
190− {
191− "title" : "",
192− "img" : {
193− "src" : "plot.gif",
194− "alt" : "plot_alt",
195− "title" : "plot_title"
196− },
197− "plaintext" : "",
198− "minput" : "plot_minput"
199− }
200− ]
201− }
202− ]
203− }}
204− """
205− response = mock.Mock(text=json, request=request)
206− results = wolframalpha_noapi.response(response)
207− self.assertEqual(type(results), list)
208− self.assertEqual(len(results), 2)
209− self.assertEqual('integral_plaintext', results[0]['infobox'])
210−
211− self.assertEqual(len(results[0]['attributes']), 2)
212− self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
213− self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
214− self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
215− self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
216− self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
217−
218− self.assertEqual(len(results[0]['urls']), 1)
219−
220− self.assertEqual(referer_url, results[0]['urls'][0]['url'])
221− self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
222− self.assertEqual(referer_url, results[1]['url'])
223− self.assertEqual('Wolfram|Alpha (integral_plaintext)', results[1]['title'])
224− self.assertIn('integral_plaintext', results[1]['content'])
tests/unit/engines/test_www1x.pydeleted−14
@@ -1,14 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import www1x
4−from searx.testing import SearxTestCase
5−
6−
7−class TestWww1xEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− params = www1x.request(query, defaultdict(dict))
12− self.assertTrue('url' in params)
13− self.assertTrue(query in params['url'])
14− self.assertTrue('1x.com' in params['url'])
tests/unit/engines/test_yacy.pydeleted−96
@@ -1,96 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import yacy
4−from searx.testing import SearxTestCase
5−
6−
7−class TestYacyEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 1
13− dicto['language'] = 'fr_FR'
14− params = yacy.request(query, dicto)
15− self.assertIn('url', params)
16− self.assertIn(query, params['url'])
17− self.assertIn('localhost', params['url'])
18− self.assertIn('fr', params['url'])
19−
20− dicto['language'] = 'all'
21− params = yacy.request(query, dicto)
22− self.assertIn('url', params)
23− self.assertNotIn('lr=lang_', params['url'])
24−
25− def test_response(self):
26− self.assertRaises(AttributeError, yacy.response, None)
27− self.assertRaises(AttributeError, yacy.response, [])
28− self.assertRaises(AttributeError, yacy.response, '')
29− self.assertRaises(AttributeError, yacy.response, '[]')
30−
31− response = mock.Mock(text='{}')
32− self.assertEqual(yacy.response(response), [])
33−
34− response = mock.Mock(text='{"data": []}')
35− self.assertEqual(yacy.response(response), [])
36−
37− json = """
38− {
39− "channels": [
40− {
41− "title": "YaCy P2P-Search for test",
42− "description": "Search for test",
43− "link": "http://search.yacy.de:7001/yacysearch.html?query=test&amp;resource=global&amp;contentdom=0",
44− "image": {
45− "url": "http://search.yacy.de:7001/env/grafics/yacy.png",
46− "title": "Search for test",
47− "link": "http://search.yacy.de:7001/yacysearch.html?query=test&amp;resource=global&amp;contentdom=0"
48− },
49− "totalResults": "249",
50− "startIndex": "0",
51− "itemsPerPage": "5",
52− "searchTerms": "test",
53− "items": [
54− {
55− "title": "This is the title",
56− "link": "http://this.is.the.url",
57− "code": "",
58− "description": "This should be the content",
59− "pubDate": "Sat, 08 Jun 2013 02:00:00 +0200",
60− "size": "44213",
61− "sizename": "43 kbyte",
62− "guid": "lzh_1T_5FP-A",
63− "faviconCode": "XTS4uQ_5FP-A",
64− "host": "www.gamestar.de",
65− "path": "/spiele/city-of-heroes-freedom/47019.html",
66− "file": "47019.html",
67− "urlhash": "lzh_1T_5FP-A",
68− "ranking": "0.20106804"
69− },
70− {
71− "title": "This is the title2",
72− "icon": "/ViewImage.png?maxwidth=96&amp;maxheight=96&amp;code=7EbAbW6BpPOA",
73− "image": "http://image.url/image.png",
74− "cache": "/ViewImage.png?quadratic=&amp;url=http://golem.ivwbox.de/cgi-bin/ivw/CP/G_INET?d=14071378",
75− "url": "http://this.is.the.url",
76− "urlhash": "7EbAbW6BpPOA",
77− "host": "www.golem.de",
78− "width": "-1",
79− "height": "-1"
80− }
81− ]
82− }
83− ]
84− }
85− """
86− response = mock.Mock(text=json)
87− results = yacy.response(response)
88− self.assertEqual(type(results), list)
89− self.assertEqual(len(results), 2)
90− self.assertEqual(results[0]['title'], 'This is the title')
91− self.assertEqual(results[0]['url'], 'http://this.is.the.url')
92− self.assertEqual(results[0]['content'], 'This should be the content')
93− self.assertEqual(results[1]['img_src'], 'http://image.url/image.png')
94− self.assertEqual(results[1]['content'], '')
95− self.assertEqual(results[1]['url'], 'http://this.is.the.url')
96− self.assertEqual(results[1]['title'], 'This is the title2')
tests/unit/engines/test_yahoo.pydeleted−190
@@ -1,190 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import yahoo
5−from searx.testing import SearxTestCase
6−
7−
8−class TestYahooEngine(SearxTestCase):
9−
10− def test_parse_url(self):
11− test_url = 'http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;_ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb' +\
12− '2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10/RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=' +\
13− 'dtcJsfP4mEeBOjnVfUQ-'
14− url = yahoo.parse_url(test_url)
15− self.assertEqual('https://this.is.the.url/', url)
16−
17− test_url = 'http://r.search.yahoo.com/_ylt=A0LElb9JUSKcAEGRXNyoA;_ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb' +\
18− '2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10/RU=https%3a%2f%2fthis.is.the.url%2f/RS=' +\
19− 'dtcJsfP4mEeBOjnVfUQ-'
20− url = yahoo.parse_url(test_url)
21− self.assertEqual('https://this.is.the.url/', url)
22−
23− test_url = 'https://this.is.the.url/'
24− url = yahoo.parse_url(test_url)
25− self.assertEqual('https://this.is.the.url/', url)
26−
27− def test_request(self):
28− yahoo.supported_languages = ['en', 'fr', 'zh-CHT', 'zh-CHS']
29− query = 'test_query'
30− dicto = defaultdict(dict)
31− dicto['pageno'] = 1
32− dicto['time_range'] = ''
33− dicto['language'] = 'fr-FR'
34− params = yahoo.request(query, dicto)
35− self.assertIn('url', params)
36− self.assertIn(query, params['url'])
37− self.assertIn('search.yahoo.com', params['url'])
38− self.assertIn('fr', params['url'])
39− self.assertIn('cookies', params)
40− self.assertIn('sB', params['cookies'])
41− self.assertIn('fr', params['cookies']['sB'])
42−
43− dicto['language'] = 'zh'
44− params = yahoo.request(query, dicto)
45− self.assertIn('zh_chs', params['url'])
46− self.assertIn('zh_chs', params['cookies']['sB'])
47−
48− dicto['language'] = 'zh-TW'
49− params = yahoo.request(query, dicto)
50− self.assertIn('zh_cht', params['url'])
51− self.assertIn('zh_cht', params['cookies']['sB'])
52−
53− dicto['language'] = 'all'
54− params = yahoo.request(query, dicto)
55− self.assertIn('cookies', params)
56− self.assertIn('sB', params['cookies'])
57− self.assertIn('en', params['cookies']['sB'])
58− self.assertIn('en', params['url'])
59−
60− def test_no_url_in_request_year_time_range(self):
61− dicto = defaultdict(dict)
62− query = 'test_query'
63− dicto['time_range'] = 'year'
64− params = yahoo.request(query, dicto)
65− self.assertEqual({}, params['url'])
66−
67− def test_response(self):
68− self.assertRaises(AttributeError, yahoo.response, None)
69− self.assertRaises(AttributeError, yahoo.response, [])
70− self.assertRaises(AttributeError, yahoo.response, '')
71− self.assertRaises(AttributeError, yahoo.response, '[]')
72−
73− response = mock.Mock(text='<html></html>')
74− self.assertEqual(yahoo.response(response), [])
75−
76− html = """
77−<ol class="reg mb-15 searchCenterMiddle">
78− <li class="first">
79− <div class="dd algo fst Sr">
80− <div class="compTitle">
81− <h3 class="title"><a class=" td-u" href="http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;
82− _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10
83− /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"
84− target="_blank" data-bid="54e712e13671c">
85− <b><b>This is the title</b></b></a>
86− </h3>
87− </div>
88− <div class="compText aAbs">
89− <p class="lh-18"><b><b>This is the </b>content</b>
90− </p>
91− </div>
92− </div>
93− </li>
94− <li>
95− <div class="dd algo lst Sr">
96− <div class="compTitle">
97− </div>
98− <div class="compText aAbs">
99− <p class="lh-18">This is the second content</p>
100− </div>
101− </div>
102− </li>
103−</ol>
104−<div class="dd assist fst lst AlsoTry" data-bid="54e712e138d04">
105− <div class="compTitle mb-4 h-17">
106− <h3 class="title">Also Try</h3> </div>
107− <table class="compTable m-0 ac-1st td-u fz-ms">
108− <tbody>
109− <tr>
110− <td class="w-50p pr-28"><a href="https://search.yahoo.com/"><B>This is the </B>suggestion<B></B></a>
111− </td>
112− </tr>
113− </table>
114−</div>
115− """
116− response = mock.Mock(text=html)
117− results = yahoo.response(response)
118− self.assertEqual(type(results), list)
119− self.assertEqual(len(results), 2)
120− self.assertEqual(results[0]['title'], 'This is the title')
121− self.assertEqual(results[0]['url'], 'https://this.is.the.url/')
122− self.assertEqual(results[0]['content'], 'This is the content')
123− self.assertEqual(results[1]['suggestion'], 'This is the suggestion')
124−
125− html = """
126−<ol class="reg mb-15 searchCenterMiddle">
127− <li class="first">
128− <div class="dd algo fst Sr">
129− <div class="compTitle">
130− <h3 class="title"><a class=" td-u" href="http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;
131− _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10
132− /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"
133− target="_blank" data-bid="54e712e13671c">
134− <b><b>This is the title</b></b></a>
135− </h3>
136− </div>
137− <div class="compText aAbs">
138− <p class="lh-18"><b><b>This is the </b>content</b>
139− </p>
140− </div>
141− </div>
142− </li>
143−</ol>
144− """
145− response = mock.Mock(text=html)
146− results = yahoo.response(response)
147− self.assertEqual(type(results), list)
148− self.assertEqual(len(results), 1)
149− self.assertEqual(results[0]['title'], 'This is the title')
150− self.assertEqual(results[0]['url'], 'https://this.is.the.url/')
151− self.assertEqual(results[0]['content'], 'This is the content')
152−
153− html = """
154− <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
155− </li>
156− """
157− response = mock.Mock(text=html)
158− results = yahoo.response(response)
159− self.assertEqual(type(results), list)
160− self.assertEqual(len(results), 0)
161−
162− def test_fetch_supported_languages(self):
163− html = """<html></html>"""
164− response = mock.Mock(text=html)
165− results = yahoo._fetch_supported_languages(response)
166− self.assertEqual(type(results), list)
167− self.assertEqual(len(results), 0)
168−
169− html = """
170− <html>
171− <div>
172− <div id="yschlang">
173− <span>
174− <label><input value="lang_ar"></input></label>
175− </span>
176− <span>
177− <label><input value="lang_zh_chs"></input></label>
178− <label><input value="lang_zh_cht"></input></label>
179− </span>
180− </div>
181− </div>
182− </html>
183− """
184− response = mock.Mock(text=html)
185− languages = yahoo._fetch_supported_languages(response)
186− self.assertEqual(type(languages), list)
187− self.assertEqual(len(languages), 3)
188− self.assertIn('ar', languages)
189− self.assertIn('zh-CHS', languages)
190− self.assertIn('zh-CHT', languages)
tests/unit/engines/test_yahoo_news.pydeleted−150
@@ -1,150 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−from datetime import datetime
4−import mock
5−from searx.engines import yahoo_news
6−from searx.testing import SearxTestCase
7−
8−
9−class TestYahooNewsEngine(SearxTestCase):
10−
11− def test_request(self):
12− yahoo_news.supported_languages = ['en', 'fr']
13− query = 'test_query'
14− dicto = defaultdict(dict)
15− dicto['pageno'] = 1
16− dicto['language'] = 'fr-FR'
17− params = yahoo_news.request(query, dicto)
18− self.assertIn('url', params)
19− self.assertIn(query, params['url'])
20− self.assertIn('news.search.yahoo.com', params['url'])
21− self.assertIn('fr', params['url'])
22− self.assertIn('cookies', params)
23− self.assertIn('sB', params['cookies'])
24− self.assertIn('fr', params['cookies']['sB'])
25−
26− dicto['language'] = 'all'
27− params = yahoo_news.request(query, dicto)
28− self.assertIn('cookies', params)
29− self.assertIn('sB', params['cookies'])
30− self.assertIn('en', params['cookies']['sB'])
31− self.assertIn('en', params['url'])
32−
33− def test_sanitize_url(self):
34− url = "test.url"
35− self.assertEqual(url, yahoo_news.sanitize_url(url))
36−
37− url = "www.yahoo.com/;_ylt=test"
38− self.assertEqual("www.yahoo.com/", yahoo_news.sanitize_url(url))
39−
40− def test_response(self):
41− self.assertRaises(AttributeError, yahoo_news.response, None)
42− self.assertRaises(AttributeError, yahoo_news.response, [])
43− self.assertRaises(AttributeError, yahoo_news.response, '')
44− self.assertRaises(AttributeError, yahoo_news.response, '[]')
45−
46− response = mock.Mock(text='<html></html>')
47− self.assertEqual(yahoo_news.response(response), [])
48−
49− html = """
50− <ol class=" reg searchCenterMiddle">
51− <li class="first">
52− <div class="compTitle">
53− <h3>
54− <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
55− This is
56− the <b>title</b>...
57− </a>
58− </h3>
59− </div>
60− <div>
61− <span class="cite">Business via Yahoo!</span>
62− <span class="tri fc-2nd ml-10">May 01 10:00 AM</span>
63− </div>
64− <div class="compText">
65− This is the content
66− </div>
67− </li>
68− <li class="first">
69− <div class="compTitle">
70− <h3>
71− <a class="yschttl spt" target="_blank">
72− </a>
73− </h3>
74− </div>
75− <div class="compText">
76− </div>
77− </li>
78− </ol>
79− """
80− response = mock.Mock(text=html)
81− results = yahoo_news.response(response)
82− self.assertEqual(type(results), list)
83− self.assertEqual(len(results), 1)
84− self.assertEqual(results[0]['title'], 'This is the title...')
85− self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
86− self.assertEqual(results[0]['content'], 'This is the content')
87−
88− html = """
89− <ol class=" reg searchCenterMiddle">
90− <li class="first">
91− <div class="compTitle">
92− <h3>
93− <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
94− This is
95− the <b>title</b>...
96− </a>
97− </h3>
98− </div>
99− <div>
100− <span class="cite">Business via Yahoo!</span>
101− <span class="tri fc-2nd ml-10">2 hours, 22 minutes ago</span>
102− </div>
103− <div class="compText">
104− This is the content
105− </div>
106− </li>
107− <li>
108− <div class="compTitle">
109− <h3>
110− <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
111− This is
112− the <b>title</b>...
113− </a>
114− </h3>
115− </div>
116− <div>
117− <span class="cite">Business via Yahoo!</span>
118− <span class="tri fc-2nd ml-10">22 minutes ago</span>
119− </div>
120− <div class="compText">
121− This is the content
122− </div>
123− </li>
124− <li>
125− <div class="compTitle">
126− <h3>
127− <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
128− This is
129− the <b>title</b>...
130− </a>
131− </h3>
132− </div>
133− <div>
134− <span class="cite">Business via Yahoo!</span>
135− <span class="tri fc-2nd ml-10">Feb 03 09:45AM 1900</span>
136− </div>
137− <div class="compText">
138− This is the content
139− </div>
140− </li>
141− </ol>
142− """
143− response = mock.Mock(text=html)
144− results = yahoo_news.response(response)
145− self.assertEqual(type(results), list)
146− self.assertEqual(len(results), 3)
147− self.assertEqual(results[0]['title'], 'This is the title...')
148− self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
149− self.assertEqual(results[0]['content'], 'This is the content')
150− self.assertEqual(results[2]['publishedDate'].year, datetime.now().year)
tests/unit/engines/test_youtube_api.pydeleted−111
@@ -1,111 +0,0 @@
1−from collections import defaultdict
2−import mock
3−from searx.engines import youtube_api
4−from searx.testing import SearxTestCase
5−
6−
7−class TestYoutubeAPIEngine(SearxTestCase):
8−
9− def test_request(self):
10− query = 'test_query'
11− dicto = defaultdict(dict)
12− dicto['pageno'] = 0
13− dicto['language'] = 'fr_FR'
14− params = youtube_api.request(query, dicto)
15− self.assertTrue('url' in params)
16− self.assertTrue(query in params['url'])
17− self.assertIn('googleapis.com', params['url'])
18− self.assertIn('youtube', params['url'])
19− self.assertIn('fr', params['url'])
20−
21− dicto['language'] = 'all'
22− params = youtube_api.request(query, dicto)
23− self.assertFalse('fr' in params['url'])
24−
25− def test_response(self):
26− self.assertRaises(AttributeError, youtube_api.response, None)
27− self.assertRaises(AttributeError, youtube_api.response, [])
28− self.assertRaises(AttributeError, youtube_api.response, '')
29− self.assertRaises(AttributeError, youtube_api.response, '[]')
30−
31− response = mock.Mock(text='{}')
32− self.assertEqual(youtube_api.response(response), [])
33−
34− response = mock.Mock(text='{"data": []}')
35− self.assertEqual(youtube_api.response(response), [])
36−
37− json = """
38− {
39− "kind": "youtube#searchListResponse",
40− "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/YJQDcTBCDcaBvl-sRZJoXdvy1ME",
41− "nextPageToken": "CAUQAA",
42− "pageInfo": {
43− "totalResults": 1000000,
44− "resultsPerPage": 20
45− },
46− "items": [
47− {
48− "kind": "youtube#searchResult",
49− "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/IbLO64BMhbHIgWLwLw7MDYe7Hs4",
50− "id": {
51− "kind": "youtube#video",
52− "videoId": "DIVZCPfAOeM"
53− },
54− "snippet": {
55− "publishedAt": "2015-05-29T22:41:04.000Z",
56− "channelId": "UCNodmx1ERIjKqvcJLtdzH5Q",
57− "title": "Title",
58− "description": "Description",
59− "thumbnails": {
60− "default": {
61− "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/default.jpg"
62− },
63− "medium": {
64− "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
65− },
66− "high": {
67− "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg"
68− }
69− },
70− "channelTitle": "MinecraftUniverse",
71− "liveBroadcastContent": "none"
72− }
73− }
74− ]
75− }
76− """
77− response = mock.Mock(text=json)
78− results = youtube_api.response(response)
79− self.assertEqual(type(results), list)
80− self.assertEqual(len(results), 1)
81− self.assertEqual(results[0]['title'], 'Title')
82− self.assertEqual(results[0]['url'], 'https://www.youtube.com/watch?v=DIVZCPfAOeM')
83− self.assertEqual(results[0]['content'], 'Description')
84− self.assertEqual(results[0]['thumbnail'], 'https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg')
85− self.assertTrue('DIVZCPfAOeM' in results[0]['embedded'])
86−
87− json = """
88− {
89− "kind": "youtube#searchListResponse",
90− "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/YJQDcTBCDcaBvl-sRZJoXdvy1ME",
91− "nextPageToken": "CAUQAA",
92− "pageInfo": {
93− "totalResults": 1000000,
94− "resultsPerPage": 20
95− }
96− }
97− """
98− response = mock.Mock(text=json)
99− results = youtube_api.response(response)
100− self.assertEqual(type(results), list)
101− self.assertEqual(len(results), 0)
102−
103− json = """
104− {"toto":{"entry":[]
105− }
106− }
107− """
108− response = mock.Mock(text=json)
109− results = youtube_api.response(response)
110− self.assertEqual(type(results), list)
111− self.assertEqual(len(results), 0)
tests/unit/engines/test_youtube_noapi.pydeleted−124
@@ -1,124 +0,0 @@
1−# -*- coding: utf-8 -*-
2−from collections import defaultdict
3−import mock
4−from searx.engines import youtube_noapi
5−from searx.testing import SearxTestCase
6−
7−
8−class TestYoutubeNoAPIEngine(SearxTestCase):
9−
10− def test_request(self):
11− query = 'test_query'
12− dicto = defaultdict(dict)
13− dicto['pageno'] = 0
14− dicto['time_range'] = ''
15− params = youtube_noapi.request(query, dicto)
16− self.assertIn('url', params)
17− self.assertIn(query, params['url'])
18− self.assertIn('youtube.com', params['url'])
19−
20− def test_time_range_search(self):
21− dicto = defaultdict(dict)
22− query = 'test_query'
23− dicto['time_range'] = 'year'
24− params = youtube_noapi.request(query, dicto)
25− self.assertIn('&sp=EgIIBQ%253D%253D', params['url'])
26−
27− dicto['time_range'] = 'month'
28− params = youtube_noapi.request(query, dicto)
29− self.assertIn('&sp=EgIIBA%253D%253D', params['url'])
30−
31− dicto['time_range'] = 'week'
32− params = youtube_noapi.request(query, dicto)
33− self.assertIn('&sp=EgIIAw%253D%253D', params['url'])
34−
35− dicto['time_range'] = 'day'
36− params = youtube_noapi.request(query, dicto)
37− self.assertIn('&sp=EgIIAg%253D%253D', params['url'])
38−
39− def test_response(self):
40− self.assertRaises(AttributeError, youtube_noapi.response, None)
41− self.assertRaises(AttributeError, youtube_noapi.response, [])
42− self.assertRaises(AttributeError, youtube_noapi.response, '')
43− self.assertRaises(AttributeError, youtube_noapi.response, '[]')
44−
45− response = mock.Mock(text='<html></html>')
46− self.assertEqual(youtube_noapi.response(response), [])
47−
48− html = """
49− <div></div>
50− <script>
51− window["ytInitialData"] = {
52− "contents": {
53− "twoColumnSearchResultsRenderer": {
54− "primaryContents": {
55− "sectionListRenderer": {
56− "contents": [
57− {
58− "itemSectionRenderer": {
59− "contents": [
60− {
61− "videoRenderer": {
62− "videoId": "DIVZCPfAOeM",
63− "title": {
64− "simpleText": "Title"
65− },
66− "descriptionSnippet": {
67− "runs": [
68− {
69− "text": "Des"
70− },
71− {
72− "text": "cription"
73− }
74− ]
75− }
76− }
77− },
78− {
79− "videoRenderer": {
80− "videoId": "9C_HReR_McQ",
81− "title": {
82− "simpleText": "Title"
83− },
84− "descriptionSnippet": {
85− "simpleText": "Description"
86− }
87− }
88− }
89− ]
90− }
91− }
92− ]
93− }
94− }
95− }
96− }
97− };
98− </script>
99− """
100− response = mock.Mock(text=html)
101− results = youtube_noapi.response(response)
102− self.assertEqual(type(results), list)
103− self.assertEqual(len(results), 2)
104− self.assertEqual(results[0]['title'], 'Title')
105− self.assertEqual(results[0]['url'], 'https://www.youtube.com/watch?v=DIVZCPfAOeM')
106− self.assertEqual(results[0]['content'], 'Description')
107− self.assertEqual(results[0]['thumbnail'], 'https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg')
108− self.assertTrue('DIVZCPfAOeM' in results[0]['embedded'])
109− self.assertEqual(results[1]['title'], 'Title')
110− self.assertEqual(results[1]['url'], 'https://www.youtube.com/watch?v=9C_HReR_McQ')
111− self.assertEqual(results[1]['content'], 'Description')
112− self.assertEqual(results[1]['thumbnail'], 'https://i.ytimg.com/vi/9C_HReR_McQ/hqdefault.jpg')
113− self.assertTrue('9C_HReR_McQ' in results[1]['embedded'])
114−
115− html = """
116− <ol id="item-section-063864" class="item-section">
117− <li>
118− </li>
119− </ol>
120− """
121− response = mock.Mock(text=html)
122− results = youtube_noapi.response(response)
123− self.assertEqual(type(results), list)
124− self.assertEqual(len(results), 0)
tests/unit/engines/unsplash_fixture.jsondeleted−241
@@ -1,241 +0,0 @@
1−{
2− "total": 2,
3− "total_pages": 1,
4− "results": [
5− {
6− "id": "FY8d721UO_4",
7− "created_at": "2018-04-12T14:20:35-04:00",
8− "updated_at": "2018-08-28T20:58:33-04:00",
9− "width": 3891,
10− "height": 5829,
11− "color": "#152C33",
12− "description": "low angle photography of swimming penguin",
13− "urls": {
14− "raw": "https://images.unsplash.com/photo-1523557148507-1b77641c7e7c?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=095c5fc319c5a77c705f49ad63e0f195",
15− "full": "https://images.unsplash.com/photo-1523557148507-1b77641c7e7c?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjEyMDd9&s=74be977849c173d6929636d491a760c3",
16− "regular": "https://images.unsplash.com/photo-1523557148507-1b77641c7e7c?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyMDd9&s=ad65df26970bd010085f0ca25434de33",
17− "small": "https://images.unsplash.com/photo-1523557148507-1b77641c7e7c?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjEyMDd9&s=5d2edfd073c31eb8ee7b305222bdc5a2",
18− "thumb": "https://images.unsplash.com/photo-1523557148507-1b77641c7e7c?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjEyMDd9&s=a9b9e56e63efc6f4611a87ce7e9a48f8"
19− },
20− "links": {
21− "self": "https://api.unsplash.com/photos/FY8d721UO_4",
22− "html": "https://unsplash.com/photos/FY8d721UO_4",
23− "download": "https://unsplash.com/photos/FY8d721UO_4/download",
24− "download_location": "https://api.unsplash.com/photos/FY8d721UO_4/download"
25− },
26− "categories": [],
27− "sponsored": false,
28− "likes": 31,
29− "liked_by_user": false,
30− "current_user_collections": [],
31− "slug": null,
32− "user": {
33− "id": "N4gE4mrG8lE",
34− "updated_at": "2018-10-03T02:51:19-04:00",
35− "username": "gaspanik",
36− "name": "Masaaki Komori",
37− "first_name": "Masaaki",
38− "last_name": "Komori",
39− "twitter_username": "cipher",
40− "portfolio_url": "https://www.instagram.com/cipher/",
41− "bio": null,
42− "location": "Tokyo, JAPAN",
43− "links": {
44− "self": "https://api.unsplash.com/users/gaspanik",
45− "html": "https://unsplash.com/@gaspanik",
46− "photos": "https://api.unsplash.com/users/gaspanik/photos",
47− "likes": "https://api.unsplash.com/users/gaspanik/likes",
48− "portfolio": "https://api.unsplash.com/users/gaspanik/portfolio",
49− "following": "https://api.unsplash.com/users/gaspanik/following",
50− "followers": "https://api.unsplash.com/users/gaspanik/followers"
51− },
52− "profile_image": {
53− "small": "https://images.unsplash.com/profile-fb-1502270358-e7c86c1011ce.jpg?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=9fe12f6d177bd6fdbd56d233a80c01a3",
54− "medium": "https://images.unsplash.com/profile-fb-1502270358-e7c86c1011ce.jpg?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=6ad7d156b62e438ae9dc794cba712fff",
55− "large": "https://images.unsplash.com/profile-fb-1502270358-e7c86c1011ce.jpg?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=13a08a2e72e7d11632410e92bd3a9406"
56− },
57− "instagram_username": "cipher",
58− "total_collections": 0,
59− "total_likes": 406,
60− "total_photos": 196
61− },
62− "tags": [
63− {
64− "title": "animal"
65− },
66− {
67− "title": "water"
68− },
69− {
70− "title": "swim"
71− },
72− {
73− "title": "aquarium"
74− },
75− {
76− "title": "wallpaper"
77− },
78− {
79− "title": "blue"
80− },
81− {
82− "title": "sealife"
83− },
84− {
85− "title": "wildlife"
86− },
87− {
88− "title": "bird"
89− },
90− {
91− "title": "deep sea"
92− },
93− {
94− "title": "fish"
95− },
96− {
97− "title": "water life"
98− }
99− ],
100− "photo_tags": [
101− {
102− "title": "animal"
103− },
104− {
105− "title": "water"
106− },
107− {
108− "title": "swim"
109− },
110− {
111− "title": "aquarium"
112− },
113− {
114− "title": "wallpaper"
115− }
116− ]
117− },
118− {
119− "id": "ayKyc01xLWA",
120− "created_at": "2018-02-16T23:14:31-05:00",
121− "updated_at": "2018-08-28T20:48:27-04:00",
122− "width": 4928,
123− "height": 3264,
124− "color": "#161618",
125− "description": "black and white penguins on ice field",
126− "urls": {
127− "raw": "https://images.unsplash.com/photo-1518840801558-9770b4a34eeb?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=4e107a2bc49ab561ba6272eea2ec725d",
128− "full": "https://images.unsplash.com/photo-1518840801558-9770b4a34eeb?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjEyMDd9&s=f9b1e4d4572ab44efb2cf3d601d2b4d9",
129− "regular": "https://images.unsplash.com/photo-1518840801558-9770b4a34eeb?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyMDd9&s=4430cedb63841f1fe055d5005316cc96",
130− "small": "https://images.unsplash.com/photo-1518840801558-9770b4a34eeb?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjEyMDd9&s=ee73c7af22ce445d408e240821ce07af",
131− "thumb": "https://images.unsplash.com/photo-1518840801558-9770b4a34eeb?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjEyMDd9&s=934302390d383cad8c571905e3a80bac"
132− },
133− "links": {
134− "self": "https://api.unsplash.com/photos/ayKyc01xLWA",
135− "html": "https://unsplash.com/photos/ayKyc01xLWA",
136− "download": "https://unsplash.com/photos/ayKyc01xLWA/download",
137− "download_location": "https://api.unsplash.com/photos/ayKyc01xLWA/download"
138− },
139− "categories": [],
140− "sponsored": false,
141− "likes": 37,
142− "liked_by_user": false,
143− "current_user_collections": [],
144− "slug": null,
145− "user": {
146− "id": "tRb_KGw60Xk",
147− "updated_at": "2018-09-20T11:51:54-04:00",
148− "username": "ghost_cat",
149− "name": "Danielle Barnes",
150− "first_name": "Danielle",
151− "last_name": "Barnes",
152− "twitter_username": null,
153− "portfolio_url": null,
154− "bio": null,
155− "location": null,
156− "links": {
157− "self": "https://api.unsplash.com/users/ghost_cat",
158− "html": "https://unsplash.com/@ghost_cat",
159− "photos": "https://api.unsplash.com/users/ghost_cat/photos",
160− "likes": "https://api.unsplash.com/users/ghost_cat/likes",
161− "portfolio": "https://api.unsplash.com/users/ghost_cat/portfolio",
162− "following": "https://api.unsplash.com/users/ghost_cat/following",
163− "followers": "https://api.unsplash.com/users/ghost_cat/followers"
164− },
165− "profile_image": {
166− "small": "https://images.unsplash.com/profile-fb-1508491082-ae77f53e9ac3.jpg?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=751bf6a557763648d52ffd7e60e79436",
167− "medium": "https://images.unsplash.com/profile-fb-1508491082-ae77f53e9ac3.jpg?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=e46cd1c8713035f045130e1b093b981e",
168− "large": "https://images.unsplash.com/profile-fb-1508491082-ae77f53e9ac3.jpg?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=352eabcf107c3ce95fe51a18485f116b"
169− },
170− "instagram_username": null,
171− "total_collections": 0,
172− "total_likes": 0,
173− "total_photos": 21
174− },
175− "tags": [
176− {
177− "title": "ice"
178− },
179− {
180− "title": "bird"
181− },
182− {
183− "title": "ice field"
184− },
185− {
186− "title": "iceberg"
187− },
188− {
189− "title": "snow"
190− },
191− {
192− "title": "frozen"
193− },
194− {
195− "title": "animal"
196− },
197− {
198− "title": "wildlife"
199− },
200− {
201− "title": "wild"
202− },
203− {
204− "title": "antarctica"
205− },
206− {
207− "title": "sunshine"
208− },
209− {
210− "title": "daylight"
211− },
212− {
213− "title": "wilderness"
214− },
215− {
216− "title": "south pole"
217− },
218− {
219− "title": "flock"
220− }
221− ],
222− "photo_tags": [
223− {
224− "title": "ice"
225− },
226− {
227− "title": "bird"
228− },
229− {
230− "title": "ice field"
231− },
232− {
233− "title": "iceberg"
234− },
235− {
236− "title": "snow"
237− }
238− ]
239− }
240− ]
241−}
utils/makefile.python+2−2
@@ -155,7 +155,7 @@ quiet_cmd_virtualenv = PYENV usage: $ source ./$@/bin/activate
155155 if [ ! -d "./$(PY_ENV)" ];then \
156156 $(VIRTUALENV) $(VIRTUALENV_VERBOSE) $(VTENV_OPTS) $2; \
157157 else \
158− echo " PYENV using virtualenv from $2"; \
158+ echo "PYENV using virtualenv from $2"; \
159159 fi
160160
161161# $2 path to lint
@@ -263,7 +263,7 @@ pydebug: $(PY_ENV)
263263# install / uninstall python objects into virtualenv (PYENV)
264264pyenv-install: $(PY_ENV)
265265 @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) install -e .
266− @echo " ACTIVATE $(call normpath,$(PY_ENV_ACT)) "
266+ @echo "ACTIVATE $(call normpath,$(PY_ENV_ACT)) "
267267
268268pyenv-uninstall: $(PY_ENV)
269269 @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) uninstall --yes .
utils/makefile.sphinxnew file+216
@@ -0,0 +1,216 @@
1+# -*- coding: utf-8; mode: makefile-gmake -*-
2+
3+# You can set these variables from the command line.
4+SPHINXOPTS ?=
5+SPHINXBUILD ?= $(PY_ENV_BIN)/sphinx-build
6+SPHINX_CONF ?= conf.py
7+
8+DOCS_FOLDER ?= docs
9+DOCS_BUILD ?= build/docs
10+DOCS_DIST ?= dist/docs
11+GH_PAGES ?= gh-pages
12+
13+BOOKS_FOLDER ?= docs
14+BOOKS_DIST ?= dist/books
15+
16+ifeq ($(KBUILD_VERBOSE),1)
17+ SPHINX_VERBOSE = "-v"
18+else
19+ SPHINX_VERBOSE =
20+endif
21+
22+## SPHINXVERS variable
23+## ===================
24+##
25+## .. _requirement-specifiers: https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers
26+##
27+## Sphinx version to use, when building documentation. Set this when calling
28+## build target. The default value is empty (install latest), to select a
29+## specific version use a requirement-specifiers_. E.g. to build your target
30+## 'doc' with a select sphinx-doc_ version 1.7.9::
31+##
32+## make SPHINXVERS='==1.7.9' docs
33+##
34+## To build with latest 1.7::
35+##
36+## make SPHINXVERS='>=1.7,<1.8' docs
37+##
38+SPHINXVERS ?=
39+
40+docs-help:
41+ @echo 'makefile.sphinx:'
42+ @echo ' docs-clean - clean intermediate doc objects'
43+ @echo ' $(GH_PAGES) - create & upload github pages'
44+ @echo ' sphinx-pdf - run sphinx latex & pdf targets'
45+ echo ''
46+ @echo ' books/{name}.html : build only the HTML of document {name}'
47+ @echo ' valid values for books/{name}.html are:'
48+ @echo ' $(BOOKS_HTML)' | $(FMT)
49+ @echo ' books/{name}.pdf : build only the PDF of document {name}'
50+ @echo ' valid values for books/{name}.pdf are:'
51+ @echo ' $(BOOKS_PDF) ' | $(FMT)
52+
53+# ------------------------------------------------------------------------------
54+# requirements
55+# ------------------------------------------------------------------------------
56+
57+sphinx-doc: $(PY_ENV)
58+ @echo "PYENV installing Sphinx$(SPHINXVERS)"
59+ $(Q)$(PY_ENV_BIN)/pip install $(PIP_VERBOSE) 'Sphinx$(SPHINXVERS)'
60+
61+sphinx-live: $(PY_ENV)
62+ @echo "PYENV installing Sphinx$(SPHINXVERS)"
63+ $(Q)$(PY_ENV_BIN)/pip install $(PIP_VERBOSE) 'Sphinx$(SPHINXVERS)' sphinx-autobuild
64+
65+
66+PHONY += msg-texlive texlive
67+
68+ifeq ($(shell which xelatex >/dev/null 2>&1; echo $$?), 1)
69+texlive: msg-TeXLive
70+ $(error The 'xelatex' command was not found)
71+else
72+texlive:
73+ @:
74+endif
75+
76+msg-texlive:
77+ $(Q)echo "\n\
78+The TeX/PDF output and the *math* extension require TexLive and latexmk:\n\n\
79+ Make sure you have a updated TeXLive with XeTeX engine installed, grab it\n\
80+ it from https://www.tug.org/texlive or install it from your package manager.\n\n\
81+ Install latexmk from your package manager or visit https://ctan.org/pkg/latexmk\n\n\
82+ Sphinx-doc produce (Xe)LaTeX files which might use additional TeX-packages\n\
83+ and fonts. To process these LaTeX files, a TexLive installation with the\n\
84+ additional packages is required. On debian based OS these requirements\n\
85+ are installed by::\n\n\
86+ sudo -H apt-get install\n\
87+ latexmk\n\
88+ texlive-base texlive-xetex texlive-latex-recommended\n\
89+ texlive-extra-utils dvipng ttf-dejavu\n"
90+
91+# ------------------------------------------------------------------------------
92+# commands
93+# ------------------------------------------------------------------------------
94+
95+# $2 sphinx builder e.g. "html"
96+# $3 path where configuration file (conf.py) is located
97+# $4 sourcedir
98+# $5 dest subfolder e.g. "man" for man pages at $(DOCS_DIST)/man
99+
100+quiet_cmd_sphinx = SPHINX $@ --> file://$(abspath $(DOCS_DIST)/$5)
101+ cmd_sphinx = SPHINX_CONF=$(abspath $4/$(SPHINX_CONF))\
102+ $(SPHINXBUILD) $(SPHINX_VERBOSE) $(SPHINXOPTS)\
103+ -b $2 -c $3 -d $(DOCS_BUILD)/.doctrees $4 $(DOCS_DIST)/$5
104+
105+quiet_cmd_sphinx_autobuild = SPHINX $@ --> file://$(abspath $(DOCS_DIST)/$5)
106+ cmd_sphinx_autobuild = PATH="$(PY_ENV_BIN):$(PATH)" $(PY_ENV_BIN)/sphinx-autobuild $(SPHINX_VERBOSE) --poll -B --host 0.0.0.0 --port 8080 $(SPHINXOPTS)\
107+ -b $2 -c $3 -d $(DOCS_BUILD)/.doctrees $4 $(DOCS_DIST)/$5
108+
109+quiet_cmd_sphinx_clean = CLEAN $@
110+ cmd_sphinx_clean = rm -rf $(DOCS_BUILD) $(DOCS_DIST) $(GH_PAGES)/* $(GH_PAGES)/.buildinfo
111+
112+# ------------------------------------------------------------------------------
113+# targets
114+# ------------------------------------------------------------------------------
115+
116+# build PDF of whole documentation in: $(DOCS_DIST)/pdf
117+
118+PHONY += sphinx-pdf
119+sphinx-pdf: sphinx-latex
120+ $(Q)cd $(DOCS_BUILD)/latex/; make all-pdf
121+ $(Q)mkdir -p $(DOCS_DIST)/pdf
122+ $(Q)cp $(DOCS_BUILD)/latex/*.pdf $(DOCS_DIST)/pdf
123+ @echo "SPHINX *.pdf --> file://$(abspath $(DOCS_DIST)/pdf)"
124+
125+PHONY += sphinx-latex
126+sphinx-latex: texlive sphinx-doc
127+ $(SPHINXBUILD) $(SPHINX_VERBOSE) $(SPHINXOPTS)\
128+ -b latex \
129+ -c $(DOCS_FOLDER) \
130+ -d $(DOCS_BUILD)/.doctrees \
131+ $(DOCS_FOLDER) \
132+ $(DOCS_BUILD)/latex
133+
134+# Sphinx projects, we call them *books* (what is more common). Books are
135+# folders under $(BOOKS_FOLDER) containing a conf.py file. The HTML output goes
136+# to folder $(BOOKS_DIST)/<name> while PDF is placed (BOOKS_DIST)/<name>/pdf
137+
138+BOOKS=$(patsubst $(BOOKS_FOLDER)/%/conf.py,books/%,$(wildcard $(BOOKS_FOLDER)/*/conf.py))
139+
140+# fine grained targets
141+BOOKS_HTML = $(patsubst %,%.html,$(BOOKS))
142+BOOKS_CLEAN = $(patsubst %,%.clean,$(BOOKS))
143+BOOKS_LATEX = $(patsubst %,%.latex,$(BOOKS))
144+BOOKS_PDF = $(patsubst %,%.pdf,$(BOOKS))
145+BOOKS_LIVE = $(patsubst %,%.live,$(BOOKS))
146+
147+$(BOOKS_DIST):
148+ mkdir -p $(BOOKS_DIST)
149+
150+PHONY += $(BOOKS_HTML)
151+$(BOOKS_HTML): sphinx-doc | $(BOOKS_DIST)
152+ SPHINX_CONF=$(patsubst books/%.html,%,$@)/conf.py \
153+ $(SPHINXBUILD) $(SPHINX_VERBOSE) $(SPHINXOPTS)\
154+ -b html \
155+ -c $(DOCS_FOLDER) \
156+ -d $(DOCS_BUILD)/books/$(patsubst books/%.html,%,$@)/.doctrees \
157+ $(patsubst books/%.html,%,$@) \
158+ $(BOOKS_DIST)/$(patsubst books/%.html,%,$@)
159+ @echo "SPHINX $@ --> file://$(abspath $(BOOKS_DIST)/$(patsubst books/%.html,%,$@))"
160+
161+PHONY += $(BOOKS_HTML)
162+$(BOOKS_LIVE): sphinx-live | $(BOOKS_DIST)
163+ PATH="$(PY_ENV_BIN):$(PATH)" \
164+ SPHINX_CONF=$(patsubst books/%.live,%,$@)/conf.py \
165+ $(PY_ENV_BIN)/sphinx-autobuild --poll -B --host 0.0.0.0 --port 8080 $(SPHINX_VERBOSE) $(SPHINXOPTS)\
166+ -b html \
167+ -c $(DOCS_FOLDER) \
168+ -d $(DOCS_BUILD)/books/$(patsubst books/%.live,%,$@)/.doctrees \
169+ $(patsubst books/%.live,%,$@) \
170+ $(BOOKS_DIST)/$(patsubst books/%.live,%,$@)
171+
172+$(BOOKS_PDF): %.pdf : %.latex
173+ $(Q)cd $(DOCS_BUILD)/latex/$(patsubst books/%.pdf,%,$@); make all-pdf
174+ $(Q)mkdir -p $(BOOKS_DIST)/$(patsubst books/%.pdf,%,$@)/pdf
175+ $(Q)cp -v $(DOCS_BUILD)/latex/$(patsubst books/%.pdf,%,$@)/*.pdf $(BOOKS_DIST)/$(patsubst books/%.pdf,%,$@)/pdf
176+ @echo "SPHINX $@ --> file://$(abspath $(BOOKS_DIST)/$(patsubst books/%.pdf,%,$@))/pdf"
177+
178+PHONY += $(BOOKS_LATEX)
179+$(BOOKS_LATEX): sphinx-doc | $(BOOKS_DIST)
180+ SPHINX_CONF=$(patsubst books/%.latex,%,$@)/conf.py \
181+ $(SPHINXBUILD) $(SPHINX_VERBOSE) $(SPHINXOPTS)\
182+ -b latex \
183+ -c $(DOCS_FOLDER) \
184+ -d $(DOCS_BUILD)/books/$(patsubst books/%.latex,%,$@)/.doctrees \
185+ $(patsubst books/%.latex,%,$@) \
186+ $(DOCS_BUILD)/latex/$(patsubst books/%.latex,%,$@)
187+ @echo "SPHINX $@ --> file://$(abspath $(DOCS_BUILD)/latex/$(patsubst books/%.latex,%,$@))"
188+
189+$(BOOKS_CLEAN):
190+ $(Q)rm -rf $(BOOKS_DIST)/$(patsubst books/%.clean,%,$@) \
191+ $(DOCS_BUILD)/books/$(patsubst books/%.clean,%,$@) \
192+ $(DOCS_BUILD)/latex/$(patsubst books/%.clean,%,$@)
193+
194+# github pages
195+
196+PHONY += $(GH_PAGES)
197+$(GH_PAGES)::
198+ $(MAKE) docs
199+ [ -d "gh-pages/.git" ] || git clone $(GIT_URL) gh-pages
200+ -cd $(GH_PAGES); git checkout gh-pages >/dev/null
201+ -cd $(GH_PAGES); git pull
202+ -cd $(GH_PAGES); ls -A | grep -v '.git$$' | xargs rm -rf
203+ cp -r $(DOCS_DIST)/* $(GH_PAGES)/
204+ touch $(GH_PAGES)/.nojekyll
205+ echo "<html><head><META http-equiv='refresh' content='0;URL=index.html'></head></html>" > $(GH_PAGES)/404.html
206+ cd $(GH_PAGES);\
207+ git add --all . ;\
208+ git commit -m "gh-pages: updated" ;\
209+ git push origin gh-pages
210+
211+
212+PHONY += docs-clean
213+docs-clean: $(BOOKS_CLEAN)
214+ $(call cmd,sphinx_clean)
215+
216+.PHONY: $(PHONY)