139 Commits

Author SHA1 Message Date
Bnyro 952896d29e [feat] image results: automatically guess mimetype based on path 2026-06-22 12:46:22 +02:00
Bnyro 4cc32b2457 [fix] kozmonavt: remove pagination and set to inactive by default
Pagination requires a different nextpage query parameter each
day as it seems, so it's not possible to implement this in the Xpath
engine.
2026-06-22 10:06:09 +02:00
Bnyro cce0957f54 [feat] engines: add support for iseek.com (general) 2026-06-22 09:51:57 +02:00
Bnyro 9375c0a6b6 [feat] engines: add netherlands startpagina (general, videos, images, news) 2026-06-22 09:50:19 +02:00
Bnyro a702741e4e [feat] engines: add giphy (images/videos) 2026-06-22 09:49:47 +02:00
Bnyro aeced67249 [feat] engines: add findfiles.net file search engine
FindFiles.net is a specialized file search engine designed to help you search
files online with precision. Unlike traditional search engines that mainly index
web pages, FindFiles focuses on finding real files on the internet - including
PDFs, documents, archives, videos, datasets, and more. [1]

[1] https://findfiles.net
2026-06-22 09:44:27 +02:00
Bnyro 199e03de1d [feat] engines: add kozmonavt.su (general) 2026-06-22 09:42:55 +02:00
Bnyro 9cd2439e5e [feat] engines: add kukei.eu (general) 2026-06-22 09:42:45 +02:00
Bnyro 9f4d8bca02 [feat] engines: add xonaly.com (general) 2026-06-22 09:41:29 +02:00
Bnyro de76a4a39b [feat] engines: add cl0q.com (foss domain search) 2026-06-22 09:41:18 +02:00
Bnyro a85a5e2794 [feat] engines: add unobtanium.rocks (personal websites search) 2026-06-22 09:41:07 +02:00
Bnyro 92abd98a55 [feat] engines: add tusksearch (web, news, videos, images) (#6267)
The code that reads the value of variable `x` from `embed.js`, decodes
it to ASCII and based on that sets `window["tuskheader"]` and `window["tuskkey"]`
is attached below. The only real way to figure out what this is doing is
by stepping through it with the debugger, otherwise it's almost hopeless.

```js
function fe() {
  const B = pe => pe.map(_e => String.fromCharCode(_e)).join(''),
  ae = window,
  o = ae.x;
  if (o?.length) {
    const pe = o.length / 2;
    for (let _e = 0; _e < pe; _e++) ae[B(o[_e])] = B(o[pe + _e]);
    ae.x = void 0
  }
}
```

Minimal script for testing the engine:

```py
import random
from json import loads
import requests

resp = requests.get("https://api.tusksearch.com/revcontent/embed.js")
data = loads(resp.text[6:])

def _decode(text: list[int]) -> str:
    return "".join([chr(x) for x in text])

header = _decode(data[3])
value = _decode(data[4])

resp = requests.get(
    "https://api.tusksearch.com/Search/Web?q=test&p=1&l=center&nextArgs=&prevArgs=",
    # "https://api.tusksearch.com/Search/Image?q=test&p=1&l=center",
    headers={
        header: value,
        'x-lon': str(random.random() * 90),
        'x-lat': str(random.random() * 90),
    },
)
print(resp.text)
```
2026-06-22 09:40:32 +02:00
Bnyro 93e867c6b1 [feat] engine categories: add blogs category
Category for searching personal blogs and websites.
Useful if searching for interesting articles on a topic
rather than the mainstream Wikipedia etc. results.
2026-06-22 09:39:40 +02:00
dependabot[bot] 75c1b1dade [upd] web-client (simple): Bump less (#6289)
Bumps the minor group in /client/simple with 1 update: [less](https://github.com/less/less.js).


Updates `less` from 4.6.4 to 4.6.6
- [Release notes](https://github.com/less/less.js/releases)
- [Changelog](https://github.com/less/less.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/less/less.js/commits/v4.6.6)

---
updated-dependencies:
- dependency-name: less
  dependency-version: 4.6.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 08:03:15 +02:00
Bnyro 097ab64c70 [del] aol: remove engine (eol) (#6299) 2026-06-22 07:32:23 +02:00
dependabot[bot] 0e9f513efc [upd] pypi: Bump the minor group with 5 updates (#6291)
Bumps the minor group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [certifi](https://github.com/certifi/python-certifi) | `2026.5.20` | `2026.6.17` |
| [pylint](https://github.com/pylint-dev/pylint) | `4.0.5` | `4.0.6` |
| [selenium](https://github.com/SeleniumHQ/Selenium) | `4.44.0` | `4.45.0` |
| [sphinxcontrib-programoutput](https://github.com/OpenNTI/sphinxcontrib-programoutput) | `0.19` | `0.20` |
| [basedpyright](https://github.com/detachhead/basedpyright) | `1.39.7` | `1.39.8` |
2026-06-22 07:30:41 +02:00
Bnyro fd42d4fda1 [fix] chatnoir: don't re-use/cache session keys
They're invalidated very quickly, so even caching them for
60 seconds results in a lot of unauthorized access errors.
2026-06-20 21:52:14 +02:00
dependabot[bot] 5c38d2feab [upd] web-client (simple): Bump @types/node in /client/simple (#6290)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.9.3 to 26.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 16:58:47 +02:00
dependabot[bot] 38b678c493 [upd] github-actions: Bump actions/checkout from 6.0.3 to 7.0.0 (#6288)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 16:58:27 +02:00
github-actions[bot] fe1848673f [l10n] update translations from Weblate (#6293)
0f1c1d570 - 2026-06-18 - lugged9922 <lugged9922@noreply.codeberg.org>
81d208307 - 2026-06-18 - Raithlin <raithlin@noreply.codeberg.org>
bf09069e8 - 2026-06-17 - return42 <return42@noreply.codeberg.org>
c010ba929 - 2026-06-17 - return42 <return42@noreply.codeberg.org>
f92ba4e98 - 2026-06-17 - M Alif fadlan <maliffadlan@gmail.com>
442e504e2 - 2026-06-17 - return42 <return42@noreply.codeberg.org>
e2ffb2275 - 2026-06-17 - return42 <return42@noreply.codeberg.org>
cc26d0794 - 2026-06-17 - return42 <return42@noreply.codeberg.org>
9639f4e84 - 2026-06-17 - return42 <return42@noreply.codeberg.org>
63059d4e7 - 2026-06-15 - AndersNordh <andersnordh@noreply.codeberg.org>
460c5260f - 2026-06-15 - kratos <makesocialfoss32@keemail.me>
b212184d9 - 2026-06-16 - ghose <ghose@noreply.codeberg.org>
c9ac8e6d7 - 2026-06-15 - AndersNordh <andersnordh@noreply.codeberg.org>
cc1f5ab59 - 2026-06-15 - Fjuro <fjuro@noreply.codeberg.org>
84f985a9f - 2026-06-14 - Outbreak2096 <outbreak2096@noreply.codeberg.org>
bdb7e25bc - 2026-06-13 - SomeTr <sometr@noreply.codeberg.org>
c3eac4c37 - 2026-06-14 - Stephan-P <stephan-p@noreply.codeberg.org>
d94ab494b - 2026-06-13 - Priit Jõerüüt <jrtcdbrg@noreply.codeberg.org>
3387bab27 - 2026-06-13 - gallegonovato <gallegonovato@noreply.codeberg.org>
2026-06-19 15:11:48 +02:00
Bnyro 8b10095e8a [fix] settings.yml: explicitely set category for xpath engines (ayo, gabanza, zapmeta, abcnyheter) (#6282) 2026-06-19 09:10:27 +02:00
Jayant Sharma b5ef7ec8f3 [fix] calculator: move math.parse inside try-catch (#6278) (#6280)
* [fix] calculator: move math.parse inside try-catch (#6278)

* build static

---------

Co-authored-by: Ivan Gabaldon <igabaldon@inetol.net>
2026-06-18 17:36:47 +02:00
Bnyro bd73cc09ea [feat] engines: add support for search.ch/web (Swiss) 2026-06-18 14:02:52 +02:00
Butui Hu 4dfdc822cf [fix] engines: chinaso: handle empty upstream results gracefully (#6266)
Signed-off-by: Hu Butui <hot123tea123@gmail.com>
2026-06-17 19:36:22 +02:00
Ivan Gabaldon 502c820a25 [fix] container: setup minimal (#6268)
Start minimal, use defaults, and extend later on. The templates are no longer
checked for changes, which was confusing and annoying after a while.

See: https://github.com/searxng/searxng/issues/6261#issuecomment-4716008282
2026-06-16 15:32:47 +02:00
Markus Heiser 4fb49b4498 [chore] add DeprecationWarning for obsolete engine.about.language property (#6265)
The old property should still be supported for a transitional period; the
reasons for this can be seen from the discussion in [1] / the further procedure
is also discussed there.

[1] https://github.com/searxng/searxng/issues/6261

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-16 10:31:21 +02:00
Markus Heiser cf1410af8d [fix] set language_support for engines with languages in traits (#6258)
In the past, the engine option ``language_support`` was not consistently
maintained; with this patch, a ValueError is now thrown if an engine has
languages in its traits but language_support is not set to True.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-15 10:52:00 +02:00
Markus Heiser 6c9dcd4242 [chore] complete and normalize the attributes of engine objects (#6258)
Drop outdated engine attributes: supported_languages, language_aliases

Complete, normalize and document the type definitions for the engine-module and
engine-class.

For the ``engine.about`` section of the configuration, a type check is performed
based on structure ``searx.enginelib.EngineAbout``.

The property ``engine.about.language`` no longer exists; existing values have
been migrated to ``engine.language``.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-15 10:52:00 +02:00
Bnyro b3e08f2a44 [feat] engines: add searchzee engine (general, news)
The results seem to be from Brave (i.e. they are exactly
the same). But it doesn't have any strict rate-limits,
so that's nice.

News support time ranges, but apart from that, unfortunately it doesn't
support any advanced features like safesearch or languages.
2026-06-14 09:59:39 +02:00
Bnyro a857041afc [feat] engines: add support for search.ayo.de 2026-06-14 09:32:58 +02:00
Bnyro 31a8a22aa6 [feat] engines: add German tonline engine (general, news, images, videos) (#6250)
T-Online_ is a German news portal.

It gets its web results from Google, image results from Flickr and videos results
from YouTube.

For images and videos, it additionally returns result from its
news catalog. However, for pagination we have to specify the result
type (e.g. either videos from YouTube or from T-Online), so we use
flickr/youtube there instead of tonline because the tonline results
are usually irrelevant.
2026-06-14 08:46:07 +02:00
Bnyro a29cda858c [feat] engines: add luxxle (general, news, images, videos)
Add support for https://luxxle.com

Localization is not yet supported because it doesn't seem to work on their
website either, no matter which language I select, it only returns English web
results
2026-06-13 20:39:31 +02:00
Bnyro 2e10a2f614 [feat] engines: add rawweb engine (foss, hand-indexed blogs) (#6234)
RawWeb is a search engine for personal websites / blog posts.
It has its own index and the personal websites were selected
by hand. Results are quite good for what it is imo. [^1]

[^1]: https://github.com/0x2E/RawWeb.org
2026-06-13 19:09:58 +02:00
Bnyro 2100eb04e1 [feat] engines: add reloado engine (general, german) (#6233)
- adds support for https://reloado.com (german)
- as it has its own index, the results are hit or miss and mostly German, 
  but still worth integrating imo
2026-06-13 19:06:18 +02:00
Bnyro c58391d673 [feat] engines: add fastbot engine (general) (#6232)
- adds support for https://fastbot.de
- the results are really fast and mostly in English (even though it's a German
  engine)
2026-06-13 19:04:39 +02:00
Bnyro c3284c8238 [chore] make data.traits (#6211) 2026-06-13 18:37:57 +02:00
Bnyro 290d3e0c6a [feat] engines: add privacywall engine (#6211)
- add https://privacywall.org support
- the engine seems to use the Bing index, but not 100% sure
- it claims to be privacy friendly, but it's not really by itself [1]

[1]: https://discuss.privacyguides.net/t/how-is-privacy-wall-search-engine/29486
2026-06-13 18:37:57 +02:00
Bnyro 0608dfa4d1 [feat] autocomplete: add privacywall autocompleter (#6211) 2026-06-13 18:37:57 +02:00
Bnyro 1184b3212f [feat] engines: add podchaser podcast engine (#6202)
- add podchaser podcast engine
- the motivation is that podcastindex had to be removed, see #6140
2026-06-13 18:04:21 +02:00
Bnyro 65e0e4c069 [feat] engines: add vuhuv engine (#6196) 2026-06-13 17:52:43 +02:00
Bnyro d14fa1f6e2 [chore] data: add resulthunter engine traits 2026-06-13 17:21:52 +02:00
Bnyro 2d248704fa [feat] engines: add resulthunter 2026-06-13 17:21:52 +02:00
Markus Heiser 3096b1218f [mod] add type definitions for engine's "about" section (#6231)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-13 17:05:59 +02:00
Bnyro 82a8a90230 [feat] engines: add abcnyheter engine (general, norway) (#6231)
Add support for https://startsiden.abcnyheter.no, a netherlandish search engine
that probably uses Google or Bing? idk it also returns English results, but
e.g. ``test`` returns mostly results from netherlands.
2026-06-13 17:05:59 +02:00
Bnyro e3d4fbe570 [feat] engines: add s1search general engine (#6186)
S1Search provides various different search services, which all seem
to be somewhat based on Google and Yahoo. The site looks kinda suspicious,
but the results are fine.

You can find a list of their engines by using a subdomain finder like
https://web-toolbox.dev/en/tools/subdomain-lookup and search for `s1search.co`.
2026-06-13 14:18:04 +02:00
Bnyro 031747f29e [feat] engines: add chatnoir general engine (#6183)
Chatnoir is an open source search engine developed by universities, based on
CommonCrawl (and others).  It's uncommented by default - we don't want to
overload the universities with bot traffic that targets SearXNG (sad truth why
we can't have nice things anymore)
2026-06-13 13:52:01 +02:00
Markus Heiser e3bd7f5df1 [mod] image results: add list of alternative formats (#6153)
* [mod] template images.html: reformatted for readability (no func change)

In preparation for upcoming changes, the template is being reformatted for
better readability; no functional changes are being made.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>

* [mod] image results: add list of alternative formats

To test alternatives formats apply patch from below, query ``!flaticon bmw`` and
open the detail view for the image.

    diff --git a/searx/engines/flaticon.py b/searx/engines/flaticon.py
    index 06b6a8e25..d88388705 100644
    --- a/searx/engines/flaticon.py
    +++ b/searx/engines/flaticon.py
    @@ -8,7 +8,7 @@ from urllib.parse import urlencode

     import typing as t

    -from searx.result_types import EngineResults
    +from searx.result_types import EngineResults, ImageRef

     if t.TYPE_CHECKING:
         from searx.extended_types import SXNG_Response
    @@ -61,6 +61,14 @@ def response(resp: "SXNG_Response"):
                     thumbnail_src=_fix_url(result["png"]),
                     img_src=_fix_url(result["png512"]),
                     author=result["team_name"],
    +                formats=[
    +                    ImageRef(label="PNG 100x100", url="https://example.org/test.png", subtype="png"),
    +                    ImageRef(label="SVG", url="https://example.org/test.svg", subtype="svg+xml"),
    +                    ImageRef(url="https://example.org/test.jpg", subtype="jpeg"),
    +                    ImageRef(url="https://example.org/test.bmp", subtype="bmp"),
    +                    ImageRef(url="https://example.org/test.ico", subtype="x-icon"),
    +                    ImageRef(url="https://example.org/test.tif", subtype="tiff"),
    +                ],
                 )
             )

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>

---------

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-13 13:28:05 +02:00
Bnyro b48205b384 [fix] tiger: crashes on empty result (#6251)
e.g. when searching for "!tiger pottering github", it crashes.
not really sure why - the problem is that the HTML doesn't
really uses descriptive classes or ids, only Tailwind,
so it's very hard to select only the results HTML.
2026-06-13 09:37:43 +02:00
Bnyro 8522638b00 [fix] duckduckgo web: result title contains html (#6253) 2026-06-13 09:35:14 +02:00
dependabot[bot] ab81c77533 [upd] pypi: Bump the minor group with 2 updates (#6247)
Bumps the minor group with 2 updates: [granian](https://github.com/emmett-framework/granian) and [basedpyright](https://github.com/detachhead/basedpyright).


Updates `granian` from 2.7.5 to 2.7.6
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.7.5...v2.7.6)

Updates `basedpyright` from 1.39.6 to 1.39.7
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.39.6...v1.39.7)

---
updated-dependencies:
- dependency-name: granian
  dependency-version: 2.7.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: basedpyright
  dependency-version: 1.39.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 22:42:26 +02:00
dependabot[bot] cc196f2a5b [upd] web-client (simple): Bump the minor group across 1 directory with 4 updates (#6249)
Bumps the minor group with 4 updates in the /client/simple directory: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [sharp](https://github.com/lovell/sharp) and [stylelint](https://github.com/stylelint/stylelint).

Updates `@biomejs/biome` from 2.4.16 to 2.5.0
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.0/packages/@biomejs/biome)

Updates `@types/node` from 25.9.1 to 25.9.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `sharp` from 0.34.5 to 0.35.1
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.1)

Updates `stylelint` from 17.12.0 to 17.13.0
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/17.12.0...17.13.0)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 25.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: sharp
  dependency-version: 0.35.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: stylelint
  dependency-version: 17.13.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 20:40:51 +02:00
dependabot[bot] dd3022d680 [upd] web-client (simple): Bump sort-package-json in /client/simple (#6246)
Bumps [sort-package-json](https://github.com/keithamus/sort-package-json) from 3.6.1 to 4.0.0.
- [Release notes](https://github.com/keithamus/sort-package-json/releases)
- [Commits](https://github.com/keithamus/sort-package-json/compare/v3.6.1...v4.0.0)

---
updated-dependencies:
- dependency-name: sort-package-json
  dependency-version: 4.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 19:51:22 +02:00
Bnyro de8a3de15a [feat] engines: add support for Kagi (requires API key) 2026-06-12 14:48:47 +02:00
Bnyro 4dd0bf4867 [fix] fireball: all results are shown in general category 2026-06-11 17:30:46 +02:00
Bnyro 1957876dd6 [feat] engines: add dogpile (general, news, images, videos)
Add support for the Dogpile search engine, found at:

https://seirdy.one/posts/2021/03/10/search-engines-with-own-indexes/

It seems to use the same index as startpage because results are similar and they
share the ``qadf`` (Safe-Search) request parameter.
2026-06-11 16:09:13 +02:00
Bnyro ab13451086 [mod] odysee: move format_duration helper into utils.py 2026-06-11 16:09:13 +02:00
Bnyro a1490676e3 [mod] fireball: small fixup from code review (#6240)
Co-authored-by: Markus Heiser <markus.heiser@darmarIT.de>
2026-06-11 12:09:57 +02:00
Bnyro 3a382cb3f3 [chore] helix config: enable pyling and use black via pylsp 2026-06-11 11:03:38 +02:00
Ivan Gabaldon 9d9d605b15 [fix] ci: use install buildhost script (#6105) 2026-06-11 08:23:37 +02:00
Bnyro de03f4eb11 [feat] engines: add fireball engine (general, news, videos) 2026-06-10 21:00:49 +02:00
Markus Heiser 00f7c68a6f [chore] drop emacs' obsolete .dir-locals template (#6236)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-10 17:38:19 +02:00
Bnyro 41c98b3b41 [chore] devops: add languages config for helix editor
The default Helix configuration for Python is different,
so the pylint warnings aren't shown and the formatter
re-formats files by accident when you edit an existing file.

Therefore, this commit adds `python` language configuration
to ease developing SearXNG with Helix Editor [^1].

[^1]: https://helix-editor.com
2026-06-10 17:38:01 +02:00
Bnyro f4c63c8eb0 [feat] engines: add duckduckgo web engine as alternative to html.duckduckgo.com
html.duckduckgo.com captchas all my IPs very fast. I figured out that using
duckduckgo.com works even if html.duckduckgo.com is captcha-ed, hence adding
support for duckduckgo.com's general web search here.

This implementation fetches the link to the first API page
(i.e. ``links.duckduckgo.com/d.js?...``) from duckduckgo.com and uses the ``n``
parameter of the API to fetch all subsequent pages.

This also means that it's not possible to immediately search for the third
page - the first and the second page would need to be loaded first.

The reason why we can't just normally use the `vqd` value is that the API URLs
require an additional parameter `dp` which seems generated at server-side, so we
can't build it ourselves and must scrape it from the HTML pages.
2026-06-10 16:49:56 +02:00
Markus Heiser 26801e92af [fix] sqlitedb: create DB Schema (DDL) during app initialization (hardening) (#6187)
The initialization of the DB schema ("base schema") has so far been done on
demand, which causes race conditions with competing threads and processes.

The DDL statements for creating the "base schema" are now executed as part of
the initialization of the app.

Further improvements were made to harden the database applications:

- Wikidata & Radio-Browser engine perform their initialization only once (so far
  the initialization was carried out in each thread/process).

- If multiple processes try to set DB's WAL mode when opening the DB at the same
  time, this usually leads to another race condition, which is now also caught.

Related:

- https://github.com/searxng/searxng/issues/6181#issuecomment-4586705

Closes: #6181

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-10 15:48:49 +02:00
Bnyro f3fab143be [feat] engines: add tiger.ch engine
Add support for https://tiger.ch (general, news)

It is disabled and inactive by default because it's just a metasearch engine
like SearXNG is, so it's mostly useful for bypassing rate-limits on other
engines: (it has its own German index, but it's not that great) in theory it
supports different locales, but I was too lazy to implement that (I only need
German and English results anyways, which are returned by default...)
2026-06-08 13:35:13 +02:00
Bnyro 72a827ae93 [fix] yep: send Sec-Fetch headers to bypass "access denied" (#6223)
Avoids yep's botblocking by sending Sec-Fetch-* headers (as the browser does).
2026-06-08 10:55:17 +02:00
Bnyro 6ca9d3784c [feat] engines: add seek-ninja general engine (#6217)
Add support for https://seek.ninja (general)

It's very slow because the engine uses Server-side events, that incrementally
send data in their HTTP response [1].

I.e. we wait for the end of the response (7+ seconds), even though the results
data arrives within a few seconds -> it's very slow, because SearXNG wants to
get the full response body before it calls the `response(resp)` method

We could use httpx-sse [2], but I'm not sure how to integrate this into SearXNG
and if it's worth it

[1] https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/
[2] https://github.com/florimondmanca/httpx-sse
2026-06-08 07:09:06 +02:00
Bnyro 63f264220b [feat] engines: add heexy engine (general, images) (#6218) 2026-06-08 05:54:35 +02:00
Austin-Olacsi 41fcf0be4b [fix] aol engine uses wikidata id for C++ (#6221) 2026-06-08 05:32:26 +02:00
Bnyro 86903a2c66 [fix] flaticon: crash if result tag has no name (#6219) 2026-06-07 14:16:44 +02:00
Markus Heiser 70de3cc561 Revert "[fix] no such table during engine init (#6185)" (#6215)
This reverts commit 9d49a9f344.
2026-06-07 09:23:35 +02:00
Bnyro 51b6fd4f23 [del] karmasearch: remove engine (cloudflared) (#6213)
The engine is using very aggressive Cloudflare blocking for
a while now, no matter if using a normal browser like Firefox
or not.

Closes: https://github.com/searxng/searxng/issues/5976
2026-06-07 06:49:09 +02:00
Brock Vojkovic 9d49a9f344 [fix] no such table during engine init (#6185) 2026-06-07 06:04:12 +02:00
Bnyro e260a732c8 [fix] online engine processor: accept language headers doesn't get sent for 'all' language 2026-06-06 18:24:16 +02:00
Markus Heiser 0429198415 [mod] swisscows WEB: ignore video results from the first page
On the first page of the WEB search, there are, among other things, sections for
videos and news.  The video results from these sections should not be used as
results in the WEB search of SearXNG.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-06 18:04:19 +02:00
Markus Heiser e7cf57e9ae [mod] swisscows engines: add language / region support
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-06-06 18:04:19 +02:00
Bnyro ed369ac0ec [feat] engines: add support for swisscows general 2026-06-06 18:04:19 +02:00
Bnyro 94bdbb5c63 [feat] engines: add support for swisscows videos 2026-06-06 18:04:19 +02:00
Bnyro 465b5229c6 [feat] engines: add swisscows news engine 2026-06-06 18:04:19 +02:00
Bnyro cbf97fd262 [feat] engines: add swisscows images engine
The implementation is basically a 1:1 port of the reverse engineered
swisscows JavaScript code. (it's been obfuscated, so I've restructured it
and made the variable names idiomatic instead of obfuscated var names like "a", "o", "i")

```js
/*
e: "/v5/images/search"
t: {
	itemsCount: "50"
	locale: "de-DE"
	offset: "50"
	query: "test"
	spellcheck: "true"
}
*/
// HASH library used: https://github.com/h2non/jshashes
function generateNonceAndSignature(queryParams, urlPath) {
  // urlPath = "/v5/images/search"
  // sort keys alphabetically and join to query string
  let queryStringSorted = '?' + U().stringify(queryParams, {
    arrayFormat: 'repeat',
    allowDots: !0
  }).split('&').map(e => {
    let[key, value] = e.split('=');
    return [key, decodeURIComponent(value)]
  }).sort((e, t) => e[0].localeCompare(t[0])).map(e => e.join('=')).join('&');

  function caesarShift(str, offset = 13) {
      const alphabet = 'abcdefghijklmnopqrstuvwxyz';
      let result = [];
      for (let a = 0; a < str.length; a++) {
        let c = str[a],
        alphabetIndex = alphabet.indexOf(c.toLowerCase());
        if ( - 1 !== alphabetIndex) {
          alphabetIndex += offset;
          while (alphabetIndex >= alphabet.length) alphabetIndex -= alphabet.length;
          c = c === c.toUpperCase() ? alphabet[alphabetIndex] : alphabet[alphabetIndex].toUpperCase()
        }
        result.push(c)
      }
      return result.join('')
    }
  const r = new (sha256Instance()).SHA256;
  const random = randomString(32);
  const randomShifted = caesarShift(random);
  let to_hash = [urlPath, queryStringSorted, randomShifted].join('');
  let signature = r.b64(to_hash);
  signature = signature.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
  return {
    nonce: random,
    signature: signature
  }
}

function randomString(length) {
  let t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~',
  n = '';
  for (let r = 0; r < length; r++) n += t.charAt(Math.floor(Math.random() * t.length));
  return n
}
```
2026-06-06 18:04:19 +02:00
dependabot[bot] 37187dc2d8 [upd] web-client (simple): Bump the minor group across 1 directory with 5 updates (#6169)
Bumps the minor group with 5 updates in the /client/simple directory:

| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.4.15` | `2.4.16` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.8.0` | `25.9.1` |
| [edge.js](https://github.com/edge-js/edge) | `6.5.0` | `6.5.1` |
| [stylelint](https://github.com/stylelint/stylelint) | `17.11.1` | `17.12.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.13` | `8.0.16` |

Updates `@biomejs/biome` from 2.4.15 to 2.4.16
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.16/packages/@biomejs/biome)

Updates `@types/node` from 25.8.0 to 25.9.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `edge.js` from 6.5.0 to 6.5.1
- [Release notes](https://github.com/edge-js/edge/releases)
- [Changelog](https://github.com/edge-js/edge/blob/6.x/CHANGELOG.md)
- [Commits](https://github.com/edge-js/edge/compare/v6.5.0...v6.5.1)

Updates `stylelint` from 17.11.1 to 17.12.0
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/17.11.1...17.12.0)

Updates `vite` from 8.0.13 to 8.0.16
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.4.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 25.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: edge.js
  dependency-version: 6.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: stylelint
  dependency-version: 17.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: vite
  dependency-version: 8.0.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 16:26:27 +02:00
dependabot[bot] 2f049cb037 [upd] github-actions: Bump actions/checkout from 6.0.2 to 6.0.3 (#6204)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 16:17:01 +02:00
dependabot[bot] eb39bc0dc1 [upd] github-actions: Bump github/codeql-action from 4.36.0 to 4.36.2 (#6203)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.0 to 4.36.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/7211b7c8077ea37d8641b6271f6a365a22a5fbfa...8aad20d150bbac5944a9f9d289da16a4b0d87c1e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 16:16:35 +02:00
dependabot[bot] 007a4e2155 [upd] pypi: Bump typer from 0.26.3 to 0.26.7 in the minor group (#6205)
Bumps the minor group with 1 update: [typer](https://github.com/fastapi/typer).


Updates `typer` from 0.26.3 to 0.26.7
- [Release notes](https://github.com/fastapi/typer/releases)
- [Changelog](https://github.com/fastapi/typer/blob/master/docs/release-notes.md)
- [Commits](https://github.com/fastapi/typer/compare/0.26.3...0.26.7)
2026-06-05 11:54:28 +02:00
github-actions[bot] 13ce187e64 [l10n] update translations from Weblate (#6206)
19b2047a9 - 2026-05-30 - daemul72 <daemul72@noreply.codeberg.org>
2026-06-05 11:52:35 +02:00
Bnyro 26fa181b84 [feat] gmx: detect captchas 2026-06-05 08:07:30 +02:00
Bnyro 0f35ef7cd6 [feat] json engine: add option to not send page num on first page 2026-06-05 08:04:49 +02:00
Bnyro b1ae576b2d [fix] xpath engine: add missing send_page_num_on_first_page docstring 2026-06-05 08:04:49 +02:00
Bnyro e6559c9ad6 [fix] gabanza: result URLs are invalid 2026-06-04 08:55:19 +02:00
Bnyro 5bae05514b [feat] engines: add zapmeta general search engine 2026-06-03 22:38:59 +02:00
Bnyro 00ca5776f2 [feat] engines: add gabanza general engine 2026-06-03 22:38:23 +02:00
Bnyro 577f5f2f30 [fix] online engines: send_accept_language_header is sent even if disabled 2026-06-03 22:37:13 +02:00
Bnyro 253dc86c10 [fix] duckduckgo: image requests get blocked 2026-06-03 22:37:13 +02:00
Bnyro 3066bc19eb [fix] public domain image archive: fails to extract API url 2026-06-03 22:35:21 +02:00
Austin-Olacsi e964708c00 [fix] bilibili engine: fix Referer and add Accept HTTP header (#6189) 2026-06-02 06:06:31 +02:00
Bnyro 7159b8aed3 [feat] marginalia: add support for pagination 2026-05-31 12:54:53 +02:00
Bnyro 246f5a5499 [mod] svgrepo: remove engine
- SVGRepo uses Cloudflare for every session, no matter
if you're opening it in a browser or not
2026-05-31 12:54:32 +02:00
vojkovic 300695de5c [fix] crash when lock is omitted 2026-05-31 01:37:37 +08:00
Markus Heiser bd863f16b1 [build] /static 2026-05-30 22:43:50 +08:00
Markus Heiser 4ac822fd7f [mod] typification of the preference settings
no functional change / except the missing online doc which is now available::

    $ make docs.live
    $ xdg-open "http://127.0.0.1:8000/admin/settings/settings_preferences.html"

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-30 22:43:50 +08:00
vojkovic e1d25c5078 [mod] enable image proxy for public instances 2026-05-30 22:43:50 +08:00
dependabot[bot] 01159b82fe [upd] pypi: Bump the minor group with 3 updates (#6164)
Bumps the minor group with 3 updates: [granian](https://github.com/emmett-framework/granian), [basedpyright](https://github.com/detachhead/basedpyright) and [typer](https://github.com/fastapi/typer).


Updates `granian` from 2.7.4 to 2.7.5
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.7.4...v2.7.5)

Updates `basedpyright` from 1.39.5 to 1.39.6
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.39.5...v1.39.6)

Updates `typer` from 0.25.1 to 0.26.3
- [Release notes](https://github.com/fastapi/typer/releases)
- [Changelog](https://github.com/fastapi/typer/blob/master/docs/release-notes.md)
- [Commits](https://github.com/fastapi/typer/compare/0.25.1...0.26.3)
2026-05-30 11:33:10 +02:00
Bnyro 780ee32564 [fix] pexels: fix engine crashes with SearxEngineAccessDeniedException 2026-05-29 22:03:22 +02:00
github-actions[bot] 217c9a1597 [l10n] update translations from Weblate (#6170)
207f98ecc - 2026-05-26 - mustafa-phd <mustafa-phd@noreply.codeberg.org>
3b51fbca7 - 2026-05-25 - Amirkhandrend-Nicest-XII <amirkhandrend-nicest-xii@noreply.codeberg.org>

Co-authored-by: searxng-bot <searxng-bot@users.noreply.github.com>
2026-05-29 14:47:43 +02:00
dependabot[bot] 70e810bd7b [upd] github-actions: Bump docker/setup-qemu-action from 4.0.0 to 4.1.0 (#6166)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/ce360397dd3f832beb865e1373c09c0e9f86d70a...06116385d9baf250c9f4dcb4858b16962ea869c3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 14:42:48 +02:00
dependabot[bot] baab1c160a [upd] github-actions: Bump github/codeql-action from 4.35.5 to 4.36.0 (#6167)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.5 to 4.36.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/9e0d7b8d25671d64c341c19c0152d693099fb5ba...7211b7c8077ea37d8641b6271f6a365a22a5fbfa)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 14:42:28 +02:00
dependabot[bot] dd4664e03a [upd] github-actions: Bump docker/login-action from 4.1.0 to 4.2.0 (#6168)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/4907a6ddec9925e35a0a9e82d7399ccc52663121...650006c6eb7dba73a995cc03b0b2d7f5ca915bee)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 14:41:49 +02:00
github-actions[bot] 4ea5c57a84 [data] update searx.data - update_firefox_version.py (#6161) 2026-05-29 06:25:18 +02:00
github-actions[bot] 6917395dc1 [data] update searx.data - update_wikidata_units.py (#6160) 2026-05-29 06:24:50 +02:00
github-actions[bot] 128e28fe3f [data] update searx.data - update_gsa_useragents.py (#6158) 2026-05-29 06:24:11 +02:00
github-actions[bot] fb3ed5b081 [data] update searx.data - update_ahmia_blacklist.py (#6159) 2026-05-29 06:22:55 +02:00
github-actions[bot] 4ebe6b90d6 [data] update searx.data - update_currencies.py (#6162) 2026-05-29 06:21:55 +02:00
github-actions[bot] 0657217a3e [data] update searx.data - update_engine_descriptions.py (#6163) 2026-05-29 06:20:57 +02:00
Bnyro 0037d43d87 [fix] aol: disable http2 to prevent request fingerprinting (#6149) 2026-05-26 12:15:35 +02:00
Bnyro f5be39e245 [mod] podcastindex: remove engine (#6140)
PodcastIndex.org started using a Proof-of-Work JavaScript
challenge whose results are sent as `X-Pow-*` request headers.
Although it is technically possible to re-implement the
PoW challenge in Python, it's likely impossible to maintain
because

- the actual Proof of Concept logic might change very often

- the whole idea of the Proof of Work challenge is to use
  a "big" amount of resources (about 1s on my PC); so executing the challenge
  would almost block all other work on the SearXNG instance

At first glance, the challenge looks very similar to what
Anubis does, because it also uses SHA256 hashes.
2026-05-26 11:53:20 +02:00
Bnyro 1574939441 [fix] json, xpath engine: rename safe_search_support option to safesearch (#6143) 2026-05-26 11:38:07 +02:00
Markus Heiser f1a22dec9e [fix] disable qwant engine / the rate-limits are just very strict (#6148)
Qwant is set to inactive by default due to its strict rate-limits

Related:

- https://github.com/searxng/searxng/pull/6127

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-26 11:05:06 +02:00
Markus Heiser 3db8b424a8 [mod] engine flaticon: migrate from LegacyResult to Image (#6142)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-26 09:19:17 +02:00
Markus Heiser a16a3dedb4 [build] /static (#6142) 2026-05-26 09:19:17 +02:00
Markus Heiser c629dd4f3c [mod] typification of SearXNG: add new result type Image (#6142)
- Python class:   searx/result_types/image.py
- Jinja template: searx/templates/simple/result_templates/images.html
- CSS (less)      client/simple/src/less/result_types/image.less

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-26 09:19:17 +02:00
Markus Heiser 28ef4f7447 [mod] hardening of the Result.filter_urls() method (#6117)
Exceptions in the execution of the callback must be caught / ignored and logged
on the ERROR log.

To test, apply this patch to provoke a ValueError exception::

    diff --git a/searx/data/tracker_patterns.py b/searx/data/tracker_patterns.py
    index ed4415bce..695ed05d2 100644
    --- a/searx/data/tracker_patterns.py
    +++ b/searx/data/tracker_patterns.py
    @@ -114,6 +114,7 @@ class TrackerPatternsDB:
             Returns bool ``True`` to use URL unchanged (``False`` to ignore URL).
             If URL should be modified, the returned string is the new URL to use.
             """
    +        raise ValueError("test callback exceptions")

             new_url = url
             parsed_new_url = urlparse(url=new_url)

Start a `make run` instance and query for example `amazon` .. have a look at the
ERROR log:

    ERROR   searx.result_types: filter_urls (field 'url'): ignore ValueError('test callback exceptions') from callback searx/data/tracker_patterns.py:117

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-25 18:12:40 +02:00
Bnyro cb4b70ac50 [fix] qwant news: results don't have any descriptions (#6135)
BTW: fix some typecast issues
2026-05-25 18:04:14 +02:00
Markus Heiser e29e861e2c [fix] bing engines - geoblocking in China (#6134)
In regions like China, the domain must be adjusted to avoid a redirect.

- https://github.com/searxng/searxng/issues/5243
- https://github.com/searxng/searxng/pull/5324
- https://github.com/searxng/searxng/pull/6133

Suggested / tested by @hubutui in https://github.com/searxng/searxng/pull/6133#issuecomment-4534637069

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-25 17:05:08 +02:00
Markus Heiser 89b89a88fe [mod] engine: MyMemory Translated - typification and html to text (#6132)
The implementation is normalized, type annotations are applied, and the results
are freed from the HTML markup (which is partially present).

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-25 16:38:06 +02:00
Bnyro 46071a011a [mod] qwant: remove web lite and improve request spoofing (#6127)
- https://lite.qwant.com seems to be dead.
- The request parameters were changed to match the ones from the Qwant website.
- Qwant is now set to inactive by default due to its strict rate-limits
2026-05-25 15:46:40 +02:00
Bnyro b0d8af96bf [feat] engines: add flaticon icons engine (#6122) 2026-05-25 13:41:44 +02:00
Markus Heiser dd27fce3b7 [unbload] drop meaningless field `number_of_results_xpath` from results (#6130)
In the result-list, the ``number_of_results`` indicate the number of hits in the
Index, they do not indicate how many results are in the answer.

In the past, search engines such as google or ddg had an indication on the first
page of a search term of how many hits there were for this term in total in
their index.

This info was added up in SearXNG and delivered under ``number_of_results``.
Nowadays the search engines no longer indicate how many hits there are in the
index and so this field in SearXNG is also superfluous.

- https://github.com/searxng/searxng/issues/2457#issuecomment-2566181574
- https://github.com/searxng/searxng/issues/2987
- https://github.com/searxng/searxng/issues/5034

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-25 12:43:02 +02:00
Markus Heiser efc305b7f9 [mod] normalize variable name for the max number of results per request (#6131)
[mod] normalize variable name for the max number of results per request

In the past, we have used different names for the variable that specifies the
maximum number of hits in the outgoing request.

- ``page_size``
- ``number_of_results``
- ``nb_per_page``

Since *page_size* is the most accurate term and is also used in the XPath
engines, all other engines are adjusted accordingly within this
patch .. documentation adjusted accordingly.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-25 12:41:31 +02:00
Bnyro 323ce76004 [fix] startpage: all requests get blocked with CAPTCHA
Changes:
- Setting the "abp" query parameter causes instant blocks, it's no longer
used at Startpage
- The safesearch map changed for both the request form and the cookies. As
we were sending invalid values, that also made it easier to detect us
2026-05-23 09:43:17 +02:00
Bnyro dfc2da707b [fix] mojeek: access denied because of wrong request parameters 2026-05-23 09:43:03 +02:00
Bnyro fc90c5b09c [fix] yep: api path changed 2026-05-22 21:38:26 +02:00
dependabot[bot] c57f772ad0 [upd] github-actions: Bump github/codeql-action from 4.35.4 to 4.35.5 (#6114)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.4 to 4.35.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/68bde559dea0fdcac2102bfdf6230c5f70eb485e...9e0d7b8d25671d64c341c19c0152d693099fb5ba)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 14:50:44 +02:00
dependabot[bot] b8498d7891 [upd] github-actions: Bump docker/scout-action from 1.20.4 to 1.21.0 (#6112)
Bumps [docker/scout-action](https://github.com/docker/scout-action) from 1.20.4 to 1.21.0.
- [Release notes](https://github.com/docker/scout-action/releases)
- [Commits](https://github.com/docker/scout-action/compare/bacf462e8d090c09660de30a6ccc718035f961e3...cd72f264beff1cd72735de31148b9d3244a0234a)

---
updated-dependencies:
- dependency-name: docker/scout-action
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 14:50:31 +02:00
github-actions[bot] d791a3906a [l10n] update translations from Weblate (#6116) 2026-05-22 14:29:10 +02:00
dependabot[bot] 295e0bffaf [upd] pypi: Bump the minor group with 3 updates (#6113)
Bumps the minor group with 3 updates: [certifi](https://github.com/certifi/python-certifi), [lxml](https://github.com/lxml/lxml) and [basedpyright](https://github.com/detachhead/basedpyright).


Updates `certifi` from 2026.4.22 to 2026.5.20
- [Commits](https://github.com/certifi/python-certifi/compare/2026.04.22...2026.05.20)

Updates `lxml` from 6.1.0 to 6.1.1
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-6.1.0...lxml-6.1.1)

Updates `basedpyright` from 1.39.4 to 1.39.5
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.39.4...v1.39.5)
2026-05-22 14:28:09 +02:00
Bnyro b9340f50c2 [fix] preferences: opening preferences page is very slow (multiple seconds on bad hardware) (#6090)
I've been profiling the `/preferences` endpoint using werkzeug's
`ProfilerMiddleware` (i.e. just do `app.wsgi_app = ProfilerMiddleware(app.wsgi_app)`)
and look at the outputs in the terminal when doing `make run`.

It turns out that 95%+ of the time spent were inside babel's
Locale parsing (> 700ms on my machine). That's because, when opening the settings,
we loaded the full engine traits of each engine and checked if it matches
the user-defined search language. As we have 250+ engines, and babel is
very slow when parsing Locale's, this took a very long time.

By removing this feature that shows whether the selected search language
is supported by the engine, the load time went down from 800ms to 50ms
on my machine (which is still very slow, but well, that's future work on
optimizing).
2026-05-21 21:15:09 +02:00
Markus Heiser d3deacc6d4 [mod] engine fyyd: typing added, no functional change (#6103)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-21 12:06:55 +02:00
Bnyro d8f74af3d1 [mod] engine 500px: calc cursor instead of relying on pageInfo (#6091) 2026-05-21 07:31:13 +02:00
Bnyro 24b1a1b6a8 [feat] engines: add 500px.com engine (#6091) 2026-05-21 07:31:13 +02:00
330 changed files with 16030 additions and 8603 deletions
-163
View File
@@ -1,163 +0,0 @@
;;; .dir-locals.el
;;
;; Per-Directory Local Variables:
;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html
;;
;; For full fledge developer tools install emacs packages:
;;
;; M-x package-install ...
;;
;; magit gitconfig
;; nvm lsp-mode lsp-pyright lsp-eslint
;; pyvenv pylint pip-requirements
;; jinja2-mode
;; json-mode
;; company company-jedi company-quickhelp company-shell
;; realgud
;; sphinx-doc markdown-mode graphviz-dot-mode
;; apache-mode nginx-mode
;;
;; To setup a developer environment, build target::
;;
;; $ make node.env.dev pyenv.install
;;
;; Some buffer locals are referencing the project environment:
;;
;; - prj-root --> <repo>/
;; - nvm-dir --> <repo>/.nvm
;; - python-environment-directory --> <repo>/local
;; - python-environment-default-root-name --> py3
;; - python-shell-virtualenv-root --> <repo>/local/py3
;; When this variable is set with the path of the virtualenv to use,
;; `process-environment' and `exec-path' get proper values in order to run
;; shells inside the specified virtualenv, example::
;; (setq python-shell-virtualenv-root "/path/to/env/")
;; - python-shell-interpreter --> <repo>/local/py3/bin/python
;;
;; Python development:
;;
;; Jedi, flycheck & other python stuff should use the 'python-shell-interpreter'
;; from the local py3 environment.
;;
((nil
. ((fill-column . 80)
(indent-tabs-mode . nil)
(eval . (progn
(add-to-list 'auto-mode-alist '("\\.html\\'" . jinja2-mode))
;; project root folder is where the `.dir-locals.el' is located
(setq-local prj-root
(locate-dominating-file default-directory ".dir-locals.el"))
(setq-local python-environment-directory
(expand-file-name "./local" prj-root))
;; to get in use of NVM environment, install https://github.com/rejeep/nvm.el
(setq-local nvm-dir (expand-file-name "./.nvm" prj-root))
;; use nodejs from the (local) NVM environment (see nvm-dir)
(nvm-use-for-buffer)
(ignore-errors (require 'lsp))
(setq-local lsp-server-install-dir (car (cdr nvm-current-version)))
(setq-local lsp-enable-file-watchers nil)
;; use 'py3' environment as default
(setq-local python-environment-default-root-name
"py3")
(setq-local python-shell-virtualenv-root
(expand-file-name
python-environment-default-root-name python-environment-directory))
(setq-local python-shell-interpreter
(expand-file-name
"bin/python" python-shell-virtualenv-root))))))
(makefile-gmake-mode
. ((indent-tabs-mode . t)))
(yaml-mode
. ((eval . (progn
;; flycheck should use the local py3 environment
(setq-local flycheck-yaml-yamllint-executable
(expand-file-name "bin/yamllint" python-shell-virtualenv-root))
(setq-local flycheck-yamllintrc
(expand-file-name ".yamllint.yml" prj-root))
(flycheck-checker . yaml-yamllint)))))
(json-mode
. ((eval . (progn
(setq-local js-indent-level 4)
(flycheck-checker . json-python-json)))))
(js-mode
. ((eval . (progn
(ignore-errors (require 'lsp-eslint))
(setq-local js-indent-level 2)
;; flycheck should use the eslint checker from developer tools
(setq-local flycheck-javascript-eslint-executable
(expand-file-name "node_modules/.bin/eslint" prj-root))
;; (flycheck-mode)
(if (featurep 'lsp-eslint)
(lsp))
))))
(python-mode
. ((eval . (progn
(ignore-errors (require 'jedi-core))
(ignore-errors (require 'lsp-pyright))
(ignore-errors (sphinx-doc-mode))
(setq-local python-environment-virtualenv
(list (expand-file-name "bin/virtualenv" python-shell-virtualenv-root)
;;"--system-site-packages"
"--quiet"))
(setq-local pylint-command
(expand-file-name "bin/pylint" python-shell-virtualenv-root))
(if (featurep 'lsp-pyright)
(lsp))
;; pylint will find the '.pylintrc' file next to the CWD
;; https://pylint.readthedocs.io/en/latest/user_guide/run.html#command-line-options
(setq-local flycheck-pylintrc
".pylintrc")
;; flycheck & other python stuff should use the local py3 environment
(setq-local flycheck-python-pylint-executable
python-shell-interpreter)
;; use 'M-x jedi:show-setup-info' and 'M-x epc:controller' to inspect jedi server
;; https://tkf.github.io/emacs-jedi/latest/#jedi:environment-root -- You
;; can specify a full path instead of a name (relative path). In that case,
;; python-environment-directory is ignored and Python virtual environment
;; is created at the specified path.
(setq-local jedi:environment-root
python-shell-virtualenv-root)
;; https://tkf.github.io/emacs-jedi/latest/#jedi:server-command
(setq-local jedi:server-command
(list python-shell-interpreter
jedi:server-script))
;; jedi:environment-virtualenv --> see above 'python-environment-virtualenv'
;; is set buffer local! No need to setup jedi:environment-virtualenv:
;;
;; Virtualenv command to use. A list of string. If it is nil,
;; python-environment-virtualenv is used instead. You must set non-nil
;; value to jedi:environment-root in order to make this setting work.
;;
;; https://tkf.github.io/emacs-jedi/latest/#jedi:environment-virtualenv
;;
;; (setq-local jedi:environment-virtualenv
;; (list (expand-file-name "bin/virtualenv" python-shell-virtualenv-root)
;; "--python"
;; "/usr/bin/python3.4"
;; ))
))))
)
+1
View File
@@ -1,5 +1,6 @@
* *
!container/*.template.*
!container/entrypoint.sh !container/entrypoint.sh
!searx/** !searx/**
!requirements*.txt !requirements*.txt
+9 -9
View File
@@ -78,7 +78,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}" python-version: "${{ env.PYTHON_VERSION }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
fetch-depth: "0" fetch-depth: "0"
@@ -106,10 +106,10 @@ jobs:
- if: ${{ matrix.emulation }} - if: ${{ matrix.emulation }}
name: Setup QEMU name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with: with:
registry: "ghcr.io" registry: "ghcr.io"
username: "${{ github.repository_owner }}" username: "${{ github.repository_owner }}"
@@ -141,16 +141,16 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
- if: ${{ matrix.emulation }} - if: ${{ matrix.emulation }}
name: Setup QEMU name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with: with:
registry: "ghcr.io" registry: "ghcr.io"
username: "${{ github.repository_owner }}" username: "${{ github.repository_owner }}"
@@ -175,19 +175,19 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with: with:
registry: "ghcr.io" registry: "ghcr.io"
username: "${{ github.repository_owner }}" username: "${{ github.repository_owner }}"
password: "${{ secrets.GITHUB_TOKEN }}" password: "${{ secrets.GITHUB_TOKEN }}"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with: with:
registry: "docker.io" registry: "docker.io"
username: "${{ secrets.DOCKER_USER }}" username: "${{ secrets.DOCKER_USER }}"
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}" python-version: "${{ env.PYTHON_VERSION }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
+5 -2
View File
@@ -37,7 +37,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}" python-version: "${{ env.PYTHON_VERSION }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
fetch-depth: "0" fetch-depth: "0"
@@ -50,11 +50,14 @@ jobs:
python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}- python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-
path: "./local/" path: "./local/"
- name: Setup dependencies
run: sudo ./utils/searxng.sh install buildhost
- name: Setup venv - name: Setup venv
run: make V=1 install run: make V=1 install
- name: Build documentation - name: Build documentation
run: make V=1 docs.clean docs.html run: make V=1 docs.html
- if: github.ref_name == 'master' - if: github.ref_name == 'master'
name: Release name: Release
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
python-version: "${{ matrix.python-version }}" python-version: "${{ matrix.python-version }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
@@ -67,7 +67,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}" python-version: "${{ env.PYTHON_VERSION }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
+2 -2
View File
@@ -40,7 +40,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}" python-version: "${{ env.PYTHON_VERSION }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}" token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}"
fetch-depth: "0" fetch-depth: "0"
@@ -88,7 +88,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}" python-version: "${{ env.PYTHON_VERSION }}"
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}" token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}"
fetch-depth: "0" fetch-depth: "0"
+3 -3
View File
@@ -24,12 +24,12 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
persist-credentials: "false" persist-credentials: "false"
- name: Sync GHCS from Docker Scout - name: Sync GHCS from Docker Scout
uses: docker/scout-action@bacf462e8d090c09660de30a6ccc718035f961e3 # v1.20.4 uses: docker/scout-action@cd72f264beff1cd72735de31148b9d3244a0234a # v1.21.0
with: with:
organization: "searxng" organization: "searxng"
dockerhub-user: "${{ secrets.DOCKER_USER }}" dockerhub-user: "${{ secrets.DOCKER_USER }}"
@@ -41,6 +41,6 @@ jobs:
write-comment: "false" write-comment: "false"
- name: Upload SARIFs - name: Upload SARIFs
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with: with:
sarif_file: "./scout.sarif" sarif_file: "./scout.sarif"
+11
View File
@@ -0,0 +1,11 @@
[[language]]
name = "python"
language-servers = ["basedpyright", "pylsp"]
auto-format = true
[language-server.pylsp.config.pylsp]
plugins.pylint.enabled = true
plugins.isort.enabled = true
plugins.black.enabled = true
plugins.black.skip_string_normalization = true
plugins.black.line_length = 120
+15 -29
View File
@@ -2,12 +2,12 @@
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": { "files": {
"ignoreUnknown": true, "ignoreUnknown": true,
"includes": ["**", "!node_modules"] "includes": ["**", "!node_modules", "!src/brand", "!src/svg"]
}, },
"assist": { "assist": {
"enabled": true, "enabled": true,
"actions": { "actions": {
"recommended": true, "preset": "recommended",
"source": { "source": {
"useSortedAttributes": "on", "useSortedAttributes": "on",
"useSortedProperties": "on" "useSortedProperties": "on"
@@ -27,12 +27,14 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true, "preset": "recommended",
"complexity": { "complexity": {
"noForEach": "error", "noForEach": "error",
"noImplicitCoercions": "error", "noImplicitCoercions": "error",
"noRedundantDefaultExport": "error",
"noUselessCatchBinding": "error", "noUselessCatchBinding": "error",
"noUselessUndefined": "error", "noUselessUndefined": "error",
"useArrayFind": "error",
"useSimplifiedLogicExpression": "error" "useSimplifiedLogicExpression": "error"
}, },
"correctness": { "correctness": {
@@ -42,25 +44,11 @@
"useSingleJsDocAsterisk": "error" "useSingleJsDocAsterisk": "error"
}, },
"nursery": { "nursery": {
"noContinue": "warn",
"noEqualsToNull": "warn",
"noFloatingPromises": "warn", "noFloatingPromises": "warn",
"noForIn": "warn",
"noIncrementDecrement": "warn",
"noMisusedPromises": "warn", "noMisusedPromises": "warn",
"noMultiAssign": "warn",
"noMultiStr": "warn",
"noNestedPromises": "warn",
"noParametersOnlyUsedInRecursion": "warn",
"noRedundantDefaultExport": "warn",
"noReturnAssign": "warn",
"noUselessReturn": "off",
"useAwaitThenable": "off", "useAwaitThenable": "off",
"useConsistentEnumValueType": "warn",
"useDestructuring": "warn",
"useExhaustiveSwitchCases": "warn", "useExhaustiveSwitchCases": "warn",
"useExplicitType": "off", "useExplicitType": "off",
"useFind": "warn",
"useRegexpExec": "warn" "useRegexpExec": "warn"
}, },
"performance": { "performance": {
@@ -75,23 +63,15 @@
"noCommonJs": "error", "noCommonJs": "error",
"noEnum": "error", "noEnum": "error",
"noImplicitBoolean": "error", "noImplicitBoolean": "error",
"noIncrementDecrement": "error",
"noInferrableTypes": "error", "noInferrableTypes": "error",
"noMultiAssign": "error",
"noMultilineString": "error",
"noNamespace": "error", "noNamespace": "error",
"noNegationElse": "error", "noNegationElse": "error",
"noNestedTernary": "error", "noNestedTernary": "error",
"noParameterAssign": "error", "noParameterAssign": "error",
"noParameterProperties": "error", "noParameterProperties": "error",
"noRestrictedTypes": {
"level": "error",
"options": {
"types": {
"Element": {
"message": "Element is too generic",
"use": "HTMLElement"
}
}
}
},
"noSubstr": "error", "noSubstr": "error",
"noUnusedTemplateLiteral": "error", "noUnusedTemplateLiteral": "error",
"noUselessElse": "error", "noUselessElse": "error",
@@ -107,6 +87,7 @@
} }
}, },
"useConsistentBuiltinInstantiation": "error", "useConsistentBuiltinInstantiation": "error",
"useConsistentEnumValueType": "error",
"useConsistentMemberAccessibility": { "useConsistentMemberAccessibility": {
"level": "error", "level": "error",
"options": { "options": {
@@ -126,6 +107,7 @@
} }
}, },
"useDefaultSwitchClause": "error", "useDefaultSwitchClause": "error",
"useDestructuring": "error",
"useExplicitLengthCheck": "error", "useExplicitLengthCheck": "error",
"useForOf": "error", "useForOf": "error",
"useGroupedAccessorPairs": "error", "useGroupedAccessorPairs": "error",
@@ -142,13 +124,17 @@
"useUnifiedTypeSignatures": "error" "useUnifiedTypeSignatures": "error"
}, },
"suspicious": { "suspicious": {
"noAlert": "error",
"noBitwiseOperators": "error", "noBitwiseOperators": "error",
"noConstantBinaryExpressions": "error", "noConstantBinaryExpressions": "error",
"noDeprecatedImports": "error", "noDeprecatedImports": "error",
"noEmptyBlockStatements": "error", "noEmptyBlockStatements": "error",
"noEqualsToNull": "error",
"noEvolvingTypes": "error", "noEvolvingTypes": "error",
"noForIn": "error",
"noImportCycles": "error", "noImportCycles": "error",
"noNestedPromises": "error",
"noParametersOnlyUsedInRecursion": "error",
"noReturnAssign": "error",
"noUnassignedVariables": "error", "noUnassignedVariables": "error",
"noVar": "error", "noVar": "error",
"useNumberToFixedDigitsArgument": "error", "useNumberToFixedDigitsArgument": "error",
+379 -354
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -29,21 +29,21 @@
"swiped-events": "1.2.0" "swiped-events": "1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.4.15", "@biomejs/biome": "2.5.0",
"@types/node": "^25.8.0", "@types/node": "^26.0.0",
"browserslist": "^4.28.2", "browserslist": "^4.28.2",
"browserslist-to-esbuild": "^2.1.1", "browserslist-to-esbuild": "^2.1.1",
"edge.js": "^6.5.0", "edge.js": "^6.5.1",
"less": "^4.6.4", "less": "^4.6.6",
"mathjs": "^15.2.0", "mathjs": "^15.2.0",
"sharp": "~0.34.5", "sharp": "~0.35.1",
"sort-package-json": "^3.6.1", "sort-package-json": "^4.0.0",
"stylelint": "^17.11.1", "stylelint": "^17.13.0",
"stylelint-config-standard-less": "^4.1.0", "stylelint-config-standard-less": "^4.1.0",
"stylelint-prettier": "^5.0.3", "stylelint-prettier": "^5.0.3",
"svgo": "^4.0.1", "svgo": "^4.0.1",
"typescript": "~6.0.3", "typescript": "~6.0.3",
"vite": "^8.0.13", "vite": "^8.0.16",
"vite-bundle-analyzer": "^1.3.8" "vite-bundle-analyzer": "^1.3.8"
} }
} }
+1 -1
View File
@@ -77,9 +77,9 @@ export default class Calculator extends Plugin {
protected async run(): Promise<string | undefined> { protected async run(): Promise<string | undefined> {
const searchInput = getElement<HTMLInputElement>("q"); const searchInput = getElement<HTMLInputElement>("q");
const node = Calculator.math.parse(searchInput.value);
try { try {
const node = Calculator.math.parse(searchInput.value);
return `${node.toString()} = ${node.evaluate()}`; return `${node.toString()} = ${node.evaluate()}`;
} catch { } catch {
// not a compatible math expression // not a compatible math expression
-1
View File
@@ -5,7 +5,6 @@ import type { KeyBindingLayout } from "./main/keyboard.ts";
// synced with searx/webapp.py get_client_settings // synced with searx/webapp.py get_client_settings
type Settings = { type Settings = {
plugins?: string[]; plugins?: string[];
advanced_search?: boolean;
autocomplete?: string; autocomplete?: string;
autocomplete_min?: number; autocomplete_min?: number;
doi_resolver?: string; doi_resolver?: string;
+1 -1
View File
@@ -7,7 +7,6 @@
@import "mixins.less"; @import "mixins.less";
@import "toolkit.less"; @import "toolkit.less";
@import "autocomplete.less"; @import "autocomplete.less";
@import "detail.less";
@import "animations.less"; @import "animations.less";
@import "embedded.less"; @import "embedded.less";
@import "info.less"; @import "info.less";
@@ -1165,3 +1164,4 @@ pre code {
@import "result_types/code.less"; @import "result_types/code.less";
@import "result_types/paper.less"; @import "result_types/paper.less";
@import "result_types/file.less"; @import "result_types/file.less";
@import "result_types/image.less";
+1 -4
View File
@@ -21,8 +21,6 @@ RUN --mount=type=cache,id=uv,target=/root/.cache/uv set -eux -o pipefail; \
COPY --exclude=./searx/version_frozen.py ./searx/ ./searx/ COPY --exclude=./searx/version_frozen.py ./searx/ ./searx/
ARG TIMESTAMP_SETTINGS="0"
RUN set -eux -o pipefail; \ RUN set -eux -o pipefail; \
python -m compileall -q -f -j 0 --invalidation-mode=unchecked-hash ./searx/; \ python -m compileall -q -f -j 0 --invalidation-mode=unchecked-hash ./searx/; \
find ./searx/static/ -type f \ find ./searx/static/ -type f \
@@ -30,5 +28,4 @@ RUN set -eux -o pipefail; \
-exec gzip -9 -k {} + \ -exec gzip -9 -k {} + \
-exec brotli -9 -k {} + \ -exec brotli -9 -k {} + \
-exec gzip --test {}.gz + \ -exec gzip --test {}.gz + \
-exec brotli --test {}.br +; \ -exec brotli --test {}.br +
touch -c --date="@$TIMESTAMP_SETTINGS" ./searx/settings.yml
+9 -30
View File
@@ -77,43 +77,23 @@ volume_handler() {
setup_ownership "$target" "directory" setup_ownership "$target" "directory"
} }
# Handle configuration file updates setup() {
config_handler() { local template_settings="/usr/local/searxng/settings.template.yml"
local target="$1" local target_settings="$__SEARXNG_CONFIG_PATH/settings.yml"
local template="$2"
local new_template_target="$target.new"
# Create/Update the configuration file if [ ! -f "$target_settings" ]; then
if [ -f "$target" ]; then
setup_ownership "$target" "file"
if [ "$template" -nt "$target" ]; then
cp -pfT "$template" "$new_template_target"
cat <<EOF
...
... INFORMATION
... Update available for "$target"
... It is recommended to update the configuration file to ensure proper functionality
...
... New version placed at "$new_template_target"
... Please review and merge changes
...
EOF
fi
else
cat <<EOF cat <<EOF
... ...
... INFORMATION ... INFORMATION
... "$target" does not exist, creating from template... ... "$target_settings" does not exist, creating from template...
... ...
EOF EOF
cp -pfT "$template" "$target" cp -pfT "$template_settings" "$target_settings"
sed -i "s/ultrasecretkey/$(head -c 24 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9')/g" "$target" sed -i "s/ultrasecretkey/$(head -c 24 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9')/g" "$target_settings"
fi fi
check_file "$target" check_file "$target_settings"
} }
cat <<EOF cat <<EOF
@@ -124,8 +104,7 @@ EOF
volume_handler "$__SEARXNG_CONFIG_PATH" volume_handler "$__SEARXNG_CONFIG_PATH"
volume_handler "$__SEARXNG_DATA_PATH" volume_handler "$__SEARXNG_DATA_PATH"
# Check for files setup
config_handler "$__SEARXNG_SETTINGS_PATH" "/usr/local/searxng/searx/settings.yml"
# root only features # root only features
if [ "$(id -u)" -eq 0 ]; then if [ "$(id -u)" -eq 0 ]; then
+8
View File
@@ -0,0 +1,8 @@
# Read the documentation before extending the defaults:
# https://docs.searxng.org/admin/settings/
use_default_settings: true
server:
secret_key: "ultrasecretkey"
image_proxy: true
+1
View File
@@ -19,6 +19,7 @@ Settings
settings_search settings_search
settings_server settings_server
settings_ui settings_ui
settings_preferences
settings_redis settings_redis
settings_valkey settings_valkey
settings_outgoing settings_outgoing
@@ -0,0 +1,8 @@
.. _settings preferences:
================
``preferences:``
================
.. autoclass:: searx._settings.SettingsPref
:members:
+1
View File
@@ -43,6 +43,7 @@
- ``google`` - ``google``
- ``mwmbl`` - ``mwmbl``
- ``naver`` - ``naver``
- ``privacywall``
- ``quark`` - ``quark``
- ``qwant`` - ``qwant``
- ``seznam`` - ``seznam``
+1
View File
@@ -47,6 +47,7 @@
activated: activated:
- :py:obj:`searx.botdetection.link_token` in the :ref:`limiter` - :py:obj:`searx.botdetection.link_token` in the :ref:`limiter`
- :ref:`image_proxy`
.. _image_proxy: .. _image_proxy:
+1 -1
View File
@@ -107,7 +107,7 @@ module:
======================= =========== =========================================== ======================= =========== ===========================================
base_url string base-url, can be overwritten to use same base_url string base-url, can be overwritten to use same
engine on other URL engine on other URL
number_of_results int maximum number of results per request page_size int maximum number of results per request
language string ISO code of language and country like en_US language string ISO code of language and country like en_US
api_key string api-key if required by engine api_key string api-key if required by engine
======================= =========== =========================================== ======================= =========== ===========================================
+8
View File
@@ -0,0 +1,8 @@
.. _500px engine:
=====
500px
=====
.. automodule:: searx.engines.500px
:members:
-8
View File
@@ -1,8 +0,0 @@
.. _aol engine:
===
AOL
===
.. automodule:: searx.engines.aol
:members:
+9
View File
@@ -0,0 +1,9 @@
.. _kagi engines:
============
Kagi Engines
============
.. automodule:: searx.engines.kagi
:members:
-8
View File
@@ -1,8 +0,0 @@
.. _karmasearch engine:
===========
Karmasearch
===========
.. automodule:: searx.engines.karmasearch
:members:
+7
View File
@@ -0,0 +1,7 @@
.. _result_types.image:
=============
Image Results
=============
.. automodule:: searx.result_types.image
@@ -1,4 +1,8 @@
.. _result_types.mainresult: .. _result_types.mainresult:
============
Main Results
============
.. autoclass:: searx.result_types._base.MainResult .. autoclass:: searx.result_types._base.MainResult
:members: :members:
+1 -1
View File
@@ -18,13 +18,13 @@ following types have been implemented so far ..
main/code main/code
main/paper main/paper
main/file main/file
main/image
The :ref:`LegacyResult <LegacyResult>` is used internally for the results that The :ref:`LegacyResult <LegacyResult>` is used internally for the results that
have not yet been typed. The templates can be used as orientation until the have not yet been typed. The templates can be used as orientation until the
final typing is complete. final typing is complete.
- :ref:`template default` / :py:obj:`Result` - :ref:`template default` / :py:obj:`Result`
- :ref:`template images`
- :ref:`template videos` - :ref:`template videos`
- :ref:`template torrent` - :ref:`template torrent`
- :ref:`template map` - :ref:`template map`
+1 -1
View File
@@ -87,7 +87,7 @@ Parameters
``autocomplete`` : default from :ref:`settings search` ``autocomplete`` : default from :ref:`settings search`
[ ``google``, ``dbpedia``, ``duckduckgo``, ``mwmbl``, ``startpage``, [ ``google``, ``dbpedia``, ``duckduckgo``, ``mwmbl``, ``startpage``,
``wikipedia``, ``swisscows``, ``qwant`` ] ``privacywall``, ``wikipedia``, ``swisscows``, ``qwant`` ]
Service which completes words as you type. Service which completes words as you type.
-47
View File
@@ -129,53 +129,6 @@ audio_src : uri,
URL of an embedded ``<audio controls>``. URL of an embedded ``<audio controls>``.
.. _template images:
``images.html``
---------------
The images are displayed as small thumbnails in the main results list.
title : :py:class:`str`
Title of the image.
thumbnail_src : :py:class:`str`
URL of a preview of the image.
resolution :py:class:`str`
The resolution of the image (e.g. ``1920 x 1080`` pixel)
Image labels
~~~~~~~~~~~~
Clicking on the preview opens a gallery view in which all further metadata for
the image is displayed. Addition fields used in the :origin:`images.html
<searx/templates/simple/result_templates/images.html>`:
img_src : :py:class:`str`
URL of the full size image.
content: :py:class:`str`
Description of the image.
author: :py:class:`str`
Name of the author of the image.
img_format : :py:class:`str`
The format of the image (e.g. ``png``).
source : :py:class:`str`
Source of the image.
filesize: :py:class:`str`
Size of bytes in :py:obj:`human readable <searx.humanize_bytes>` notation
(e.g. ``MB`` for 1024 \* 1024 Bytes filesize).
url : :py:class:`str`
URL of the page from where the images comes from (source).
.. _template videos: .. _template videos:
``videos.html`` ``videos.html``
+2 -2
View File
@@ -58,8 +58,8 @@ Configured Engines
{% for mod in engines %} {% for mod in engines %}
* - `{{mod.name}} <{{mod.about and mod.about.website}}>`_ * - `{{mod.name}} <{{mod.about and mod.about.website}}>`_
{%- if mod.about and mod.about.language %} {%- if mod.language %}
({{mod.about.language | upper}}) ({{mod.language | upper}})
{%- endif %} {%- endif %}
- ``!{{mod.shortcut}}`` - ``!{{mod.shortcut}}``
- {%- if 'searx.engines.' + mod.__name__ in documented_modules %} - {%- if 'searx.engines.' + mod.__name__ in documented_modules %}
+5 -5
View File
@@ -2,16 +2,16 @@ mock==5.2.0
nose2[coverage_plugin]==0.16.0 nose2[coverage_plugin]==0.16.0
cov-core==1.15.0 cov-core==1.15.0
black==25.9.0 black==25.9.0
pylint==4.0.5 pylint==4.0.6
splinter==0.21.0 splinter==0.21.0
selenium==4.44.0 selenium==4.45.0
Sphinx==8.2.3;python_version <= "3.11" Sphinx==8.2.3;python_version <= "3.11"
Sphinx==9.1.0; python_version > "3.11" Sphinx==9.1.0; python_version > "3.11"
sphinx-issues==6.0.0 sphinx-issues==6.0.0
sphinx-jinja==2.0.2 sphinx-jinja==2.0.2
sphinx-tabs==3.5.0 sphinx-tabs==3.5.0
furo==2025.12.19 furo==2025.12.19
sphinxcontrib-programoutput==0.19 sphinxcontrib-programoutput==0.20
sphinx-autobuild==2025.8.25 sphinx-autobuild==2025.8.25
sphinx-notfound-page==1.1.0 sphinx-notfound-page==1.1.0
myst-parser==5.0.0 myst-parser==5.0.0
@@ -23,6 +23,6 @@ coloredlogs==15.0.1
docutils>=0.21.2;python_version <= "3.11" docutils>=0.21.2;python_version <= "3.11"
docutils>=0.22.4; python_version > "3.11" docutils>=0.22.4; python_version > "3.11"
parameterized==0.9.0 parameterized==0.9.0
granian[reload]==2.7.4 granian[reload]==2.7.6
basedpyright==1.39.4 basedpyright==1.39.8
types-lxml==2026.2.16 types-lxml==2026.2.16
+2 -2
View File
@@ -1,2 +1,2 @@
granian==2.7.4 granian==2.7.6
granian[pname]==2.7.4 granian[pname]==2.7.6
+3 -3
View File
@@ -1,9 +1,9 @@
certifi==2026.4.22 certifi==2026.6.17
babel==2.18.0 babel==2.18.0
flask-babel==4.0.0 flask-babel==4.0.0
flask==3.1.3 flask==3.1.3
jinja2==3.1.6 jinja2==3.1.6
lxml==6.1.0 lxml==6.1.1
pygments==2.20.0 pygments==2.20.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
pyyaml==6.0.3 pyyaml==6.0.3
@@ -13,7 +13,7 @@ sniffio==1.3.1
valkey==6.1.1 valkey==6.1.1
markdown-it-py==4.2.0 markdown-it-py==4.2.0
msgspec==0.21.1 msgspec==0.21.1
typer==0.25.1 typer==0.26.7
isodate==0.7.2 isodate==0.7.2
whitenoise==6.12.0 whitenoise==6.12.0
typing-extensions==4.15.0 typing-extensions==4.15.0
+8 -1
View File
@@ -10,6 +10,7 @@ from os.path import dirname, abspath
import logging import logging
import msgspec import msgspec
from ._settings import SettingsPref
# Debug # Debug
LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s' LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s'
@@ -47,6 +48,12 @@ def init_settings():
settings.clear() settings.clear()
settings.update(cfg) settings.update(cfg)
if get_setting("server.public_instance"):
# enable image proxy for public instances #6125
settings["server"]["image_proxy"] = True
pref: SettingsPref = get_setting("preferences")
pref.lock.add("image_proxy")
sxng_debug = get_setting("general.debug") sxng_debug = get_setting("general.debug")
if sxng_debug: if sxng_debug:
_logging_config_debug() _logging_config_debug()
@@ -66,7 +73,7 @@ def init_settings():
if settings['server']['public_instance']: if settings['server']['public_instance']:
logger.warning( logger.warning(
"Be aware you have activated features intended only for public instances. " "Be aware you have activated features intended only for public instances. "
"This force the usage of the limiter and link_token / " "This force the usage of the limiter, link_token and image proxy / "
"see https://docs.searxng.org/admin/searx.limiter.html" "see https://docs.searxng.org/admin/searx.limiter.html"
) )
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Implementation of the :py:obj:`preference <searx.preference>` settings."""
# pylint: disable = too-few-public-methods
import typing as t
import msgspec
class SettingsPref(msgspec.Struct, kw_only=True, forbid_unknown_fields=True):
"""Options for configuring the preferences
.. code:: yaml
preferences:
lock:
- favicon_resolver
- image_proxy
- method
# ...
"""
lock: set[
t.Literal[
"categories",
"language",
"locale",
"autocomplete",
"favicon_resolver",
"image_proxy",
"method",
"safesearch",
"theme",
"results_on_new_tab",
"doi_resolver",
"simple_style",
"center_alignment",
"query_in_title",
"search_on_category_select",
]
] = set()
"""Lock arbitrary settings on the preferences page."""
+18
View File
@@ -179,6 +179,23 @@ def naver(query: str, _sxng_locale: str) -> list[str]:
return results return results
def privacywall(query: str, sxng_locale: str) -> list[str]:
# Privacywall search autocompleter
country = None
if "-" in sxng_locale:
country = sxng_locale.split("-")[1]
args = {'q': query, 'cc': country}
url = f"https://www.privacywall.org/search/secure/suggestions.php?{urlencode(args)}"
response = get(url)
if not response.ok:
return []
data: list[list[str]] = response.json()
return data[1]
def qihu360search(query: str, _sxng_locale: str) -> list[str]: def qihu360search(query: str, _sxng_locale: str) -> list[str]:
# 360Search search autocompleter # 360Search search autocompleter
url = f"https://sug.so.360.cn/suggest?{urlencode({'format': 'json', 'word': query})}" url = f"https://sug.so.360.cn/suggest?{urlencode({'format': 'json', 'word': query})}"
@@ -361,6 +378,7 @@ backends: dict[str, t.Callable[[str, str], list[str]]] = {
'google': google_complete, 'google': google_complete,
'mwmbl': mwmbl, 'mwmbl': mwmbl,
'naver': naver, 'naver': naver,
'privacywall': privacywall,
'quark': quark, 'quark': quark,
'qwant': qwant, 'qwant': qwant,
'seznam': seznam, 'seznam': seznam,
+8 -5
View File
@@ -444,12 +444,10 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache):
def get(self, key: str, default: typing.Any = None, ctx: str | None = None) -> typing.Any: def get(self, key: str, default: typing.Any = None, ctx: str | None = None) -> typing.Any:
"""Get value of ``key`` from table given by argument ``ctx``. If """Get value of ``key`` from table given by argument ``ctx``. If
``ctx`` argument is ``None`` (the default), a table name is generated ``ctx`` argument is ``None`` (the default), a table name is generated
from the :py:obj:`ExpireCacheCfg.name`. If ``key`` not exists (in from the :py:obj:`ExpireCacheCfg.name`. If ``key`` not exists in
table), the ``default`` value is returned. the table or the table not exists, the ``default`` value is returned.
""" """
table = ctx table = ctx
self.maintenance()
if not table: if not table:
table = self.normalize_name(self.cfg.name) table = self.normalize_name(self.cfg.name)
@@ -457,6 +455,9 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache):
if table not in self.table_names: if table not in self.table_names:
return default return default
# Before values are taken from the table, a maintenance interval may
# need to be carried out.
self.maintenance()
sql = f"SELECT value FROM {table} WHERE key = ?" sql = f"SELECT value FROM {table} WHERE key = ?"
row = self.DB.execute(sql, (key,)).fetchone() row = self.DB.execute(sql, (key,)).fetchone()
if row is None: if row is None:
@@ -469,12 +470,14 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache):
If ``ctx`` argument is ``None`` (the default), a table name is If ``ctx`` argument is ``None`` (the default), a table name is
generated from the :py:obj:`ExpireCacheCfg.name`.""" generated from the :py:obj:`ExpireCacheCfg.name`."""
table = ctx table = ctx
self.maintenance()
if not table: if not table:
table = self.normalize_name(self.cfg.name) table = self.normalize_name(self.cfg.name)
if table in self.table_names: if table in self.table_names:
# Before values are taken from the table, a maintenance interval may
# need to be carried out.
self.maintenance()
for row in self.DB.execute(f"SELECT key, value FROM {table}"): for row in self.DB.execute(f"SELECT key, value FROM {table}"):
yield row[0], self.deserialize(row[1]) yield row[0], self.deserialize(row[1])
File diff suppressed because it is too large Load Diff
+61 -23
View File
@@ -84,6 +84,7 @@
"sr": "УАЕ дирхам", "sr": "УАЕ дирхам",
"sv": "Emiratisk dirham", "sv": "Emiratisk dirham",
"ta": "ஐக்கிய அரபு அமீரக திர்கம்", "ta": "ஐக்கிய அரபு அமீரக திர்கம்",
"te": "యూఏఈ దిర్హామ్",
"tr": "Birleşik Arap Emirlikleri dirhemi", "tr": "Birleşik Arap Emirlikleri dirhemi",
"tt": "БГӘ дирһәме", "tt": "БГӘ дирһәме",
"uk": "дирхам ОАЕ" "uk": "дирхам ОАЕ"
@@ -165,7 +166,7 @@
"pap": "lek albanes", "pap": "lek albanes",
"pl": "lek", "pl": "lek",
"pt": "lek", "pt": "lek",
"ro": "Lek", "ro": "lek albanez",
"ru": "албанский лек", "ru": "албанский лек",
"sk": "Albánsky lek", "sk": "Albánsky lek",
"sl": "albanski lek", "sl": "albanski lek",
@@ -738,6 +739,7 @@
"sr": "брунејски долар", "sr": "брунејски долар",
"sv": "Bruneisk dollar", "sv": "Bruneisk dollar",
"ta": "புரூணை டாலர்", "ta": "புரூணை டாலர்",
"te": "బ్రూనై డాలర్",
"th": "ดอลลาร์บรูไน", "th": "ดอลลาร์บรูไน",
"tr": "Brunei doları", "tr": "Brunei doları",
"tt": "Бруней доллары", "tt": "Бруней доллары",
@@ -911,6 +913,7 @@
"sr": "бутански нгултрум", "sr": "бутански нгултрум",
"sv": "Ngultrum", "sv": "Ngultrum",
"ta": "பூட்டானின் இங்குல்ட்ரம்", "ta": "பூட்டானின் இங்குல்ட்ரம்",
"te": "భూటానీస్ గుల్త్రమ్",
"th": "งุลตรัมภูฏาน", "th": "งุลตรัมภูฏาน",
"tr": "Ngultrum", "tr": "Ngultrum",
"tt": "ңгултрум", "tt": "ңгултрум",
@@ -989,7 +992,7 @@
"pa": "ਬੈਲਾਰੂਸੀ ਰੂਬਲ", "pa": "ਬੈਲਾਰੂਸੀ ਰੂਬਲ",
"pl": "Rubel białoruski", "pl": "Rubel białoruski",
"pt": "Rublo bielorrusso", "pt": "Rublo bielorrusso",
"ro": "Rublă belarusă", "ro": "rublă belarusă",
"ru": "Белорусский рубль", "ru": "Белорусский рубль",
"sk": "Bieloruský rubeľ", "sk": "Bieloruský rubeľ",
"sl": "beloruski rubelj", "sl": "beloruski rubelj",
@@ -1083,6 +1086,7 @@
"sr": "канадски долар", "sr": "канадски долар",
"sv": "kanadensisk dollar", "sv": "kanadensisk dollar",
"ta": "கனடா டொலர்", "ta": "கனடா டொலர்",
"te": "కెనడియన్ డాలర్",
"th": "ดอลลาร์แคนาดา", "th": "ดอลลาร์แคนาดา",
"tr": "Kanada doları", "tr": "Kanada doları",
"tt": "Канада дуллыры", "tt": "Канада дуллыры",
@@ -1231,6 +1235,7 @@
"sl": "čilski peso", "sl": "čilski peso",
"sr": "чилеански пезос", "sr": "чилеански пезос",
"sv": "Chilensk peso", "sv": "Chilensk peso",
"te": "చిలీ పెసో",
"th": "เปโซชิลี", "th": "เปโซชิลี",
"tr": "Şili pesosu", "tr": "Şili pesosu",
"tt": "Чили песосы", "tt": "Чили песосы",
@@ -1284,6 +1289,7 @@
"sr": "ренминби", "sr": "ренминби",
"sv": "Renminbi", "sv": "Renminbi",
"ta": "ரென்மின்பி", "ta": "ரென்மின்பி",
"te": "రెన్మిన్బి",
"th": "เหรินหมินปี้", "th": "เหรินหมินปี้",
"tr": "Renminbi", "tr": "Renminbi",
"tt": "юән", "tt": "юән",
@@ -1702,7 +1708,7 @@
"vi": "Bảng Ai Cập" "vi": "Bảng Ai Cập"
}, },
"ERN": { "ERN": {
"ar": اكفا", "ar": قفة",
"ca": "nakfa", "ca": "nakfa",
"cs": "Eritrejská nakfa", "cs": "Eritrejská nakfa",
"da": "Nakfa", "da": "Nakfa",
@@ -1776,11 +1782,11 @@
"bg": "евро", "bg": "евро",
"bn": "ইউরো", "bn": "ইউরো",
"ca": "euro", "ca": "euro",
"cs": "euro", "cs": "Euro",
"cy": "Ewro", "cy": "Ewro",
"da": "Euro", "da": "Euro",
"de": "Euro", "de": "Euro",
"en": "euro", "en": "Euro",
"eo": "eŭro", "eo": "eŭro",
"es": "Euro", "es": "Euro",
"et": "Euro", "et": "Euro",
@@ -1794,7 +1800,7 @@
"hu": "euró", "hu": "euró",
"ia": "Euro", "ia": "Euro",
"id": "Euro", "id": "Euro",
"it": "euro", "it": "Euro",
"ja": "ユーロ", "ja": "ユーロ",
"ko": "유로", "ko": "유로",
"lt": "Euras", "lt": "Euras",
@@ -1805,11 +1811,11 @@
"oc": "Èuro", "oc": "Èuro",
"pa": "ਯੂਰੋ", "pa": "ਯੂਰੋ",
"pap": "Euro", "pap": "Euro",
"pl": "euro", "pl": "Euro",
"pt": "Euro", "pt": "Euro",
"ro": "euro", "ro": "Euro",
"ru": "евро", "ru": "евро",
"sk": "euro", "sk": "Euro",
"sl": "evro", "sl": "evro",
"sr": "евро", "sr": "евро",
"sv": "Euro", "sv": "Euro",
@@ -1935,7 +1941,7 @@
"sk": "libra šterlingov", "sk": "libra šterlingov",
"sl": "funt šterling", "sl": "funt šterling",
"sr": "британска фунта", "sr": "британска фунта",
"sv": "Brittiskt pund", "sv": "brittiskt pund",
"ta": "பிரித்தானிய பவுண்டு", "ta": "பிரித்தானிய பவுண்டு",
"th": "ปอนด์สเตอร์ลิง", "th": "ปอนด์สเตอร์ลิง",
"tr": "İngiliz sterlini", "tr": "İngiliz sterlini",
@@ -2410,6 +2416,7 @@
"pl": "rupia indonezyjska", "pl": "rupia indonezyjska",
"pt": "rupia indonésia", "pt": "rupia indonésia",
"ru": "индонезийская рупия", "ru": "индонезийская рупия",
"sk": "Indonézska rupia",
"sl": "indonezijska rupija", "sl": "indonezijska rupija",
"sr": "индонежанска рупија", "sr": "индонежанска рупија",
"sv": "Rupiah", "sv": "Rupiah",
@@ -2463,6 +2470,7 @@
"sr": "нови израелски шекел", "sr": "нови израелски шекел",
"sv": "Shekel", "sv": "Shekel",
"ta": "புது இசுரேலிய சேக்கல்", "ta": "புது இசுரேலிய சேக்கல்",
"te": "ఇజ్రాయెల్ షెకెల్",
"tr": "Yeni İsrail Şekeli", "tr": "Yeni İsrail Şekeli",
"tt": "Исраил шекеле", "tt": "Исраил шекеле",
"uk": "ізраїльський новий шекель" "uk": "ізраїльський новий шекель"
@@ -2758,6 +2766,7 @@
"sr": "јапански јен", "sr": "јапански јен",
"sv": "yen", "sv": "yen",
"ta": "யென்", "ta": "யென்",
"te": "జపనీస్ యెన్",
"th": "เยน", "th": "เยน",
"tr": "Japon yeni", "tr": "Japon yeni",
"tt": "япон иенасы", "tt": "япон иенасы",
@@ -2823,6 +2832,7 @@
"ja": "キルギス・ソム", "ja": "キルギス・ソム",
"ko": "키르기스스탄 솜", "ko": "키르기스스탄 솜",
"lt": "somas", "lt": "somas",
"lv": "Kirgizstānas soms",
"nl": "Kirgizische som", "nl": "Kirgizische som",
"pa": "ਕਿਰਗਿਜ਼ਸਤਾਨੀ ਸੋਮ", "pa": "ਕਿਰਗਿਜ਼ਸਤਾਨੀ ਸੋਮ",
"pl": "som", "pl": "som",
@@ -3236,6 +3246,7 @@
"sr": "шриланчанска рупија", "sr": "шриланчанска рупија",
"sv": "Lankesisk rupie", "sv": "Lankesisk rupie",
"ta": "இலங்கை ரூபாய்", "ta": "இலங்கை ரூபாய்",
"te": "శ్రీలంక రూపాయి",
"tr": "Sri Lanka rupisi", "tr": "Sri Lanka rupisi",
"tt": "Шри-Ланка рупиясе", "tt": "Шри-Ланка рупиясе",
"uk": "ланкійська рупія", "uk": "ланкійська рупія",
@@ -3810,6 +3821,7 @@
"MXV": { "MXV": {
"de": "UNIDAD DE INVERSION", "de": "UNIDAD DE INVERSION",
"en": "unidad de inversión", "en": "unidad de inversión",
"eo": "Meksika unuo de investo",
"es": "Unidades de Inversión", "es": "Unidades de Inversión",
"ja": "メキシコ投資単位" "ja": "メキシコ投資単位"
}, },
@@ -3901,7 +3913,7 @@
"de": "Namibia-Dollar", "de": "Namibia-Dollar",
"en": "Namibian dollar", "en": "Namibian dollar",
"eo": "namibia dolaro", "eo": "namibia dolaro",
"es": "Dólar namibio", "es": "dólar namibio",
"fi": "Namibian dollari", "fi": "Namibian dollari",
"fr": "Dollar namibien", "fr": "Dollar namibien",
"ga": "dollar na Namaibe", "ga": "dollar na Namaibe",
@@ -4090,6 +4102,7 @@
"sr": "непалска рупија", "sr": "непалска рупија",
"sv": "Nepalesisk rupee", "sv": "Nepalesisk rupee",
"ta": "நேபாள ரூபாய்", "ta": "நேபாள ரூபாய்",
"te": "నేపాలీ రూపాయి",
"th": "รูปีเนปาล", "th": "รูปีเนปาล",
"tr": "Nepal rupisi", "tr": "Nepal rupisi",
"tt": "Непал рупиясе", "tt": "Непал рупиясе",
@@ -4325,6 +4338,7 @@
"sr": "филипински пезо", "sr": "филипински пезо",
"sv": "Filippinsk peso", "sv": "Filippinsk peso",
"ta": "பிலிப்பைன் பெசோ", "ta": "பிலிப்பைன் பெசோ",
"te": "ఫిలిప్పీన్ పెసో",
"th": "เปโซฟิลิปปินส์", "th": "เปโซฟิลิปปินส์",
"tr": "Filipinler pesosu", "tr": "Filipinler pesosu",
"tt": "Филипин писысы", "tt": "Филипин писысы",
@@ -4370,6 +4384,7 @@
"sr": "пакистанска рупија", "sr": "пакистанска рупија",
"sv": "Pakistansk rupee", "sv": "Pakistansk rupee",
"ta": "பாக்கித்தானிய ரூபாய்", "ta": "பாக்கித்தானிய ரூபாய்",
"te": "పాకిస్థానీ రూపాయి",
"tr": "Pakistan rupisi", "tr": "Pakistan rupisi",
"tt": "Пакстан рупиясе", "tt": "Пакстан рупиясе",
"uk": "пакистанська рупія", "uk": "пакистанська рупія",
@@ -4549,7 +4564,7 @@
"de": "rumänischer Leu", "de": "rumänischer Leu",
"en": "Romanian Leu", "en": "Romanian Leu",
"eo": "rumana leo", "eo": "rumana leo",
"es": "Leu rumano", "es": "leu rumano",
"et": "Rumeenia leu", "et": "Rumeenia leu",
"fi": "Romanian leu", "fi": "Romanian leu",
"fr": "leu roumain", "fr": "leu roumain",
@@ -4750,6 +4765,7 @@
"sr": "саудијски ријал", "sr": "саудијски ријал",
"sv": "Saudiarabisk rial", "sv": "Saudiarabisk rial",
"ta": "சவூதி ரியால்", "ta": "சவூதி ரியால்",
"te": "సౌదీ రియాల్",
"tr": "Suudi Arabistan riyali", "tr": "Suudi Arabistan riyali",
"tt": "Согуд риялы", "tt": "Согуд риялы",
"uk": "саудівський ріал", "uk": "саудівський ріал",
@@ -4948,6 +4964,7 @@
"sr": "сингапурски долар", "sr": "сингапурски долар",
"sv": "Singaporiansk dollar", "sv": "Singaporiansk dollar",
"ta": "சிங்கப்பூர் வெள்ளி", "ta": "சிங்கப்பூர் வெள்ளி",
"te": "సింగపూర్ డాలర్",
"th": "ดอลลาร์สิงคโปร์", "th": "ดอลลาร์สิงคโปร์",
"tr": "Singapur doları", "tr": "Singapur doları",
"tt": "Сингапур доллары", "tt": "Сингапур доллары",
@@ -5175,7 +5192,7 @@
"de": "syrische Lira", "de": "syrische Lira",
"en": "Syrian pound", "en": "Syrian pound",
"eo": "siria pundo", "eo": "siria pundo",
"es": "Dolar sirio", "es": "libra siria",
"fi": "Syyrian punta", "fi": "Syyrian punta",
"fr": "livre syrienne", "fr": "livre syrienne",
"ga": "punt na Siria", "ga": "punt na Siria",
@@ -5977,7 +5994,7 @@
"ms": "Franc CFA Afrika Tengah", "ms": "Franc CFA Afrika Tengah",
"nl": "Central African CFA franc", "nl": "Central African CFA franc",
"oc": "Franc CFA d'Africa Centrala", "oc": "Franc CFA d'Africa Centrala",
"pl": "środkowoafrykański frank CFA", "pl": "frank CFA",
"pt": "franco", "pt": "franco",
"ro": "Franc CFA BEAC", "ro": "Franc CFA BEAC",
"ru": "франк КФА BEAC", "ru": "франк КФА BEAC",
@@ -5999,6 +6016,7 @@
"fr": "argent d'investissement", "fr": "argent d'investissement",
"ja": "投資対象としての銀", "ja": "投資対象としての銀",
"ms": "Perak sebagai pelaburan", "ms": "Perak sebagai pelaburan",
"pt": "Prata como investimento",
"ru": "серебро как инвестиция", "ru": "серебро как инвестиция",
"sv": "Silver som investering", "sv": "Silver som investering",
"tr": "Yatırım olarak gümüş", "tr": "Yatırım olarak gümüş",
@@ -6017,6 +6035,7 @@
"lv": "zelts kā investīcija", "lv": "zelts kā investīcija",
"ml": "സ്വർണവും സാമ്പത്തിക ശാസ്ത്രവും", "ml": "സ്വർണവും സാമ്പത്തിക ശാസ്ത്രവും",
"ms": "emas sebagai pelaburan", "ms": "emas sebagai pelaburan",
"pt": "Ouro como investimento",
"ru": "золото как инвестиция", "ru": "золото как инвестиция",
"sr": "investiciono zlato", "sr": "investiciono zlato",
"sv": "investeringsguld", "sv": "investeringsguld",
@@ -7905,7 +7924,6 @@
"dolar de las islas caimán": "KYD", "dolar de las islas caimán": "KYD",
"dolar de las islas salomon": "SBD", "dolar de las islas salomon": "SBD",
"dolar de las islas salomón": "SBD", "dolar de las islas salomón": "SBD",
"dolar de namibia": "NAD",
"dolar de nueva zelanda": "NZD", "dolar de nueva zelanda": "NZD",
"dolar de singapor": "SGD", "dolar de singapor": "SGD",
"dolar de singapur": "SGD", "dolar de singapur": "SGD",
@@ -7963,7 +7981,6 @@
"dolar namibia": "NAD", "dolar namibia": "NAD",
"dolar namibian": "NAD", "dolar namibian": "NAD",
"dolar namibijski": "NAD", "dolar namibijski": "NAD",
"dolar namibio": "NAD",
"dolar neocelandes": "NZD", "dolar neocelandes": "NZD",
"dolar neocelandés": "NZD", "dolar neocelandés": "NZD",
"dolar neozeelandez": "NZD", "dolar neozeelandez": "NZD",
@@ -7978,7 +7995,6 @@
"dolar singapura": "SGD", "dolar singapura": "SGD",
"dolar singapurski": "SGD", "dolar singapurski": "SGD",
"dolar singapurtar": "SGD", "dolar singapurtar": "SGD",
"dolar sirio": "SYP",
"dolar sua": "USD", "dolar sua": "USD",
"dolar surinamdar": "SRD", "dolar surinamdar": "SRD",
"dolar suriname": "SRD", "dolar suriname": "SRD",
@@ -8179,6 +8195,7 @@
"dominicaanse peso": "DOP", "dominicaanse peso": "DOP",
"dominican peso": "DOP", "dominican peso": "DOP",
"dominican peso oro": "DOP", "dominican peso oro": "DOP",
"dominički pezo": "DOP",
"dominik pesosu": "DOP", "dominik pesosu": "DOP",
"dominika peso": "DOP", "dominika peso": "DOP",
"dominikaanisen tasavallan peso": "DOP", "dominikaanisen tasavallan peso": "DOP",
@@ -8528,7 +8545,6 @@
"etiópsky birr": "ETB", "etiópsky birr": "ETB",
"eua 17": "XBD", "eua 17": "XBD",
"eua 9": "XBC", "eua 9": "XBC",
"eur": "EUR",
"euras": "EUR", "euras": "EUR",
"eurco": "XBA", "eurco": "XBA",
"euro": "EUR", "euro": "EUR",
@@ -8859,7 +8875,6 @@
"frank kongijski": "CDF", "frank kongijski": "CDF",
"frank rwandyjski": "RWF", "frank rwandyjski": "RWF",
"frank szwajcarski": "CHF", "frank szwajcarski": "CHF",
"frank zachodnioafrykański": "XOF",
"franko suitzar": "CHF", "franko suitzar": "CHF",
"frw": "RWF", "frw": "RWF",
"ft": "HUF", "ft": "HUF",
@@ -9180,6 +9195,7 @@
"indonezijska rupija": "IDR", "indonezijska rupija": "IDR",
"indonéská rupie": "IDR", "indonéská rupie": "IDR",
"indonéz rúpia": "IDR", "indonéz rúpia": "IDR",
"indonézska rupia": "IDR",
"indonēziešu rūpija": "IDR", "indonēziešu rūpija": "IDR",
"indonēzijas rūpija": "IDR", "indonēzijas rūpija": "IDR",
"inglise nael": "GBP", "inglise nael": "GBP",
@@ -9494,6 +9510,8 @@
"kirgizia somo": "KGS", "kirgizia somo": "KGS",
"kirgizische som": "KGS", "kirgizische som": "KGS",
"kirgizistansk som": "KGS", "kirgizistansk som": "KGS",
"kirgizstānas soms": "KGS",
"kirgīzu soms": "KGS",
"kiwi dollar": "NZD", "kiwi dollar": "NZD",
"kínai jüan": "CNY", "kínai jüan": "CNY",
"kíp": "LAK", "kíp": "LAK",
@@ -9810,7 +9828,6 @@
"lek na halbáine": "ALL", "lek na halbáine": "ALL",
"lek novo": "ALL", "lek novo": "ALL",
"lekas": "ALL", "lekas": "ALL",
"lekă albaneză": "ALL",
"lekë": "ALL", "lekë": "ALL",
"leko": "ALL", "leko": "ALL",
"lempira": "HNL", "lempira": "HNL",
@@ -9981,6 +9998,7 @@
"lira libanesa": "LBP", "lira libanesa": "LBP",
"lira libanese": "LBP", "lira libanese": "LBP",
"lira na tuirce": "TRY", "lira na tuirce": "TRY",
"lira siria": "SYP",
"lira siriana": "SYP", "lira siriana": "SYP",
"lira síria": "SYP", "lira síria": "SYP",
"lira thổ nhĩ kỳ": "TRY", "lira thổ nhĩ kỳ": "TRY",
@@ -10291,6 +10309,7 @@
"meksički pezo": "MXN", "meksički pezo": "MXN",
"meksika peso": "MXN", "meksika peso": "MXN",
"meksika pesosu": "MXN", "meksika pesosu": "MXN",
"meksika unuo de investo": "MXV",
"meksikaanse peso": "MXN", "meksikaanse peso": "MXN",
"meksikas peso": "MXN", "meksikas peso": "MXN",
"meksikon peso": "MXN", "meksikon peso": "MXN",
@@ -10746,6 +10765,7 @@
"ouguiya mauritanien": "MRU", "ouguiya mauritanien": "MRU",
"ouguiya mawritania": "MRU", "ouguiya mawritania": "MRU",
"ouguiya na máratáine": "MRU", "ouguiya na máratáine": "MRU",
"ouro como investimento": "XAU",
"ouro do zimbábue": "ZWG", "ouro do zimbábue": "ZWG",
"örmény dram": "AMD", "örmény dram": "AMD",
"östkaribisk dollar": "XCD", "östkaribisk dollar": "XCD",
@@ -11111,6 +11131,7 @@
"põhja korea vonn": "KPW", "põhja korea vonn": "KPW",
"põhja korea won": "KPW", "põhja korea won": "KPW",
"põhja makedoonia denaar": "MKD", "põhja makedoonia denaar": "MKD",
"prata como investimento": "XAG",
"pula": "BWP", "pula": "BWP",
"pula botswana": "BWP", "pula botswana": "BWP",
"pula botswanais": "BWP", "pula botswanais": "BWP",
@@ -11901,6 +11922,7 @@
"somoni taxico": "TJS", "somoni taxico": "TJS",
"somoni tayiko": "TJS", "somoni tayiko": "TJS",
"somonis": "TJS", "somonis": "TJS",
"soms": "KGS",
"sonderziehungsrecht": "XDR", "sonderziehungsrecht": "XDR",
"sos": "SOS", "sos": "SOS",
"sosh": "SOS", "sosh": "SOS",
@@ -12102,7 +12124,6 @@
"szyling tanzański": "TZS", "szyling tanzański": "TZS",
"szyling ugandyjski": "UGX", "szyling ugandyjski": "UGX",
"sırp dinarı": "RSD", "sırp dinarı": "RSD",
"środkowoafrykański frank cfa": "XAF",
"šalamounský dolar": "SBD", "šalamounský dolar": "SBD",
"šalomounský dolar": "SBD", "šalomounský dolar": "SBD",
"šekel chadaš": "ILS", "šekel chadaš": "ILS",
@@ -14017,6 +14038,7 @@
"самоанский доллар": "WST", "самоанский доллар": "WST",
"самоанська тала": "WST", "самоанська тала": "WST",
"сан томе һәм принсипи добрасы": "STN", "сан томе һәм принсипи добрасы": "STN",
"саотомеанска добра": "STN",
"саотомска добра": "STN", "саотомска добра": "STN",
"саудитски риал": "SAR", "саудитски риал": "SAR",
"саудијски риал": "SAR", "саудијски риал": "SAR",
@@ -15350,13 +15372,28 @@
"ஹங்கேரிய போரிண்ட்": "HUF", "ஹங்கேரிய போரிண்ட்": "HUF",
"ஹிருன்யா": "UAH", "ஹிருன்யா": "UAH",
"ஹொங்கொங் டொலர்": "HKD", "ஹொங்கொங் டொலர்": "HKD",
"అమెరికన్ డాలర్": "USD",
"ఇజ్రాయెల్ షెకెల్": "ILS",
"కెనడియన్ డాలర్": "CAD",
"చిలీ పెసో": "CLP",
"జపనీస్ యెన్": "JPY",
"డిజిటల్ రూపాయి": "INR", "డిజిటల్ రూపాయి": "INR",
"నేపాలీ రూపాయి": "NPR",
"పాకిస్థానీ రూపాయి": "PKR",
"ఫిలిప్పీన్ పెసో": "PHP",
"బ్రూనై డాలర్": "BND",
"భారత రూపాయి": "INR", "భారత రూపాయి": "INR",
"భారతదేశ రూపాయి": "INR", "భారతదేశ రూపాయి": "INR",
"భారతీయ రూపాయి": "INR", "భారతీయ రూపాయి": "INR",
"భూటానీస్ గుల్త్రమ్": "BTN",
"యునైటెడ్ స్టేట్స్ డాలర్": "USD", "యునైటెడ్ స్టేట్స్ డాలర్": "USD",
"యూఏఈ దిర్హామ్": "AED",
"యూరో": "EUR", "యూరో": "EUR",
"రూపాయి": "INR", "రూపాయి": "INR",
"రెన్మిన్బి": "CNY",
"శ్రీలంక రూపాయి": "LKR",
"సింగపూర్ డాలర్": "SGD",
"సౌదీ రియాల్": "SAR",
"స్విస్ ఫ్రాంక్": "CHF", "స్విస్ ఫ్రాంక్": "CHF",
"അൾജീരിയൻ ദിനാർ": "DZD", "അൾജീരിയൻ ദിനാർ": "DZD",
"ഇന്തോനേഷ്യൻ റുപിയ": "IDR", "ഇന്തോനേഷ്യൻ റുപിയ": "IDR",
@@ -15607,6 +15644,8 @@
"부탄 뉘땀": "BTN", "부탄 뉘땀": "BTN",
"부탄눌트럼": "BTN", "부탄눌트럼": "BTN",
"북마케도니아 데나르": "MKD", "북마케도니아 데나르": "MKD",
"북조선 원": "KPW",
"북한 원": "KPW",
"브라질 레알": "BRL", "브라질 레알": "BRL",
"브라질 헤알": "BRL", "브라질 헤알": "BRL",
"브루나이 달러": "BND", "브루나이 달러": "BND",
@@ -16199,7 +16238,6 @@
"香港・ドル": "HKD", "香港・ドル": "HKD",
"香港元": "HKD", "香港元": "HKD",
"﷼": "IRR", "﷼": "IRR",
"﷼'": "YER", "﷼'": "YER"
"💶": "EUR"
} }
} }
File diff suppressed because it is too large Load Diff
+465 -180
View File
@@ -5740,186 +5740,6 @@
"zu-ZA": "ZA" "zu-ZA": "ZA"
} }
}, },
"karmasearch": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"da-DK": "da-DK",
"de-AT": "de-AT",
"de-CH": "de-CH",
"de-DE": "de-DE",
"en-AU": "en-AU",
"en-CA": "en-CA",
"en-GB": "en-GB",
"en-ID": "en-ID",
"en-IN": "en-IN",
"en-MY": "en-MY",
"en-NZ": "en-NZ",
"en-PH": "en-PH",
"en-US": "en-US",
"en-ZA": "en-ZA",
"es-AR": "es-AR",
"es-CL": "es-CL",
"es-ES": "es-ES",
"es-MX": "es-MX",
"es-US": "es-US",
"fi-FI": "fi-FI",
"fr-BE": "fr-BE",
"fr-CA": "fr-CA",
"fr-CH": "fr-CH",
"fr-FR": "fr-FR",
"it-IT": "it-IT",
"ja-JP": "ja-JP",
"ko-KR": "ko-KR",
"nl-BE": "nl-BE",
"nl-NL": "nl-NL",
"pl-PL": "pl-PL",
"pt-BR": "pt-BR",
"ru-RU": "ru-RU",
"sv-SE": "sv-SE",
"tr-TR": "tr-TR",
"zh-CN": "zh-CN",
"zh-HK": "zh-HK",
"zh-TW": "zh-TW"
}
},
"karmasearch images": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"da-DK": "da-DK",
"de-AT": "de-AT",
"de-CH": "de-CH",
"de-DE": "de-DE",
"en-AU": "en-AU",
"en-CA": "en-CA",
"en-GB": "en-GB",
"en-ID": "en-ID",
"en-IN": "en-IN",
"en-MY": "en-MY",
"en-NZ": "en-NZ",
"en-PH": "en-PH",
"en-US": "en-US",
"en-ZA": "en-ZA",
"es-AR": "es-AR",
"es-CL": "es-CL",
"es-ES": "es-ES",
"es-MX": "es-MX",
"es-US": "es-US",
"fi-FI": "fi-FI",
"fr-BE": "fr-BE",
"fr-CA": "fr-CA",
"fr-CH": "fr-CH",
"fr-FR": "fr-FR",
"it-IT": "it-IT",
"ja-JP": "ja-JP",
"ko-KR": "ko-KR",
"nl-BE": "nl-BE",
"nl-NL": "nl-NL",
"pl-PL": "pl-PL",
"pt-BR": "pt-BR",
"ru-RU": "ru-RU",
"sv-SE": "sv-SE",
"tr-TR": "tr-TR",
"zh-CN": "zh-CN",
"zh-HK": "zh-HK",
"zh-TW": "zh-TW"
}
},
"karmasearch news": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"da-DK": "da-DK",
"de-AT": "de-AT",
"de-CH": "de-CH",
"de-DE": "de-DE",
"en-AU": "en-AU",
"en-CA": "en-CA",
"en-GB": "en-GB",
"en-ID": "en-ID",
"en-IN": "en-IN",
"en-MY": "en-MY",
"en-NZ": "en-NZ",
"en-PH": "en-PH",
"en-US": "en-US",
"en-ZA": "en-ZA",
"es-AR": "es-AR",
"es-CL": "es-CL",
"es-ES": "es-ES",
"es-MX": "es-MX",
"es-US": "es-US",
"fi-FI": "fi-FI",
"fr-BE": "fr-BE",
"fr-CA": "fr-CA",
"fr-CH": "fr-CH",
"fr-FR": "fr-FR",
"it-IT": "it-IT",
"ja-JP": "ja-JP",
"ko-KR": "ko-KR",
"nl-BE": "nl-BE",
"nl-NL": "nl-NL",
"pl-PL": "pl-PL",
"pt-BR": "pt-BR",
"ru-RU": "ru-RU",
"sv-SE": "sv-SE",
"tr-TR": "tr-TR",
"zh-CN": "zh-CN",
"zh-HK": "zh-HK",
"zh-TW": "zh-TW"
}
},
"karmasearch videos": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"da-DK": "da-DK",
"de-AT": "de-AT",
"de-CH": "de-CH",
"de-DE": "de-DE",
"en-AU": "en-AU",
"en-CA": "en-CA",
"en-GB": "en-GB",
"en-ID": "en-ID",
"en-IN": "en-IN",
"en-MY": "en-MY",
"en-NZ": "en-NZ",
"en-PH": "en-PH",
"en-US": "en-US",
"en-ZA": "en-ZA",
"es-AR": "es-AR",
"es-CL": "es-CL",
"es-ES": "es-ES",
"es-MX": "es-MX",
"es-US": "es-US",
"fi-FI": "fi-FI",
"fr-BE": "fr-BE",
"fr-CA": "fr-CA",
"fr-CH": "fr-CH",
"fr-FR": "fr-FR",
"it-IT": "it-IT",
"ja-JP": "ja-JP",
"ko-KR": "ko-KR",
"nl-BE": "nl-BE",
"nl-NL": "nl-NL",
"pl-PL": "pl-PL",
"pt-BR": "pt-BR",
"ru-RU": "ru-RU",
"sv-SE": "sv-SE",
"tr-TR": "tr-TR",
"zh-CN": "zh-CN",
"zh-HK": "zh-HK",
"zh-TW": "zh-TW"
}
},
"mojeek": { "mojeek": {
"all_locale": null, "all_locale": null,
"custom": { "custom": {
@@ -6814,6 +6634,255 @@
}, },
"regions": {} "regions": {}
}, },
"privacywall": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"bg-BG": "BG",
"cs-CZ": "CZ",
"da-DK": "DK",
"de-AT": "AT",
"de-BE": "BE",
"de-CH": "CH",
"de-DE": "DE",
"de-LI": "LI",
"de-LU": "LU",
"el-CY": "CY",
"el-GR": "GR",
"en-AU": "AU",
"en-CA": "CA",
"en-GB": "GB",
"en-HK": "HK",
"en-IE": "IE",
"en-IN": "IN",
"en-MT": "MT",
"en-NZ": "NZ",
"en-PH": "PH",
"en-SG": "SG",
"en-US": "US",
"es-AR": "AR",
"es-CL": "CL",
"es-CO": "CO",
"es-ES": "ES",
"es-MX": "MX",
"es-PE": "PE",
"es-VE": "VE",
"et-EE": "EE",
"fi-FI": "FI",
"fil-PH": "PH",
"fr-BE": "BE",
"fr-CA": "CA",
"fr-CH": "CH",
"fr-FR": "FR",
"fr-LU": "LU",
"ga-IE": "IE",
"gsw-CH": "CH",
"gsw-LI": "LI",
"hi-IN": "IN",
"hr-HR": "HR",
"hu-HU": "HU",
"id-ID": "ID",
"it-CH": "CH",
"it-IT": "IT",
"ja-JP": "JP",
"ko-KR": "KR",
"lb-LU": "LU",
"lt-LT": "LT",
"lv-LV": "LV",
"mi-NZ": "NZ",
"ms-MY": "MY",
"ms-SG": "SG",
"mt-MT": "MT",
"nb-NO": "NO",
"nl-BE": "BE",
"nl-NL": "NL",
"nn-NO": "NO",
"pl-PL": "PL",
"pt-BR": "BR",
"pt-PT": "PT",
"qu-PE": "PE",
"ro-RO": "RO",
"sk-SK": "SK",
"sl-SI": "SI",
"sv-FI": "FI",
"sv-SE": "SE",
"ta-SG": "SG",
"th-TH": "TH",
"tr-CY": "CY",
"vi-VN": "VN",
"zh-HK": "HK",
"zh-SG": "SG",
"zh-TW": "TW"
}
},
"privacywall images": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"bg-BG": "BG",
"cs-CZ": "CZ",
"da-DK": "DK",
"de-AT": "AT",
"de-BE": "BE",
"de-CH": "CH",
"de-DE": "DE",
"de-LI": "LI",
"de-LU": "LU",
"el-CY": "CY",
"el-GR": "GR",
"en-AU": "AU",
"en-CA": "CA",
"en-GB": "GB",
"en-HK": "HK",
"en-IE": "IE",
"en-IN": "IN",
"en-MT": "MT",
"en-NZ": "NZ",
"en-PH": "PH",
"en-SG": "SG",
"en-US": "US",
"es-AR": "AR",
"es-CL": "CL",
"es-CO": "CO",
"es-ES": "ES",
"es-MX": "MX",
"es-PE": "PE",
"es-VE": "VE",
"et-EE": "EE",
"fi-FI": "FI",
"fil-PH": "PH",
"fr-BE": "BE",
"fr-CA": "CA",
"fr-CH": "CH",
"fr-FR": "FR",
"fr-LU": "LU",
"ga-IE": "IE",
"gsw-CH": "CH",
"gsw-LI": "LI",
"hi-IN": "IN",
"hr-HR": "HR",
"hu-HU": "HU",
"id-ID": "ID",
"it-CH": "CH",
"it-IT": "IT",
"ja-JP": "JP",
"ko-KR": "KR",
"lb-LU": "LU",
"lt-LT": "LT",
"lv-LV": "LV",
"mi-NZ": "NZ",
"ms-MY": "MY",
"ms-SG": "SG",
"mt-MT": "MT",
"nb-NO": "NO",
"nl-BE": "BE",
"nl-NL": "NL",
"nn-NO": "NO",
"pl-PL": "PL",
"pt-BR": "BR",
"pt-PT": "PT",
"qu-PE": "PE",
"ro-RO": "RO",
"sk-SK": "SK",
"sl-SI": "SI",
"sv-FI": "FI",
"sv-SE": "SE",
"ta-SG": "SG",
"th-TH": "TH",
"tr-CY": "CY",
"vi-VN": "VN",
"zh-HK": "HK",
"zh-SG": "SG",
"zh-TW": "TW"
}
},
"privacywall videos": {
"all_locale": null,
"custom": {},
"data_type": "traits_v1",
"languages": {},
"regions": {
"bg-BG": "BG",
"cs-CZ": "CZ",
"da-DK": "DK",
"de-AT": "AT",
"de-BE": "BE",
"de-CH": "CH",
"de-DE": "DE",
"de-LI": "LI",
"de-LU": "LU",
"el-CY": "CY",
"el-GR": "GR",
"en-AU": "AU",
"en-CA": "CA",
"en-GB": "GB",
"en-HK": "HK",
"en-IE": "IE",
"en-IN": "IN",
"en-MT": "MT",
"en-NZ": "NZ",
"en-PH": "PH",
"en-SG": "SG",
"en-US": "US",
"es-AR": "AR",
"es-CL": "CL",
"es-CO": "CO",
"es-ES": "ES",
"es-MX": "MX",
"es-PE": "PE",
"es-VE": "VE",
"et-EE": "EE",
"fi-FI": "FI",
"fil-PH": "PH",
"fr-BE": "BE",
"fr-CA": "CA",
"fr-CH": "CH",
"fr-FR": "FR",
"fr-LU": "LU",
"ga-IE": "IE",
"gsw-CH": "CH",
"gsw-LI": "LI",
"hi-IN": "IN",
"hr-HR": "HR",
"hu-HU": "HU",
"id-ID": "ID",
"it-CH": "CH",
"it-IT": "IT",
"ja-JP": "JP",
"ko-KR": "KR",
"lb-LU": "LU",
"lt-LT": "LT",
"lv-LV": "LV",
"mi-NZ": "NZ",
"ms-MY": "MY",
"ms-SG": "SG",
"mt-MT": "MT",
"nb-NO": "NO",
"nl-BE": "BE",
"nl-NL": "NL",
"nn-NO": "NO",
"pl-PL": "PL",
"pt-BR": "BR",
"pt-PT": "PT",
"qu-PE": "PE",
"ro-RO": "RO",
"sk-SK": "SK",
"sl-SI": "SI",
"sv-FI": "FI",
"sv-SE": "SE",
"ta-SG": "SG",
"th-TH": "TH",
"tr-CY": "CY",
"vi-VN": "VN",
"zh-HK": "HK",
"zh-SG": "SG",
"zh-TW": "TW"
}
},
"qwant": { "qwant": {
"all_locale": null, "all_locale": null,
"custom": {}, "custom": {},
@@ -7355,6 +7424,222 @@
}, },
"regions": {} "regions": {}
}, },
"resulthunter": {
"all_locale": "all",
"custom": {
"ui_lang": {
"az": "az",
"bg": "bg",
"br": "br",
"ca": "ca",
"cs": "cs",
"cy": "cy",
"da": "da",
"de-DE": "de-de",
"el": "el",
"en-CA": "en-ca",
"en-GB": "en-gb",
"en-IN": "en-in",
"en-US": "en-us",
"es": "es",
"et": "et",
"eu": "eu",
"fi-FI": "fi-fi",
"fr-CA": "fr-ca",
"fr-FR": "fr-fr",
"gl": "gl",
"hr": "hr",
"hu": "hu",
"id": "id",
"it": "it",
"ja-JP": "ja-jp",
"ka": "ka",
"ko": "ko",
"lt": "lt",
"lv": "lv",
"ms": "ms",
"nb": "nb",
"nl": "nl",
"pl": "pl",
"pt-BR": "pt-br",
"ro": "ro",
"ru": "ru",
"sk": "sk",
"sl": "sl",
"sq-AL": "sq-al",
"sr": "sr",
"sr_Latn": "sr-latn",
"sv": "sv",
"sw-KE": "sw-ke",
"th": "th",
"tr": "tr",
"uk": "uk",
"vi": "vi",
"zh": "zh",
"zh-TW": "zh-tw"
}
},
"data_type": "traits_v1",
"languages": {},
"regions": {
"ar-SA": "sa",
"da-DK": "dk",
"de-AT": "at",
"de-BE": "be",
"de-CH": "ch",
"de-DE": "de",
"en-AU": "au",
"en-CA": "ca",
"en-GB": "gb",
"en-HK": "hk",
"en-IN": "in",
"en-NZ": "nz",
"en-PH": "ph",
"en-US": "us",
"en-ZA": "za",
"es-AR": "ar",
"es-CL": "cl",
"es-ES": "es",
"es-MX": "mx",
"fi-FI": "fi",
"fil-PH": "ph",
"fr-BE": "be",
"fr-CA": "ca",
"fr-CH": "ch",
"fr-FR": "fr",
"gsw-CH": "ch",
"hi-IN": "in",
"id-ID": "id",
"it-CH": "ch",
"it-IT": "it",
"ja-JP": "jp",
"ko-KR": "kr",
"mi-NZ": "nz",
"ms-MY": "my",
"nb-NO": "no",
"nl-BE": "be",
"nl-NL": "nl",
"nn-NO": "no",
"pl-PL": "pl",
"pt-BR": "br",
"pt-PT": "pt",
"ru-RU": "ru",
"sv-FI": "fi",
"sv-SE": "se",
"tr-TR": "tr",
"zh-CN": "cn",
"zh-HK": "hk",
"zh-TW": "tw"
}
},
"resulthunter images": {
"all_locale": "all",
"custom": {
"ui_lang": {
"az": "az",
"bg": "bg",
"br": "br",
"ca": "ca",
"cs": "cs",
"cy": "cy",
"da": "da",
"de-DE": "de-de",
"el": "el",
"en-CA": "en-ca",
"en-GB": "en-gb",
"en-IN": "en-in",
"en-US": "en-us",
"es": "es",
"et": "et",
"eu": "eu",
"fi-FI": "fi-fi",
"fr-CA": "fr-ca",
"fr-FR": "fr-fr",
"gl": "gl",
"hr": "hr",
"hu": "hu",
"id": "id",
"it": "it",
"ja-JP": "ja-jp",
"ka": "ka",
"ko": "ko",
"lt": "lt",
"lv": "lv",
"ms": "ms",
"nb": "nb",
"nl": "nl",
"pl": "pl",
"pt-BR": "pt-br",
"ro": "ro",
"ru": "ru",
"sk": "sk",
"sl": "sl",
"sq-AL": "sq-al",
"sr": "sr",
"sr_Latn": "sr-latn",
"sv": "sv",
"sw-KE": "sw-ke",
"th": "th",
"tr": "tr",
"uk": "uk",
"vi": "vi",
"zh": "zh",
"zh-TW": "zh-tw"
}
},
"data_type": "traits_v1",
"languages": {},
"regions": {
"ar-SA": "sa",
"da-DK": "dk",
"de-AT": "at",
"de-BE": "be",
"de-CH": "ch",
"de-DE": "de",
"en-AU": "au",
"en-CA": "ca",
"en-GB": "gb",
"en-HK": "hk",
"en-IN": "in",
"en-NZ": "nz",
"en-PH": "ph",
"en-US": "us",
"en-ZA": "za",
"es-AR": "ar",
"es-CL": "cl",
"es-ES": "es",
"es-MX": "mx",
"fi-FI": "fi",
"fil-PH": "ph",
"fr-BE": "be",
"fr-CA": "ca",
"fr-CH": "ch",
"fr-FR": "fr",
"gsw-CH": "ch",
"hi-IN": "in",
"id-ID": "id",
"it-CH": "ch",
"it-IT": "it",
"ja-JP": "jp",
"ko-KR": "kr",
"mi-NZ": "nz",
"ms-MY": "my",
"nb-NO": "no",
"nl-BE": "be",
"nl-NL": "nl",
"nn-NO": "no",
"pl-PL": "pl",
"pt-BR": "br",
"pt-PT": "pt",
"ru-RU": "ru",
"sv-FI": "fi",
"sv-SE": "se",
"tr-TR": "tr",
"zh-CN": "cn",
"zh-HK": "hk",
"zh-TW": "tw"
}
},
"sepiasearch": { "sepiasearch": {
"all_locale": null, "all_locale": null,
"custom": {}, "custom": {},
+4502 -2286
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,7 +5,7 @@
], ],
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}", "ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
"versions": [ "versions": [
"150.0", "151.0",
"149.0" "150.0"
] ]
} }
+258 -143
View File
@@ -69,11 +69,6 @@
"symbol": "pk (US)", "symbol": "pk (US)",
"to_si_factor": 0.008809768 "to_si_factor": 0.008809768
}, },
"Q101427917": {
"si_name": "Q25517",
"symbol": "pk (UK)",
"to_si_factor": 0.00909218
},
"Q101428098": { "Q101428098": {
"si_name": "Q44395", "si_name": "Q44395",
"symbol": "dbar", "symbol": "dbar",
@@ -84,6 +79,11 @@
"symbol": "μbar", "symbol": "μbar",
"to_si_factor": 0.1 "to_si_factor": 0.1
}, },
"Q101435213": {
"si_name": "Q101435195",
"symbol": "Jy s",
"to_si_factor": 1e-26
},
"Q101435332": { "Q101435332": {
"si_name": "Q44395", "si_name": "Q44395",
"symbol": "cm Hg", "symbol": "cm Hg",
@@ -124,11 +124,6 @@
"symbol": "pm²", "symbol": "pm²",
"to_si_factor": 1e-24 "to_si_factor": 1e-24
}, },
"Q101463679": {
"si_name": "Q25343",
"symbol": "hm²",
"to_si_factor": 10000.0
},
"Q101464050": { "Q101464050": {
"si_name": "Q25343", "si_name": "Q25343",
"symbol": "Mm²", "symbol": "Mm²",
@@ -199,6 +194,11 @@
"symbol": "d⁻¹", "symbol": "d⁻¹",
"to_si_factor": 1.15741e-05 "to_si_factor": 1.15741e-05
}, },
"Q102129764": {
"si_name": "Q6137407",
"symbol": "a⁻¹",
"to_si_factor": 3.17098e-08
},
"Q102130673": { "Q102130673": {
"si_name": "Q182429", "si_name": "Q182429",
"symbol": "ym/s", "symbol": "ym/s",
@@ -425,9 +425,9 @@
"to_si_factor": 10.0 "to_si_factor": 10.0
}, },
"Q1042866": { "Q1042866": {
"si_name": null, "si_name": "Q199",
"symbol": "Zib", "symbol": "Zib",
"to_si_factor": null "to_si_factor": 1.1805916207174113e+21
}, },
"Q104317437": { "Q104317437": {
"si_name": "Q25381181", "si_name": "Q25381181",
@@ -995,9 +995,9 @@
"to_si_factor": 1000000000000.0 "to_si_factor": 1000000000000.0
}, },
"Q106247880": { "Q106247880": {
"si_name": null, "si_name": "Q6137407",
"symbol": "digit/s", "symbol": "digit/s",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q106247940": { "Q106247940": {
"si_name": null, "si_name": null,
@@ -1504,6 +1504,11 @@
"symbol": "m²/(s² K)", "symbol": "m²/(s² K)",
"to_si_factor": 1.0 "to_si_factor": 1.0
}, },
"Q106707206": {
"si_name": null,
"symbol": "HU",
"to_si_factor": null
},
"Q106707404": { "Q106707404": {
"si_name": "Q106707404", "si_name": "Q106707404",
"symbol": "kg m²/(s² K)", "symbol": "kg m²/(s² K)",
@@ -2314,79 +2319,124 @@
"symbol": "cm²/(sr erg)", "symbol": "cm²/(sr erg)",
"to_si_factor": 1000.0 "to_si_factor": 1000.0
}, },
"Q107710161": {
"si_name": "Q199",
"symbol": "J/bit",
"to_si_factor": 1.0
},
"Q107821494": { "Q107821494": {
"si_name": null, "si_name": "Q21401573",
"symbol": "bit/m³", "symbol": "bit/m³",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q107822428": { "Q107822428": {
"si_name": null, "si_name": "Q11547251",
"symbol": "bit/m", "symbol": "bit/m",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q107824325": { "Q107824325": {
"si_name": null, "si_name": "Q11547252",
"symbol": "bit/m²", "symbol": "bit/m²",
"to_si_factor": null "to_si_factor": 1.0
},
"Q107825143": {
"si_name": "Q11547251",
"symbol": "Eibit/m",
"to_si_factor": 1.152921504606847e+18
},
"Q107825584": {
"si_name": "Q11547252",
"symbol": "Eibit/m²",
"to_si_factor": 1.152921504606847e+18
},
"Q107825716": {
"si_name": "Q21401573",
"symbol": "Eibit/m³",
"to_si_factor": 1.152921504606847e+18
},
"Q107862736": {
"si_name": "Q11547251",
"symbol": "Gibit/m",
"to_si_factor": 1073741824.0
},
"Q107862746": {
"si_name": "Q11547252",
"symbol": "Gibit/m²",
"to_si_factor": 1073741824.0
},
"Q107862762": {
"si_name": "Q21401573",
"symbol": "Gibit/m³",
"to_si_factor": 1073741824.0
}, },
"Q107862770": { "Q107862770": {
"si_name": null, "si_name": "Q11547251",
"symbol": "Kibit/m", "symbol": "Kibit/m",
"to_si_factor": null "to_si_factor": 1024.0
}, },
"Q107862783": { "Q107862783": {
"si_name": null, "si_name": "Q11547252",
"symbol": "Kibit/m", "symbol": "Kibit/m",
"to_si_factor": null "to_si_factor": 1024.0
}, },
"Q107862850": { "Q107862850": {
"si_name": null, "si_name": "Q21401573",
"symbol": "Kibit/m³", "symbol": "Kibit/m³",
"to_si_factor": null "to_si_factor": 1024.0
}, },
"Q107862870": { "Q107862870": {
"si_name": null, "si_name": "Q11547251",
"symbol": "Mibit/m", "symbol": "Mibit/m",
"to_si_factor": null "to_si_factor": 1048576.0
}, },
"Q107862884": { "Q107862884": {
"si_name": null, "si_name": "Q11547252",
"symbol": "Mibit/m²", "symbol": "Mibit/m²",
"to_si_factor": null "to_si_factor": 1048576.0
}, },
"Q107862898": { "Q107862898": {
"si_name": null, "si_name": "Q21401573",
"symbol": "Mibit/m³", "symbol": "Mibit/m³",
"to_si_factor": null "to_si_factor": 1048576.0
}, },
"Q107970215": { "Q107970215": {
"si_name": null, "si_name": "Q11547251",
"symbol": "Pibit/m", "symbol": "Pibit/m",
"to_si_factor": null "to_si_factor": 1125899906842624.0
}, },
"Q107970224": { "Q107970224": {
"si_name": null, "si_name": "Q11547252",
"symbol": "Pibit/m²", "symbol": "Pibit/m²",
"to_si_factor": null "to_si_factor": 1125899906842624.0
}, },
"Q107970230": { "Q107970230": {
"si_name": null, "si_name": "Q21401573",
"symbol": "Pibit/m³", "symbol": "Pibit/m³",
"to_si_factor": null "to_si_factor": 1125899906842624.0
}, },
"Q107970235": { "Q107970235": {
"si_name": null, "si_name": "Q11547251",
"symbol": "Tibit/m", "symbol": "Tibit/m",
"to_si_factor": null "to_si_factor": 1099511627776.0
}, },
"Q107970256": { "Q107970256": {
"si_name": null, "si_name": "Q21401573",
"symbol": "Tibit/m³", "symbol": "Tibit/m³",
"to_si_factor": null "to_si_factor": 1099511627776.0
}, },
"Q107970266": { "Q107970266": {
"si_name": null, "si_name": "Q11547252",
"symbol": "Tibit/m²", "symbol": "Tibit/m²",
"to_si_factor": 1099511627776.0
},
"Q108112819": {
"si_name": null,
"symbol": "€/kWh",
"to_si_factor": null
},
"Q108112891": {
"si_name": null,
"symbol": "€/(MW h)",
"to_si_factor": null "to_si_factor": null
}, },
"Q108270163": { "Q108270163": {
@@ -2395,9 +2445,9 @@
"to_si_factor": 3.169e-05 "to_si_factor": 3.169e-05
}, },
"Q1084321": { "Q1084321": {
"si_name": null, "si_name": "Q6137407",
"symbol": "Tb/s", "symbol": "Tb/s",
"to_si_factor": null "to_si_factor": 1000000000000.0
}, },
"Q108533173": { "Q108533173": {
"si_name": "Q108533173", "si_name": "Q108533173",
@@ -2434,6 +2484,16 @@
"symbol": "GeV/c²", "symbol": "GeV/c²",
"to_si_factor": 1.7826619216278976e-27 "to_si_factor": 1.7826619216278976e-27
}, },
"Q108913970": {
"si_name": null,
"symbol": "person/km²",
"to_si_factor": null
},
"Q108914485": {
"si_name": null,
"symbol": "person/m²",
"to_si_factor": null
},
"Q108920356": { "Q108920356": {
"si_name": "Q25406", "si_name": "Q25406",
"symbol": "esu", "symbol": "esu",
@@ -2464,6 +2524,11 @@
"symbol": "e.u.", "symbol": "e.u.",
"to_si_factor": 4.184 "to_si_factor": 4.184
}, },
"Q109337616": {
"si_name": "Q139710667",
"symbol": "1/(100 eV)",
"to_si_factor": 6.2415e+16
},
"Q109448508": { "Q109448508": {
"si_name": null, "si_name": null,
"symbol": "man-Sv", "symbol": "man-Sv",
@@ -2499,6 +2564,11 @@
"symbol": "nm²", "symbol": "nm²",
"to_si_factor": 1e-18 "to_si_factor": 1e-18
}, },
"Q11123": {
"si_name": "Q25517",
"symbol": "pt (UK)",
"to_si_factor": 0.00056826125
},
"Q111494193": { "Q111494193": {
"si_name": "Q111494193", "si_name": "Q111494193",
"symbol": "J/(Hz mol)", "symbol": "J/(Hz mol)",
@@ -2509,6 +2579,11 @@
"symbol": "%", "symbol": "%",
"to_si_factor": 0.01 "to_si_factor": 0.01
}, },
"Q112659472": {
"si_name": null,
"symbol": "PVU",
"to_si_factor": null
},
"Q1131660": { "Q1131660": {
"si_name": "Q11570", "si_name": "Q11570",
"symbol": "st", "symbol": "st",
@@ -2540,14 +2615,14 @@
"to_si_factor": 31688087.81402895 "to_si_factor": 31688087.81402895
}, },
"Q1140444": { "Q1140444": {
"si_name": null, "si_name": "Q199",
"symbol": "Zb", "symbol": "Zb",
"to_si_factor": null "to_si_factor": 1e+21
}, },
"Q1140577": { "Q1140577": {
"si_name": null, "si_name": "Q199",
"symbol": "Yb", "symbol": "Yb",
"to_si_factor": null "to_si_factor": 0.0
}, },
"Q114559346": { "Q114559346": {
"si_name": null, "si_name": null,
@@ -2560,14 +2635,14 @@
"to_si_factor": null "to_si_factor": null
}, },
"Q1152074": { "Q1152074": {
"si_name": null, "si_name": "Q199",
"symbol": "Pb", "symbol": "Pb",
"to_si_factor": null "to_si_factor": 1000000000000000.0
}, },
"Q1152323": { "Q1152323": {
"si_name": null, "si_name": "Q199",
"symbol": "Tb", "symbol": "Tb",
"to_si_factor": null "to_si_factor": 1000000000000.0
}, },
"Q115277430": { "Q115277430": {
"si_name": null, "si_name": null,
@@ -2810,14 +2885,14 @@
"to_si_factor": 4.4482216152605 "to_si_factor": 4.4482216152605
}, },
"Q1194580": { "Q1194580": {
"si_name": null, "si_name": "Q199",
"symbol": "Mib", "symbol": "Mib",
"to_si_factor": null "to_si_factor": 1048576.0
}, },
"Q1195111": { "Q1195111": {
"si_name": null, "si_name": "Q199",
"symbol": "Eb", "symbol": "Eb",
"to_si_factor": null "to_si_factor": 1e+18
}, },
"Q1196837": { "Q1196837": {
"si_name": "Q1063756", "si_name": "Q1063756",
@@ -2865,15 +2940,20 @@
"to_si_factor": 0.03110348 "to_si_factor": 0.03110348
}, },
"Q1204894": { "Q1204894": {
"si_name": null, "si_name": "Q199",
"symbol": "Gib", "symbol": "Gib",
"to_si_factor": null "to_si_factor": 1073741824.0
}, },
"Q12129": { "Q12129": {
"si_name": "Q11573", "si_name": "Q11573",
"symbol": "pc", "symbol": "pc",
"to_si_factor": 3.085677581491367e+16 "to_si_factor": 3.085677581491367e+16
}, },
"Q12145303": {
"si_name": "Q11573",
"symbol": "rd",
"to_si_factor": 5.02921
},
"Q121965382": { "Q121965382": {
"si_name": "Q121965382", "si_name": "Q121965382",
"symbol": "mol/mol", "symbol": "mol/mol",
@@ -3114,6 +3194,11 @@
"symbol": "QF", "symbol": "QF",
"to_si_factor": 1e+30 "to_si_factor": 1e+30
}, },
"Q12558489": {
"si_name": "Q25517",
"symbol": "pk (UK)",
"to_si_factor": 0.00909218
},
"Q125962250": { "Q125962250": {
"si_name": null, "si_name": null,
"symbol": "Ry", "symbol": "Ry",
@@ -3240,14 +3325,14 @@
"to_si_factor": 1e-30 "to_si_factor": 1e-30
}, },
"Q131395783": { "Q131395783": {
"si_name": null, "si_name": "Q199",
"symbol": "Rib", "symbol": "Rib",
"to_si_factor": null "to_si_factor": 1.2379400392853803e+27
}, },
"Q131395793": { "Q131395793": {
"si_name": null, "si_name": "Q199",
"symbol": "Qib", "symbol": "Qib",
"to_si_factor": null "to_si_factor": 1.2676506002282294e+30
}, },
"Q13147228": { "Q13147228": {
"si_name": "Q844211", "si_name": "Q844211",
@@ -3309,6 +3394,11 @@
"symbol": "1/K", "symbol": "1/K",
"to_si_factor": null "to_si_factor": null
}, },
"Q133796439": {
"si_name": null,
"symbol": "T/mm²",
"to_si_factor": null
},
"Q13400897": { "Q13400897": {
"si_name": "Q1051665", "si_name": "Q1051665",
"symbol": "g", "symbol": "g",
@@ -3317,18 +3407,23 @@
"Q13479685": { "Q13479685": {
"si_name": "Q44395", "si_name": "Q44395",
"symbol": "mm H2O", "symbol": "mm H2O",
"to_si_factor": 9.80638 "to_si_factor": 9.80665
}, },
"Q1351253": { "Q1351253": {
"si_name": null, "si_name": "Q199",
"symbol": "Eib", "symbol": "Eib",
"to_si_factor": null "to_si_factor": 1.152921504606847e+18
}, },
"Q1351334": { "Q1351334": {
"si_name": null, "si_name": null,
"symbol": "Pib", "symbol": "Pib",
"to_si_factor": null "to_si_factor": null
}, },
"Q135373020": {
"si_name": null,
"symbol": "GTexel",
"to_si_factor": null
},
"Q135415097": { "Q135415097": {
"si_name": "Q25236", "si_name": "Q25236",
"symbol": "shp", "symbol": "shp",
@@ -3380,9 +3475,9 @@
"to_si_factor": 1e-06 "to_si_factor": 1e-06
}, },
"Q136039973": { "Q136039973": {
"si_name": null, "si_name": "Q6137407",
"symbol": "FPS", "symbol": "FPS",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q1361854": { "Q1361854": {
"si_name": "Q11570", "si_name": "Q11570",
@@ -3450,9 +3545,9 @@
"to_si_factor": 1000000000000.0 "to_si_factor": 1000000000000.0
}, },
"Q139054848": { "Q139054848": {
"si_name": null, "si_name": "Q117899185",
"symbol": "A·h/m²", "symbol": "A·h/m²",
"to_si_factor": null "to_si_factor": 3600.0
}, },
"Q139086088": { "Q139086088": {
"si_name": "Q69425409", "si_name": "Q69425409",
@@ -3642,7 +3737,7 @@
"Q1654435": { "Q1654435": {
"si_name": "Q25250", "si_name": "Q25250",
"symbol": "IRE", "symbol": "IRE",
"to_si_factor": 0.007 "to_si_factor": 0.007143
}, },
"Q16673974": { "Q16673974": {
"si_name": null, "si_name": null,
@@ -3820,9 +3915,9 @@
"to_si_factor": 0.01 "to_si_factor": 0.01
}, },
"Q18434272": { "Q18434272": {
"si_name": null, "si_name": "Q199",
"symbol": "°Balling", "symbol": "°Balling",
"to_si_factor": null "to_si_factor": 0.01
}, },
"Q185078": { "Q185078": {
"si_name": "Q25343", "si_name": "Q25343",
@@ -3850,9 +3945,9 @@
"to_si_factor": 1e-21 "to_si_factor": 1e-21
}, },
"Q188768": { "Q188768": {
"si_name": null, "si_name": "Q6137407",
"symbol": "FLOPS", "symbol": "FLOPS",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q190095": { "Q190095": {
"si_name": "Q190095", "si_name": "Q190095",
@@ -3989,6 +4084,11 @@
"symbol": "ppb", "symbol": "ppb",
"to_si_factor": 1e-09 "to_si_factor": 1e-09
}, },
"Q206037": {
"si_name": "Q6137407",
"symbol": "r/min",
"to_si_factor": 0.0166667
},
"Q2064166": { "Q2064166": {
"si_name": "Q179836", "si_name": "Q179836",
"symbol": "fc", "symbol": "fc",
@@ -4320,9 +4420,9 @@
"to_si_factor": 10.0 "to_si_factor": 10.0
}, },
"Q2243141": { "Q2243141": {
"si_name": null, "si_name": "Q6137407",
"symbol": "Gb/s", "symbol": "Gb/s",
"to_si_factor": null "to_si_factor": 1000000000.0
}, },
"Q2254856": { "Q2254856": {
"si_name": "Q25343", "si_name": "Q25343",
@@ -4335,9 +4435,9 @@
"to_si_factor": 0.00508 "to_si_factor": 0.00508
}, },
"Q2269250": { "Q2269250": {
"si_name": null, "si_name": "Q6137407",
"symbol": "kb/s", "symbol": "kb/s",
"to_si_factor": null "to_si_factor": 1000.0
}, },
"Q2278977": { "Q2278977": {
"si_name": null, "si_name": null,
@@ -4535,9 +4635,9 @@
"to_si_factor": 898755178700.0 "to_si_factor": 898755178700.0
}, },
"Q25325238": { "Q25325238": {
"si_name": null, "si_name": "Q106919394",
"symbol": "bhp/cm³", "symbol": "bhp/cm³",
"to_si_factor": null "to_si_factor": 745700000.0
}, },
"Q253276": { "Q253276": {
"si_name": "Q11573", "si_name": "Q11573",
@@ -4545,9 +4645,9 @@
"to_si_factor": 1609.344 "to_si_factor": 1609.344
}, },
"Q2533495": { "Q2533495": {
"si_name": null, "si_name": "Q199",
"symbol": "°P", "symbol": "°P",
"to_si_factor": null "to_si_factor": 0.01
}, },
"Q25343": { "Q25343": {
"si_name": "Q25343", "si_name": "Q25343",
@@ -4885,9 +4985,9 @@
"to_si_factor": 9.80665 "to_si_factor": 9.80665
}, },
"Q28657331": { "Q28657331": {
"si_name": null, "si_name": "Q106688958",
"symbol": "erg/(s cm²)", "symbol": "erg/(s cm²)",
"to_si_factor": null "to_si_factor": 0.001
}, },
"Q28683485": { "Q28683485": {
"si_name": "Q28683485", "si_name": "Q28683485",
@@ -4920,9 +5020,9 @@
"to_si_factor": 0.001 "to_si_factor": 0.001
}, },
"Q29463526": { "Q29463526": {
"si_name": null, "si_name": "Q199",
"symbol": "hr/yr", "symbol": "hr/yr",
"to_si_factor": null "to_si_factor": 1.14155e-06
}, },
"Q296936": { "Q296936": {
"si_name": "Q25269", "si_name": "Q25269",
@@ -5155,9 +5255,9 @@
"to_si_factor": 1e-15 "to_si_factor": 1e-15
}, },
"Q3194304": { "Q3194304": {
"si_name": null, "si_name": "Q199",
"symbol": "kb", "symbol": "kb",
"to_si_factor": null "to_si_factor": 1000.0
}, },
"Q3196665": { "Q3196665": {
"si_name": "Q215571", "si_name": "Q215571",
@@ -5295,9 +5395,9 @@
"to_si_factor": 3517.0 "to_si_factor": 3517.0
}, },
"Q3332814": { "Q3332814": {
"si_name": null, "si_name": "Q199",
"symbol": "Mb", "symbol": "Mb",
"to_si_factor": null "to_si_factor": 1000000.0
}, },
"Q33680": { "Q33680": {
"si_name": "Q33680", "si_name": "Q33680",
@@ -5390,9 +5490,9 @@
"to_si_factor": 3.085677581e+22 "to_si_factor": 3.085677581e+22
}, },
"Q3815076": { "Q3815076": {
"si_name": null, "si_name": "Q199",
"symbol": "Kib", "symbol": "Kib",
"to_si_factor": null "to_si_factor": 1024.0
}, },
"Q3858002": { "Q3858002": {
"si_name": "Q25406", "si_name": "Q25406",
@@ -5410,9 +5510,9 @@
"to_si_factor": 0.3048 "to_si_factor": 0.3048
}, },
"Q389062": { "Q389062": {
"si_name": null, "si_name": "Q199",
"symbol": "Tib", "symbol": "Tib",
"to_si_factor": null "to_si_factor": 1099511627776.0
}, },
"Q3902688": { "Q3902688": {
"si_name": "Q25517", "si_name": "Q25517",
@@ -5534,6 +5634,11 @@
"symbol": "D", "symbol": "D",
"to_si_factor": 3.335640951981521e-30 "to_si_factor": 3.335640951981521e-30
}, },
"Q4063874": {
"si_name": "Q21401573",
"symbol": "amg",
"to_si_factor": 2.6868e+25
},
"Q4068266": { "Q4068266": {
"si_name": "Q11570", "si_name": "Q11570",
"symbol": "Ʒ", "symbol": "Ʒ",
@@ -5640,9 +5745,9 @@
"to_si_factor": null "to_si_factor": null
}, },
"Q474533": { "Q474533": {
"si_name": null, "si_name": "Q25272",
"symbol": "At", "symbol": "At",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q48013": { "Q48013": {
"si_name": "Q11570", "si_name": "Q11570",
@@ -5705,14 +5810,14 @@
"to_si_factor": 0.01 "to_si_factor": 0.01
}, },
"Q50094": { "Q50094": {
"si_name": null, "si_name": "Q199",
"symbol": "Np", "symbol": "Np",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q50098": { "Q50098": {
"si_name": null, "si_name": "Q199",
"symbol": "B", "symbol": "B",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q50190518": { "Q50190518": {
"si_name": "Q25377184", "si_name": "Q25377184",
@@ -5755,9 +5860,9 @@
"to_si_factor": 1000.0 "to_si_factor": 1000.0
}, },
"Q524410": { "Q524410": {
"si_name": null, "si_name": "Q11574",
"symbol": "Ga", "symbol": "Ga",
"to_si_factor": null "to_si_factor": 3.15576e+16
}, },
"Q531": { "Q531": {
"si_name": "Q11573", "si_name": "Q11573",
@@ -5765,9 +5870,9 @@
"to_si_factor": 9460730472580800.0 "to_si_factor": 9460730472580800.0
}, },
"Q5329": { "Q5329": {
"si_name": null, "si_name": "Q50098",
"symbol": "dB", "symbol": "dB",
"to_si_factor": null "to_si_factor": 0.1
}, },
"Q53393488": { "Q53393488": {
"si_name": "Q39369", "si_name": "Q39369",
@@ -6410,9 +6515,9 @@
"to_si_factor": 3.4262590996353905 "to_si_factor": 3.4262590996353905
}, },
"Q549389": { "Q549389": {
"si_name": null, "si_name": "Q6137407",
"symbol": "b/s", "symbol": "b/s",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q550341": { "Q550341": {
"si_name": "Q550341", "si_name": "Q550341",
@@ -6440,9 +6545,9 @@
"to_si_factor": null "to_si_factor": null
}, },
"Q5558595": { "Q5558595": {
"si_name": null, "si_name": "Q199",
"symbol": "GFLOPS", "symbol": "GFLOPS",
"to_si_factor": null "to_si_factor": 1000000000.0
}, },
"Q55663153": { "Q55663153": {
"si_name": "Q55663153", "si_name": "Q55663153",
@@ -6519,20 +6624,25 @@
"symbol": "GtCO2eq", "symbol": "GtCO2eq",
"to_si_factor": null "to_si_factor": null
}, },
"Q57084839": {
"si_name": null,
"symbol": "gCDE/km",
"to_si_factor": null
},
"Q57084901": { "Q57084901": {
"si_name": null, "si_name": null,
"symbol": "KgCO2eq", "symbol": "KgCO2eq",
"to_si_factor": null "to_si_factor": null
}, },
"Q57084921": { "Q57084921": {
"si_name": null, "si_name": "Q11570",
"symbol": "gCO2eq", "symbol": "gCO2eq",
"to_si_factor": null "to_si_factor": 0.001
}, },
"Q5711255": { "Q5711255": {
"si_name": null, "si_name": "Q25517",
"symbol": "aL", "symbol": "aL",
"to_si_factor": null "to_si_factor": 1e-21
}, },
"Q5711261": { "Q5711261": {
"si_name": "Q25517", "si_name": "Q25517",
@@ -6674,11 +6784,6 @@
"symbol": "in", "symbol": "in",
"to_si_factor": 0.0254 "to_si_factor": 0.0254
}, },
"Q61793198": {
"si_name": "Q11573",
"symbol": "rd",
"to_si_factor": 5.02921
},
"Q61794766": { "Q61794766": {
"si_name": "Q11573", "si_name": "Q11573",
"symbol": "ch (US survey)", "symbol": "ch (US survey)",
@@ -6710,9 +6815,9 @@
"to_si_factor": null "to_si_factor": null
}, },
"Q61995006": { "Q61995006": {
"si_name": null, "si_name": "Q106682512",
"symbol": "KWth", "symbol": "KWth",
"to_si_factor": null "to_si_factor": 1000.0
}, },
"Q61996348": { "Q61996348": {
"si_name": "Q794261", "si_name": "Q794261",
@@ -6755,14 +6860,14 @@
"to_si_factor": 1.1574074e-05 "to_si_factor": 1.1574074e-05
}, },
"Q64740041": { "Q64740041": {
"si_name": null, "si_name": "Q106688958",
"symbol": "kWh/(m² yr)", "symbol": "kWh/(m² yr)",
"to_si_factor": null "to_si_factor": 0.000114
}, },
"Q64740314": { "Q64740314": {
"si_name": null, "si_name": "Q106688958",
"symbol": "kWh/(m² day)", "symbol": "kWh/(m² day)",
"to_si_factor": null "to_si_factor": 41.67
}, },
"Q64748817": { "Q64748817": {
"si_name": "Q80374519", "si_name": "Q80374519",
@@ -6825,9 +6930,9 @@
"to_si_factor": 1016.0469088 "to_si_factor": 1016.0469088
}, },
"Q66778234": { "Q66778234": {
"si_name": null, "si_name": "Q199",
"symbol": "TFLOPS", "symbol": "TFLOPS",
"to_si_factor": null "to_si_factor": 1000000000000.0
}, },
"Q66778809": { "Q66778809": {
"si_name": null, "si_name": null,
@@ -6839,6 +6944,11 @@
"symbol": "PFLOPS", "symbol": "PFLOPS",
"to_si_factor": null "to_si_factor": null
}, },
"Q66778951": {
"si_name": null,
"symbol": "ZFLOPS",
"to_si_factor": null
},
"Q67060736": { "Q67060736": {
"si_name": "Q67060736", "si_name": "Q67060736",
"symbol": "W/kg", "symbol": "W/kg",
@@ -7225,9 +7335,9 @@
"to_si_factor": 1.0 "to_si_factor": 1.0
}, },
"Q7350781": { "Q7350781": {
"si_name": null, "si_name": "Q6137407",
"symbol": "Mb/s", "symbol": "Mb/s",
"to_si_factor": null "to_si_factor": 1000000.0
}, },
"Q743895": { "Q743895": {
"si_name": "Q39369", "si_name": "Q39369",
@@ -7545,9 +7655,9 @@
"to_si_factor": 1.0 "to_si_factor": 1.0
}, },
"Q836941": { "Q836941": {
"si_name": null, "si_name": "Q199",
"symbol": "°Bx", "symbol": "°Bx",
"to_si_factor": null "to_si_factor": 0.01
}, },
"Q83853845": { "Q83853845": {
"si_name": "Q83853845", "si_name": "Q83853845",
@@ -7599,6 +7709,11 @@
"symbol": "hm", "symbol": "hm",
"to_si_factor": 100.0 "to_si_factor": 100.0
}, },
"Q84451486": {
"si_name": "Q84451486",
"symbol": "K m/W",
"to_si_factor": 1.0
},
"Q844976": { "Q844976": {
"si_name": "Q2844478", "si_name": "Q2844478",
"symbol": "Oe", "symbol": "Oe",
@@ -7630,9 +7745,9 @@
"to_si_factor": 1000000000.0 "to_si_factor": 1000000000.0
}, },
"Q855161": { "Q855161": {
"si_name": null, "si_name": "Q199",
"symbol": "Yib", "symbol": "Yib",
"to_si_factor": null "to_si_factor": 1.2089258196146292e+24
}, },
"Q856240": { "Q856240": {
"si_name": "Q794261", "si_name": "Q794261",
@@ -7659,6 +7774,11 @@
"symbol": "abA", "symbol": "abA",
"to_si_factor": 10.0 "to_si_factor": 10.0
}, },
"Q86897783": {
"si_name": "Q86897783",
"symbol": "Pa² s",
"to_si_factor": 1.0
},
"Q87047886": { "Q87047886": {
"si_name": "Q87047886", "si_name": "Q87047886",
"symbol": "Pa s/m³", "symbol": "Pa s/m³",
@@ -7680,14 +7800,14 @@
"to_si_factor": 1e-12 "to_si_factor": 1e-12
}, },
"Q8799": { "Q8799": {
"si_name": null, "si_name": "Q199",
"symbol": "B", "symbol": "B",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q8805": { "Q8805": {
"si_name": null, "si_name": "Q8805",
"symbol": "bit", "symbol": "bit",
"to_si_factor": null "to_si_factor": 1.0
}, },
"Q88296091": { "Q88296091": {
"si_name": "Q25517", "si_name": "Q25517",
@@ -7714,11 +7834,6 @@
"symbol": "bu (UK)", "symbol": "bu (UK)",
"to_si_factor": 0.03636872 "to_si_factor": 0.03636872
}, },
"Q89662131": {
"si_name": "Q25517",
"symbol": "pt (UK)",
"to_si_factor": 0.00056826125
},
"Q89992008": { "Q89992008": {
"si_name": "Q89992008", "si_name": "Q89992008",
"symbol": "F⁻¹", "symbol": "F⁻¹",
@@ -7745,9 +7860,9 @@
"to_si_factor": null "to_si_factor": null
}, },
"Q9048643": { "Q9048643": {
"si_name": null, "si_name": "Q25517",
"symbol": "nl", "symbol": "nl",
"to_si_factor": null "to_si_factor": 1e-12
}, },
"Q905912": { "Q905912": {
"si_name": "Q281096", "si_name": "Q281096",
+163 -112
View File
@@ -3,6 +3,7 @@
- :py:obj:`searx.enginelib.EngineCache` - :py:obj:`searx.enginelib.EngineCache`
- :py:obj:`searx.enginelib.Engine` - :py:obj:`searx.enginelib.Engine`
- :py:obj:`searx.enginelib.EngineAbout`
- :py:obj:`searx.enginelib.traits` - :py:obj:`searx.enginelib.traits`
There is a command line for developer purposes and for deeper analysis. Here is There is a command line for developer purposes and for deeper analysis. Here is
@@ -23,7 +24,7 @@ an example in which the command line is called in the development environment::
""" """
__all__ = ["EngineCache", "Engine", "ENGINES_CACHE"] __all__ = ["EngineCache", "Engine", "EngineAbout", "ENGINES_CACHE"]
import typing as t import typing as t
import abc import abc
@@ -31,6 +32,7 @@ from collections.abc import Callable
import logging import logging
import string import string
import typer import typer
import msgspec
from ..cache import ExpireCacheSQLite, ExpireCacheCfg from ..cache import ExpireCacheSQLite, ExpireCacheCfg
@@ -39,7 +41,7 @@ if t.TYPE_CHECKING:
from searx.enginelib.traits import EngineTraits from searx.enginelib.traits import EngineTraits
from searx.extended_types import SXNG_Response from searx.extended_types import SXNG_Response
from searx.result_types import EngineResults from searx.result_types import EngineResults
from searx.search.processors import OfflineParamTypes, OnlineParamTypes from searx.search.processors import OfflineParamTypes, OnlineParamTypes, ProcessorType
ENGINES_CACHE: ExpireCacheSQLite = ExpireCacheSQLite.build_cache( ENGINES_CACHE: ExpireCacheSQLite = ExpireCacheSQLite.build_cache(
ExpireCacheCfg( ExpireCacheCfg(
@@ -160,7 +162,8 @@ class EngineCache:
def __init__(self, engine_name: str, expire: int | None = None): def __init__(self, engine_name: str, expire: int | None = None):
self.expire: int = expire or ENGINES_CACHE.cfg.MAXHOLD_TIME self.expire: int = expire or ENGINES_CACHE.cfg.MAXHOLD_TIME
_valid = "-_." + string.ascii_letters + string.digits _valid = "-_." + string.ascii_letters + string.digits
self.table_name: str = "".join([c if c in _valid else "_" for c in engine_name]) # engine_name is a table and SQL table names must start with a letter
self.table_name: str = "eng_" + "".join([c if c in _valid else "_" for c in engine_name])
def set(self, key: str, value: t.Any, expire: int | None = None) -> bool: def set(self, key: str, value: t.Any, expire: int | None = None) -> bool:
return ENGINES_CACHE.set( return ENGINES_CACHE.set(
@@ -177,111 +180,7 @@ class EngineCache:
return ENGINES_CACHE.secret_hash(name=name) return ENGINES_CACHE.secret_hash(name=name)
class Engine(abc.ABC): # pylint: disable=too-few-public-methods class EngineAbout(msgspec.Struct, kw_only=True):
"""Class of engine instances build from YAML settings.
Further documentation see :ref:`general engine configuration`.
.. hint::
This class is currently never initialized and only used for type hinting.
"""
logger: logging.Logger
# Common options in the engine module
engine_type: str
"""Type of the engine (:ref:`searx.search.processors`)"""
paging: bool
"""Engine supports multiple pages."""
max_page: int = 0
"""If the engine supports paging, then this is the value for the last page
that is still supported. ``0`` means unlimited numbers of pages."""
time_range_support: bool
"""Engine supports search time range."""
safesearch: bool
"""Engine supports SafeSearch"""
language_support: bool
"""Engine supports languages (locales) search."""
language: str
"""For an engine, when there is ``language: ...`` in the YAML settings the engine
does support only this one language:
.. code:: yaml
- name: google french
engine: google
language: fr
"""
region: str
"""For an engine, when there is ``region: ...`` in the YAML settings the engine
does support only this one region::
.. code:: yaml
- name: google belgium
engine: google
region: fr-BE
"""
fetch_traits: "Callable[[EngineTraits, bool], None]"
"""Function to to fetch engine's traits from origin."""
traits: "traits.EngineTraits"
"""Traits of the engine."""
# settings.yml
categories: list[str]
"""Specifies to which :ref:`engine categories` the engine should be added."""
name: str
"""Name that will be used across SearXNG to define this engine. In settings, on
the result page .."""
engine: str
"""Name of the python file used to handle requests and responses to and from
this search engine (file name from :origin:`searx/engines` without
``.py``)."""
enable_http: bool
"""Enable HTTP (by default only HTTPS is enabled)."""
shortcut: str
"""Code used to execute bang requests (``!foo``)"""
timeout: float
"""Specific timeout for search-engine."""
display_error_messages: bool
"""Display error messages on the web UI."""
proxies: dict[str, dict[str, str]]
"""Set proxies for a specific engine (YAML):
.. code:: yaml
proxies :
http: socks5://proxy:port
https: socks5://proxy:port
"""
disabled: bool
"""To disable by default the engine, but not deleting it. It will allow the
user to manually activate it in the settings."""
inactive: bool
"""Remove the engine from the settings (*disabled & removed*)."""
about: dict[str, dict[str, str]]
"""Additional fields describing the engine. """Additional fields describing the engine.
.. code:: yaml .. code:: yaml
@@ -295,21 +194,173 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
results: HTML results: HTML
""" """
using_tor_proxy: bool # pylint: disable=too-few-public-methods
website: str = ""
"""Official web-site of the origin."""
wikidata_id: str = ""
"""`Wikidata ID <https://www.wikidata.org/wiki/Wikidata:Identifiers>`_"""
official_api_documentation: str = ""
"""URL of the official API (regardless of whether it is used)"""
use_official_api: bool = False
"""SearXNG engine makes use of the official API or not"""
require_api_key: bool = False
"""API requires a key or not."""
results: str = ""
"""Data format of the source (online-engines: of the response)."""
description: str = ""
"""Brief description of the engine and where it gets its data from.
This value should only be set as long as no description of the data source
is available via a :py:obj:`EngineAbout.wikidata_id`.
"""
language: str = ""
"""Deprecated! Migrate your setting from `engine.about.language` to
`engine.language`"""
class Engine(abc.ABC): # pylint: disable=too-few-public-methods
"""Class of engine instances build from YAML settings.
Further documentation see :ref:`general engine configuration`.
The defaults are taken from :py:obj:`searx.engines.ENGINE_DEFAULT_ARGS`.
.. hint::
This class is currently never initialized and only used for type hinting.
"""
logger: logging.Logger
# Common options of the engine module
engine_type: "ProcessorType" = "online"
"""Type of the engine (:ref:`searx.search.processors`)"""
paging: bool = False
"""Engine supports multiple pages."""
max_page: int = 0
"""If the engine supports paging, then this is the value for the last page
that is still supported. ``0`` means unlimited numbers of pages."""
time_range_support: bool = False
"""Engine supports search time range."""
safesearch: bool = False
"""Engine supports SafeSearch"""
language_support: bool = False
"""Engine supports languages (locales) search."""
fetch_traits: "Callable[[EngineTraits, bool], None]"
"""Function to to fetch engine's traits from origin."""
traits: "traits.EngineTraits"
"""Traits of the engine."""
# settings.yml
name: str
"""Name that will be used across SearXNG to define this engine. In settings, on
the result page .."""
engine: str
"""Name of the python file used to handle requests and responses to and from
this search engine (file name from :origin:`searx/engines` without
``.py``)."""
categories: list[str] = ["general"]
"""Specifies to which :ref:`engine categories` the engine should be added."""
language: str = ""
"""If the engine supports only one language, this language is specified here
(``en``, ``de``, ``"no"`` or ..); otherwise, the value remains empty. For
the YAML configuration: think of the `YAML-Norway problem
<https://ruuda.nl/2023/the-yaml-document-from-hell#the-norway-problem>`_
.. code:: yaml
- name: google norway
engine: google
language: "no"
Depending on ``language_support``, this value has similar but also slightly
different meanings.
- When ``language_support`` is **true**, the map of
:py:obj:`traits.EngineTraits.languages` is reduced to the selected
language
- When ``language_support`` is **false**, then the implementation of the
engine only supports this one ``language``
"""
region: str = ""
"""For an engine, when there is ``region: ...`` in the YAML settings the engine
does support only this one region::
.. code:: yaml
- name: google belgium
engine: google
region: fr-BE
"""
enable_http: bool
"""Enable HTTP (by default only HTTPS is enabled)."""
shortcut: str
"""Code used to execute bang requests (``!foo``)"""
timeout: float
"""Specific timeout for search-engine."""
display_error_messages: bool
"""Display error messages on the web UI."""
disabled: bool = False
"""To disable by default the engine, but not deleting it. It will allow the
user to manually activate it in the settings."""
inactive: bool = False
"""Remove the engine from the settings (*disabled & removed*)."""
about: EngineAbout = EngineAbout()
"""Additional fields describing the engine."""
using_tor_proxy: bool = False
"""Using tor proxy (``true``) or not (``false``) for this engine.""" """Using tor proxy (``true``) or not (``false``) for this engine."""
send_accept_language_header: bool send_accept_language_header: bool = True
"""When this option is activated (default), the language (locale) that is """When this option is activated (default), the language (locale) that is
selected by the user is used to build and send a ``Accept-Language`` header selected by the user is used to build and send a ``Accept-Language`` header
in the request to the origin search engine.""" in the request to the origin search engine."""
tokens: list[str] tokens: list[str] = []
"""A list of secret tokens to make this engine *private*, more details see """A list of secret tokens to make this engine *private*, more details see
:ref:`private engines`.""" :ref:`private engines`."""
weight: int weight: float = 1.0
"""Weighting of the results of this engine (:ref:`weight <settings engines>`).""" """Weighting of the results of this engine (:ref:`weight <settings engines>`)."""
proxies: dict[str, dict[str, str]]
"""Set proxies for a specific engine (YAML):
.. code:: yaml
proxies :
http: socks5://proxy:port
https: socks5://proxy:port
"""
def setup(self, engine_settings: dict[str, t.Any]) -> bool: # pylint: disable=unused-argument def setup(self, engine_settings: dict[str, t.Any]) -> bool: # pylint: disable=unused-argument
"""Dynamic setup of the engine settings. """Dynamic setup of the engine settings.
+15 -25
View File
@@ -116,19 +116,6 @@ class EngineTraits:
return self.all_locale return self.all_locale
return locales.get_engine_locale(searxng_locale, self.regions, default=default) return locales.get_engine_locale(searxng_locale, self.regions, default=default)
def is_locale_supported(self, searxng_locale: str) -> bool:
"""A *locale* (SearXNG's internal representation) is considered to be
supported by the engine if the *region* or the *language* is supported
by the engine.
For verification the functions :py:func:`EngineTraits.get_region` and
:py:func:`EngineTraits.get_language` are used.
"""
if self.data_type == "traits_v1":
return bool(self.get_region(searxng_locale) or self.get_language(searxng_locale))
raise TypeError("engine traits of type %s is unknown" % self.data_type)
def copy(self): def copy(self):
"""Create a copy of the dataclass object.""" """Create a copy of the dataclass object."""
return EngineTraits(**dataclasses.asdict(self)) return EngineTraits(**dataclasses.asdict(self))
@@ -155,11 +142,11 @@ class EngineTraits:
""" """
if self.data_type == "traits_v1": if self.data_type == "traits_v1":
self._set_traits_v1(engine) self._set_traits_v1(engine) # pyright: ignore[reportArgumentType]
else: else:
raise TypeError("engine traits of type %s is unknown" % self.data_type) raise TypeError("engine traits of type %s is unknown" % self.data_type)
def _set_traits_v1(self, engine: "Engine | types.ModuleType") -> None: def _set_traits_v1(self, engine: "Engine") -> None:
# For an engine, when there is `language: ...` in the YAML settings the engine # For an engine, when there is `language: ...` in the YAML settings the engine
# does support only this one language (region):: # does support only this one language (region)::
# #
@@ -172,22 +159,25 @@ class EngineTraits:
_msg = "settings.yml - engine: '%s' / %s: '%s' not supported" _msg = "settings.yml - engine: '%s' / %s: '%s' not supported"
languages = traits.languages if engine.language:
if hasattr(engine, "language"): if engine.language_support:
if engine.language not in languages: if not len(traits.languages) > 1:
raise ValueError(_msg % (engine.name, "language", engine.language)) raise ValueError(
traits.languages = {engine.language: languages[engine.language]} f"engine {engine.name}: activated language_support with just one or less languages"
)
if engine.language not in traits.languages:
raise ValueError(_msg % (engine.name, "language", engine.language))
traits.languages = {engine.language: traits.languages[engine.language]}
regions = traits.regions if engine.region:
if hasattr(engine, "region"): if engine.region not in traits.regions:
if engine.region not in regions:
raise ValueError(_msg % (engine.name, "region", engine.region)) raise ValueError(_msg % (engine.name, "region", engine.region))
traits.regions = {engine.region: regions[engine.region]} traits.regions = {engine.region: traits.regions[engine.region]}
engine.language_support = bool(traits.languages or traits.regions) engine.language_support = bool(traits.languages or traits.regions)
# set the copied & modified traits in engine's namespace # set the copied & modified traits in engine's namespace
engine.traits = traits # pyright: ignore[reportAttributeAccessIssue] engine.traits = traits
class EngineTraitsMap(dict[str, EngineTraits]): class EngineTraitsMap(dict[str, EngineTraits]):
+1 -1
View File
@@ -22,8 +22,8 @@ about = {
"use_official_api": False, "use_official_api": False,
"require_api_key": False, "require_api_key": False,
"results": "HTML", "results": "HTML",
"language": "zh",
} }
language = "zh"
# Engine Configuration # Engine Configuration
categories = ["general"] categories = ["general"]
+137
View File
@@ -0,0 +1,137 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=invalid-name
"""500px_ is a online network for photographers with millions of members
worldwide. Photographers come to 500px to discover and share incredible photos,
gain meaningful exposure, compete in photo contests, and license their photos
through our exclusive distribution partners.
.. _500px: https://500px.com
"""
import typing as t
import codecs
import random
import string
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
# about
about = {
"website": "https://500px.com",
"wikidata_id": "Q354894",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://500px.com"
api_url = "https://api.500px.com"
categories = ["images"]
paging = True
results_per_page = 30
"""Number of results to return in the request.
The default was taken from the WEB UI, where the GraphQL query sets the value to
*static*: ``first: 30``.
"""
SXNG_query = """query PhotoSearchPaginationContainerQuery(
$first: Int, $cursor: String, $search: String!, $sort: PhotoSort, $filters: [PhotoSearchFilter!], $nlp: Boolean
) {
...SXNG_query
}
fragment SXNG_query on Query {
photoSearch(sort: $sort, first: $first, after: $cursor, search: $search, filters: $filters, nlp: $nlp) {
edges {
node {
id
canonicalPath
name
description
width
height
photographer: uploader {
displayName
}
images(sizes: [35, 33]) {
size
url
jpegUrl
webpUrl
id
}
}
cursor
}
}
}
"""
def setup(_) -> bool:
global SXNG_query # pylint: disable=global-statement
rand_str: str = "".join(random.choice(string.ascii_letters) for _ in range(5))
SXNG_query = SXNG_query.replace("SXNG_query", "PhotoSearchPaginationContainer_query_1" + rand_str)
return True
def request(query: str, params: "OnlineParams") -> None:
# cursor is the base64 hash of the string "pos-<offset-1>", e.g. "pos-29" -> "cG9zLTI5"
offset = ((params["pageno"] - 1) * results_per_page) - 1
cursor = codecs.encode(f"pos-{offset}".encode("utf-8"), "base64").decode("utf-8")
params["url"] = f"{api_url}/graphql"
params["method"] = "POST"
params["json"] = {
"operationName": "PhotoSearchPaginationContainerQuery",
"variables": {
"first": results_per_page,
"cursor": cursor,
"search": query,
"sort": "RELEVANCE",
"filters": [],
"nlp": False,
},
"query": SXNG_query,
}
def response(resp: "SXNG_Response"):
res = EngineResults()
json_data = resp.json()["data"]["photoSearch"]
for edge in json_data["edges"]:
node = edge["node"] # pyright: ignore[reportAny]
if not node["images"]:
continue
images: list[dict[str, str]] = sorted(node["images"], key=lambda i: i["size"])
thumbnail_src = images[0]["url"]
img_src = images[-1]["url"]
res.add(
res.types.LegacyResult(
{
"template": "images.html",
"url": base_url + node["canonicalPath"],
"thumbnail_src": thumbnail_src,
"img_src": img_src,
"title": node["name"],
"content": node["description"],
"author": node["photographer"]["displayName"],
"resolution": f"{node['width']}x{node['height']}",
}
)
)
return res
+6 -6
View File
@@ -5,19 +5,19 @@ intended monkey patching of the engine modules.
.. attention:: .. attention::
Monkey-patching modules is a practice from the past that shouldn't be Monkey-patching modules is a practice from the past that shouldn't be
expanded upon. In the long run, there should be an engine class that can be expanded upon. In the long run, engines should be instances of
inherited. However, as long as this class doesn't exist, and as long as all :py:obj:`searx.enginelib.Engine`. However, as long as long as all engine
engine modules aren't converted to an engine class, these builtin types will modules aren't converted to this class, these builtin types will still be
still be needed. needed.
""" """
import logging import logging
from searx.enginelib import traits as _traits from searx.enginelib import traits as _traits
logger: logging.Logger logger: logging.Logger
supported_languages: str
language_aliases: str
language_support: bool language_support: bool
language: str
region: str
traits: _traits.EngineTraits traits: _traits.EngineTraits
# from searx.engines.ENGINE_DEFAULT_ARGS # from searx.engines.ENGINE_DEFAULT_ARGS
+50 -9
View File
@@ -12,41 +12,50 @@ import typing as t
import sys import sys
import copy import copy
import os
from os.path import realpath, dirname from os.path import realpath, dirname
import warnings
import types import types
import inspect import inspect
import msgspec
from searx import logger, settings from searx import logger, settings
from searx.utils import load_module from searx.utils import load_module
from searx.data import ENGINE_TRAITS
if t.TYPE_CHECKING: from searx.enginelib import Engine, EngineAbout
from searx.enginelib import Engine
logger = logger.getChild('engines') logger = logger.getChild('engines')
ENGINE_DIR = dirname(realpath(__file__)) ENGINE_DIR = dirname(realpath(__file__))
# Defaults for the namespace of an engine module, see load_engine() # Defaults for the namespace of an engine module, see load_engine()
ENGINE_DEFAULT_ARGS: dict[str, int | str | list[t.Any] | dict[str, t.Any] | bool] = { ENGINE_DEFAULT_ARGS: dict[str, t.Any] = {
# Common options in the engine module # Common options in the engine module
"engine_type": "online", "engine_type": "online",
"paging": False, "paging": False,
"max_page": 0,
"time_range_support": False, "time_range_support": False,
"safesearch": False, "safesearch": False,
"language_support": False,
# settings.yml # settings.yml
"categories": ["general"], "categories": ["general"],
"language": "",
"region": "",
"enable_http": False, "enable_http": False,
"shortcut": "-", "shortcut": "-",
"timeout": settings["outgoing"]["request_timeout"], "timeout": settings["outgoing"]["request_timeout"],
"display_error_messages": True, "display_error_messages": True,
"disabled": False, "disabled": False,
"inactive": False, "inactive": False,
"about": {}, "about": EngineAbout(),
"using_tor_proxy": False, "using_tor_proxy": False,
"send_accept_language_header": True, "send_accept_language_header": True,
"tokens": [], "tokens": [],
"max_page": 0, "weight": 1.0,
} }
"""Default values that are set in an engine of type *module*, please compare
with the class :py:obj:`searx.enginelib.Engine`."""
# set automatically when an engine does not have any tab category # set automatically when an engine does not have any tab category
DEFAULT_CATEGORY = 'other' DEFAULT_CATEGORY = 'other'
@@ -176,14 +185,41 @@ def set_loggers(engine: "Engine|types.ModuleType", engine_name: str):
def update_engine_attributes(engine: "Engine | types.ModuleType", engine_data: dict[str, t.Any]): def update_engine_attributes(engine: "Engine | types.ModuleType", engine_data: dict[str, t.Any]):
# pylint: disable=too-many-branches
# set engine attributes from engine_data # set engine attributes from engine_data
kvargs: dict[str, t.Any]
if isinstance(engine.about, EngineAbout):
kvargs = {**msgspec.to_builtins(engine.about), **engine_data.get("about", {})}
else:
kvargs = {**engine.about, **engine_data.get("about", {})}
try:
engine.about = EngineAbout(**kvargs)
except TypeError as exc:
raise TypeError(
f"engine '{engine_data['name']}' ({engine_data['engine']}) - in the about section --> {exc}"
) from exc
# warn about deprecated engine settings
if engine.about.language:
if hasattr(engine, "language") and not engine.language:
engine.language = engine.about.language
warnings.warn(
f"engine '{engine_data['name']}' ({engine_data['engine']})"
f" - migrate engine.about.language to engine.language!",
DeprecationWarning,
2,
)
for param_name, param_value in engine_data.items(): for param_name, param_value in engine_data.items():
if param_name == "about":
continue
if param_name == 'categories': if param_name == 'categories':
if isinstance(param_value, str): if isinstance(param_value, str):
param_value = list(map(str.strip, param_value.split(','))) param_value = list(map(str.strip, param_value.split(',')))
engine.categories = param_value # type: ignore engine.categories = param_value # type: ignore
elif hasattr(engine, 'about') and param_name == 'about':
engine.about = {**engine.about, **engine_data['about']} # type: ignore
else: else:
setattr(engine, param_name, param_value) setattr(engine, param_name, param_value)
@@ -192,6 +228,9 @@ def update_engine_attributes(engine: "Engine | types.ModuleType", engine_data: d
if not hasattr(engine, arg_name): if not hasattr(engine, arg_name):
setattr(engine, arg_name, copy.deepcopy(arg_value)) setattr(engine, arg_name, copy.deepcopy(arg_value))
if ENGINE_TRAITS.get(engine.name, {}).get("languages") and not engine.language_support:
raise ValueError(f"engine '{engine.name}' ({engine_data['engine']}) language_support should be set to True")
def update_attributes_for_tor(engine: "Engine | types.ModuleType"): def update_attributes_for_tor(engine: "Engine | types.ModuleType"):
if using_tor_proxy(engine) and hasattr(engine, 'onion_url'): if using_tor_proxy(engine) and hasattr(engine, 'onion_url'):
@@ -278,6 +317,8 @@ def load_engines(engine_list: list[dict[str, t.Any]]):
else: else:
# if an engine can't be loaded (if for example the engine is missing # if an engine can't be loaded (if for example the engine is missing
# tor or some other requirements) its set to inactive! # tor or some other requirements) its set to inactive!
logger.error("loading engine %s failed: set engine to inactive!", engine_data.get("name", "???")) logger.error(
f"(PID {os.getpid()}) loading engine %s failed: set engine to inactive!", engine_data.get("name", "???")
)
engine_data["inactive"] = True engine_data["inactive"] = True
return engines return engines
+1 -1
View File
@@ -16,12 +16,12 @@ about = {
"use_official_api": False, "use_official_api": False,
"require_api_key": False, "require_api_key": False,
"results": "HTML", "results": "HTML",
"language": "zh",
} }
# Engine Configuration # Engine Configuration
categories = ["videos"] categories = ["videos"]
paging = True paging = True
language = "zh"
# Base URL # Base URL
base_url = "https://www.acfun.cn" base_url = "https://www.acfun.cn"
-9
View File
@@ -39,7 +39,6 @@ url_xpath = './h4/a/@href'
title_xpath = './h4/a[1]' title_xpath = './h4/a[1]'
content_xpath = './/p[1]' content_xpath = './/p[1]'
correction_xpath = '//*[@id="didYouMean"]//a' correction_xpath = '//*[@id="didYouMean"]//a'
number_of_results_xpath = '//*[@id="totalResults"]'
name_token_xpath = '//form[@id="searchForm"]/input[@type="hidden"]/@name' name_token_xpath = '//form[@id="searchForm"]/input[@type="hidden"]/@name'
value_token_xpath = '//form[@id="searchForm"]/input[@type="hidden"]/@value' value_token_xpath = '//form[@id="searchForm"]/input[@type="hidden"]/@value'
@@ -107,14 +106,6 @@ def response(resp):
for correction in eval_xpath_list(dom, correction_xpath): for correction in eval_xpath_list(dom, correction_xpath):
results.append({'correction': extract_text(correction)}) results.append({'correction': extract_text(correction)})
# get number of results
number_of_results = eval_xpath(dom, number_of_results_xpath)
if number_of_results:
try:
results.append({'number_of_results': int(extract_text(number_of_results))})
except: # pylint: disable=bare-except
pass
# Update the tokens to the newest ones # Update the tokens to the newest ones
token_str = _get_tokens(dom) token_str = _get_tokens(dom)
CACHE.set('ahmia-tokens', token_str, expire=60 * 60) CACHE.set('ahmia-tokens', token_str, expire=60 * 60)
+1
View File
@@ -64,6 +64,7 @@ about: dict[str, t.Any] = {
# engine dependent config # engine dependent config
categories = ["files", "books"] categories = ["files", "books"]
paging: bool = True paging: bool = True
language_support = True
# search-url # search-url
base_url: list[str] | str = [] base_url: list[str] | str = []
+1 -1
View File
@@ -42,8 +42,8 @@ about = {
'use_official_api': False, 'use_official_api': False,
'require_api_key': False, 'require_api_key': False,
'results': 'HTML', 'results': 'HTML',
'language': 'it',
} }
language = "it"
def request(query, params): def request(query, params):
-208
View File
@@ -1,208 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""AOL supports WEB, image, and video search. Internally, it uses the Bing
index.
AOL doesn't seem to support setting the language via request parameters, instead
the results are based on the URL. For example, there is
- `search.aol.com <https://search.aol.com>`_ for English results
- `suche.aol.de <https://suche.aol.de>`_ for German results
However, AOL offers its services only in a few regions:
- en-US: search.aol.com
- de-DE: suche.aol.de
- fr-FR: recherche.aol.fr
- en-GB: search.aol.co.uk
- en-CA: search.aol.ca
In order to still offer sufficient support for language and region, the `search
keywords`_ known from Bing, ``language`` and ``loc`` (region), are added to the
search term (AOL is basically just a proxy for Bing).
.. _search keywords:
https://support.microsoft.com/en-us/topic/advanced-search-keywords-ea595928-5d63-4a0b-9c6b-0b769865e78a
"""
from urllib.parse import urlencode, unquote_plus
import typing as t
from lxml import html
from dateutil import parser
from searx.result_types import EngineResults
from searx.utils import eval_xpath_list, eval_xpath, extract_text
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://www.aol.com",
"wikidata_id": "Q2407",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
categories = ["general"]
search_type = "search" # supported: search, image, video
paging = True
safesearch = True
time_range_support = True
results_per_page = 10
base_url = "https://search.aol.com"
time_range_map = {"day": "1d", "week": "1w", "month": "1m", "year": "1y"}
safesearch_map = {0: "p", 1: "r", 2: "i"}
def init(_):
if search_type not in ("search", "image", "video"):
raise ValueError(f"unsupported search type {search_type}")
def request(query: str, params: "OnlineParams") -> None:
language, region = (params["searxng_locale"].split("-") + [None])[:2]
if language and language != "all":
query = f"{query} language:{language}"
if region:
query = f"{query} loc:{region}"
args: dict[str, str | int | None] = {
"q": query,
"b": params["pageno"] * results_per_page + 1, # page is 1-indexed
"pz": results_per_page,
}
if params["time_range"]:
args["fr2"] = "time"
args["age"] = params["time_range"]
else:
args["fr2"] = "sb-top-search"
params["cookies"]["sB"] = f"vm={safesearch_map[params['safesearch']]}"
params["url"] = f"{base_url}/aol/{search_type}?{urlencode(args)}"
logger.debug(params)
def _deobfuscate_url(obfuscated_url: str) -> str | None:
# URL looks like "https://search.aol.com/click/_ylt=AwjFSDjd;_ylu=JfsdjDFd/RV=2/RE=1774058166/RO=10/RU=https%3a%2f%2fen.wikipedia.org%2fwiki%2fTree/RK=0/RS=BP2CqeMLjscg4n8cTmuddlEQA2I-" # pylint: disable=line-too-long
if not obfuscated_url:
return None
for part in obfuscated_url.split("/"):
if part.startswith("RU="):
return unquote_plus(part[3:])
# pattern for de-obfuscating URL not found, fall back to Yahoo's tracking link
return obfuscated_url
def _general_results(doc: html.HtmlElement) -> EngineResults:
res = EngineResults()
for result in eval_xpath_list(doc, "//div[@id='web']//ol/li[not(contains(@class, 'first'))]"):
obfuscated_url = extract_text(eval_xpath(result, ".//h3/a/@href"))
if not obfuscated_url:
continue
url = _deobfuscate_url(obfuscated_url)
if not url:
continue
res.add(
res.types.MainResult(
url=url,
title=extract_text(eval_xpath(result, ".//h3/a")) or "",
content=extract_text(eval_xpath(result, ".//div[contains(@class, 'compText')]")) or "",
thumbnail=extract_text(eval_xpath(result, ".//a[contains(@class, 'thm')]/img/@data-src")) or "",
)
)
return res
def _video_results(doc: html.HtmlElement) -> EngineResults:
res = EngineResults()
for result in eval_xpath_list(doc, "//div[contains(@class, 'results')]//ol/li"):
obfuscated_url = extract_text(eval_xpath(result, ".//a/@href"))
if not obfuscated_url:
continue
url = _deobfuscate_url(obfuscated_url)
if not url:
continue
published_date_raw = extract_text(eval_xpath(result, ".//div[contains(@class, 'v-age')]"))
try:
published_date = parser.parse(published_date_raw or "")
except parser.ParserError:
published_date = None
res.add(
res.types.LegacyResult(
{
"template": "videos.html",
"url": url,
"title": extract_text(eval_xpath(result, ".//h3")),
"content": extract_text(eval_xpath(result, ".//div[contains(@class, 'compText')]")),
"thumbnail": extract_text(eval_xpath(result, ".//img[contains(@class, 'thm')]/@src")),
"length": extract_text(eval_xpath(result, ".//span[contains(@class, 'v-time')]")),
"publishedDate": published_date,
}
)
)
return res
def _image_results(doc: html.HtmlElement) -> EngineResults:
res = EngineResults()
for result in eval_xpath_list(doc, "//section[@id='results']//ul/li"):
obfuscated_url = extract_text(eval_xpath(result, "./a/@href"))
if not obfuscated_url:
continue
url = _deobfuscate_url(obfuscated_url)
if not url:
continue
res.add(
res.types.LegacyResult(
{
"template": "images.html",
# results don't have an extra URL, only the image source
"url": url,
"title": extract_text(eval_xpath(result, ".//a/@aria-label")),
"thumbnail_src": extract_text(eval_xpath(result, ".//img/@src")),
"img_src": url,
}
)
)
return res
def response(resp: "SXNG_Response") -> EngineResults:
doc = html.fromstring(resp.text)
match search_type:
case "search":
results = _general_results(doc)
case "image":
results = _image_results(doc)
case "video":
results = _video_results(doc)
case _:
raise ValueError("unsupported search type")
for suggestion in eval_xpath_list(doc, ".//ol[contains(@class, 'searchRightBottom')]//table//a"):
results.add(results.types.LegacyResult({"suggestion": extract_text(suggestion)}))
return results
+1
View File
@@ -35,6 +35,7 @@ about = {
categories = ["it", "software wikis"] categories = ["it", "software wikis"]
paging = True paging = True
main_wiki = "wiki.archlinux.org" main_wiki = "wiki.archlinux.org"
language_support = True
def request(query, params): def request(query, params):
+2 -2
View File
@@ -21,7 +21,7 @@ about = {
categories = ['images'] categories = ['images']
paging = True paging = True
nb_per_page = 20 page_size = 20
search_api = 'https://api.artic.edu/api/v1/artworks/search?' search_api = 'https://api.artic.edu/api/v1/artworks/search?'
image_api = 'https://www.artic.edu/iiif/2/' image_api = 'https://www.artic.edu/iiif/2/'
@@ -34,7 +34,7 @@ def request(query, params):
'q': query, 'q': query,
'page': params['pageno'], 'page': params['pageno'],
'fields': 'id,title,artist_display,medium_display,image_id,date_display,dimensions,artist_titles', 'fields': 'id,title,artist_display,medium_display,image_id,date_display,dimensions,artist_titles',
'limit': nb_per_page, 'limit': page_size,
} }
) )
params['url'] = search_api + args params['url'] = search_api + args
+1 -1
View File
@@ -54,8 +54,8 @@ about = {
"use_official_api": True, "use_official_api": True,
"require_api_key": True, "require_api_key": True,
"results": "JSON", "results": "JSON",
"language": "en",
} }
language = "en"
CACHE: EngineCache CACHE: EngineCache
"""Persistent (SQLite) key/value cache that deletes its values after ``expire`` """Persistent (SQLite) key/value cache that deletes its values after ``expire``
+1 -1
View File
@@ -23,8 +23,8 @@ about = {
"use_official_api": False, "use_official_api": False,
"require_api_key": False, "require_api_key": False,
"results": "JSON", "results": "JSON",
"language": "zh",
} }
language = "zh"
paging = True paging = True
categories = [] categories = []
+3 -3
View File
@@ -26,7 +26,7 @@ base_url = (
# engine dependent config # engine dependent config
paging = True paging = True
number_of_results = 10 page_size = 10
# shortcuts for advanced search # shortcuts for advanced search
shortcut_dict = { shortcut_dict = {
@@ -57,12 +57,12 @@ def request(query, params):
query = re.sub(key, val, query) query = re.sub(key, val, query)
# basic search # basic search
offset = (params['pageno'] - 1) * number_of_results offset = (params['pageno'] - 1) * page_size
string_args = { string_args = {
'query': urlencode({'query': query}), 'query': urlencode({'query': query}),
'offset': offset, 'offset': offset,
'hits': number_of_results, 'hits': page_size,
} }
params['url'] = base_url.format(**string_args) params['url'] = base_url.format(**string_args)
+2 -3
View File
@@ -51,11 +51,10 @@ def request(query, params):
} }
params["url"] = f"{base_url}?{urlencode(query_params)}" params["url"] = f"{base_url}?{urlencode(query_params)}"
params["headers"]["Referer"] = "https://www.bilibili.com" params["headers"]["Referer"] = "https://www.bilibili.com/"
params["headers"]["Accept"] = "application/json, text/javascript, */*; q=0.01"
params["cookies"] = cookie params["cookies"] = cookie
return params
def response(resp): def response(resp):
search_res = resp.json() search_res = resp.json()
+3 -16
View File
@@ -13,7 +13,6 @@ implementations are shared by other engines:
""" """
import base64 import base64
import re
import typing as t import typing as t
from urllib.parse import parse_qs, urlencode, urlparse from urllib.parse import parse_qs, urlencode, urlparse
@@ -48,7 +47,7 @@ _safesearch_map: dict[int, str] = {
} }
"""Filter results. 0: None, 1: Moderate, 2: Strict""" """Filter results. 0: None, 1: Moderate, 2: Strict"""
base_url = "https://www.bing.com/search" base_url = "https://www.bing.com"
"""Bing-Web search URL""" """Bing-Web search URL"""
@@ -94,7 +93,7 @@ def override_accept_language(params: "OnlineParams", engine_region: str | None)
params["headers"]["Accept-Language"] = f"{engine_region},{lang};q=0.9" params["headers"]["Accept-Language"] = f"{engine_region},{lang};q=0.9"
def request(query: str, params: "OnlineParams") -> "OnlineParams": def request(query: str, params: "OnlineParams"):
"""Assemble a Bing-Web request.""" """Assemble a Bing-Web request."""
engine_region = traits.get_region(params["searxng_locale"], traits.all_locale) engine_region = traits.get_region(params["searxng_locale"], traits.all_locale)
@@ -110,13 +109,7 @@ def request(query: str, params: "OnlineParams") -> "OnlineParams":
if locale_params: if locale_params:
query_params.update(locale_params) query_params.update(locale_params)
params["url"] = f"{base_url}?{urlencode(query_params)}" params["url"] = f"{base_url}/search?{urlencode(query_params)}"
# in some regions where geoblocking is employed (e.g. China),
# www.bing.com redirects to the regional version of Bing
params["allow_redirects"] = True
return params
def response(resp: "SXNG_Response") -> list[dict[str, t.Any]]: def response(resp: "SXNG_Response") -> list[dict[str, t.Any]]:
@@ -159,12 +152,6 @@ def response(resp: "SXNG_Response") -> list[dict[str, t.Any]]:
results.append({"url": href, "title": title, "content": content}) results.append({"url": href, "title": title, "content": content})
if results:
result_len_container = "".join(eval_xpath(dom, '//span[@class="sb_count"]//text()'))
result_len_container = re.sub(r"[^0-9]", "", result_len_container)
if result_len_container:
results.append({"number_of_results": int(result_len_container)})
return results return results
+2 -4
View File
@@ -34,7 +34,7 @@ time_map = {
"year": 60 * 24 * 365, "year": 60 * 24 * 365,
} }
base_url = "https://www.bing.com/images/async" base_url = "https://www.bing.com"
"""Bing-Image search URL""" """Bing-Image search URL"""
@@ -64,9 +64,7 @@ def request(query, params):
if params["time_range"]: if params["time_range"]:
query_params["qft"] = "filterui:age-lt%s" % time_map[params["time_range"]] query_params["qft"] = "filterui:age-lt%s" % time_map[params["time_range"]]
params["url"] = base_url + "?" + urlencode(query_params) params["url"] = base_url + "/images/async?" + urlencode(query_params)
return params
def response(resp): def response(resp):
+2 -4
View File
@@ -44,7 +44,7 @@ time_map = {
difference of *last day* and *last week* in the result list is just marginally. difference of *last day* and *last week* in the result list is just marginally.
Bing does not have news range ``year`` / we use ``month`` instead.""" Bing does not have news range ``year`` / we use ``month`` instead."""
base_url = "https://www.bing.com/news/infinitescrollajax" base_url = "https://www.bing.com"
"""Bing (News) search URL""" """Bing (News) search URL"""
@@ -74,9 +74,7 @@ def request(query, params):
if params["time_range"]: if params["time_range"]:
query_params["qft"] = time_map.get(params["time_range"], 'interval="9"') query_params["qft"] = time_map.get(params["time_range"], 'interval="9"')
params["url"] = base_url + "?" + urlencode(query_params) params["url"] = base_url + "/news/infinitescrollajax?" + urlencode(query_params)
return params
def response(resp): def response(resp):
+2 -2
View File
@@ -29,7 +29,7 @@ paging = True
safesearch = True safesearch = True
time_range_support = True time_range_support = True
base_url = "https://www.bing.com/videos/asyncv2" base_url = "https://www.bing.com"
"""Bing-Video search URL""" """Bing-Video search URL"""
@@ -60,7 +60,7 @@ def request(query, params):
query_params["form"] = "VRFLTR" query_params["form"] = "VRFLTR"
query_params["qft"] = " filterui:videoage-lt%s" % time_map[params["time_range"]] query_params["qft"] = " filterui:videoage-lt%s" % time_map[params["time_range"]]
params["url"] = base_url + "?" + urlencode(query_params) params["url"] = base_url + "/videos/asyncv2?" + urlencode(query_params)
return params return params
+1
View File
@@ -34,6 +34,7 @@ about = {
categories = ["general", "social media"] categories = ["general", "social media"]
paging = True paging = True
time_range_support = True time_range_support = True
language_support = True
base_url = "https://boardreader.com" base_url = "https://boardreader.com"
time_range_map = {"day": "1", "week": "7", "month": "30", "year": "365"} time_range_map = {"day": "1", "week": "7", "month": "30", "year": "365"}
+1 -1
View File
@@ -13,8 +13,8 @@ about = {
'use_official_api': False, 'use_official_api': False,
'require_api_key': False, 'require_api_key': False,
'results': 'JSON', 'results': 'JSON',
'language': 'de',
} }
language = "de"
paging = True paging = True
categories = ['general'] categories = ['general']
+115
View File
@@ -0,0 +1,115 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Chatnoir is an open source search engine developed by Webis, a network of
researchers from the universities of Weimar, Halle and Leipzig. It supports
different different text corpora as indexes, e.g. CommonCrawl. See its
`announcement`_ for more information.
.. _announcement : https://groups.google.com/g/common-crawl/c/3o2dOHpeRxo/m/H2Osqz9dAAAJ
"""
import typing as t
from searx.exceptions import SearxEngineAPIException
from searx.extended_types import SXNG_Response
from searx.network import get, post
from searx.result_types import EngineResults
from searx.utils import html_to_text
if t.TYPE_CHECKING:
from searx.search.processors import OnlineParams
about = {
"website": "https://www.chatnoir.eu",
"official_api_documentation": "https://www.chatnoir.eu/docs/api-general",
"use_official_api": True,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://www.chatnoir.eu"
categories = ["general"]
paging = True
page_size = 10
api_key = ""
"""You can optionally provide your own API key here. This one will then be used
instead of scraping an API key."""
search_index = "cw22"
"""Search index to browse in. See `the API documentation
<https://www.chatnoir.eu/docs/api-general>`_ for a full list."""
def _obtain_api_key() -> tuple[str, str, str]:
home_resp = get(base_url)
if not home_resp.ok:
raise SearxEngineAPIException("failed to obtain api key")
csrf_token = home_resp.cookies["csrftoken"]
token_resp = post(
"https://www.chatnoir.eu/?init",
headers={
"Referer": f"{base_url}/",
"X-Requested-With": "XMLHttpRequest",
"X-Csrf-Token": csrf_token,
},
cookies=home_resp.cookies,
)
if not token_resp.ok:
raise SearxEngineAPIException("failed to obtain api key")
session_id = token_resp.cookies["sessionid"]
scraped_api_key = token_resp.json()["token"]["token"]
return csrf_token, session_id, scraped_api_key
def request(query: str, params: "OnlineParams"):
if api_key:
# use user-provided API key instead of scraping one
headers = {
"Authorization": f"Bearer {api_key}",
}
params["headers"].update(headers)
else:
csrf_token, session_id, scraped_api_key = _obtain_api_key()
headers = {
"Authorization": f"Bearer {scraped_api_key}",
"X-Csrf-Token": csrf_token,
}
params["headers"].update(headers)
params["cookies"] = {"csrftoken": session_id, "sessionid": session_id}
params["url"] = f"{base_url}/api/v1/_search"
params["method"] = "POST"
json_data = {
"query": query,
"index": [
search_index,
],
"from": (params["pageno"] - 1) * page_size,
"size": page_size,
"_extended_meta": True,
}
params["json"] = json_data
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
results = resp.json()["results"]
for result in results:
res.add(
res.types.MainResult(
url=result["target_uri"],
title=html_to_text(result["title"]),
content=html_to_text(result["snippet"]),
)
)
return res
+3 -3
View File
@@ -10,13 +10,13 @@ about = {
'use_official_api': False, 'use_official_api': False,
'require_api_key': False, 'require_api_key': False,
'results': 'JSON', 'results': 'JSON',
'language': 'de',
} }
language = "de"
paging = True paging = True
categories = [] categories = []
number_of_results = 20 page_size = 20
skip_premium = True skip_premium = True
@@ -25,7 +25,7 @@ thumbnail_format = "crop-240x300"
def request(query, params): def request(query, params):
args = {'query': query, 'limit': number_of_results, 'offset': (params['pageno'] - 1) * number_of_results} args = {'query': query, 'limit': page_size, 'offset': (params['pageno'] - 1) * page_size}
params['url'] = f"{base_url}/v2/search-gateway/recipes?{urlencode(args)}" params['url'] = f"{base_url}/v2/search-gateway/recipes?{urlencode(args)}"
return params return params
+8 -1
View File
@@ -70,13 +70,13 @@ about = {
"use_official_api": False, "use_official_api": False,
"require_api_key": False, "require_api_key": False,
"results": "JSON", "results": "JSON",
"language": "zh",
} }
paging = True paging = True
time_range_support = True time_range_support = True
results_per_page = 10 results_per_page = 10
categories = [] categories = []
language = "zh"
ChinasoCategoryType = t.Literal['news', 'videos', 'images'] ChinasoCategoryType = t.Literal['news', 'videos', 'images']
"""ChinaSo supports news, videos, images search. """ChinaSo supports news, videos, images search.
@@ -156,6 +156,13 @@ def response(resp):
except Exception as e: except Exception as e:
raise SearxEngineAPIException(f"Invalid response: {e}") from e raise SearxEngineAPIException(f"Invalid response: {e}") from e
# Upstream returns {'status': 0, 'msg': 'empty result', 'data': {}} when there
# are no results; this is a valid empty result rather than an API error.
if not isinstance(data, dict) or "data" not in data:
raise SearxEngineAPIException("Invalid response")
if not data["data"]:
return []
parsers = {'news': parse_news, 'images': parse_images, 'videos': parse_videos} parsers = {'news': parse_news, 'images': parse_images, 'videos': parse_videos}
return parsers[chinaso_category](data) return parsers[chinaso_category](data)
+3 -3
View File
@@ -56,7 +56,7 @@ the API key in the engine :ref:`core engine config`."""
categories = ["science", "scientific publications"] categories = ["science", "scientific publications"]
paging = True paging = True
nb_per_page = 10 page_size = 10
base_url = "https://api.core.ac.uk/v3/search/works/" base_url = "https://api.core.ac.uk/v3/search/works/"
@@ -77,8 +77,8 @@ def request(query: str, params: "OnlineParams") -> None:
# API v3 uses different parameters # API v3 uses different parameters
search_params = { search_params = {
"q": query, "q": query,
"offset": (params["pageno"] - 1) * nb_per_page, "offset": (params["pageno"] - 1) * page_size,
"limit": nb_per_page, "limit": page_size,
"sort": "relevance", "sort": "relevance",
} }
+3 -2
View File
@@ -38,8 +38,9 @@ about = {
# engine dependent config # engine dependent config
categories = ["videos"] categories = ["videos"]
paging = True paging = True
number_of_results = 10 page_size = 10
language_support = True
time_range_support = True time_range_support = True
time_delta_dict = { time_delta_dict = {
"day": timedelta(days=1), "day": timedelta(days=1),
@@ -113,7 +114,7 @@ def request(query, params):
"password_protected": "false", "password_protected": "false",
"private": "false", "private": "false",
"sort": "relevance", "sort": "relevance",
"limit": number_of_results, "limit": page_size,
"fields": ",".join(result_fields), "fields": ",".join(result_fields),
} }
+6 -9
View File
@@ -24,7 +24,7 @@ import typing as t
import json import json
from searx.result_types import EngineResults from searx.result_types import EngineResults
from searx.enginelib import EngineCache from searx.enginelib import EngineCache, EngineAbout
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
from searx.search.processors import RequestParams from searx.search.processors import RequestParams
@@ -35,13 +35,11 @@ categories = ["general"]
disabled = True disabled = True
timeout = 2.0 timeout = 2.0
about = { language = "en"
"wikidata_id": None, about = EngineAbout(
"official_api_documentation": None, results="JSON",
"use_official_api": False, description="Demo offline engine Engine with results in the English language.",
"require_api_key": False, )
"results": "JSON",
}
# if there is a need for globals, use a leading underline # if there is a need for globals, use a leading underline
_my_offline_engine: str = "" _my_offline_engine: str = ""
@@ -109,7 +107,6 @@ def search(query: str, params: "RequestParams") -> EngineResults:
kvmap=kvmap, kvmap=kvmap,
) )
) )
res.add(res.types.LegacyResult(number_of_results=count))
# cache counter value for 20sec # cache counter value for 20sec
CACHE.set("count", count, expire=20) CACHE.set("count", count, expire=20)
+9 -8
View File
@@ -25,6 +25,7 @@ import typing as t
from urllib.parse import urlencode from urllib.parse import urlencode
from searx.result_types import EngineResults from searx.result_types import EngineResults
from searx.enginelib import EngineAbout
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response from searx.extended_types import SXNG_Response
@@ -43,14 +44,14 @@ page_size = 20
search_api = "https://api.artic.edu/api/v1/artworks/search" search_api = "https://api.artic.edu/api/v1/artworks/search"
image_api = "https://www.artic.edu/iiif/2/" image_api = "https://www.artic.edu/iiif/2/"
about = { about = EngineAbout(
"website": "https://www.artic.edu", website="https://www.artic.edu",
"wikidata_id": "Q239303", wikidata_id="Q239303",
"official_api_documentation": "http://api.artic.edu/docs/", official_api_documentation="http://api.artic.edu/docs/",
"use_official_api": True, use_official_api=True,
"require_api_key": False, require_api_key=False,
"results": "JSON", results="JSON",
} )
# if there is a need for globals, use a leading underline # if there is a need for globals, use a leading underline
+1 -1
View File
@@ -11,8 +11,8 @@ about = {
'use_official_api': False, 'use_official_api': False,
'require_api_key': False, 'require_api_key': False,
'results': 'HTML', 'results': 'HTML',
'language': 'de',
} }
language = "de"
categories = [] categories = []
paging = True paging = True
-2
View File
@@ -176,6 +176,4 @@ def response(resp):
results.append(result) results.append(result)
results.append({'number_of_results': len(json_data['topics'])})
return results return results
+101
View File
@@ -0,0 +1,101 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Dogpile is a metasearch engine by the American advertising company `System1`_.
.. _System1: https://system1.com/
"""
import typing as t
from datetime import datetime, timezone
import html
from searx.utils import format_duration, html_to_text, humanize_number
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://www.dogpile.com",
"wikidata_id": "Q3595363",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
paging = True
safesearch = True
categories = ["general"]
dogpile_categ = "search"
"""Category to search in. Can be either "search", "images", "videos" or "news"."""
base_url = "https://www.dogpile.com"
safe_search_map = {0: "none", 1: "moderate", 2: "heavy"}
def init(_):
if dogpile_categ not in ("search", "images", "videos", "news"):
raise ValueError("invalid search type: %s" % dogpile_categ)
def request(query: str, params: "OnlineParams"):
params["url"] = f"{base_url}/api/{dogpile_categ}"
params["method"] = "POST"
params["json"] = {"q": query, "qadf": safe_search_map[params["safesearch"]], "page": params["pageno"]}
return params
def response(resp: "SXNG_Response"):
res = EngineResults()
json_resp = resp.json()
for result in json_resp["results"]:
if dogpile_categ == "search":
res.add(
res.types.MainResult(
url=result["clickUrl"],
title=html_to_text(result["title"]),
content=html_to_text(result["description"]),
)
)
elif dogpile_categ == "news":
res.add(
res.types.MainResult(
url=result["clickUrl"],
title=html_to_text(html.unescape(result["title"])),
content=html_to_text(html.unescape(result["description"])),
thumbnail=result["thumbnailUrl"],
publishedDate=datetime.fromtimestamp(result["date"], tz=timezone.utc),
)
)
elif dogpile_categ == "videos":
res.add(
res.types.LegacyResult(
template="videos.html",
url=result["clickUrl"],
title=html_to_text(result["title"]),
content=html_to_text(result["description"]),
thumbnail=result["thumbnailUrl"],
publishedDate=datetime.fromisoformat(result["publishDate"]),
length=format_duration(result["duration"]),
views=humanize_number(result["viewCount"]),
)
)
elif dogpile_categ == "images":
res.add(
res.types.Image(
url=result["altClickUrl"],
title=html_to_text(result["title"]),
content=html_to_text(result["description"]),
img_src=result["clickUrl"],
thumbnail_src=result["thumbnailUrl"],
resolution=f"{result['width']}x{result['height']}",
img_format=result["format"],
)
)
return res
-1
View File
@@ -21,7 +21,6 @@ about = {
# engine dependent config # engine dependent config
categories = ['general'] # 'images', 'music', 'videos', 'files' categories = ['general'] # 'images', 'music', 'videos', 'files'
paging = False paging = False
number_of_results = 5
# search-url # search-url
# Doku is OpenSearch compatible # Doku is OpenSearch compatible
+1
View File
@@ -203,6 +203,7 @@ about: dict[str, str | bool] = {
categories: list[str] = ["general", "web"] categories: list[str] = ["general", "web"]
paging: bool = True paging: bool = True
time_range_support: bool = True time_range_support: bool = True
language_support = True
safesearch: bool = True safesearch: bool = True
"""DDG-lite: user can't select but the results are filtered.""" """DDG-lite: user can't select but the results are filtered."""
+3
View File
@@ -28,6 +28,7 @@ about = {
"require_api_key": False, "require_api_key": False,
"results": "JSON (site requires js to get images)", "results": "JSON (site requires js to get images)",
} }
language_support = True
# engine dependent config # engine dependent config
categories = [] categories = []
@@ -41,7 +42,9 @@ safesearch_cookies = {0: "-2", 1: None, 2: "1"}
safesearch_args = {0: "1", 1: None, 2: "1"} safesearch_args = {0: "1", 1: None, 2: "1"}
search_path_map = {"images": "i", "videos": "v", "news": "news"} search_path_map = {"images": "i", "videos": "v", "news": "news"}
_HTTP_User_Agent: str = gen_useragent() _HTTP_User_Agent: str = gen_useragent()
send_accept_language_header = False
def init(engine_settings: dict[str, t.Any]): def init(engine_settings: dict[str, t.Any]):
+1
View File
@@ -26,6 +26,7 @@ about = {
"require_api_key": False, "require_api_key": False,
"results": "JSON", "results": "JSON",
} }
language_support = True
# engine dependent config # engine dependent config
categories = ["weather"] categories = ["weather"]
+156
View File
@@ -0,0 +1,156 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""DuckDuckGo Web (general)
This implementation fetches the link to the first API page
(i.e. ``links.duckduckgo.com/d.js?...``) from duckduckgo.com and uses the ``n``
parameter of the API to fetch all subsequent pages.
This also means that it's not possible to immediately search for the third
page - the first and the second page would need to be loaded first.
The reason why we can't just normally use the `vqd` value is that the API URLs
require an additional parameter `dp` which seems generated at server-side, so we
can't build it ourselves and must scrape it from the HTML pages.
"""
import typing as t
from urllib.parse import quote_plus
from lxml import html
from searx.utils import html_to_text, gen_useragent, extract_text, eval_xpath
from searx.result_types import EngineResults
from searx.enginelib import EngineCache
from searx.network import get
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://duckduckgo.com/",
"wikidata_id": "Q12805",
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
# engine dependent config
categories = ["general"]
paging = True
_HTTP_User_Agent: str = gen_useragent()
base_url = "https://duckduckgo.com"
CACHE: EngineCache
"""Cache to store the API URLs for combinations of (query, page)."""
def setup(engine_settings: dict[str, str]):
global CACHE # pylint:disable=global-statement
CACHE = EngineCache(engine_settings["name"])
return CACHE
def _fetch_first_page_link(
query: str,
headers: dict[str, str],
):
"""Search for a::
<link id="deep_preload_link" rel="preload" as="script"
href="https://links.duckduckgo.com/d.js?q=rust&t=D&l=us-en&s=0&a=h_&ct=DE&vqd=VQD_VALUE&bing_market=en-US&p_ent=&ex=-1&dp=LONG_TOKEN
>
This points to the first page
""" # pylint:disable=line-too-long
cache_key = _cache_key(query, 1)
cached: str | None = CACHE.get(cache_key)
if cached:
return cached
resp = get(
url=f"{base_url}/?q={quote_plus(query)}&t=h_&ia=web",
headers=headers,
timeout=2,
)
if resp.status_code != 200:
logger.error("vqd: got HTTP %s from duckduckgo.com", resp.status_code)
dom = html.fromstring(resp.text)
first_page_link = extract_text(eval_xpath(dom, "//link[@id='deep_preload_link']/@href"))
if not first_page_link:
logger.error("vqd: failed to load first page JS url from ddg response (return empty string)")
return ""
logger.debug("got link to first page from duckduckgo.com request: '%s'", first_page_link)
CACHE.set(cache_key, first_page_link, expire=7200)
return first_page_link
def _cache_key(query: str, pageno: int) -> str:
return f"nextpage_url|{query}|{pageno}"
def request(query: str, params: "OnlineParams") -> None:
if len(query) >= 500:
# DDG does not accept queries with more than 499 chars
params["url"] = None
return
headers = params["headers"]
# The vqd value is generated from the query and the UA header. To be able
# to reuse the vqd value, the UA header must be static.
headers["User-Agent"] = _HTTP_User_Agent
headers["Accept"] = "*/*"
headers["Referer"] = f"{base_url}/"
headers["Host"] = "duckduckgo.com"
# Sec-Fetch headers are required to not get blocked when sending a Firefox user agent
headers["Sec-Fetch-Dest"] = "script"
headers["Sec-Fetch-Mode"] = "no-cors"
headers["Sec-Fetch-Site"] = "same-site"
api_url = ""
if params["pageno"] > 1:
api_url = CACHE.get(_cache_key(query, params["pageno"]))
else:
api_url = _fetch_first_page_link(query, headers)
if not api_url:
params["url"] = None
return
params["url"] = api_url.replace("/d.js?", "/d.js?o=json&")
# TODO: support safesearch, timerange and engine traits # pylint:disable=fixme
def response(resp: "SXNG_Response"):
res = EngineResults()
res_json = resp.json()
for result in res_json["results"]:
if "u" not in result:
continue
res.add(
res.types.MainResult(url=result["u"], title=html_to_text(result["t"]), content=html_to_text(result["a"]))
)
# link to next page
next_page_path = res_json["results"][-1].get("n")
if next_page_path:
CACHE.set(
_cache_key(resp.search_params["query"], resp.search_params["pageno"] + 1),
base_url + next_page_path,
expire=60 * 60,
)
return res
+1 -9
View File
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
"""Duden""" """Duden"""
import re
from urllib.parse import quote, urljoin from urllib.parse import quote, urljoin
from lxml import html from lxml import html
from searx.utils import extract_text, eval_xpath, eval_xpath_list, eval_xpath_getindex from searx.utils import extract_text, eval_xpath, eval_xpath_list, eval_xpath_getindex
@@ -15,8 +14,8 @@ about = {
"use_official_api": False, "use_official_api": False,
"require_api_key": False, "require_api_key": False,
"results": 'HTML', "results": 'HTML',
"language": 'de',
} }
language = "de"
categories = ['dictionaries'] categories = ['dictionaries']
paging = True paging = True
@@ -51,13 +50,6 @@ def response(resp):
dom = html.fromstring(resp.text) dom = html.fromstring(resp.text)
number_of_results_element = eval_xpath_getindex(
dom, '//a[@class="active" and contains(@href,"/suchen/dudenonline")]/span/text()', 0, default=None
)
if number_of_results_element is not None:
number_of_results_string = re.sub('[^0-9]', '', number_of_results_element)
results.append({'number_of_results': int(number_of_results_string)})
for result in eval_xpath_list(dom, '//section[not(contains(@class, "essay"))]'): for result in eval_xpath_list(dom, '//section[not(contains(@class, "essay"))]'):
url = eval_xpath_getindex(result, './/h2/a', 0).get('href') url = eval_xpath_getindex(result, './/h2/a', 0).get('href')
url = urljoin(base_url, url) url = urljoin(base_url, url)
+1 -1
View File
@@ -55,7 +55,7 @@ about = {
'official_api_documentation': 'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html', 'official_api_documentation': 'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html',
'use_official_api': True, 'use_official_api': True,
'require_api_key': False, 'require_api_key': False,
'format': 'JSON', "results": "JSON",
} }
base_url = 'http://localhost:9200' base_url = 'http://localhost:9200'
+118
View File
@@ -0,0 +1,118 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""FindFiles.net_ is a Germany-based file search engine.
FindFiles.net_ is a specialized file search engine designed to help you search
files online with precision. Unlike traditional search engines that mainly index
web pages, FindFiles focuses on finding real files on the internet - including
PDFs, documents, archives, videos, datasets, and more.
.. _FindFiles.net: https://findfiles.net
"""
from os.path import basename
from urllib.parse import urlencode
import typing as t
from lxml import html
from searx.result_types import EngineResults
from searx.utils import extract_text, eval_xpath, eval_xpath_list
if t.TYPE_CHECKING:
from extended_types import SXNG_Response
from search.processors import OnlineParams
about = {
"website": "https://findfiles.net",
"wikidata_id": None,
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
base_url = "https://findfiles.net"
categories = ["files"]
paging = True
safeserach = True
safesearch_map = {
0: "contentguard.off",
1: "contentguard.moderate",
2: "contentguard.strict",
}
FindFilesCategory = t.Literal[
"all",
"document",
"text",
"image",
"audio",
"video",
]
FINDFILES_CATEGORIES = t.get_args(FindFilesCategory)
findfiles_categ: FindFilesCategory = "all"
"""Category to search in."""
def setup(_: dict[str, t.Any]) -> bool:
if findfiles_categ not in FINDFILES_CATEGORIES:
raise ValueError("invalid category: %s" % findfiles_categ)
return True
def request(query: str, params: "OnlineParams") -> None:
args = {
"query": query,
"contentguard": safesearch_map[params["safesearch"]],
"page": params["pageno"],
}
# the language in the path doesn't change anything about the results, it
# only changes the UI
params["url"] = f"{base_url}/en/serp/{findfiles_categ}/?{urlencode(args)}"
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
dom = html.fromstring(resp.text)
if findfiles_categ == "image":
for result in eval_xpath_list(
dom, "//div[contains(@class, 'image-mosaic')]/div[contains(@class, 'image-item')]"
):
res.add(
res.types.Image(
url=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/a/@href")) or "",
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/a")) or "",
thumbnail_src=extract_text(eval_xpath(result, ".//img/@src")) or "",
)
)
elif findfiles_categ == "video":
for result in eval_xpath_list(
dom, "//div[contains(@class, 'video-mosaic')]/div[contains(@class, 'video-item')]"
):
video_src = extract_text(eval_xpath(result, ".//video/@src")) or ""
res.add(
res.types.LegacyResult(
template="videos.html",
url=video_src,
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/span")) or "",
iframe_src=video_src or "",
)
)
else:
for result in eval_xpath_list(dom, "//ol/li[contains(@class, 'result-item')]/article"):
filename = basename(extract_text(eval_xpath(result, ".//h3")) or "")
res.add(
res.types.File(
url=extract_text(eval_xpath(result, ".//h3/a/@href")) or "",
title=filename,
content=" ".join(extract_text(el) or "" for el in eval_xpath_list(result, "./div/span")),
filename=filename,
size=extract_text(eval_xpath(result, "(.//span[@id])[1]")) or "",
embedded=extract_text(eval_xpath(result, ".//audio/@src")) or "",
)
)
return res
+169
View File
@@ -0,0 +1,169 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Fireball_ is a Germany-based, privacy-focused search engine.
It likely doesn't have its own index, but it's unclear where its results come
from.
.. _Fireball: https://fireball.com
"""
import typing as t
from datetime import datetime
from urllib.parse import urlencode
from searx.enginelib import EngineCache
from searx.exceptions import SearxEngineAPIException
from searx.extended_types import SXNG_Response
from searx.result_types import EngineResults
from searx.network import post
from searx.utils import html_to_text
if t.TYPE_CHECKING:
from searx.search.processors import OnlineParams
about = {
"website": "https://fireball.com",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://fireball.com"
categories = ["general"]
fireball_category = "web" # values: "web", "news", "videos"
paging = False
safesearch = True
safe_search_map = {0: "off", 1: "moderate", 2: "strict"}
CACHE: EngineCache
"""Cache to store the settings cookie (contains e.g. language, safesearch, ...)."""
CACHE_VALID_DURATION = 30 * 24 * 3600 # one month, same as website
"""Duration how long settings cookies are valid."""
def init(engine_settings: dict[str, t.Any]):
global CACHE # pylint: disable=global-statement
CACHE = EngineCache(engine_settings["name"])
if fireball_category not in ("web", "news", "videos"):
raise ValueError(f"Unsupported category: {fireball_category}")
def _cache_key(fireball_settings: dict[str, str]) -> str:
return f"fireball_settings_{fireball_settings['safesearch']}_{fireball_settings['market']}"
def _get_search_settings_cookie(params: 'OnlineParams') -> str:
"""Get a 'fireball' cookie for the given locale and safesearch setting set
in params."""
# the language is set by only specifying the search country on their
# website, they only list DE and US, but in fact it supports much more
# countries
country = "US"
if params["searxng_locale"] != "all":
language_parts = params["searxng_locale"].split("-")
country = language_parts[-1].upper()
fireball_settings = {
"action": "save",
"language": "en", # language is irrelevant, only changes UI language
"market": country,
"adprovider": "automatic",
"target": "_blank",
"tiles": "on",
"safesearch": safe_search_map[params["safesearch"]],
}
cache_key = _cache_key(fireball_settings)
cached_cookie = CACHE.get(cache_key)
if cached_cookie:
return cached_cookie
resp = post("https://fireball.com/settings", data=fireball_settings)
if not resp.ok:
raise SearxEngineAPIException("failed to obtain cookie for settings")
cookie = resp.cookies.get("fireball")
if not cookie:
raise SearxEngineAPIException("failed to obtain cookie for settings")
CACHE.set(cache_key, cookie, expire=CACHE_VALID_DURATION)
return cookie
def request(query: str, params: "OnlineParams"):
# no matter the category, the request is always the same, i.e. we get all
# different categories with one HTTP request
args = {
"f": "web",
"q": query,
}
params["url"] = f"{base_url}/getResults/?{urlencode(args)}"
params["cookies"]["fireball"] = _get_search_settings_cookie(params)
# referer header has to be set, otherwise the requests get blocked
params["headers"]["Referer"] = f"{base_url}/search?{urlencode(args)}"
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
json_data = resp.json()
for result in json_data.get(fireball_category, {}).get("results", []):
published_date = None
if result.get("page_age"):
published_date = datetime.fromisoformat(result["page_age"])
if fireball_category == "web":
res.add(
res.types.MainResult(
url=result["url"],
title=html_to_text(result["title"]),
content=html_to_text(result["description"]),
publishedDate=published_date,
)
)
elif fireball_category == "news":
thumbnail: str | None = None
if result.get("thumbnail"):
thumbnail = result["thumbnail"]["src"]
res.add(
res.types.MainResult(
url=result["url"],
title=html_to_text(result["title"]),
content=html_to_text(result["description"]),
thumbnail=thumbnail or "",
publishedDate=published_date,
)
)
elif fireball_category == "videos":
length = None
if result.get("video"):
length = result["video"].get("duration")
res.add(
res.types.LegacyResult(
{
"template": "videos.html",
"url": result["url"],
"title": html_to_text(result["title"]),
"content": html_to_text(result["description"]),
"thumbnail": result.get("thumbnail", {}).get("original"),
"length": length,
"publishedDate": published_date,
}
)
)
return res
+71
View File
@@ -0,0 +1,71 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Flaticon_ is a database for icons.
.. _Flaticon: https://www.flaticon.com
"""
from urllib.parse import urlencode
import typing as t
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://www.flaticon.com",
"wikidata_id": "Q105283791",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://www.flaticon.com"
categories = ["images", "icons"]
paging = True
def request(query: str, params: "OnlineParams") -> None:
args = {
"word": query,
}
params["headers"].update(
{
# important: query term is not URL encoded in the referer string
"Referer": f"{base_url}/search?word={query}",
"X-Requested-With": "XMLHttpRequest",
}
)
params["url"] = f"{base_url}/ajax/search/{params['pageno']}?{urlencode(args)}"
def _fix_url(url: str) -> str:
return url.replace(r"\/", "/")
def response(resp: "SXNG_Response"):
res = EngineResults()
result: dict[str, str] # TBH: dict[str, t.Any]
for result in resp.json()["items"]:
tags = [
tag_info["tag"] for tag_info in result["tags"] if tag_info["tag"] # pyright: ignore[reportArgumentType]
]
res.add(
res.types.Image(
title=result["name"],
content=", ".join(tags),
url=_fix_url(result["slug"]),
thumbnail_src=_fix_url(result["png"]),
img_src=_fix_url(result["png512"]),
img_format="PNG",
author=result["team_name"],
)
)
return res
+3 -3
View File
@@ -20,7 +20,7 @@ about = {
categories = ['images'] categories = ['images']
nb_per_page = 15 page_size = 15
paging = True paging = True
api_key = None api_key = None
@@ -29,7 +29,7 @@ url = (
'https://api.flickr.com/services/rest/?method=flickr.photos.search' 'https://api.flickr.com/services/rest/?method=flickr.photos.search'
+ '&api_key={api_key}&{text}&sort=relevance' + '&api_key={api_key}&{text}&sort=relevance'
+ '&extras=description%2C+owner_name%2C+url_o%2C+url_n%2C+url_z' + '&extras=description%2C+owner_name%2C+url_o%2C+url_n%2C+url_z'
+ '&per_page={nb_per_page}&format=json&nojsoncallback=1&page={page}' + '&per_page={page_size}&format=json&nojsoncallback=1&page={page}'
) )
photo_url = 'https://www.flickr.com/photos/{userid}/{photoid}' photo_url = 'https://www.flickr.com/photos/{userid}/{photoid}'
@@ -42,7 +42,7 @@ def build_flickr_url(user_id, photo_id):
def request(query, params): def request(query, params):
params['url'] = url.format( params['url'] = url.format(
text=urlencode({'text': query}), api_key=api_key, nb_per_page=nb_per_page, page=params['pageno'] text=urlencode({'text': query}), api_key=api_key, page_size=page_size, page=params['pageno']
) )
return params return params
+31 -24
View File
@@ -1,15 +1,23 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
"""Fyyd (podcasts)""" """Fyyd (podcasts)"""
import typing as t
from datetime import datetime from datetime import datetime
from urllib.parse import urlencode from urllib.parse import urlencode
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = { about = {
'website': 'https://fyyd.de', "website": "https://fyyd.de",
'official_api_documentation': 'https://github.com/eazyliving/fyyd-api', "official_api_documentation": "https://github.com/eazyliving/fyyd-api",
'use_official_api': True, "use_official_api": True,
'require_api_key': False, "require_api_key": False,
'results': 'JSON', "results": "JSON",
} }
categories = [] categories = []
paging = True paging = True
@@ -18,31 +26,30 @@ base_url = "https://api.fyyd.de"
page_size = 10 page_size = 10
def request(query, params): def request(query: str, params: "OnlineParams") -> None:
args = { args = {
'term': query, "term": query,
'count': page_size, "count": page_size,
'page': params['pageno'] - 1, "page": params["pageno"] - 1,
} }
params['url'] = f"{base_url}/0.2/search/podcast?{urlencode(args)}" params["url"] = f"{base_url}/0.2/search/podcast?{urlencode(args)}"
return params
def response(resp): def response(resp: "SXNG_Response"):
results = [] res = EngineResults()
json_results = resp.json()['data'] json_results: list[dict[str, str]] = resp.json()["data"] # pyright: ignore[reportAny]
for result in json_results: for result in json_results:
results.append( res.add(
{ res.types.MainResult(
'url': result['htmlURL'], url=result["htmlURL"],
'title': result['title'], title=result["title"],
'content': result['description'], content=result["description"],
'thumbnail': result['smallImageURL'], thumbnail=result["smallImageURL"],
'publishedDate': datetime.strptime(result['status_since'], '%Y-%m-%d %H:%M:%S'), publishedDate=datetime.strptime(result["status_since"], "%Y-%m-%d %H:%M:%S"),
'metadata': f"Rank: {result['rank']} || {result['episode_count']} episodes", metadata=f"Rank: {result['rank']} || {result['episode_count']} episodes",
} )
) )
return results return res
+1 -1
View File
@@ -27,8 +27,8 @@ about = {
'official_api_documentation': None, 'official_api_documentation': None,
'require_api_key': False, 'require_api_key': False,
'results': 'HTML', 'results': 'HTML',
'language': 'de',
} }
language = "de"
paging = True paging = True
categories = ['shopping'] categories = ['shopping']
+127
View File
@@ -0,0 +1,127 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Giphy (images)"""
import random
from urllib.parse import urlencode
import re
import typing as t
from lxml import html
from searx.enginelib import EngineCache
from searx.exceptions import SearxEngineAPIException
from searx.network import get
from searx.result_types import EngineResults
from searx.result_types.image import ImageRef
from searx.utils import eval_xpath_list, humanize_bytes
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://giphy.com",
"wikidata_id": "Q17054335",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://giphy.com"
api_url = "https://api.giphy.com"
categories = ["images"]
paging = True
page_size = 15
GiphyCategs = t.Literal["gifs", "stickers", "clips"]
giphy_categ: GiphyCategs = "gifs"
"""Giphy category to search in."""
CACHE: EngineCache
"""Cache for storing the extracted api key."""
_GIPHY_API_KEY_RE = re.compile(r"[Aa]piKey\s*:\s*\"(\w+)\"")
def setup(engine_settings: dict[str, str]) -> bool:
if giphy_categ not in t.get_args(GiphyCategs):
raise ValueError("invalid category: %s" % giphy_categ)
global CACHE # pylint: disable=global-statement
CACHE = EngineCache(engine_settings["name"])
return True
def _get_api_key() -> str:
"""
Extract the Giphy API key from the JavaScript code. There are different API keys
(e.g. for mobile, desktop, ...), so we just pick a random one of these.
"""
cached = CACHE.get("api_key")
if cached:
return cached
homepage_resp = get(base_url)
homepage_doc = html.fromstring(homepage_resp.text)
for script_src in eval_xpath_list(homepage_doc, "//script[contains(@src, 'layout')]/@src"):
script_resp = get(base_url + script_src)
api_keys = _GIPHY_API_KEY_RE.findall(script_resp.text)
if api_keys:
api_key = random.choice(api_keys)
CACHE.set("api_key", api_key, expire=60 * 60 * 6) # 6 hours
return api_key
raise SearxEngineAPIException("failed to extract api keys")
def request(query: str, params: "OnlineParams") -> None:
args = {
"q": query,
"api_key": _get_api_key(),
"limit": page_size,
"offset": (params["pageno"] - 1) * page_size,
"type": giphy_categ,
}
params["url"] = f"{api_url}/v1/{giphy_categ}/search?{urlencode(args)}"
def response(resp: "SXNG_Response"):
res = EngineResults()
result: dict[str, t.Any]
for result in resp.json()["data"]:
img = result['images']['original']
formats = [
ImageRef(url=img["mp4"], subtype="mp4"), # type: ignore
ImageRef(url=img["webp"], subtype="webp"), # type: ignore
]
thumb = (
result["images"].get("downsized")
or result["images"].get("downsized_medium")
or result["images"].get("downsized_small")
or result["images"].get("downsized_large")
)
res.add(
res.types.Image(
title=result["title"],
content=", ".join(result.get("tags", [])),
url=result["url"],
thumbnail_src=thumb.get("url") or img["url"],
img_src=img["url"],
resolution=f"{img['width']}x{img['height']}",
img_format="GIF",
formats=formats,
author=result["username"],
filesize=humanize_bytes(int(img["size"])),
source=result.get("source_tld") or "",
)
)
return res
+8 -1
View File
@@ -10,10 +10,12 @@ import time
import typing as t import typing as t
from urllib.parse import urlencode from urllib.parse import urlencode
from lxml import html
from searx.result_types import EngineResults from searx.result_types import EngineResults
from searx.exceptions import SearxEngineCaptchaException
from searx.extended_types import SXNG_Response from searx.extended_types import SXNG_Response
from searx.utils import extr, gen_useragent, html_to_text from searx.utils import extr, gen_useragent, html_to_text, eval_xpath
from searx.network import get from searx.network import get
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
@@ -40,6 +42,11 @@ time_range_map = {"day": "d", "week": "w", "month": "m", "year": "y"}
def _get_page_hash(query: str, page: int, headers: dict[str, str]) -> str: def _get_page_hash(query: str, page: int, headers: dict[str, str]) -> str:
resp = get(f"{base_url}/web/result?q={query}&page={page}", headers=headers) resp = get(f"{base_url}/web/result?q={query}&page={page}", headers=headers)
# detect captcha (if any)
doc = html.fromstring(resp.text)
if eval_xpath(doc, "//*[@id='spam-messages']"):
raise SearxEngineCaptchaException()
# the text we search for looks like: # the text we search for looks like:
# load("/desk?lang="+eV.p.param['hl']+"&q="+eV['p']['q_encode']+"&page=5&h=aa45603&t=177582576&origin=web&comp=web_serp_pag&p=gmx-com&sp=&lr="+eV.p.param['lr0']+"&mkt="+eV.p.param['mkt0']+"&family="+eV.p.param['familyFilter']+"&fcons="+eV.p.perm.fCons,"google", "eMMO", "eMH","eMP"); # pylint: disable=line-too-long # load("/desk?lang="+eV.p.param['hl']+"&q="+eV['p']['q_encode']+"&page=5&h=aa45603&t=177582576&origin=web&comp=web_serp_pag&p=gmx-com&sp=&lr="+eV.p.param['lr0']+"&mkt="+eV.p.param['mkt0']+"&family="+eV.p.param['familyFilter']+"&fcons="+eV.p.perm.fCons,"google", "eMMO", "eMH","eMP"); # pylint: disable=line-too-long
return extr(resp.text, "&h=", "&t=") return extr(resp.text, "&h=", "&t=")
+1
View File
@@ -57,6 +57,7 @@ max_page = 50
.. _Google max 50 pages: https://github.com/searxng/searxng/issues/2982 .. _Google max 50 pages: https://github.com/searxng/searxng/issues/2982
""" """
time_range_support = True time_range_support = True
language_support = True
safesearch = True safesearch = True
time_range_dict = {"day": "d", "week": "w", "month": "m", "year": "y"} time_range_dict = {"day": "d", "week": "w", "month": "m", "year": "y"}
+1
View File
@@ -43,6 +43,7 @@ max_page = 50
""" """
time_range_support = True time_range_support = True
language_support = True
safesearch = True safesearch = True
filter_mapping = {0: 'images', 1: 'active', 2: 'active'} filter_mapping = {0: 'images', 1: 'active', 2: 'active'}
+1
View File
@@ -66,6 +66,7 @@ about = {
categories = ["news"] categories = ["news"]
paging = False paging = False
time_range_support = False time_range_support = False
language_support = True
# Google-News results are always *SafeSearch*. Option 'safesearch' is set to # Google-News results are always *SafeSearch*. Option 'safesearch' is set to
# False here. # False here.
+90
View File
@@ -0,0 +1,90 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Heexy_ is a minimalist search engine that focuses on privacy.
Although it also supports news and videos, these are not implemented here
because they usually return no result to very few irrelevant ones.
It seems to use Bing internally, as the image thumbnails are loaded from Bing.
.. _Heexy: https://docs.heexy.org/introduction
"""
from urllib.parse import urlencode
import typing as t
from searx.exceptions import SearxEngineAccessDeniedException
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://heexy.org",
"wikidata_id": None,
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
paging = True
safesearch = True
categories = ["general"]
heexy_categ = "web"
"""Category to search in. Can be either "web" or "image"."""
base_url = "https://seapi.heexy.org"
safe_search_map = {0: "off", 1: "on", 2: "on"}
def init(_):
if heexy_categ not in ("web", "image"):
raise ValueError("invalid search category: %s" % heexy_categ)
def request(query: str, params: "OnlineParams") -> None:
args = {
"q": query,
"page": params["pageno"],
"safe": safe_search_map[params["safesearch"]],
}
if params["searxng_locale"] != "all":
args["lang"] = params["searxng_locale"].split("-")[0]
params["url"] = f"{base_url}/search/{heexy_categ}?{urlencode(args)}"
params["headers"]["Origin"] = base_url
def response(resp: "SXNG_Response"):
res = EngineResults()
json_resp = resp.json()
if not json_resp["success"]:
raise SearxEngineAccessDeniedException()
result: dict[str, str]
for result in json_resp["results"]:
if heexy_categ == "web":
res.add(
res.types.MainResult(
url=result["url"],
title=result["title"],
content=result["description"],
)
)
elif heexy_categ == "image":
res.add(
res.types.Image(
title=result["description"],
url=result["url"],
thumbnail_src=result["image"],
img_src=result["rawImage"],
)
)
return res

Some files were not shown because too many files have changed in this diff Show More