Author SHA1 Message Date
searxng-bot 448ced4b18 [mod] data: update searx.data - update_wikidata.py 2026-10-29 00:17:16 +00:00
Bnyro c360868e5b [feat] xprivo: properly detect ratelimits instead of showing HTTP errors 2026-10-28 10:54:38 +01:00
dependabot[bot] b5ed73fcb6 [upd] pypi: Bump python-dateutil from 2.9.0.post0 to 3.0.0 (#6888)
Bumps [python-dateutil](https://github.com/dateutil/dateutil) from 2.9.0.post0 to 3.0.0.
- [Release notes](https://github.com/dateutil/dateutil/releases)
- [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS)
- [Commits](https://github.com/dateutil/dateutil/compare/2.9.0.post0...3.0.0)

---
updated-dependencies:
- dependency-name: python-dateutil
  dependency-version: 3.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-10-27 21:33:35 +01:00
dependabot[bot] 28423d332d [upd] pypi: Bump the minor group with 2 updates (#6887)
Bumps the minor group with 2 updates: [certifi](https://github.com/certifi/python-certifi) and [granian](https://github.com/emmett-framework/granian).


Updates `certifi` from 2026.10.19 to 2026.10.23
- [Commits](https://github.com/certifi/python-certifi/compare/2026.10.19...2026.10.23)

Updates `granian` from 2.8.4 to 2.8.5
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.8.4...v2.8.5)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.10.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: granian
  dependency-version: 2.8.5
  dependency-type: direct:production
  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-10-27 20:00:45 +01:00
Brock Vojkovic ef7bae9ddb [upd] pypi: Bump curl_cffi from 0.16.3 to 0.17.0 (#6890)
Fixes HTTP2Shell (CVE-2026-111497).

Signed-off-by: vojkovic <git@vojk.au>
2026-10-27 17:00:58 +08:00
Brock Vojkovic cb74e7f810 [fix] autocomplete: apply outgoing request timeout (#6886)
Fixes update_kwargs so it does not throw away the dict and instead uses the correct settings.

Signed-off-by: vojkovic <git@vojk.au>
2026-10-27 10:54:52 +08:00
Sandro 54ca1d9cfb [fix] tagesschau: remove from general category (#6882) 2026-10-25 19:34:17 +01:00
github-actions[bot]andsearxng-bot ef0ec03da0 [l10n] update translations from Weblate (#6850)
3dfe3d3f7 - 2026-10-19 - cyh21185 <cyh21185@noreply.codeberg.org>
896dc9faf - 2026-10-20 - marc-lopez <marc-lopez@noreply.codeberg.org>
f53baeeae - 2026-10-16 - marc-lopez <marc-lopez@noreply.codeberg.org>

Co-authored-by: searxng-bot <searxng-bot@users.noreply.github.com>
2026-10-24 10:46:18 +02:00
dependabot[bot] 629368e40c [upd] web-client (simple): Bump the minor group (#6880)
Bumps the minor group in /client/simple with 2 updates: [browserslist](https://github.com/browserslist/browserslist) and [sort-package-json](https://github.com/keithamus/sort-package-json).


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

Updates `sort-package-json` from 4.0.0 to 4.1.0
- [Release notes](https://github.com/keithamus/sort-package-json/releases)
- [Commits](https://github.com/keithamus/sort-package-json/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-version: 4.29.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: sort-package-json
  dependency-version: 4.1.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-10-23 20:33:46 +02:00
dependabot[bot] 68647d0806 [upd] pypi: Bump certifi from 2026.7.22 to 2026.10.19 in the minor group (#6881)
Bumps the minor group with 1 update: [certifi](https://github.com/certifi/python-certifi).


Updates `certifi` from 2026.7.22 to 2026.10.19
- [Commits](https://github.com/certifi/python-certifi/compare/2026.07.22...2026.10.19)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.10.19
  dependency-type: direct:production
  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-10-23 20:33:12 +02:00
Bnyro 81371861f6 [del] engines: remove andisearch for now - #687
This reverts commit 2a6fc07be3.
2026-10-19 23:27:52 +02:00
WookieeOnCrack d47cb96bfb [feat] brave: parse JavaScript IIFE to fix engine
- Should resolve #6819 by reverting Accept-Encoding headers back to the curl_cffi default value
- Adds IIFE Parsing / Data Extraction that appears after headers update
2026-10-19 13:30:53 +02:00
jerry155756294 62366b05c0 [fix] engine - DDG: ignore zero-click answers without URLs (#6865)
If the provider uses DDG, and a HEX color code is searched for (for example, `A87771`), it directly reports `SearxEngineXPathException: index 0 not found` (the Web UI displays a parsing error), because DDG displays an icon for a color code.  This causes the parser to be unable to parse that icon. This fix directly ignores this special function.
2026-10-19 08:44:54 +02:00
Ivan Gabaldon 1c10abe2d3 [enh] container: nitpicks (#6862)
Reduce Valkey writes to database
Should error with 1, not 127
2026-10-17 20:30:22 +02:00
Ivan Gabaldon 3afd949268 [enh] container: merge dockerfiles (#6861)
* [enh] container: merge dockerfiles

Leftover from https://github.com/searxng/searxng/pull/6720

* [enh] apply suggestion

https://github.com/searxng/searxng/pull/6861#discussion_r4302473383
2026-10-17 20:30:09 +02:00
Bnyro 2a6fc07be3 [feat] engines: add andisearch (general) 2026-10-17 11:16:04 +02:00
dependabot[bot] decdfa6f7d [upd] pypi: Bump the minor group with 4 updates (#6852)
Bumps the minor group with 4 updates: [black](https://github.com/psf/black), [pylint](https://github.com/pylint-dev/pylint), [selenium](https://github.com/SeleniumHQ/Selenium) and [typer](https://github.com/fastapi/typer).


Updates `black` from 26.10.0 to 26.10.1
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/26.10.0...26.10.1)

Updates `pylint` from 4.1.2 to 4.1.3
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v4.1.2...v4.1.3)

Updates `selenium` from 4.50.0 to 4.51.0
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.50.0...selenium-4.51.0)

Updates `typer` from 0.27.2 to 0.27.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.27.2...0.27.3)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.10.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: pylint
  dependency-version: 4.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: selenium
  dependency-version: 4.51.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: typer
  dependency-version: 0.27.3
  dependency-type: direct:production
  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-10-17 10:15:00 +02:00
Bnyro 8252c0bf6d [chore] resulthunter: fix broken linter 2026-10-16 18:24:21 +02:00
Bnyro 47aa11475b [fix] resulthunter: blocked with access denied 2026-10-16 18:17:43 +02:00
dependabot[bot] 0ce72dd2cc [upd] github-actions: Bump actions/upload-artifact from 7.0.1 to 7.0.2 (#6851)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.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-10-16 14:56:49 +02:00
dependabot[bot] c6d7fe91d3 [upd] web-client (simple): Bump @types/node (#6849)
Bumps the minor group in /client/simple with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 26.6.4 to 26.6.5
- [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.6.5
  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-10-16 14:56:19 +02:00
dependabot[bot] 7847767832 [upd] github-actions: Bump actions/download-artifact from 8.0.1 to 8.0.2 (#6848)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 8.0.1 to 8.0.2.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.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-10-16 14:55:46 +02:00
dependabot[bot] d2209bfbb0 [upd] github-actions: Bump actions/setup-node from 7.0.0 to 7.1.0 (#6847)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.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-10-16 14:55:17 +02:00
Aadniz ca8e1582be [build] /static 2026-10-16 13:27:46 +02:00
Aadniz 243d3a9df2 [fix] image descriptions disappearing on firefox 2026-10-16 13:27:46 +02:00
Bnyro ff8f5ddcc9 [fix] yep: send x-csrf-protection header to bypass botdetection 2026-10-16 00:18:44 +02:00
Brock Vojkovic b00212a836 [mod] image proxy: /image with the default network (#6816)
- Changes the path of the /image_proxy endpoint to /image. This ensures that if the user copies the image through the browser, it is named image.png rather than image_proxy.png as the latter can be attributed to searxng hence preserving the privacy of the user.
- Removed the dedicated network for the image proxy. Previously this was used to force http/2 off but since moving to curl from httpx this is not as much of a problem. Now instead uses the default network.

Signed-off-by: vojkovic <git@vojk.au>
2026-10-15 06:59:50 +08:00
Bnyro 22c64b8d39 [fix] xprivo: crashes when parsing response 2026-10-13 12:15:09 +02:00
ky0nshi ad46ebd7d8 [fix] typo in ayo engine - https://search.ayo.de (#6817)
typo in the address fixed -> https://search.ayo.de
2026-10-11 17:52:29 +02:00
dependabot[bot] f4822b3fc4 [upd] web-client (simple): Bump the minor group (#6826)
Bumps the minor group in /client/simple with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [ol](https://github.com/openlayers/openlayers) | `10.10.0` | `10.11.0` |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.14` | `2.5.15` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.6.3` | `26.6.4` |
| [browserslist](https://github.com/browserslist/browserslist) | `4.29.2` | `4.29.3` |
| [stylelint](https://github.com/stylelint/stylelint) | `17.15.0` | `17.16.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.3.1` | `8.3.3` |

Updates `ol` from 10.10.0 to 10.11.0
- [Release notes](https://github.com/openlayers/openlayers/releases)
- [Commits](https://github.com/openlayers/openlayers/compare/v10.10.0...v10.11.0)

Updates `@biomejs/biome` from 2.5.14 to 2.5.15
- [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.15/packages/@biomejs/biome)

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

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

Updates `stylelint` from 17.15.0 to 17.16.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.15.0...17.16.0)

Updates `vite` from 8.3.1 to 8.3.3
- [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.3.3/packages/vite)

---
updated-dependencies:
- dependency-name: ol
  dependency-version: 10.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 26.6.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: browserslist
  dependency-version: 4.29.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: stylelint
  dependency-version: 17.16.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: vite
  dependency-version: 8.3.3
  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-10-09 20:59:16 +02:00
Markus Heiser 9f042d2f67 [build] /static 2026-10-09 16:16:48 +02:00
DangTAnh 815f58994a [feat] simple theme: translate hotkeys help dialog (#6806)
Move hardcoded English strings of the hotkeys help (keyboard.ts) to the
server side get_translations() so translators can pick them up. The client
looks them up via settings.translations with identical English fallbacks.
2026-10-09 16:16:48 +02:00
Markus Heiser 6a0ac6a391 [upd] update to gecko driver v0.37.1 (#6827)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-10-09 15:19:50 +02:00
Markus Heiser bba4f64e8a [fix] issues reported by pylint v4.1.2 (#6827)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-10-09 15:19:50 +02:00
dependabot[bot] 619696aa6c [upd] pypi: Bump the minor group with 5 updates (#6827)
Bumps the minor group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [granian](https://github.com/emmett-framework/granian) | `2.8.3` | `2.8.4` |
| [black](https://github.com/psf/black) | `26.5.1` | `26.10.0` |
| [pylint](https://github.com/pylint-dev/pylint) | `4.0.9` | `4.1.2` |
| [selenium](https://github.com/SeleniumHQ/Selenium) | `4.49.0` | `4.50.0` |
| [basedpyright](https://github.com/detachhead/basedpyright) | `1.40.1` | `1.40.2` |

Updates `granian` from 2.8.3 to 2.8.4
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.8.3...v2.8.4)

Updates `black` from 26.5.1 to 26.10.0
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/26.5.1...26.10.0)

Updates `pylint` from 4.0.9 to 4.1.2
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.9...v4.1.2)

Updates `selenium` from 4.49.0 to 4.50.0
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.49.0...selenium-4.50.0)

Updates `basedpyright` from 1.40.1 to 1.40.2
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.40.1...v1.40.2)
2026-10-09 15:19:50 +02:00
github-actions[bot] 2dc2b14628 [l10n] update translations from Weblate (#6828)
cf8a68cc8 - 2026-10-05 - lugged9922 <lugged9922@noreply.codeberg.org>
046b92a92 - 2026-10-06 - kratos <makesocialfoss32@keemail.me>
475f33d38 - 2026-10-04 - SomeTr <sometr@noreply.codeberg.org>
2f508cdb2 - 2026-10-05 - Stephan-P <stephan-p@noreply.codeberg.org>
0de45dca2 - 2026-10-05 - Kito <q1t0@noreply.codeberg.org>
76a5f5248 - 2026-10-05 - ghose <ghose@noreply.codeberg.org>
d7bfd43bd - 2026-10-04 - Priit Jõerüüt <jrtcdbrg@noreply.codeberg.org>
bcc67b083 - 2026-10-04 - gallegonovato <gallegonovato@noreply.codeberg.org>
2026-10-09 14:08:51 +02:00
Guillaume P.andGuillaume PUTIER 6671d89bed [fix] engines: wikidata, no list as content when an entity has no description (#6824)
Wikidata kept showing up in `unresponsive_engines` as "unexpected crash", but only for some queries (`!wikidata loi anti gaspillage` for instance).

In `get_results`, `itemDescription` defaults to `[]`. When an entity has no description and `display_type` contains `"list"`, that empty list goes out as the `content` of a list result, and `ResultContainer.extend` fails with `TypeError: expected string or bytes-like object, got 'list'`. Every wikidata result of the query is dropped with it.

The default is now an empty string. `len(infobox_content) == 0` still works, and the infobox branch already expected a string.

`tests/unit/test_engine_wikidata.py` covers an entity with no description, once with `display_type` `["list"]` and once with `["infobox"]`. Without the fix both tests fail (`'content': []` instead of `''`), with it both pass.




itemDescription defaulted to [] in get_results. With "list" in display_type, that empty list was sent as the content of a list result and ResultContainer.extend raised a TypeError, so the whole wikidata answer of the query was lost. Default to an empty string instead.

Signed-off-by: Guillaume PUTIER <putier.guillaume@gmail.com>
Co-authored-by: Guillaume PUTIER <putier.guillaume@gmail.com>
2026-10-07 11:18:19 +02:00
Brock Vojkovic d48c4b5554 [feat] sort infobox so images are first (#6815)
Often there will be two or more results in the infobox.

We now sort this so infoboxes with an image show above those that do not.

Signed-off-by: vojkovic <git@vojk.au>
2026-10-04 08:16:51 +02:00
Brock Vojkovic 44b98e6102 [fix] engines: privacywall referer to bypass botdetection (#6813)
Privacywall needs a referer header now or else returns access denied.
2026-10-04 07:49:12 +02:00
Bnyro 7a3e41d420 [feat] s1search: parse suggestions (#6810)
- parse s1search suggestions
- thereby also fixes that some queries would crash the engine if using `https://search.nation.online` as base url because it tried to parse the suggestions as real results

### How to test this PR locally?

- set `base_url = "https://search.nation.online"` and `inactive: false`
- !s1 rust
2026-10-04 07:47:00 +02:00
Brock Vojkovic ce913b8934 [fix] engines: dogpile cors headers (#6814)
Added Sec-Fetch-Mode header to fix the engine. Dogpile now has better bot detection, similar to tusksearch, we now require CORS headers in the request.
2026-10-04 07:44:15 +02:00
dependabot[bot] 19ffbcd306 [upd] web-client (simple): Bump the minor group (#6807)
Bumps the minor group in /client/simple with 4 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [browserslist](https://github.com/browserslist/browserslist), [sharp](https://github.com/lovell/sharp) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

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

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

Updates `sharp` from 0.35.4 to 0.35.5
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.4...v0.35.5)

Updates `vite` from 8.3.0 to 8.3.1
- [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.3.1/packages/vite)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: browserslist
  dependency-version: 4.29.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: sharp
  dependency-version: 0.35.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: vite
  dependency-version: 8.3.1
  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-10-02 16:37:24 +02:00
dependabot[bot] e652ca7d29 [upd] pypi: Bump msgspec from 0.21.1 to 0.22.0 in the minor group (#6811)
Bumps the minor group with 1 update: [msgspec](https://github.com/msgspec/msgspec).


Updates `msgspec` from 0.21.1 to 0.22.0
- [Release notes](https://github.com/msgspec/msgspec/releases)
- [Changelog](https://github.com/msgspec/msgspec/blob/main/docs/changelog.md)
- [Commits](https://github.com/msgspec/msgspec/compare/0.21.1...0.22.0)
2026-10-02 16:30:24 +02:00
Brock Vojkovic 26b90797e0 [enh] simplify and clean up preferences (#6126)
- The 'engine tokens' field is only shown now if there are any engines with tokens configured.

- DOI Resolver now has combined the previous two settings (one dropdown and one toggle) into just a single dropdown similar to the favicon resolver.
2026-10-02 18:36:07 +08:00
dependabot[bot] a5659a5364 [upd] pypi: Bump the minor group with 2 updates (#6808)
Bumps the minor group with 2 updates: [pylint](https://github.com/pylint-dev/pylint) and [wlc](https://github.com/WeblateOrg/wlc).


Updates `pylint` from 4.0.8 to 4.0.9
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.8...v4.0.9)

Updates `wlc` from 2.2.0 to 2.2.1
- [Release notes](https://github.com/WeblateOrg/wlc/releases)
- [Changelog](https://github.com/WeblateOrg/wlc/blob/main/CHANGES.md)
- [Commits](https://github.com/WeblateOrg/wlc/compare/2.2.0...2.2.1)
2026-10-02 12:12:46 +02:00
kcs81 a9d9900334 [fix] map Brave API SafeSearch levels correctly (#6799)
API description:

- https://api-dashboard.search.brave.com/api-reference/web/search/get#parameter-safesearch
2026-09-30 07:39:34 +02:00
quacker 4e2c1ea7f4 [fix] duckduckgo_web: make preload link timeout use the global configuration - #6768 2026-09-29 14:33:54 +02:00
github-actions[bot] f5035873ad [mod] data: update searx.data - update_firefox_version.py (#6792) 2026-09-29 08:44:07 +02:00
github-actions[bot] c11cbda117 [mod] data: update searx.data - update_ahmia_blacklist.py (#6793) 2026-09-29 08:35:03 +02:00
github-actions[bot] bad451b3da [mod] data: update searx.data - update_currencies.py (#6794) 2026-09-29 08:08:20 +02:00
github-actions[bot] 896ec8d3af [mod] data: update searx.data - update_engine_descriptions.py (#6796) 2026-09-29 07:43:48 +02:00
Ivan Gabaldon 12f8b6515c [enh] ci: convert script to esm (#6725)
Embraces the ESM format. Allow spaces between the checkbox `([ x ])`
2026-09-25 18:50:35 +02:00
dependabot[bot] d8ae3abd59 [upd] web-client (simple): Bump the minor group (#6785)
Bumps the minor group in /client/simple with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@biomejs/biome` from 2.5.13 to 2.5.14
- [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.14/packages/@biomejs/biome)

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

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 26.6.2
  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-09-25 16:30:37 +02:00
github-actions[bot] 487f519227 [l10n] update translations from Weblate (#6771)
4bf358751 - 2026-09-24 - bittin <bittin@noreply.codeberg.org>
ca4696cb4 - 2026-09-23 - ghose <ghose@noreply.codeberg.org>
2026-09-25 11:04:23 +02:00
Brock Vojkovic 3cd69d30e2 [fix] engines: tusksearch requires cors headers for botdetection (#6773)
Sec-Fetch-Mode and Sec-Fetch-Dest headers now mandatory
2026-09-23 23:18:45 +08:00
Bnyro 783a094625 [feat] engines: add xprivo - general, news, videos (#6641)
Add support for xprivo.com.  Xprivo has a rate-limit of smth like 20 requests
per day without an API key.  However, if you use a proxy like the IPv6 rotating
proxy [1] you basically get unlimited requests.

[1] https://github.com/vojkovic/http-proxy-ipv6-pool-docker
2026-09-23 13:00:51 +02:00
Bnyro dcf1d477ae [feat] engines: add iconify - icons (#6541) 2026-09-23 11:47:20 +02:00
Bnyro d63bbef7a0 [feat] engines: add findborg.com - general, news, images, videos (#6688)
Adds support for findborg.com which also uses the Brave results.
2026-09-23 09:31:34 +02:00
Bnyro 4d9c982cda [mod] brave-based engines: reuse result parsing methods (#6688)
Many engines use the JSON results from Brave (e.g. Brave, BraveAPI, Tusksearch
and more), so the result parsing logic is always the same. This PR deduplicates
this code.
2026-09-23 09:31:34 +02:00
Ivan Gabaldon 2ed96e6fcf [feat] ci: oneway sync to CB (#6770) 2026-09-22 18:16:59 +02:00
vojkovic 019460e07d [build] /static (#6766) 2026-09-22 09:14:25 +02:00
vojkovic 8a8b4eaec0 [fix] video thumbnails on left side again (#6766)
- Imports video.less now to apply the stylesheet.
- Ammends thumbnail_bg to dim the background and blur it more to make the thumbnail easier to see.
2026-09-22 09:14:25 +02:00
Crizomb b3c3895294 [docs] indicate modification of settings.yml is necessary to add new … (#6685)
* [docs] indicate modification of settings.yml is necessary to add new plugin + small exemple code correction
* [docs] plugins: add more details about adding new plugins
2026-09-22 09:13:02 +02:00
Austin-Olacsi df67eb6c26 [fix] rewrite vimeo engine 2026-09-21 17:56:49 +02:00
Markus Heiser f372096fb1 [build] /static (#6742) 2026-09-21 14:49:04 +02:00
Bnyro 52284fd320 [feat] categories: add icons for remaining category tabs (#6759)
Icons are provided for the main categories known to us.

The distinction between CATEGORY_GROUPS and CATEGORY_NAMES is abandoned in favor
of the latter.

HINT: we need single-color icons; transparent background and the color must be
`currentColor` so that the icons can be used in light/dark themes.

The following SVGs from the ionicons had to be revised (use ``currentColor``).

- client/simple/src/svg/ionicons/logo-rss.svg
- client/simple/src/svg/ionicons/radio-outline.svg
- client/simple/src/svg/ionicons/color-palette-outline.svg

For the following categories, I did not find any usable icons in Ionicons; new
SVGs were created for these categories.

- onions: client/simple/src/svg/ionicons/tor-browser.svg
- lyrics: client/simple/src/svg/ionicons/song-lyric-music.svg [1]
- packages: client/simple/src/svg/ionicons/package_2.svg [1]

[1] Based on Google's Material icons: https://fonts.google.com/icons

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-21 14:49:04 +02:00
Bnyro 49064747a2 [fix] c3tv: crashes on missing description (#6752) 2026-09-21 10:07:38 +02:00
Bnyro 591ad564a9 [mod] c3tv: modernize engine (#6752) 2026-09-21 10:07:38 +02:00
Markus Heiser 2e624bed40 [fix] utils.parse_duration_string - the last three of [00, HH, MM, SS] (#6743)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-20 16:57:45 +02:00
Markus Heiser 2e41cf7407 [mod] migration of some engine results to the video class (#6743)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-20 16:57:45 +02:00
Markus Heiser ce48993997 [build] /static 2026-09-20 16:57:45 +02:00
Markus Heiser e40de2d992 [mod] typification of SearXNG: add new result type Video (#6743)
- Python class:   searx/result_types/video.py
- Jinja template: searx/templates/simple/result_templates/videos.html
- CSS (less)      client/simple/src/less/result_types/video.less

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-20 16:57:45 +02:00
vojkovic fdd8525b1a [build] /static 2026-09-20 18:58:14 +08:00
vojkovic bcd2b709fe [mod] theme: improve thumbnails display on results page
Deletes thumbnails instead of showing an error image and also moves them to the right side. Also gives them a background and makes them into rounded 4:3 rectangles for better ui.
2026-09-20 18:58:14 +08:00
Brock Vojkovic e75fd22d09 [mod] defaults: disable image engines with poor results (#6690)
The image search defaults are a bit of a mix of results atm with random stock photos, some broken thumbnails (artic) and svg icons.
2026-09-20 11:10:16 +08:00
Bnyro e831fc2a1c [fix] categories: rename stock_images category to stock images (#6760)
Category ``stock_images`` is referenced to as ``stock images`` everywhere else,
so SXNG didn't find any translation for ``stock_images``.
2026-09-19 15:01:12 +02:00
WookieeOnCrack c0014f015e [mod] brave - modernize response handling (#6742) 2026-09-19 13:04:37 +02:00
Bnyro fe5bdd1bf2 [feat] magnific: solve captcha to bypass botblocking
Magnific now employs a very simple to solve CAPTCHA before
all API requests (essentially summing up some numbers).

Python script for testing:
```python
import re
from lxml import html
import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:156.0) Gecko/20100101 Firefox/156.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
    'Sec-GPC': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Priority': 'u=0, i',
    'Pragma': 'no-cache',
    'Cache-Control': 'no-cache',
}

resp = requests.get(
    "https://www.magnific.com/api/regular/search?term=tree&filters%5Bai-generated%5D%5Bexcluded%5D=1&page=1&locale=en&filters%5Blicense%5D=free",
    headers=headers,
)
cookies = resp.cookies
print(resp.text)

_NUMBER_RE = re.compile("\d+")
_BM_VERIFY = re.compile(r"\"bm-verify\":\s*\"(.*?)\"")

doc = html.fromstring(resp.text)
script = doc.xpath("//script")[0].text
numbers = [int(m) for m in _NUMBER_RE.findall(script)]
bm_verify = _BM_VERIFY.search(resp.text)
if not bm_verify:
    exit(1)
bm_verify = bm_verify.group(1)
print("bm-verify: " + bm_verify)

solution = sum(numbers)
print(f"sum({numbers}) = {solution}")

resp = requests.post(
    "https://www.magnific.com/_sec/verify?provider=interstitial",
    json={"bm-verify": bm_verify, "pow": solution},
    headers=headers,
    cookies=cookies,
)

resp = requests.get(
    "https://www.magnific.com/api/regular/search?term=bird&filters%5Bai-generated%5D%5Bexcluded%5D=1&page=1&locale=en&filters%5Blicense%5D=free",
    headers=headers,
    cookies=resp.cookies,
)
print(resp.text)
```
2026-09-19 11:31:00 +02:00
Brock Vojkovic 367fb6537c [fix] engines: tusksearch cors request headers (#6758)
Added the ``Origin`` and ``Sec-Fetch-Site`` http headers to bypass tusksearch's latest bot detection.
2026-09-19 07:30:06 +02:00
dependabot[bot]andIvan Gabaldon c0042add30 [upd] web-client (simple): Bump the minor group in /client/simple with 3 updates (#6750)
* [upd] web-client (simple): Bump the minor group

Bumps the minor group in /client/simple with 3 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `@biomejs/biome` from 2.5.12 to 2.5.13
- [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.13/packages/@biomejs/biome)

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

Updates `vite` from 8.2.2 to 8.3.0
- [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/create-vite@8.3.0/packages/vite)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: vite
  dependency-version: 8.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* [upd] sync lockfile

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Gabaldon <igabaldon@inetol.net>
2026-09-18 17:23:06 +02:00
Bnyro c0ec29fdc3 [fix] pixabay: crashes when there are no results 2026-09-18 17:18:59 +02:00
dependabot[bot] 579809a053 [upd] github-actions: Bump docker/setup-qemu-action from 4.3.0 to 4.4.0 (#6747)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/1f40c72289eff860ee54a304f1438e3cff362e0a...99012661954931238ded8c8b007157a8430204e1)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.4.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-09-18 17:10:17 +02:00
dependabot[bot] 9cde918dd2 [upd] github-actions: Bump actions/download-artifact from 4.3.0 to 8.0.1 (#6748)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 8.0.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/d3f86a106a0bac45b974a628896c90dbdf5c8093...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  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-09-18 17:08:07 +02:00
dependabot[bot] 63b827edc7 [upd] github-actions: Bump actions/upload-artifact from 4.6.2 to 7.0.1 (#6749)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  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-09-18 17:07:54 +02:00
Bnyro 74ffd01152 [feat] engines: add littlelayer (general / indie) engine 2026-09-18 16:45:36 +02:00
Markus Heiser 280aceb1ae [mod] add new category 'stock_images' - a specialization of 'images' (#6703)
Engines like Pexels provide images that one would more likely expect in a
category named ``stock_images``.

By classifying engines like Pexels more precisely, we enable a more specific
handling of them, which also helps to avoid mixing them with the more general
image search on the internet. [1]

We leave these engines *enabled* by default, take them out of ``images`` and
move them into the specialization ``stock_images``.

This makes it possible, on the one hand, for the admin to configure a tab in the
UI, and on the other hand, the user can always select the group directly using
the search syntax ``!stock_images ...``.

What we need to keep in mind:

1. Technically speaking, there are no subcategories in the strict
   sense.  Organizationally, the *subcategories* are derived from the
   `categories_as_tabs`.

2. From an admin’s perspective, `categories_as_tabs` is the tool for
   structuring their content.

3. In the context of “enabled/disabled by default”: We should avoid having
   the admin have to activate many individual engines when they want to
   structure their content (UI tabs).

So if we leave an engine enabled by default and **move** it from `images` to a
new category, the admin only needs to add the new category to
`categories_as_tabs` to restructure their content.  If the new category is also
part of the translations (see `searx/searxng.msg` in this PR), this UI structure
would also be available internationally.

[1] https://github.com/searxng/searxng/pull/6690#issuecomment-5646291679

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-18 16:13:42 +02:00
Bnyro 1f7711ef1f [mod] categories: move app search engines to it category instead of files 2026-09-18 13:19:28 +02:00
Bnyro 12df7aed60 [del] library of congress: now behind cloudflare (#6756) 2026-09-18 12:27:30 +02:00
Bnyro 96c35f1ed3 [fix] findfiles images: crashes due to changed HTML layout 2026-09-18 11:47:45 +02:00
dependabot[bot] 0d6910ae5a [upd] pypi: Bump the minor group with 3 updates (#6751)
Bumps the minor group with 3 updates: [granian](https://github.com/emmett-framework/granian), [selenium](https://github.com/SeleniumHQ/Selenium) and [basedpyright](https://github.com/detachhead/basedpyright).


Updates `granian` from 2.8.2 to 2.8.3
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.8.2...v2.8.3)

Updates `selenium` from 4.48.0 to 4.49.0
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.48.0...selenium-4.49.0)

Updates `basedpyright` from 1.40.0 to 1.40.1
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.40.0...v1.40.1)
2026-09-18 09:32:40 +02:00
Bnyro 087ec6fbda [fix] 500px: crashes when there's no description (#6745) 2026-09-18 07:42:08 +02:00
Bnyro fc028154a4 [fix] giphy: API key extraction fails (#6744) 2026-09-18 07:41:40 +02:00
Bnyro 274b63b677 [refactor] engines: migrate to SXNG_Response.html() (#6718) 2026-09-17 12:22:38 +02:00
Bnyro 3169aafd28 [feat] SXNG_Response: add method to parse html body (#6718) 2026-09-17 12:22:38 +02:00
Bnyro c497719921 [feat] yandex web: add time range support 2026-09-17 09:46:21 +02:00
Bnyro e57ebb0aa5 [refactor] engine processors: add type for time range and safesearch in OnlineParams 2026-09-17 09:46:21 +02:00
Bnyro 77d07c8743 [mod] yandex: simplify request method and drop unused params/cookies
We previously set the 'cookie' cookie value with a timestamp
from 2024 to `yq=...`. The cookie is not actually used in Yandex,
instead we probably wanted to set the `yq` cookie instead. But
since the engine works without it, it can be dropped.

Also simplifies the request method a bit to be easier to read.
2026-09-17 09:46:21 +02:00
Brock Vojkovic 9aab16aef7 [fix] network: stop http redirects (#6740)
Stop curl from following from a HTTPS origin to a HTTP page

See: https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html

Reported-by: Rohit Dixit <00.00.xit@gmail.com>
2026-09-17 15:27:01 +08:00
Bnyro 3accbaca79 [feat] mojeek: automatically solve altcha captchas 2026-09-17 09:00:33 +02:00
Bnyro 9d82be9d89 [feat] utils.py: add altcha captcha solver 2026-09-17 09:00:33 +02:00
Markus Heiser 461f174b09 [mod] engine: pexels - added type hints and migrate to Image-Results (#6739)
Code style somewhat unified, type annotations added, ``LegacyResult`` replaced
by ``Image``, but no functional modifications.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-16 16:46:16 +02:00
Markus Heiser 1354f3952b [feat] mediathekviewweb: add description to queried fields (#6717)
Queries like `!mvw hercule poirot` usually yield no results if the `description`
is not also included in the search.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-16 16:35:47 +02:00
Bnyro f9c9c35546 [fix] mediathekviewweb: crashes when result has no hd video url (#6717) 2026-09-16 16:35:47 +02:00
Bnyro 71a2424c24 [feat] mediathekviewweb: modernize engine to use EngineResults (#6717) 2026-09-16 16:35:47 +02:00
Brock Vojkovic ce19e9fae3 [fix] image_proxy compressed images (#6730)
Prevents the image proxy from decompressing brotli/gzip in transit.

See: https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html

Setting accept_encoding to none prevents curl from automatically decompressing the received contents.

Signed-off-by: vojkovic <git@vojk.au>
2026-09-16 22:23:11 +08:00
WookieeOnCrack f725cc7936 [fix] yandex: image result xpath + modernization (#6728) 2026-09-16 10:38:32 +02:00
vojkovic ca49650407 [fix] engines: yahoo modernisation and ybv cookie 2026-09-15 17:37:19 +08:00
vojkovic 94218a3ac6 [build] /static 2026-09-15 09:56:24 +08:00
vojkovic beb324c0f5 [mod] simple: apply nojs via stylesheet 2026-09-15 09:56:24 +08:00
vojkovic 604ee1698c [fix] engines: flaticon encode non ascii words 2026-09-15 08:50:46 +08:00
Ivan Gabaldon ef05645f09 [mod] container: rework builds (#6720)
Free from ghcr.io/searxng/cache! Now we save and load images without polluting
this shared (and useless) layer cache, which also makes it easy to download
artifacts for local debugging.

I also removed this nonsense about virtualenv normalization, this will be
properly fixed by using lockfiles on py dependencies.
2026-09-14 16:06:00 +02:00
Ivan Gabaldon d4ce87c234 [mod] py: format (#6560) 2026-09-13 23:48:01 +02:00
dependabot[bot] ab5dd7599e [upd] pypi: Bump black from 25.9.0 to 26.5.1 (#6560)
Bumps [black](https://github.com/psf/black) from 25.9.0 to 26.5.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/25.9.0...26.5.1)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-13 23:48:01 +02:00
vojkovic 230c3632d9 [mod] github: convert issue templates to yaml forms 2026-09-13 22:37:48 +08:00
Bnyro 42829cecd6 [feat] results: limit titles and content to fixed length 2026-09-13 15:05:47 +02:00
Bnyro 3b80090654 [del] adobe stock: remove because it's behind a datadome captcha 2026-09-13 14:49:03 +02:00
Bnyro e61d097562 [fix] 500px: migrate to new search endpoint 2026-09-13 14:48:44 +02:00
Bnyro 32f2da4ef0 [fix] public domain image archive: fails to extract API url 2026-09-13 13:43:01 +02:00
Brock Vojkovic 96479fff65 [fix] ci: run armv7 test on the amd64 runner (#6711) 2026-09-13 09:25:21 +02:00
dependabot[bot] d4f00d15d4 [upd] pypi: Bump the minor group with 4 updates (#6693)
Bumps the minor group with 4 updates: [lxml](https://github.com/lxml/lxml), [curl-cffi](https://github.com/lexiforest/curl_cffi), [wlc](https://github.com/WeblateOrg/wlc) and [basedpyright](https://github.com/detachhead/basedpyright).


Updates `lxml` from 6.1.2 to 6.1.3
- [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.2...lxml-6.1.3)

Updates `curl-cffi` from 0.16.1 to 0.16.3
- [Release notes](https://github.com/lexiforest/curl_cffi/releases)
- [Changelog](https://github.com/lexiforest/curl_cffi/blob/main/docs/changelog.rst)
- [Commits](https://github.com/lexiforest/curl_cffi/compare/v0.16.1...v0.16.3)

Updates `wlc` from 2.1.1 to 2.2.0
- [Release notes](https://github.com/WeblateOrg/wlc/releases)
- [Changelog](https://github.com/WeblateOrg/wlc/blob/main/CHANGES.md)
- [Commits](https://github.com/WeblateOrg/wlc/compare/2.1.1...2.2.0)

Updates `basedpyright` from 1.39.10 to 1.40.0
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.39.10...v1.40.0)
2026-09-12 18:20:51 +02:00
Markus Heiser a667e8c905 [mod] openverse - use of internal APIs and addition result fields (#6701)
Additional fields:
  add resolution, thumbnail and author to the image result

Types:
  use EngineResults and add type hints from internall APIs

Disabled engine:
  as of 09/2026: The availability of https://openverse.org/ is poor, and queries
  and image requests often time out. However, these issues also occur when using
  a WEB browser to search on openverse.org or view images.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-12 18:18:32 +02:00
vojkovic 0a94da4368 [fix] engines: naver images updated matcher 2026-09-12 18:17:41 +02:00
Markus Heiser 56b1f64541 [mod] engine: bing images - use internal API & type hints (#6699)
Switching to internal APIs such as ``EngineResults`` and
adding more type hints.  No functional changes.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-12 11:22:01 +02:00
Markus Heiser 923a307454 [fix] engine: bing images - remove market place argument mkt (#6699)
The ``mkt`` argument does no longer exists and the ``async`` argument has been
renamed to ``mmasync``.

For still unknown reasons (IP based?), some users had to observe that the title
was missing [1].  I myself was not able to reproduce this error, however the
evaluation of the title was additionally expanded by the attribute value of the
``<a title=".."`` element.

Related:

- [1] https://github.com/searxng/searxng/pull/6690#issuecomment-5636811572

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-12 11:22:01 +02:00
vojkovic 87bf8c86ed [fix] engines: dogpile web needs api token 2026-09-12 16:32:42 +08:00
dependabot[bot] 61d660276f [upd] web-client (simple): Bump the minor group (#6692)
Bumps the minor group in /client/simple with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@biomejs/biome` from 2.5.11 to 2.5.12
- [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.12/packages/@biomejs/biome)

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

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 26.5.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-09-11 16:57:14 +02:00
Markus Heiserand@vojkovic 6a27c21008 [fix] engine: pinterest - fix empty titles and complete modernization (#6694)
* [fix] engine: pinterest - fix empty titles and complete modernization

The titles from the fields ``title`` and ``grid_title`` are mostly empty or have
short strings without meaningful content.  Various fields for the title are now
being queried, which have more informative value.

As part of the bug fix, the engine was completely revised and modernized.

Related:

- https://github.com/searxng/searxng/pull/6690#issuecomment-5631113121

Co-authored-by: @vojkovic
2026-09-11 16:24:58 +02:00
Brock Vojkovic ffe96f8a6f [fix] engines: bing first word results (#6671)
Fixes the bing web engine, it was just using the first word of the query for the search and return random junk other times. see: vojkovic#10

Swapped to use bing's setlang and cc params. I found us, cn, ru return complete garbage 100% of the time. I reckon that if you don't have an ip address from there it will just return garbage, so those three are skipped. Also removed accept language override because it didn't change anything anymore.


- Closes: https://github.com/searxng/searxng/issues/4964
- Related: https://github.com/vojkovic/searxng/issues/10
2026-09-11 08:41:13 +02:00
Markus Heiser 931fd9787b [fix] engine: core.ac.uk - don't split a string into a tag list (#6689) 2026-09-10 14:45:43 +02:00
vojkovic 42e1d61296 [fix] engines: startpage anubis solver 2026-09-10 19:12:21 +08:00
vojkovic 765a9999df [fix] engines: duckduckgo web bypass botdetection 2026-09-10 18:56:12 +08:00
rdurnik ba055b3e09 [feat] engines: add europepmc (science, scientific publications) 2026-09-10 11:27:28 +02:00
Markus Heiser 3fdc6d753a [del] remove searx_engine - no use case (#6680)
There is no meaningful use-case for the engine, moreover the implementation
is 10 years old (formerly searx) and no longer fits the current data model of
SearXNG.

Closes: - https://github.com/searxng/searxng/issues/6676

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-08 09:29:41 +02:00
Ted Lanham 3e454637fb [fix] braveapi: set JSON Accept header (#6666) 2026-09-07 13:18:07 +02:00
dependabot[bot]andIvan Gabaldon c7f3080aac [upd] web-client (simple): Bump the minor group in /client/simple with 3 updates (#6645)
* [upd] web-client (simple): Bump the minor group

Bumps the minor group in /client/simple with 3 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [sharp](https://github.com/lovell/sharp).


Updates `@biomejs/biome` from 2.5.10 to 2.5.11
- [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.11/packages/@biomejs/biome)

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

Updates `sharp` from 0.35.3 to 0.35.4
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.3...v0.35.4)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 26.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: sharp
  dependency-version: 0.35.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* update lock

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Gabaldon <igabaldon@inetol.net>
2026-09-05 22:57:09 +02:00
dependabot[bot] 072311b5e0 [upd] pypi: Bump the minor group with 3 updates (#6647)
Bumps the minor group with 3 updates: [typer](https://github.com/fastapi/typer), [pylint](https://github.com/pylint-dev/pylint) and [selenium](https://github.com/SeleniumHQ/Selenium).


Updates `typer` from 0.27.1 to 0.27.2
- [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.27.1...0.27.2)

Updates `pylint` from 4.0.7 to 4.0.8
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v4.0.7...v4.0.8)

Updates `selenium` from 4.47.0 to 4.48.0
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.47.0...selenium-4.48.0)

---
updated-dependencies:
- dependency-name: typer
  dependency-version: 0.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: pylint
  dependency-version: 4.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: selenium
  dependency-version: 4.48.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-09-05 22:48:19 +02:00
Bnyro c06e9f0889 [del] cara engine: remove because it requires signup for searching 2026-09-05 22:44:32 +02:00
Bnyro 4781754dc4 [mod] engines: remove uses of enable_http2 = False
This has only been needed because we were flagged by HTTP2 fingerprinting.
Now, since we use curl cffi, we can bypass the fingerpinting, so we can
use HTTP2 just fine without getting blocked.
2026-09-05 19:51:58 +02:00
Bnyro 28b61729c7 [fix] resulthunter: detect blocked requests instead of returning 0 results 2026-09-05 19:47:23 +02:00
Bnyro 14a9f84c6c [fix] chatnoir: returns empty responses 2026-09-05 19:46:48 +02:00
vojkovic 8b01679e8f [fix] engines: update brave images/videos parser and news xpath 2026-09-06 01:45:40 +08:00
vojkovic eaf1fcb349 [fix] engines: resulthunter search_source arg 2026-09-06 01:43:11 +08:00
vojkovic e20e370353 [fix] engines: tusksearch referer header 2026-09-06 01:38:09 +08:00
vojkovic 3605a2d58b [fix] engines: dogpile request api token 2026-09-06 01:22:09 +08:00
Bnyro aef258321c [feat] engines: add searchrockit.com (general, news, images) 2026-09-05 19:16:29 +02:00
Bnyro a303e9c0ca [fix] neosearch: requests blocked due to missing xsrf token 2026-09-05 19:14:16 +02:00
Bnyro ccffbfc164 [fix] searchzee: bypass botblocking
requires https://github.com/searxng/searxng/pull/6620
2026-09-05 19:11:49 +02:00
Ivan Gabaldon 242dc6e398 [mod] ci: disable armv7 container test (#6661) 2026-09-05 18:34:25 +02:00
Ivan Gabaldon 22056605a6 [mod] ci: increase container test delay
Testing under armv7 is having trouble running before the delay ends. Increasing
to 20 seconds should help.
2026-09-05 12:37:39 +08:00
dependabot[bot] 23e7e4da00 [upd] github-actions: Bump docker/setup-qemu-action from 4.2.0 to 4.3.0 (#6644)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/96fe6ef7f33517b61c61be40b68a1882f3264fb8...1f40c72289eff860ee54a304f1438e3cff362e0a)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.3.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-09-04 15:16:28 +02:00
Markus Heiser 15a91992e4 [docs] fix some minor reST markup issues in the doc-string (#6646)
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-04 11:18:59 +02:00
Bnyro 03c439a5b9 [fix] marginalia: crashes with missing filter name 2026-09-04 10:33:42 +02:00
vojkovic be836e614a [mod] network: migrate to curl_cffi 2026-09-04 16:16:09 +08:00
Bnyro 15b0c8ef3a [feat] engines: add JS-based s1search engine 2026-09-04 09:49:07 +02:00
Bnyro a1144dda3e [mod] engines: migrate video engines away from get_embedded_stream_url 2026-09-03 17:28:29 +02:00
Bnyro 86008c9dd6 [feat] results: automatically set embedded stream url for video results
Currently, many engines that provide video results call the same
`get_embedded_stream_url` method for setting the `iframe_src`. There
is no value in that because this logic is engine-specific and probably
many video engines forgot to implement this.

With these changes, it's done automatically, so engine implementors
don't have to worry about setting an `iframe_src` (unless the engine
explicitly has a field for it).
2026-09-03 17:28:29 +02:00
Bnyro 8f452ee892 [fix] chatnoir: fails to extract API session parameters 2026-09-03 10:03:05 +02:00
Markus Heiser 745d5b6fc5 [fix] engine: braveapi - braveapi pagination sends an invalid offset (#6627)
Closes: https://github.com/searxng/searxng/issues/6545
2026-09-03 07:52:31 +02:00
Bnyro 05cd77f71b [mod] s1search: update mirrors to the ones that are not cloudflared 2026-09-02 16:59:05 +02:00
Om Alve b4f616fed7 [mod] engines: migrate to setup() from init() for simple tasks 2026-09-02 16:30:00 +02:00
Markus Heiser fd29de6c55 [fix] engine: meilisearch - AttributeError: no attribute 'about' (#6626)
- Closes: https://github.com/searxng/searxng/issues/6622

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-02 14:26:22 +02:00
Bnyro eca1af4be1 [del] heexy: remove engine due to cloudflare captcha (#6625) 2026-09-02 13:43:03 +02:00
Markus Heiser 7a01f5830c [fix] hardening engine: ddg weather - get GEO location from response (#6623)
The previous implementation ran into an error if the search term contained words
other than just the location (ValueError was raised).

To test engine use search terms like:

    !ddw weather berlin germany

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-09-02 13:40:53 +02:00
Ivan Gabaldon 18af21159b [mod] py: clearurls update location (#6618)
We should opt to retrieve the lists through jsDelivr service as our first choice
and avoid hitting the GH (GitHub) and GL (GitLab) servers.
2026-09-01 21:43:50 +02:00
dependabot[bot] 248e37991c [upd] web-client (simple): Bump the minor group (#6588)
Bumps the minor group in /client/simple with 4 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [svgo](https://github.com/svg/svgo) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `@biomejs/biome` from 2.5.9 to 2.5.10
- [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.10/packages/@biomejs/biome)

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

Updates `svgo` from 4.0.2 to 4.1.0
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](https://github.com/svg/svgo/compare/v4.0.2...v4.1.0)

Updates `vite` from 8.2.1 to 8.2.2
- [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.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor
- dependency-name: "@types/node"
  dependency-version: 26.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: svgo
  dependency-version: 4.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor
- dependency-name: vite
  dependency-version: 8.2.2
  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-09-01 21:42:44 +02:00
github-actions[bot]andsearxng-bot ff424745fd [mod] data: update searx.data - update_external_bangs.py (#6593)
Co-authored-by: searxng-bot <searxng-bot@users.noreply.github.com>
2026-09-01 21:38:36 +02:00
Bnyro 79c8ffe0da [update] data: update wikidata data 2026-09-01 13:56:11 +02:00
Bnyro 2b1c88c54c [refactor] wikidata: cache wikidata properties in searxng data 2026-09-01 13:56:11 +02:00
475 changed files with 52167 additions and 22552 deletions
-50
View File
@@ -1,50 +0,0 @@
---
name: "Bug report"
about: Report a bug in SearXNG"
labels: ["bug"]
type: "bug"
---
_Replace this placeholder with a meaningful and precise description of the bug._
<!-- FILL IN THESE FIELDS .. and delete the comments after reading.
Use Markdown for formatting -> https://www.markdowntools.io/cheat-sheet
-->
### How To Reproduce?
<!-- How can we reproduce this issue? (as minimally and as precisely as
possible) -->
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Screenshots & Logs
<!-- If applicable, add screenshots, logs to help explain your problem. -->
### Version of SearXNG
<!-- Commit number if you are using on master branch and stipulate if you forked
SearXNG -->
<!-- Look at the bottom of the SearXNG page and check for the version after
"Powered by SearXNG" If you are using a forked version of SearXNG include a
link to the fork source code. -->
### How did you install SearXNG?
<!-- Did you install SearXNG using the official documentation or using
searxng-docker? -->
### Additional context
<!-- Add any other context about the problem here. -->
### Code of Conduct
[AI Policy]: https://github.com/searxng/searxng/blob/master/AI_POLICY.rst
- [ ] I read the [AI Policy] and hereby confirm that this issue conforms with the policy.
+62
View File
@@ -0,0 +1,62 @@
name: Bug report
description: Report a bug in SearXNG
labels: ["bug"]
type: bug
body:
- type: textarea
id: description
attributes:
label: Description
description: A meaningful and precise description of the bug.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: How to reproduce
description: How can we reproduce this issue? As minimally and as precisely as possible.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Screenshots & Logs
description: If applicable, add screenshots or logs to help explain your problem.
- type: input
id: version
attributes:
label: Version of SearXNG
description: >
Look at the bottom of the SearXNG page after "Powered by SearXNG".
If you are on master, use the commit. If you forked SearXNG, include a link to the fork.
placeholder: e.g. 2026.9.13+96479fff6
validations:
required: true
- type: dropdown
id: install
attributes:
label: How did you install SearXNG?
description: https://docs.searxng.org/admin/installation.html
options:
- Public instance
- Installation script
- Docker Compose
- Docker (docker run)
- Podman
- Other
validations:
required: true
- type: checkboxes
id: ai-policy
attributes:
label: Code of Conduct
options:
- label: >
I read the [AI Policy](https://github.com/searxng/searxng/blob/master/AI_POLICY.rst)
and hereby confirm that this issue conforms with the policy.
required: true
-46
View File
@@ -1,46 +0,0 @@
---
name: Engine request"
about: Request a new engine in SearXNG"
labels: ["engine request"]
type: "feature"
---
<!-- FILL IN THESE FIELDS .. and delete the comments after reading.
Use Markdown for formatting -> https://www.markdowntools.io/cheat-sheet
-->
### Working URL to the engine
<!-- Please check if the engine is responding correctly before submitting -->
### Why do you want to add this engine?
<!-- What's special about this engine? -->
### Features of this engine
<!-- Features of this engine: Serves special content, is fast, is easy to
integrate, ... ? -->
### How can SearXNG fetch results from this engine?
<!-- List API URL, example code and more that could be useful for the developers
in order to implement this engine. If you don't know what to write, let
this part blank. -->
### Applicable category of this engine
<!-- Where should this new engine fit in SearXNG? Current categories in
SearXNG: general, files, images, it, map, music, news, science, social
media and videos. -->
### Additional context
<!-- Add any other context about the problem here. -->
### Code of Conduct
[AI Policy]: https://github.com/searxng/searxng/blob/master/AI_POLICY.rst
- [ ] I read the [AI Policy] and hereby confirm that this issue conforms with the policy.
+56
View File
@@ -0,0 +1,56 @@
name: Engine request
description: Request a new engine in SearXNG
labels: ["engine request"]
type: feature
body:
- type: input
id: url
attributes:
label: Working URL to the engine
description: Please check if the engine is responding correctly before submitting.
placeholder: https://example.com/search?q=foobar
validations:
required: true
- type: textarea
id: why
attributes:
label: Why do you want to add this engine?
description: What's special about this engine?
validations:
required: true
- type: dropdown
id: category
attributes:
label: Category
description: Where should this new engine fit in SearXNG?
options:
- general
- files
- images
- it
- map
- music
- news
- science
- social media
- videos
validations:
required: true
- type: textarea
id: fetch
attributes:
label: How can SearXNG fetch results?
description: API URL, example code, etc... anything that you think could be useful. Leave blank if you don't know.
- type: checkboxes
id: ai-policy
attributes:
label: Code of Conduct
options:
- label: >
I read the [AI Policy](https://github.com/searxng/searxng/blob/master/AI_POLICY.rst)
and hereby confirm that this issue conforms with the policy.
required: true
-32
View File
@@ -1,32 +0,0 @@
---
name: "Feature request"
about: "Request a new feature in SearXNG"
labels: ["new feature"]
type: "feature"
---
_Replace this placeholder with a concise description of the feature._
<!-- FILL IN THESE FIELDS .. and delete the comments after reading.
Use Markdown for formatting -> https://www.markdowntools.io/cheat-sheet
-->
### Is your feature request related to a problem?
<!-- A clear and concise description of what the problem is. Ex. I'm always
frustrated when [...] -->
### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
### Describe alternatives you've considered
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
### Code of Conduct
[AI Policy]: https://github.com/searxng/searxng/blob/master/AI_POLICY.rst
- [ ] I read the [AI Policy] and hereby confirm that this issue conforms with the policy.
@@ -0,0 +1,45 @@
name: Feature request
description: Request a new feature in SearXNG
labels: ["new feature"]
type: feature
body:
- type: textarea
id: description
attributes:
label: Description
description: A concise description of the feature.
validations:
required: true
- type: textarea
id: problem
attributes:
label: Is your feature request related to a problem?
description: A clear and concise description of what the problem is.
placeholder: I'm always frustrated when [...]
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
placeholder: I want to be able to [...]
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
placeholder: I've considered [...]
- type: checkboxes
id: ai-policy
attributes:
label: Code of Conduct
options:
- label: >
I read the [AI Policy](https://github.com/searxng/searxng/blob/master/AI_POLICY.rst)
and hereby confirm that this issue conforms with the policy.
required: true
+41
View File
@@ -0,0 +1,41 @@
// @ts-check
// Closes issues and prs whose authors/agents don't accept the ai policy
// https://github.com/searxng/searxng/blob/master/AI_POLICY.rst
const boxCocRegex = /\[\s*[Xx]\s*\].*AI Policy/;
const refCocRegex = /\[AI Policy\](?::\s*|\()https:\/\/github\.com\/searxng\/searxng\/.*AI_POLICY/;
export default async ({ github, context }) => {
const item = context.payload.issue ?? context.payload.pull_request;
if (!item?.body) {
return;
}
if (boxCocRegex.test(item.body) && refCocRegex.test(item.body)) {
return;
}
const type = context.payload.issue ? "issue" : "pull request";
await github.rest.issues.createComment({
issue_number: item.number,
body: `Hello! Thank you for your contribution.
Unfortunately your ${type} was closed as the AI Policy has not been accepted.
Please open a new ${type} after confirming your contribution aligns with our AI Policy.`,
...context.repo
});
await github.rest.issues.addLabels({
issue_number: item.number,
labels: ["invalid:slop"],
...context.repo
});
await github.rest.issues.update({
issue_number: item.number,
state: "closed",
state_reason: "not_planned",
...context.repo
});
};
-39
View File
@@ -1,39 +0,0 @@
// Closes issues and prs whose authors/agents don't accept the ai policy
// https://github.com/searxng/searxng/blob/master/AI_POLICY.rst
module.exports = async ({ github, context }) => {
const item = context.payload.pull_request || context.payload.issue;
const body = item.body || '';
const kind = context.payload.pull_request ? 'pull request' : 'issue';
// https://github.com/searxng/searxng/pull/6476#discussion_r3683782481
const hasBox = /\[[Xx]\].*AI Policy/.test(body);
const hasRef = /\[AI Policy\]:\s*https:\/\/github\.com\/searxng\/searxng\/.*AI_POLICY/.test(body);
if (hasBox && hasRef) {
return;
}
const { owner, repo } = context.repo;
await github.rest.issues.createComment({
owner,
repo,
issue_number: item.number,
body:
'Hello! Thank you for your contribution.\n\n' +
`Unfortunately your ${kind} was closed as the AI Policy has not been accepted.\n\n` +
`Please open a new ${kind} after confirming your contribution aligns with our AI Policy.`,
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: item.number,
labels: ['invalid:slop'],
});
await github.rest.issues.update({
owner,
repo,
issue_number: item.number,
state: 'closed',
state_reason: 'not_planned',
});
};
+3 -3
View File
@@ -18,7 +18,7 @@ permissions:
jobs:
check:
name: Check AI Policy
name: Check
# for issues with an author who has not contributed before
if: >-
github.event.sender.type != 'Bot' &&
@@ -34,5 +34,5 @@ jobs:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const script = require('./.github/scripts/ai_policy.cjs');
await script({ github, context });
const script = await import("${{ github.workspace }}/.github/scripts/aiPolicy.mjs")
await script.default({ github, context });
+25 -23
View File
@@ -41,28 +41,17 @@ jobs:
- runner: ubuntu-26.04-arm
arch: armv7
permissions:
packages: write
outputs:
docker_tag: ${{ steps.build.outputs.docker_tag }}
git_url: ${{ steps.build.outputs.git_url }}
steps:
- name: Login to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "${{ env.PYTHON_VERSION }}"
- name: Setup QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -93,6 +82,14 @@ jobs:
OVERRIDE_ARCH: "${{ matrix.arch }}"
run: make container.build
- name: Upload container image
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: container-image-${{ matrix.arch }}
path: "/var/tmp/searxng-podman/*.tar"
if-no-files-found: error
retention-days: 1
test:
name: Test (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
@@ -105,19 +102,12 @@ jobs:
arch: amd64
- runner: ubuntu-26.04-arm
arch: arm64
- runner: ubuntu-26.04-arm
- runner: ubuntu-26.04
arch: armv7
steps:
- name: Login to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- name: Setup QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -125,10 +115,16 @@ jobs:
ref: "${{ github.event.workflow_run.head_sha || github.sha }}"
persist-credentials: "false"
- name: Download container image
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: container-image-${{ matrix.arch }}
path: "/var/tmp/searxng-podman"
merge-multiple: true
- name: Test
env:
OVERRIDE_ARCH: "${{ matrix.arch }}"
GIT_URL: "${{ needs.build.outputs.git_url }}"
run: make container.test
release:
@@ -163,8 +159,14 @@ jobs:
ref: "${{ github.event.workflow_run.head_sha || github.sha }}"
persist-credentials: "false"
- name: Download container images
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: container-image-*
path: "/var/tmp/searxng-podman"
merge-multiple: true
- name: Release
env:
GIT_URL: "${{ needs.build.outputs.git_url }}"
DOCKER_TAG: "${{ needs.build.outputs.docker_tag }}"
run: make container.push
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- update_external_bangs.py
- update_firefox_version.py
- update_engine_traits.py
- update_wikidata_units.py
- update_wikidata.py
- update_engine_descriptions.py
permissions:
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
python-version: "${{ env.PYTHON_VERSION }}"
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "26"
check-latest: "true"
+38
View File
@@ -0,0 +1,38 @@
---
# yamllint disable rule:line-length
name: Sync
# yamllint disable-line rule:truthy
on:
workflow_dispatch:
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
codeberg:
if: github.repository_owner == 'searxng'
name: Codeberg
runs-on: ubuntu-26.04-arm
steps:
- name: Sync
env:
ORIGIN: "github.com/searxng/searxng"
DESTINATION: "codeberg.org/searxng/searxng"
DESTINATION_USER: "${{ secrets.CODEBERG_USER }}"
DESTINATION_TOKEN: "${{ secrets.CODEBERG_TOKEN }}"
run: |
set -eu
git init --quiet --bare .sync
cd .sync
git fetch --no-tags "https://${ORIGIN}.git" \
'+refs/heads/master:refs/heads/master'
git push --force "https://${DESTINATION_USER}:${DESTINATION_TOKEN}@${DESTINATION}.git" \
'refs/heads/master:refs/heads/master'
+519 -492
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -25,25 +25,25 @@
"dependencies": {
"ionicons": "^8.1.0",
"normalize.css": "8.0.1",
"ol": "^10.10.0",
"ol": "^10.11.0",
"swiped-events": "1.2.0"
},
"devDependencies": {
"@biomejs/biome": "2.5.9",
"@types/node": "^26.2.0",
"browserslist": "^4.28.8",
"@biomejs/biome": "2.5.15",
"@types/node": "^26.6.5",
"browserslist": "^4.29.4",
"browserslist-to-esbuild": "^2.1.1",
"edge.js": "^6.5.1",
"less": "^4.9.0",
"mathjs": "^15.2.0",
"sharp": "~0.35.3",
"sort-package-json": "^4.0.0",
"stylelint": "^17.14.1",
"sharp": "~0.35.5",
"sort-package-json": "^4.1.0",
"stylelint": "^17.16.0",
"stylelint-config-standard-less": "^4.1.0",
"stylelint-prettier": "^5.0.3",
"svgo": "^4.0.2",
"svgo": "^4.1.0",
"typescript": "~7.0.2",
"vite": "^8.2.1",
"vite": "^8.3.3",
"vite-bundle-analyzer": "^1.3.9"
}
}
+47 -44
View File
@@ -8,67 +8,68 @@ export type KeyBindingLayout = "default" | "vim";
type KeyBinding = {
key: string;
fun: (event: KeyboardEvent) => void;
des: string;
cat: string;
des?: string;
cat?: string;
};
type HighlightResultElement = "down" | "up" | "visible" | "bottom" | "top";
/* common base for layouts */
const baseKeyBinding: Record<string, KeyBinding> = {
Escape: {
key: "ESC",
fun: (event: KeyboardEvent) => removeFocus(event),
des: "remove focus from the focused input",
cat: "Control"
des: settings.translations?.hotkeys_remove_focus,
cat: settings.translations?.hotkeys_category_control
},
c: {
key: "c",
fun: () => copyURLToClipboard(),
des: "copy url of the selected result to the clipboard",
cat: "Results"
des: settings.translations?.hotkeys_copy_url,
cat: settings.translations?.hotkeys_category_results
},
h: {
key: "h",
fun: () => toggleHelp(keyBindings),
des: "toggle help window",
cat: "Other"
des: settings.translations?.hotkeys_toggle_help,
cat: settings.translations?.hotkeys_category_other
},
i: {
key: "i",
fun: () => searchInputFocus(),
des: "focus on the search input",
cat: "Control"
des: settings.translations?.hotkeys_focus_search,
cat: settings.translations?.hotkeys_category_control
},
n: {
key: "n",
fun: () => GoToNextPage(),
des: "go to next page",
cat: "Results"
des: settings.translations?.hotkeys_next_page,
cat: settings.translations?.hotkeys_category_results
},
o: {
key: "o",
fun: () => openResult(false),
des: "open search result",
cat: "Results"
des: settings.translations?.hotkeys_open_result,
cat: settings.translations?.hotkeys_category_results
},
p: {
key: "p",
fun: () => GoToPreviousPage(),
des: "go to previous page",
cat: "Results"
des: settings.translations?.hotkeys_previous_page,
cat: settings.translations?.hotkeys_category_results
},
r: {
key: "r",
fun: () => reloadPage(),
des: "reload page from the server",
cat: "Control"
des: settings.translations?.hotkeys_reload_page,
cat: settings.translations?.hotkeys_category_control
},
t: {
key: "t",
fun: () => openResult(true),
des: "open the result in a new tab",
cat: "Results"
des: settings.translations?.hotkeys_open_new_tab,
cat: settings.translations?.hotkeys_category_results
}
};
@@ -78,14 +79,14 @@ const keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>> =
ArrowLeft: {
key: "←",
fun: () => highlightResult("up")(),
des: "select previous search result",
cat: "Results"
des: settings.translations?.hotkeys_select_previous,
cat: settings.translations?.hotkeys_category_results
},
ArrowRight: {
key: "→",
fun: () => highlightResult("down")(),
des: "select next search result",
cat: "Results"
des: settings.translations?.hotkeys_select_next,
cat: settings.translations?.hotkeys_category_results
},
...baseKeyBinding
},
@@ -95,56 +96,56 @@ const keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>> =
b: {
key: "b",
fun: () => scrollPage(-window.innerHeight),
des: "scroll one page up",
cat: "Navigation"
des: settings.translations?.hotkeys_page_up,
cat: settings.translations?.hotkeys_category_navigation
},
d: {
key: "d",
fun: () => scrollPage(window.innerHeight / 2),
des: "scroll half a page down",
cat: "Navigation"
des: settings.translations?.hotkeys_half_page_down,
cat: settings.translations?.hotkeys_category_navigation
},
f: {
key: "f",
fun: () => scrollPage(window.innerHeight),
des: "scroll one page down",
cat: "Navigation"
des: settings.translations?.hotkeys_page_down,
cat: settings.translations?.hotkeys_category_navigation
},
g: {
key: "g",
fun: () => scrollPageTo(-document.body.scrollHeight, "top"),
des: "scroll to the top of the page",
cat: "Navigation"
des: settings.translations?.hotkeys_scroll_top,
cat: settings.translations?.hotkeys_category_navigation
},
j: {
key: "j",
fun: () => highlightResult("down")(),
des: "select next search result",
cat: "Results"
des: settings.translations?.hotkeys_select_next,
cat: settings.translations?.hotkeys_category_results
},
k: {
key: "k",
fun: () => highlightResult("up")(),
des: "select previous search result",
cat: "Results"
des: settings.translations?.hotkeys_select_previous,
cat: settings.translations?.hotkeys_category_results
},
u: {
key: "u",
fun: () => scrollPage(-window.innerHeight / 2),
des: "scroll half a page up",
cat: "Navigation"
des: settings.translations?.hotkeys_half_page_up,
cat: settings.translations?.hotkeys_category_navigation
},
v: {
key: "v",
fun: () => scrollPageTo(document.body.scrollHeight, "bottom"),
des: "scroll to the bottom of the page",
cat: "Navigation"
des: settings.translations?.hotkeys_scroll_bottom,
cat: settings.translations?.hotkeys_category_navigation
},
y: {
key: "y",
fun: () => copyURLToClipboard(),
des: "copy url of the selected result to the clipboard",
cat: "Results"
des: settings.translations?.hotkeys_copy_url,
cat: settings.translations?.hotkeys_category_results
},
...baseKeyBinding
}
@@ -344,6 +345,7 @@ const initHelpContent = (divElement: HTMLElement, keyBindings: typeof baseKeyBin
for (const binding of Object.values(keyBindings)) {
const { cat } = binding;
if (!cat) continue;
categories[cat] ??= [];
categories[cat].push(binding);
}
@@ -352,8 +354,8 @@ const initHelpContent = (divElement: HTMLElement, keyBindings: typeof baseKeyBin
(a, b) => (categories[b]?.length ?? 0) - (categories[a]?.length ?? 0)
);
let html = '<a href="#" class="close" aria-label="close" title="close">×</a>';
html += "<h3>How to navigate SearXNG with hotkeys</h3>";
let html = `<a href="#" class="close" aria-label="${settings.translations?.close}" title="${settings.translations?.close}">×</a>`;
html += `<h3>${settings.translations?.hotkeys_navigation_title}</h3>`;
html += "<table>";
for (const [i, categoryKey] of sortedCategoryKeys.entries()) {
@@ -372,6 +374,7 @@ const initHelpContent = (divElement: HTMLElement, keyBindings: typeof baseKeyBin
html += '<ul class="list-unstyled">';
for (const binding of bindings) {
if (!binding.des) continue;
html += `<li><kbd>${binding.key}</kbd> ${binding.des}</li>`;
}
+21 -10
View File
@@ -5,6 +5,7 @@ import { listen, mutable, settings } from "../toolkit.ts";
import { assertElement } from "../util/assertElement.ts";
let imgTimeoutID: number;
const loadErrorSrc = `${settings.theme_static_path}/img/img_load_error.svg`;
const imageLoader = (resultElement: HTMLElement): void => {
if (imgTimeoutID) clearTimeout(imgTimeoutID);
@@ -15,7 +16,7 @@ const imageLoader = (resultElement: HTMLElement): void => {
// use thumbnail until full image loads
const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail");
if (thumbnail) {
if (thumbnail.src === `${settings.theme_static_path}/img/img_load_error.svg`) return;
if (thumbnail.src === loadErrorSrc) return;
imgElement.onerror = (): void => {
imgElement.src = thumbnail.src;
@@ -35,17 +36,27 @@ const imageLoader = (resultElement: HTMLElement): void => {
}, 1000) as unknown as number;
};
const imageThumbnails: NodeListOf<HTMLImageElement> = document.querySelectorAll<HTMLImageElement>(
"#urls img.image_thumbnail, img.thumbnail"
);
for (const thumbnail of imageThumbnails) {
if (thumbnail.complete && thumbnail.naturalWidth === 0) {
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
const hideBrokenThumbnail = (img: HTMLImageElement): void => {
if (img.classList.contains("thumbnail")) {
img.closest("a.thumbnail_link")?.remove();
return;
}
if (img.classList.contains("image_thumbnail") && img.src !== loadErrorSrc) {
img.src = loadErrorSrc;
}
};
thumbnail.onerror = (): void => {
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
};
document.addEventListener(
"error",
(event: Event) => {
const img = event.target;
if (img instanceof HTMLImageElement) hideBrokenThumbnail(img);
},
true
);
// some images may have already failed
for (const img of document.querySelectorAll<HTMLImageElement>("img.thumbnail, #urls img.image_thumbnail")) {
if (img.complete && img.naturalWidth === 0) hideBrokenThumbnail(img);
}
const copyUrlButton: HTMLButtonElement | null =
-9
View File
@@ -14,10 +14,6 @@ const categoryButtons: HTMLButtonElement[] = Array.from(
document.querySelectorAll<HTMLButtonElement>("#categories_container button.category")
);
if (searchInput.value.length === 0) {
searchReset.classList.add("empty");
}
// focus search input on large screens
if (!(isMobile || isResultsPage)) {
searchInput.focus();
@@ -35,15 +31,10 @@ if (isMobile) {
});
}
listen("input", searchInput, () => {
searchReset.classList.toggle("empty", searchInput.value.length === 0);
});
listen("click", searchReset, (event: MouseEvent) => {
event.preventDefault();
searchInput.value = "";
searchInput.focus();
searchReset.classList.add("empty");
});
for (const button of categoryButtons) {
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
.hide_if_nojs {
display: none !important;
}
.show_if_nojs {
display: inline !important;
}
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
Layout of the Video result class
*/
.result-videos {
a.thumbnail_link {
.ltr-float-left();
.ltr-margin-right(1rem);
.ltr-margin-left(0);
width: 10rem !important;
aspect-ratio: 16 / 9 !important;
}
.content {
overflow: hidden;
}
.embedded-video iframe {
width: 100%;
aspect-ratio: 16 / 9;
padding: 10px 0 0 0;
}
}
+6 -12
View File
@@ -164,14 +164,14 @@ button.category_button {
&:hover {
color: var(--color-search-background-hover);
}
&.empty * {
display: none;
}
}
html.no-js #clear_search.hide_if_nojs {
display: none;
#q:placeholder-shown ~ #clear_search {
pointer-events: none;
* {
display: none;
}
}
#q,
@@ -204,12 +204,6 @@ html.no-js #clear_search.hide_if_nojs {
}
}
.no-js #clear_search,
.no-js #send_search {
width: auto !important;
.ltr-border-left(1px solid var(--color-search-border));
}
.search_filters {
margin-top: 0.6rem;
.ltr-margin-right(0);
+39 -40
View File
@@ -255,6 +255,8 @@ article[data-vim-selected].category-social {
font-size: 1rem;
color: var(--color-result-url-font);
flex-flow: row nowrap;
flex: 1;
min-width: 0;
overflow: hidden;
margin: 0;
padding: 0;
@@ -305,22 +307,44 @@ article[data-vim-selected].category-social {
a.thumbnail_link {
position: relative;
margin-top: 0.6rem;
.ltr-margin-right(1rem);
.ltr-float-left();
display: block;
.ltr-float-right();
.ltr-margin-left(0.5rem);
width: 8rem;
aspect-ratio: 4 / 3;
border-radius: 0.5rem;
overflow: hidden;
background: var(--color-result-border);
img.thumbnail_bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
filter: blur(2rem) brightness(0.5);
pointer-events: none;
}
img.thumbnail {
width: 7rem;
height: unset; // remove height value that was needed for lazy loading
position: relative;
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.thumbnail_length {
.image-label-bottom-right();
right: 6px;
z-index: 1;
}
}
.result_inner {
overflow: hidden;
}
.break {
clear: both;
}
@@ -407,32 +431,10 @@ article[data-vim-selected].category-social {
padding: 10px 0 0 0;
}
.result-videos {
a.thumbnail_link img.thumbnail {
width: 20rem;
}
.content {
overflow: hidden;
}
.embedded-video iframe {
width: 100%;
aspect-ratio: 16 / 9;
padding: 10px 0 0 0;
}
}
@supports not (aspect-ratio: 1 / 1) {
// support older browsers which do not have aspect-ratio
// https://caniuse.com/?search=aspect-ratio
.result-videos .embedded-video iframe {
height: calc(@results-width * 9 / 16);
}
}
.engines {
.ltr-float-right();
clear: both;
margin-top: 0.5rem;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
@@ -471,6 +473,8 @@ article[data-vim-selected].category-social {
& > a {
position: relative;
outline: none;
display: inline-block;
height: 100%;
}
img {
@@ -930,10 +934,6 @@ summary.title {
}
.result {
.thumbnail {
max-width: 98%;
}
.url {
span.url {
display: block;
@@ -1144,13 +1144,11 @@ summary.title {
*/
@media screen and (max-width: @small-phone) {
.result-videos {
img.thumbnail {
float: none !important;
}
.content {
overflow: inherit;
.result {
a.thumbnail_link {
aspect-ratio: 1 / 1;
width: 7rem;
border-radius: 1.25rem;
}
}
}
@@ -1165,3 +1163,4 @@ pre code {
@import "result_types/paper.less";
@import "result_types/file.less";
@import "result_types/image.less";
@import "result_types/video.less";
+1 -5
View File
@@ -4,11 +4,7 @@
// stylelint-disable no-descending-specificity
html.no-js .hide_if_nojs {
display: none;
}
html.js .show_if_nojs {
.show_if_nojs {
display: none;
}
@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="ionicon">
<path
d="M430.11 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 11.99-36.6.1-47.7Z"
fill="none"
stroke="currentColor"
stroke-miterlimit="10"
stroke-width="32px"
/>
<circle
fill="currentColor"
cx="144"
cy="208"
r="32"
/>
<circle
fill="currentColor"
cx="152"
cy="311"
r="32"
/>
<circle
fill="currentColor"
cx="224"
cy="144"
r="32"
/>
<circle
fill="currentColor"
cx="256"
cy="367"
r="48"
/>
<circle
fill="currentColor"
cx="328"
cy="144"
r="32"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1,6 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M248 64C146.39 64 64 146.39 64 248s82.39 184 184 184 184-82.39 184-184S349.61 64 248 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M220 220h32v116"/>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M208 340h88"/>
<path d="M248 130a26 26 0 1026 26 26 26 0 00-26-26z" fill="currentColor" stroke="currentColor" stroke-miterlimit="10" stroke-width="1"/>
<path
fill="none"
stroke="currentColor"
stroke-miterlimit="10"
stroke-width="32"
d="M248 64C146.39 64 64 146.39 64 248s82.39 184 184 184 184-82.39 184-184S349.61 64 248 64z"
/>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
d="M220 220h32v116"
/>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-miterlimit="10"
stroke-width="32" d="M208 340h88"
/>
<path
d="M248 130a26 26 0 1026 26 26 26 0 00-26-26z"
fill="currentColor"
stroke="currentColor"
stroke-miterlimit="10"
stroke-width="1"
/>
</svg>

Before

Width:  |  Height:  |  Size: 648 B

After

Width:  |  Height:  |  Size: 852 B

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="ionicon">
<path
fill="currentColor"
stroke="currentColor"
d="M108.56 342.78a60.34 60.34 0 1 0 60.56 60.44 60.63 60.63 0 0 0-60.56-60.44"
/>
<path
fill="currentColor"
stroke="currentColor"
d="M48 186.67v86.55c52 0 101.94 15.39 138.67 52.11s52 86.56 52 138.67h86.66c0-151.56-125.66-277.33-277.33-277.33"
/>
<path
fill="currentColor"
stroke="currentColor"
d="M48 48v86.56c185.25 0 329.22 144.08 329.22 329.44H464C464 234.66 277.67 48 48 48"
/>
</svg>

After

Width:  |  Height:  |  Size: 625 B

@@ -1,6 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M368 415.86V72a24.07 24.07 0 00-24-24H72a24.07 24.07 0 00-24 24v352a40.12 40.12 0 0040 40h328" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
<path d="M416 464h0a48 48 0 01-48-48V128h72a24 24 0 0124 24v264a48 48 0 01-48 48z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
<path d="M240 128h64M240 192h64M112 256h192M112 320h192M112 384h192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
<path d="M176 208h-64a16 16 0 01-16-16v-64a16 16 0 0116-16h64a16 16 0 0116 16v64a16 16 0 01-16 16z" fill="currentColor" stroke="currentColor" stroke-linejoin="round" stroke-width="1" />
<path
fill="none"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="32"
d="M368 415.86V72a24.07 24.07 0 00-24-24H72a24.07 24.07 0 00-24 24v352a40.12 40.12 0 0040 40h328"
/>
<path
fill="none"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="32"
d="M416 464h0a48 48 0 01-48-48V128h72a24 24 0 0124 24v264a48 48 0 01-48 48z"
/>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
d="M240 128h64M240 192h64M112 256h192M112 320h192M112 384h192"
/>
<path
fill="currentColor"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="1"
d="M176 208h-64a16 16 0 01-16-16v-64a16 16 0 0116-16h64a16 16 0 0116 16v64a16 16 0 01-16 16z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 787 B

After

Width:  |  Height:  |  Size: 988 B

@@ -0,0 +1,7 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
fill="currentColor"
>
<path d="M450-154v-309L180-619v309l270 156Zm60 0 270-156v-310L510-463v309Zm-60 69L150-258q-14-8-22-22t-8-30v-340q0-16 8-30t22-22l300-173q14-8 30-8t30 8l300 173q14 8 22 22t8 30v340q0 16-8 30t-22 22L510-85q-14 8-30 8t-30-8Zm194-525 102-59-266-154-102 59 266 154Zm-164 96 104-61-267-154-104 60 267 155Z"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="ionicon">
<circle
fill="currentColor"
cx="256"
cy="256.02"
r="32"
/>
<path
d="M184.25 192.25a96 96 0 0 0 0 127.52M327.77 319.77a96 96 0 0 0 0-127.52M133.28 141.28a168 168 0 0 0 0 229.44M378.72 370.72a168 168 0 0 0 0-229.44" fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32px"
/>
<path
d="M435 416a240.34 240.34 0 0 0 0-320M77 96a240.34 240.34 0 0 0 0 320"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32px"
/>
</svg>

After

Width:  |  Height:  |  Size: 741 B

@@ -0,0 +1,11 @@
<svg
height="512"
viewBox="0 -960 20480 20480"
width="512"
version="1.1"
>
<path
d="M 19480,18100 V 2980 q 0,-693 -493.5,-1186.5 Q 18493,1300 17800,1300 H 8560 q -693,0 -1186.5,493.5 Q 6880,2287 6880,2980 v 357 q 504,231 924,567 420,336 756,756 V 2980 h 9240 v 11067 l -987,-987 H 8560 V 9700 q -336,420 -756,756 -420,336 -924,567 v 2037 q 0,693 493.5,1186.5 Q 7867,14740 8560,14740 h 7560 z M 16120,11380 H 12760 V 9700 h 3360 z M 5200,9700 q 1050,0 1785,-735 735,-735 735,-1785 0,-1050 -735,-1785 -735,-735 -1785,-735 -231,0 -441,42 -210,42 -399,105 V 460 H 1000 v 1680 h 1680 v 5040 q 0,1050 735,1785 735,735 1785,735 z M 16120,8860 H 10240 V 7180 h 5880 z m 0,-2520 H 10240 V 4660 h 5880 z m 1680,6720 V 2980 Z"
stroke="currentColor"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 808 B

@@ -0,0 +1,26 @@
<svg
viewBox="0 0 512 512"
fill="none"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="34.6274"
d="M 256,469.53574 C 373.93232,469.53574 469.53574,373.93232 469.53574,256 469.53574,138.06479 373.93232,42.464261 256,42.464261 138.06479,42.464261 42.464263,138.06479 42.464263,256 42.464263,373.93232 138.06479,469.53574 256,469.53574 Z"
/>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-width="34.6274"
d="m 256,111.7191 a 144.28091,144.28091 0 1 1 0,288.56181"
/>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-width="34.6274"
d="m 256,180.97393 a 75.034727,75.034727 0 0 1 69.31543,46.31417 75.060698,75.060698 0 0 1 0,57.4238 75.092439,75.092439 0 0 1 -16.26334,24.34019 A 75.069355,75.069355 0 0 1 256,331.02607"
/>
</svg>

After

Width:  |  Height:  |  Size: 979 B

+13 -3
View File
@@ -39,14 +39,17 @@ const simple_icons: IconSet[] = [
alert: "alert-outline.svg",
appstore: "apps-outline.svg",
book: "book-outline.svg",
camera: "camera-outline.svg",
close: "close-outline.svg",
download: "download-outline.svg",
"ellipsis-vertical": "ellipsis-vertical-outline.svg",
"file-tray-full": "file-tray-full-outline.svg",
"file-tray-stacked": "file-tray-stacked-outline.svg",
film: "film-outline.svg",
globe: "globe-outline.svg",
heart: "heart-outline.svg",
image: "image-outline.svg",
language: "language-outline.svg",
layers: "layers-outline.svg",
leecher: "arrow-down.svg",
location: "location-outline.svg",
@@ -58,13 +61,14 @@ const simple_icons: IconSet[] = [
"navigate-up": "chevron-up-outline.svg",
people: "people-outline.svg",
play: "play-outline.svg",
radio: "radio-outline.svg",
rainy: "rainy-outline.svg",
save: "save-outline.svg",
school: "school-outline.svg",
search: "search-outline.svg",
seeder: "swap-vertical.svg",
settings: "settings-outline.svg",
tv: "tv-outline.svg"
tv: "tv-outline.svg",
videocam: "videocam-outline.svg"
},
svgo_opts: sxng_icon_opts
},
@@ -75,7 +79,13 @@ const simple_icons: IconSet[] = [
base: resolve(HERE, "src/svg/ionicons"),
set: {
"information-circle": "information-circle-outline.svg",
newspaper: "newspaper-outline.svg"
lyrics: "song-lyric-music.svg",
newspaper: "newspaper-outline.svg",
palette: "color-palette-outline.svg",
package: "package_2.svg",
radio: "radio-outline.svg",
rss: "logo-rss.svg",
tor: "tor-browser.svg"
},
svgo_opts: sxng_icon_opts
}
@@ -7,9 +7,18 @@ for the backend. Example output of this EDGE template:
--}}
{#
Catalog of SVG symbols that can be inserted into the HTML output of a Jinja
template. This file from:
template. This file is generated by:
make static.build.commit
from template:
client/simple/tools/jinja_svg_catalog.html.edge
To modify the icon set, edit the script:
client/simple/theme_icons.ts
client/simple/tools/icon_catalog.edge.html
#}
{%-
+2 -1
View File
@@ -52,7 +52,8 @@ export default {
// stylesheets
ltr: `${PATH.src}/less/style-ltr.less`,
rtl: `${PATH.src}/less/style-rtl.less`,
rss: `${PATH.src}/less/rss.less`
rss: `${PATH.src}/less/rss.less`,
noscript: `${PATH.src}/less/noscript.less`
},
// file naming conventions / pathnames are relative to outDir (PATH.dist)
+1 -1
View File
@@ -1,7 +1,7 @@
# Read the documentation before using the `docker-compose.yml` file:
# https://docs.searxng.org/admin/installation-docker.html
#
# Additional ENVs:
# Additional variables:
# https://docs.searxng.org/admin/settings/settings_general.html#settings-general
# https://docs.searxng.org/admin/settings/settings_server.html#settings-server
@@ -1,7 +1,29 @@
ARG CONTAINER_IMAGE_ORGANIZATION="searxng"
ARG CONTAINER_IMAGE_NAME="searxng"
FROM docker.io/searxng/base:searxng-builder AS builder
COPY ./requirements.txt ./requirements-server.txt ./
ENV UV_NO_MANAGED_PYTHON="true"
RUN --mount=type=cache,id=uv,target=/root/.cache/uv set -eux -o pipefail; \
uv venv; \
uv pip install --requirements ./requirements.txt --requirements ./requirements-server.txt; \
uv cache prune --ci; \
find ./.venv/lib/ -type f -exec strip --strip-unneeded {} + || true; \
find ./.venv/lib/ -type d -name "__pycache__" -exec rm -rf {} +; \
find ./.venv/lib/ -type f -name "*.pyc" -delete; \
python -m compileall -q -f -j 0 --invalidation-mode=unchecked-hash ./.venv/lib/
COPY --exclude=./searx/version_frozen.py ./searx/ ./searx/
RUN set -eux -o pipefail; \
python -m compileall -q -f -j 0 --invalidation-mode=unchecked-hash ./searx/; \
find ./searx/static/ -type f \
\( -name "*.html" -o -name "*.css" -o -name "*.js" -o -name "*.svg" \) \
-exec gzip -9 -k {} + \
-exec brotli -9 -k {} + \
-exec gzip --test {}.gz + \
-exec brotli --test {}.br +
FROM localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:builder AS builder
FROM docker.io/searxng/base:searxng AS dist
COPY --chown=977:977 --from=builder /usr/local/searxng/.venv/ ./.venv/
@@ -24,6 +46,8 @@ LABEL org.opencontainers.image.created="$CREATED" \
org.opencontainers.image.url="https://searxng.org" \
org.opencontainers.image.version="$VERSION"
# Extended from:
# https://codeberg.org/searxng/base/src/branch/main/Dockerfile
ENV __SEARXNG_VERSION="$VERSION" \
__SEARXNG_SETTINGS_PATH="$__SEARXNG_CONFIG_PATH/settings.yml" \
GRANIAN_PROCESS_NAME="searxng" \
@@ -35,7 +59,6 @@ ENV __SEARXNG_VERSION="$VERSION" \
GRANIAN_WORKERS_KILL_TIMEOUT="30s" \
GRANIAN_BLOCKING_THREADS_IDLE_TIMEOUT="5m"
# "*_PATH" ENVs are defined in base images
VOLUME $__SEARXNG_CONFIG_PATH
VOLUME $__SEARXNG_DATA_PATH
-31
View File
@@ -1,31 +0,0 @@
FROM docker.io/searxng/base:searxng-builder AS builder
COPY ./requirements.txt ./requirements-server.txt ./
ENV UV_NO_MANAGED_PYTHON="true"
ENV UV_NATIVE_TLS="true"
ARG TIMESTAMP_VENV="0"
RUN --mount=type=cache,id=uv,target=/root/.cache/uv set -eux -o pipefail; \
export SOURCE_DATE_EPOCH="$TIMESTAMP_VENV"; \
uv venv; \
uv pip install --requirements ./requirements.txt --requirements ./requirements-server.txt; \
uv cache prune --ci; \
find ./.venv/lib/ -type f -exec strip --strip-unneeded {} + || true; \
find ./.venv/lib/ -type d -name "__pycache__" -exec rm -rf {} +; \
find ./.venv/lib/ -type f -name "*.pyc" -delete; \
python -m compileall -q -f -j 0 --invalidation-mode=unchecked-hash ./.venv/lib/; \
find ./.venv/lib/python*/site-packages/*.dist-info/ -type f -name "RECORD" -exec sort -t, -k1,1 -o {} {} \;; \
find ./.venv/ -exec touch -h --date="@$TIMESTAMP_VENV" {} +
COPY --exclude=./searx/version_frozen.py ./searx/ ./searx/
RUN set -eux -o pipefail; \
python -m compileall -q -f -j 0 --invalidation-mode=unchecked-hash ./searx/; \
find ./searx/static/ -type f \
\( -name "*.html" -o -name "*.css" -o -name "*.js" -o -name "*.svg" \) \
-exec gzip -9 -k {} + \
-exec brotli -9 -k {} + \
-exec gzip --test {}.gz + \
-exec brotli --test {}.br +
+1 -1
View File
@@ -18,7 +18,7 @@ services:
valkey:
container_name: searxng-valkey
image: docker.io/valkey/valkey:9-alpine
command: valkey-server --save 30 1 --loglevel warning
command: valkey-server --save 300 1 --save 60 1000 --loglevel warning
restart: always
volumes:
- valkey-data:/data/
+2 -2
View File
@@ -13,7 +13,7 @@ check_file() {
!!! "$target" is not a valid file, exiting...
!!!
EOF
exit 127
exit 1
fi
}
@@ -28,7 +28,7 @@ check_directory() {
!!! "$target" is not a valid directory, exiting...
!!!
EOF
exit 127
exit 1
fi
}
+6 -6
View File
@@ -283,12 +283,12 @@ container images are not officially supported):
$ make container
$ docker images
REPOSITORY TAG IMAGE ID SIZE
localhost/searxng/searxng 2026.6.19-93f66bfb4 ... 265 MB
localhost/searxng/searxng latest ... 265 MB
localhost/searxng/searxng builder ... 687 MB
docker.io/searxng/base searxng-builder ... 565 MB
docker.io/searxng/base searxng ... 143 MB
REPOSITORY TAG IMAGE ID SIZE
localhost/searxng/searxng latest ... 271 MB
localhost/searxng/searxng 2026.10.17-* ... 271 MB
<none> <none> ... 677 MB
docker.io/searxng/base searxng ... 144 MB
docker.io/searxng/base searxng-builder ... 550 MB
Migrate from ``searxng-docker``
===============================
+12 -11
View File
@@ -58,10 +58,9 @@ engine is shown. Most of the options have a default value or even are optional.
# overwrite values from section 'outgoing:'
enable_http2: false
enable_http3: false
retries: 1
max_connections: 100
max_keepalive_connections: 10
keepalive_expiry: 5.0
using_tor_proxy: false
proxies:
http:
@@ -163,6 +162,16 @@ engine is shown. Most of the options have a default value or even are optional.
``enable_http`` : optional
Enable HTTP for this engine (by default only HTTPS is enabled).
``enable_http3`` : optional
Use HTTP/3 (falls back to HTTP/2). Default ``false``.
Ignored when a proxy is set.
.. hint::
HTTP/3 places demands on the IP infrastructure that are not met in every
environment. Enable this option only if you are aware of these requirements
and the extent to which they are met.
``retry_on_http_error`` : optional
Retry request on some HTTP status code.
@@ -179,20 +188,12 @@ engine is shown. Most of the options have a default value or even are optional.
Using tor proxy (``true``) or not (``false``) for this engine. The default is
taken from ``using_tor_proxy`` of the :ref:`settings outgoing`.
.. _Pool limit configuration: https://www.python-httpx.org/advanced/#pool-limit-configuration
``max_keepalive_connection#s`` :
`Pool limit configuration`_, overwrites value ``pool_maxsize`` from
:ref:`settings outgoing` for this engine.
.. _Pool limit configuration: https://curl-cffi.readthedocs.io/en/latest/api.html#sessions
``max_connections`` :
`Pool limit configuration`_, overwrites value ``pool_connections`` from
:ref:`settings outgoing` for this engine.
``keepalive_expiry`` :
`Pool limit configuration`_, overwrites value ``keepalive_expiry`` from
:ref:`settings outgoing` for this engine.
.. _private engines:
+18 -33
View File
@@ -12,20 +12,12 @@ Communication with search engines.
request_timeout: 2.0 # default timeout in seconds, can be override by engine
max_request_timeout: 10.0 # the maximum timeout in seconds
useragent_suffix: "" # information like an email address to the administrator
pool_connections: 100 # Maximum number of allowable connections, or null
# for no limits. The default is 100.
pool_maxsize: 10 # Number of allowable keep-alive connections, or null
# to always allow. The default is 10.
enable_http2: true # See https://www.python-httpx.org/http2/
pool_connections: 100 # Maximum number of concurrent connections (default: 100)
enable_http2: true # Enables the use of HTTP2
# uncomment below section if you want to use a custom server certificate
# see https://www.python-httpx.org/advanced/#changing-the-verification-defaults
# and https://www.python-httpx.org/compatibility/#ssl-configuration
# verify: ~/.mitmproxy/mitmproxy-ca-cert.cer
#
# uncomment below section if you want to use a proxyq see: SOCKS proxies
# https://2.python-requests.org/en/latest/user/advanced/#proxies
# are also supported: see
# https://2.python-requests.org/en/latest/user/advanced/#socks
# uncomment below section if you want to use a proxy
#
# proxies:
# all://:
@@ -46,30 +38,26 @@ Communication with search engines.
timeout to load). Can be override by ``timeout`` in the :ref:`settings engines`.
``useragent_suffix`` :
Suffix to the user-agent SearXNG uses to send requests to others engines. If an
engine wish to block you, a contact info here may be useful to avoid that.
Suffix to add when an engine's User-Agent is set via searxng_useragent().
Contact info here may be useful to avoid an engine blocking you.
.. _Pool limit configuration: https://www.python-httpx.org/advanced/#pool-limit-configuration
``pool_maxsize``:
Number of allowable keep-alive connections, or ``null`` to always allow. The
default is 10. See ``max_keepalive_connections`` `Pool limit configuration`_.
.. _Pool limit configuration: https://curl-cffi.readthedocs.io/en/latest/api.html#sessions
``pool_connections`` :
Maximum number of allowable connections, or ``null`` # for no limits. The
default is 100. See ``max_connections`` `Pool limit configuration`_.
Maximum number of concurrent connections. The default is 100.
See ``max_clients`` `Pool limit configuration`_.
``keepalive_expiry`` :
Number of seconds to keep a connection in the pool. By default 5.0 seconds.
See ``keepalive_expiry`` `Pool limit configuration`_.
.. _httpx proxies: https://www.python-httpx.org/advanced/#http-proxying
.. _curl_cffi proxies: https://curl-cffi.readthedocs.io/en/latest/quick_start.html
``proxies`` :
Define one or more proxies you wish to use, see `httpx proxies`_.
Define one or more proxies you wish to use, see `curl_cffi proxies`_.
If there are more than one proxy for one protocol (http, https),
requests to the engines are distributed in a round-robin fashion.
HTTP, HTTPS, SOCKS4, SOCKS5 and SOCKS5h proxies are supported
(``http://``, ``https://``, ``socks4://``, ``socks5://``, ``socks5h://``). You should
use ``socks5h://`` when using Tor so hostnames are resolved by the proxy.
``source_ips`` :
If you use multiple network interfaces, define from which IP the requests must
be made. Example:
@@ -87,18 +75,15 @@ Communication with search engines.
different proxy and source ip.
``enable_http2`` :
Enable by default. Set to ``false`` to disable HTTP/2.
.. _httpx verification defaults: https://www.python-httpx.org/advanced/#changing-the-verification-defaults
.. _httpx ssl configuration: https://www.python-httpx.org/compatibility/#ssl-configuration
Enable by default (HTTP/2). Set to ``false`` to force HTTP/1.1.
HTTP/3 is opt-in per engine (``enable_http3``).
``verify``: : ``$SSL_CERT_FILE``, ``$SSL_CERT_DIR``
Allow to specify a path to certificate.
see `httpx verification defaults`_.
HTTPS verification uses the OS's trust store by default.
Set a path to use a custom CA file.
In addition to ``verify``, SearXNG supports the ``$SSL_CERT_FILE`` (for a file) and
``$SSL_CERT_DIR`` (for a directory) OpenSSL variables.
see `httpx ssl configuration`_.
``max_redirects`` :
30 by default. Maximum redirect before it is an error.
+3 -1
View File
@@ -143,7 +143,7 @@ parameters with default value can be redefined for special purposes.
data dict ``{}``
cookies dict ``{}``
verify bool ``True``
headers.User-Agent str a random User-Agent
headers.User-Agent str ``''``
category str current category, like ``'general'``
safesearch int ``0``, between ``0`` and ``2`` (normal, moderate, strict)
time_range Optional[str] ``None``, can be ``day``, ``week``, ``month``, ``year``
@@ -229,6 +229,8 @@ following parameters can be used to specify a search request:
max_redirects int maximum redirects, hard limit
soft_max_redirects int maximum redirects, soft limit. Record an error but don't stop the engine
raise_for_httperror bool True by default: raise an exception if the HTTP code of response is >= 300
impersonate str curl_cffi impersonate target (default: chrome, none to disable)
curl_options dict Any extra libcurl options for the request
=================== =========== ==========================================================================
-8
View File
@@ -1,8 +0,0 @@
.. _adobe stock engine:
===========
Adobe Stock
===========
.. automodule:: searx.engines.adobe_stock
:members:
-8
View File
@@ -1,8 +0,0 @@
.. _cara engine:
===========
Cara Images
===========
.. automodule:: searx.engines.cara
:members:
+8
View File
@@ -0,0 +1,8 @@
.. _europepmc engine:
==========
Europe PMC
==========
.. automodule:: searx.engines.europepmc
:members:
-8
View File
@@ -1,8 +0,0 @@
.. _loc engine:
===================
Library of Congress
===================
.. automodule:: searx.engines.loc
:members:
+2 -2
View File
@@ -80,8 +80,8 @@ same environment, here are a few examples::
# to test one of the update scripts
(dev.env)$ searxng_extra/update/update_engine_traits.py --help
# to test the update of the wikidata units
(dev.env)$ searxng_extra/update/update_wikidata_units.py
# to test the update of the wikidata units and property names
(dev.env)$ searxng_extra/update/update_wikidata.py
.. sidebar:: further read
+1 -1
View File
@@ -286,7 +286,7 @@ content becomes smart.
files & folders origin :origin:`docs/dev/reST.rst` ``:origin:`docs/dev/reST.rst```
pull request :pull:`4` ``:pull:`4```
patch :patch:`af2cae6` ``:patch:`af2cae6```
PyPi package :pypi:`httpx` ``:pypi:`httpx```
PyPi package :pypi:`curl_cffi` ``:pypi:`curl_cffi```
manual page man :man:`bash` ``:man:`bash```
intersphinx_
--------------------------------------------------------------------------------------------------
+7
View File
@@ -0,0 +1,7 @@
.. _result_types.video:
=============
Video Results
=============
.. automodule:: searx.result_types.video
+1 -1
View File
@@ -19,13 +19,13 @@ following types have been implemented so far ..
main/paper
main/file
main/image
main/video
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
final typing is complete.
- :ref:`template default` / :py:obj:`Result`
- :ref:`template videos`
- :ref:`template torrent`
- :ref:`template map`
- :ref:`template packages`
+3 -3
View File
@@ -90,10 +90,10 @@ Scripts to update static data in :origin:`searx/data/`
:members:
``update_wikidata_units.py``
``update_wikidata.py``
============================
:origin:`[source] <searxng_extra/update/update_wikidata_units.py>`
:origin:`[source] <searxng_extra/update/update_wikidata.py>`
.. automodule:: searxng_extra.update.update_wikidata_units
.. automodule:: searxng_extra.update.update_wikidata
:members:
-23
View File
@@ -129,29 +129,6 @@ audio_src : uri,
URL of an embedded ``<audio controls>``.
.. _template videos:
``videos.html``
---------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`videos.html
<searx/templates/simple/result_templates/videos.html>`:
iframe_src : :py:class:`str`
URL of an embedded ``<iframe>`` / the frame is collapsible.
The videos are displayed as small thumbnails in the main results list, there
is an additional button to collaps/open the embeded video.
content : :py:class:`str`
Description of the code fragment.
.. _template torrent:
``torrent.html``
+1 -1
View File
@@ -48,7 +48,7 @@ PATH="${PY_ENV}/bin:${REPO_ROOT}/node_modules/.bin:${GOROOT}/bin:${GOPATH}/bin:$
PYOBJECTS="searx"
PY_SETUP_EXTRAS='[test]'
GECKODRIVER_VERSION="v0.37.0"
GECKODRIVER_VERSION="v0.37.1"
# SPHINXOPTS=
BLACK_OPTIONS=("--target-version" "py311" "--line-length" "120" "--skip-string-normalization")
BLACK_TARGETS=("--exclude" "(searx/static|searx/languages.py)" "--include" 'searxng.msg|\.pyi?$' "searx" "searxng_extra" "tests")
+6 -6
View File
@@ -1,10 +1,10 @@
mock==5.2.0
nose2[coverage_plugin]==0.16.0
cov-core==1.15.0
black==25.9.0
pylint==4.0.7
black==26.10.1
pylint==4.1.3
splinter==0.21.0
selenium==4.47.0
selenium==4.51.0
Sphinx==8.2.3;python_version <= "3.11"
Sphinx==9.1.0; python_version > "3.11"
sphinx-issues==6.0.0
@@ -18,11 +18,11 @@ myst-parser==5.0.0
linuxdoc==20260504
aiounittest==1.5.0
yamllint==1.38.0
wlc==2.1.1
wlc==2.2.1
coloredlogs==15.0.1
docutils>=0.21.2;python_version <= "3.11"
docutils>=0.22.4; python_version > "3.11"
parameterized==0.9.0
granian[reload]==2.8.2
basedpyright==1.39.10
granian[reload]==2.8.5
basedpyright==1.40.2
types-lxml==2026.2.16
+2 -2
View File
@@ -1,2 +1,2 @@
granian==2.8.2
granian[pname]==2.8.2
granian==2.8.5
granian[pname]==2.8.5
+6 -8
View File
@@ -1,19 +1,17 @@
certifi==2026.7.22
certifi==2026.10.23
babel==2.18.0
flask-babel==4.0.0
flask==3.1.3
jinja2==3.1.6
lxml==6.1.2
lxml==6.1.3
pygments==2.21.0
python-dateutil==2.9.0.post0
python-dateutil==3.0.0
pyyaml==6.0.3
httpx[http2]==0.28.1
httpx-socks[asyncio]==0.13.1
sniffio==1.3.1
curl_cffi==0.17.0
valkey==6.1.1
markdown-it-py==4.2.0
msgspec==0.21.1
typer==0.27.1
msgspec==0.22.0
typer==0.27.3
isodate==0.7.2
whitenoise==6.12.0
typing-extensions==4.16.0
+1
View File
@@ -1,5 +1,6 @@
# 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
-1
View File
@@ -38,7 +38,6 @@ area:
"""
__all__ = ["AnswererInfo", "Answerer", "AnswerStorage"]
-1
View File
@@ -13,7 +13,6 @@ from dataclasses import dataclass
from searx.utils import load_module
from searx.result_types.answer import BaseAnswer
_default = pathlib.Path(__file__).parent
log: logging.Logger = logging.getLogger("searx.answerers")
+8 -8
View File
@@ -11,7 +11,7 @@ from urllib.parse import urlencode
import lxml.etree
import lxml.html
from httpx import HTTPError
from curl_cffi.requests.exceptions import RequestException
from searx import settings
from searx.engines import (
@@ -28,19 +28,19 @@ if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
def update_kwargs(**kwargs) -> None: # type: ignore
def update_kwargs(kwargs) -> None: # type: ignore
if 'timeout' not in kwargs:
kwargs['timeout'] = settings['outgoing']['request_timeout']
kwargs['raise_for_httperror'] = True
def get(*args, **kwargs) -> "SXNG_Response": # type: ignore
update_kwargs(**kwargs) # pyright: ignore[reportUnknownArgumentType]
update_kwargs(kwargs)
return http_get(*args, **kwargs) # pyright: ignore[reportUnknownArgumentType]
def post(*args, **kwargs) -> "SXNG_Response": # type: ignore
update_kwargs(**kwargs) # pyright: ignore[reportUnknownArgumentType]
update_kwargs(kwargs)
return http_post(*args, **kwargs) # pyright: ignore[reportUnknownArgumentType]
@@ -63,7 +63,7 @@ def bing(query: str, _sxng_locale: str) -> list[str]:
base_url = "https://www.bing.com/AS/Suggestions?"
# cvid has to be a 32 character long string consisting of numbers and uppsercase characters
cvid = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
response = get(base_url + urlencode({'qry': query, 'csr': 1, 'cvid': cvid}))
response = get(base_url + urlencode({'qry': query, 'csr': 1, 'cvid': cvid}), enable_http3=True)
results: list[str] = []
if response.ok:
@@ -83,7 +83,7 @@ def brave(query: str, _sxng_locale: str) -> list[str]:
url = 'https://search.brave.com/api/suggest?'
url += urlencode({'q': query})
country = 'all'
kwargs = {'cookies': {'country': country}}
kwargs = {'cookies': {'country': country}, 'enable_http3': True}
resp = get(url, **kwargs)
results: list[str] = []
@@ -147,7 +147,7 @@ def google_complete(query: str, sxng_locale: str) -> list[str]:
)
results: list[str] = []
resp = get('https://www.google.com/complete/search?' + args)
resp = get('https://www.google.com/complete/search?' + args, enable_http3=True)
if resp and resp.ok:
json_txt = resp.text[resp.text.find('[') : resp.text.find(']', -3) + 1]
data = json.loads(json_txt)
@@ -418,5 +418,5 @@ def search_autocomplete(backend_name: str, query: str, sxng_locale: str) -> list
return []
try:
return backend(query, sxng_locale)
except (HTTPError, SearxEngineResponseException):
except (RequestException, SearxEngineResponseException):
return []
-1
View File
@@ -5,7 +5,6 @@ Implementations used for bot detection.
"""
__all__ = ["init", "dump_request", "get_network", "too_many_requests", "ProxyFix"]
+1 -1
View File
@@ -182,7 +182,7 @@ class Config:
if default is UNSET:
raise KeyError(name)
return default
(modulename, name) = str(fqn).rsplit('.', 1)
modulename, name = str(fqn).rsplit('.', 1)
m = __import__(modulename, {}, {}, [name], 0)
return getattr(m, name)
-1
View File
@@ -13,7 +13,6 @@ Accept_ header ..
"""
from ipaddress import (
IPv4Network,
IPv6Network,
@@ -14,7 +14,6 @@ bot if the Accept-Encoding_ header ..
"""
from ipaddress import (
IPv4Network,
IPv6Network,
@@ -11,7 +11,6 @@ if the Accept-Language_ header is unset.
"""
from ipaddress import (
IPv4Network,
IPv6Network,
-1
View File
@@ -11,7 +11,6 @@ the Connection_ header is set to ``close``.
"""
from ipaddress import (
IPv4Network,
IPv6Network,
+1
View File
@@ -20,6 +20,7 @@ Metadata`_. A request is filtered out in case of:
"""
# pylint: disable=unused-argument
-2
View File
@@ -12,7 +12,6 @@ the User-Agent_ header is unset or matches the regular expression
"""
import re
from ipaddress import (
IPv4Network,
@@ -25,7 +24,6 @@ import flask
from . import config
from ._helpers import too_many_requests
USER_AGENT = (
r'('
+ r'unknown'
-1
View File
@@ -55,7 +55,6 @@ from ._helpers import (
logger,
)
logger = logger.getChild('ip_limit')
BURST_WINDOW = 20
+1
View File
@@ -23,6 +23,7 @@ The ``ip_lists`` method implements :py:obj:`block-list <block_ip>` and
]
"""
# pylint: disable=unused-argument
+1
View File
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Implementation of a middleware to determine the real IP of an HTTP request
(:py:obj:`flask.request.remote_addr`) behind a proxy chain."""
# pylint: disable=too-many-branches
-1
View File
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Providing a Valkey database for the botdetection methods."""
import valkey
__all__ = ["set_valkey_client", "get_valkey_client"]
+1
View File
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Implementations needed for a branding of SearXNG."""
# pylint: disable=too-few-public-methods
# Struct fields aren't discovered in Python 3.14
+1 -1
View File
@@ -465,7 +465,7 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache):
# Check if value is expired. It's possible that it's expired but has not
# yet been automatically deleted by the periodic maintenance
(value, expire) = row
value, expire = row
now = time.time()
if expire < now:
# The record is deleted during the maintenance interval. Deleting
-1
View File
@@ -3,7 +3,6 @@
import warnings
# limiter backward compatibility
# ------------------------------
+12 -1
View File
@@ -4,6 +4,7 @@
make data.all
"""
# pylint: disable=invalid-name
__all__ = ["ahmia_blacklist_loader", "data_dir", "get_cache"]
@@ -32,6 +33,13 @@ class WikiDataUnitType(t.TypedDict):
to_si_factor: float
WikiDataPropertyNameType = str | dict[str, str]
"""Name of a Wikidata property. Can be either the plain name or a dictionary of
language code to property name, e.g. ``{"en": "Date of birth"}``."""
WikiDataPropertiesType = dict[str, WikiDataPropertyNameType]
"""Dictionary from wikidata property ID to property name."""
class LocalesType(t.TypedDict):
"""Data structure of an item in ``locales.json``"""
@@ -41,6 +49,7 @@ class LocalesType(t.TypedDict):
USER_AGENTS: UserAgentType
WIKIDATA_UNITS: dict[str, WikiDataUnitType]
WIKIDATA_PROPERTIES: WikiDataPropertiesType
TRACKER_PATTERNS: TrackerPatternsDB
LOCALES: LocalesType
CURRENCIES: CurrenciesDB
@@ -52,11 +61,12 @@ ENGINE_DESCRIPTIONS: dict[str, dict[str, t.Any]]
ENGINE_TRAITS: dict[str, dict[str, t.Any]]
lazy_globals = {
lazy_globals: dict[str, t.Any] = {
"CURRENCIES": CurrenciesDB(),
"USER_AGENTS": None,
"EXTERNAL_URLS": None,
"WIKIDATA_UNITS": None,
"WIKIDATA_PROPERTIES": None,
"EXTERNAL_BANGS": None,
"OSM_KEYS_TAGS": None,
"ENGINE_DESCRIPTIONS": None,
@@ -69,6 +79,7 @@ data_json_files = {
"USER_AGENTS": "useragents.json",
"EXTERNAL_URLS": "external_urls.json",
"WIKIDATA_UNITS": "wikidata_units.json",
"WIKIDATA_PROPERTIES": "wikidata_properties.json",
"EXTERNAL_BANGS": "external_bangs.json",
"OSM_KEYS_TAGS": "osm_keys_tags.json",
"ENGINE_DESCRIPTIONS": "engine_descriptions.json",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Simple implementation to store TrackerPatterns data in a SQL database."""
# pylint: disable=too-many-branches
import typing as t
@@ -10,7 +11,7 @@ import re
from collections.abc import Iterator
from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
from httpx import HTTPError
from curl_cffi.requests.exceptions import RequestException
from searx.data.core import get_cache, log
from searx.network import get as http_get
@@ -28,11 +29,11 @@ class TrackerPatternsDB:
ctx_name = "data_tracker_patterns"
# ClearURL rule lists, the first one that responds HTTP 200 is used
CLEAR_LIST_URL = [
# ClearURL rule lists, the first one that responds HTTP 200 is used
"https://rules1.clearurls.xyz/data.minify.json",
"https://cdn.jsdelivr.net/gh/clearurls/rules@refs/heads/gh-pages/data.minify.json",
"https://rules2.clearurls.xyz/data.minify.json",
"https://raw.githubusercontent.com/ClearURLs/Rules/refs/heads/master/data.min.json",
"https://rules1.clearurls.xyz/data.minify.json",
]
class Fields:
@@ -87,12 +88,12 @@ class TrackerPatternsDB:
try:
resp = http_get(url, timeout=3)
except HTTPError as exc:
log.warning("TRACKER_PATTERNS: HTTPError (%s) occured while fetching %s", url, exc)
except RequestException as exc:
log.warning("TRACKER_PATTERNS: RequestException while fetching %s: %s", url, exc)
continue
if resp.status_code != 200:
log.warning(f"TRACKER_PATTERNS: ClearURL ignore HTTP {resp.status_code} {url}")
log.warning("TRACKER_PATTERNS: ClearURL ignore HTTP %s %s", resp.status_code, url)
continue
break
+2 -2
View File
@@ -5,7 +5,7 @@
],
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
"versions": [
"154.0",
"153.0"
"157.0",
"156.0"
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -305,7 +305,7 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
region: str = ""
"""For an engine, when there is ``region: ...`` in the YAML settings the engine
does support only this one region::
does support only this one region:
.. code:: yaml
@@ -317,6 +317,9 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
enable_http: bool
"""Enable HTTP (by default only HTTPS is enabled)."""
enable_http3: bool = False
"""Enables the use of HTTP/3 if available"""
shortcut: str
"""Code used to execute bang requests (``!foo``)"""
+1 -2
View File
@@ -3,7 +3,6 @@
"""1337x"""
from urllib.parse import quote, urljoin
from lxml import html
from searx.utils import extract_text, eval_xpath, eval_xpath_list, eval_xpath_getindex
# about
@@ -31,7 +30,7 @@ def request(query, params):
def response(resp):
results = []
dom = html.fromstring(resp.text)
dom = resp.html()
for result in eval_xpath_list(dom, '//table[contains(@class, "table-list")]/tbody//tr'):
href = urljoin(url, eval_xpath_getindex(result, './td[contains(@class, "name")]/a[2]/@href', 0))
+1 -2
View File
@@ -5,7 +5,6 @@
import typing as t
from urllib.parse import urlencode
from lxml import html
from searx import logger
from searx.enginelib import EngineCache
@@ -88,7 +87,7 @@ def response(resp):
if not resp.text or not resp.text.strip():
return []
dom = html.fromstring(resp.text)
dom = resp.html()
results = []
for item in dom.xpath('//li[contains(@class, "res-list")]'):
+14 -14
View File
@@ -6,7 +6,8 @@ from urllib.parse import urlencode
from datetime import datetime
from searx.exceptions import SearxEngineAPIException
from searx.utils import html_to_text, get_embeded_stream_url
from searx.result_types import EngineResults
from searx.utils import html_to_text
about = {
"website": "https://tv.360kan.com/",
@@ -29,12 +30,12 @@ def request(query, params):
return params
def response(resp):
def response(resp) -> EngineResults:
try:
data = resp.json()
except Exception as e:
raise SearxEngineAPIException(f"Invalid response: {e}") from e
results = []
res = EngineResults()
if "data" not in data or "result" not in data["data"]:
raise SearxEngineAPIException("Invalid response")
@@ -50,16 +51,15 @@ def response(resp):
except (ValueError, TypeError):
published_date = None
results.append(
{
'url': entry["play_url"],
'title': html_to_text(entry["title"]),
'content': html_to_text(entry["description"]),
'template': 'videos.html',
'publishedDate': published_date,
'thumbnail': entry["cover_img"],
"iframe_src": get_embeded_stream_url(entry["play_url"]),
}
res.add(
res.types.Video(
url=entry["play_url"],
title=html_to_text(entry["title"]),
content=html_to_text(entry["description"]),
template='videos.html',
publishedDate=published_date,
thumbnail=entry["cover_img"],
)
)
return results
return res
+71 -56
View File
@@ -12,8 +12,6 @@ through our exclusive distribution partners.
import typing as t
import codecs
import random
import string
from searx.result_types import EngineResults
@@ -33,11 +31,11 @@ about = {
}
base_url = "https://500px.com"
api_url = "https://api.500px.com"
categories = ["images"]
api_url = "https://api-neo.500px.com"
categories = ["stock images"]
paging = True
results_per_page = 30
"""Number of results to return in the request.
@@ -46,47 +44,71 @@ The default was taken from the WEB UI, where the GraphQL query sets the value to
"""
SXNG_query = """query PhotoSearchPaginationContainerQuery(
$first: Int, $cursor: String, $search: String!, $sort: PhotoSort, $filters: [PhotoSearchFilter!], $nlp: Boolean
SXNG_query = """
query searchResource(
$keyword: String!
$type: SearchType = TEXT
$first: Int!
$after: String
$resourceTypes: [SearchResourceType!]
$categories: [String!]
$equipments: [String!]
$downloadable: PhotoDownloadable
$sort: SearchSortOption = RELEVANCE
$excludeNsfw: Boolean
) {
...SXNG_query
}
fragment SXNG_query on Query {
photoSearch(sort: $sort, first: $first, after: $cursor, search: $search, filters: $filters, nlp: $nlp) {
searchResource(
keyword: $keyword
type: $type
first: $first
after: $after
resourceTypes: $resourceTypes
categories: $categories
equipments: $equipments
downloadable: $downloadable
sort: $sort
excludeNsfw: $excludeNsfw
) {
edges {
cursor
node {
id
canonicalPath
name
description
width
height
photographer: uploader {
displayName
}
images(sizes: [35, 33]) {
size
url
jpegUrl
webpUrl
__typename
... on Photo {
id
title
description
licensing {
status
__typename
}
urls {
size_600
size_1024
size_2048
size_4k
__typename
}
uploader {
displayName
__typename
}
isNsfw
width
height
dominantColorLight
dominantColorDark
uploadedAt
__typename
}
}
cursor
__typename
}
__typename
}
}
"""
def setup(_) -> bool:
global SXNG_query # pylint: disable=global-statement
rand_str: str = "".join(random.choices(string.ascii_letters, k=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
@@ -95,14 +117,14 @@ def request(query: str, params: "OnlineParams") -> None:
params["url"] = f"{api_url}/graphql"
params["method"] = "POST"
params["json"] = {
"operationName": "PhotoSearchPaginationContainerQuery",
"operationName": "searchResource",
"variables": {
"after": cursor,
"first": results_per_page,
"cursor": cursor,
"search": query,
"keyword": query,
"resourceTypes": ["PHOTO"],
"sort": "RELEVANCE",
"filters": [],
"nlp": False,
"type": "TEXT",
},
"query": SXNG_query,
}
@@ -110,27 +132,20 @@ def request(query: str, params: "OnlineParams") -> None:
def response(resp: "SXNG_Response"):
res = EngineResults()
json_data = resp.json()["data"]["photoSearch"]
json_data = resp.json()["data"]["searchResource"]
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"]
image_urls = [url for (resolution, url) in node["urls"].items() if resolution.startswith("size_")]
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']}",
}
res.types.Image(
url=f"{base_url}/photo/{node['id']}",
thumbnail_src=image_urls[0],
img_src=image_urls[-1],
title=node["title"],
content=node["description"] or "",
author=node["uploader"]["displayName"],
resolution=f"{node['width']}x{node['height']}",
)
)
+1
View File
@@ -26,6 +26,7 @@ categories: list[str]
disabled: bool
display_error_messages: bool
enable_http: bool
enable_http3: bool
engine_type: str
inactive: bool
max_page: int
+16 -13
View File
@@ -115,26 +115,26 @@ def load_engine(engine_data: dict[str, t.Any]) -> "Engine | types.ModuleType | N
logger.error('An engine does not have a "name" field')
return None
if '_' in engine_name:
logger.error('Engine name contains underscore: "{}"'.format(engine_name))
logger.error('Engine name contains underscore: "%s"', engine_name)
return None
if engine_name.lower() != engine_name:
logger.warning('Engine name is not lowercase: "{}", converting to lowercase'.format(engine_name))
logger.warning('Engine name is not lowercase: "%s", converting to lowercase', engine_name)
engine_name = engine_name.lower()
engine_data['name'] = engine_name
# load_module
module_name = engine_data.get('engine')
if module_name is None:
logger.error('The "engine" field is missing for the engine named "{}"'.format(engine_name))
logger.error('The "engine" field is missing for the engine named "%s"', engine_name)
return None
try:
engine = load_module(module_name + '.py', ENGINE_DIR)
except (SyntaxError, KeyboardInterrupt, SystemExit, SystemError, ImportError, RuntimeError):
logger.exception('Fatal exception in engine "{}"'.format(module_name))
logger.exception('Fatal exception in engine "%s"', module_name)
sys.exit(1)
except BaseException:
logger.exception('Cannot load engine "{}"'.format(module_name))
logger.exception('Cannot load engine "%s"', module_name)
return None
check_engine_module(engine)
@@ -187,8 +187,9 @@ def set_loggers(engine: "Engine|types.ModuleType", engine_name: str):
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 / update engine attributes from engine_data
kvargs: dict[str, t.Any]
engine.about = getattr(engine, "about", EngineAbout())
if isinstance(engine.about, EngineAbout):
kvargs = {**msgspec.to_builtins(engine.about), **engine_data.get("about", {})}
else:
@@ -246,7 +247,7 @@ def is_missing_required_attributes(engine: "Engine | types.ModuleType"):
missing = False
for engine_attr in dir(engine):
if not engine_attr.startswith('_') and getattr(engine, engine_attr) is None:
logger.error('Missing engine config attribute: "{0}.{1}"'.format(engine.name, engine_attr))
logger.error('Missing engine config attribute: "%s.%s"', engine.name, engine_attr)
missing = True
return missing
@@ -276,12 +277,12 @@ def call_engine_setup(engine: "Engine | types.ModuleType", engine_data: dict[str
if setup_func is None:
setup_ok = True
elif not callable(setup_func):
logger.error(f"engine's setup method isn't a callable (is of type: {type(setup_func)})")
logger.error("engine's setup method isn't a callable (is of type: %s)", type(setup_func))
else:
try:
setup_ok = engine.setup(engine_data)
except Exception as e: # pylint: disable=broad-except
logger.exception(f"(PID {os.getpid()}) {engine.name}: engine SETUP failed, exception: {e}")
logger.exception("(PID %s) %s: engine SETUP failed, exception: %s", os.getpid(), engine.name, e)
setup_ok = False
# The evaluation of the return value is analogous to Engine.init
@@ -289,18 +290,18 @@ def call_engine_setup(engine: "Engine | types.ModuleType", engine_data: dict[str
setup_ok = True
if not setup_ok:
logger.error(f"(PID {os.getpid()}) {engine.name}: engine setup was not successful")
logger.error("(PID %s) %s: engine setup was not successful", os.getpid(), engine.name)
return setup_ok
def register_engine(engine: "Engine | types.ModuleType"):
if engine.name in engines:
logger.error('Engine config error: ambiguous name: {0}'.format(engine.name))
logger.error('Engine config error: ambiguous name: %s', engine.name)
sys.exit(1)
engines[engine.name] = engine
if engine.shortcut in engine_shortcuts:
logger.error('Engine config error: ambiguous shortcut: {0}'.format(engine.shortcut))
logger.error('Engine config error: ambiguous shortcut: %s', engine.shortcut)
sys.exit(1)
engine_shortcuts[engine.shortcut] = engine.name
@@ -326,7 +327,9 @@ def load_engines(engine_list: list[dict[str, t.Any]]):
# if an engine can't be loaded (if for example the engine is missing
# tor or some other requirements) its set to inactive!
logger.error(
f"(PID {os.getpid()}) {engine_data.get('name', '???')}: can't register engine (loading engine failed)"
"(PID %s) %s: can't register engine (loading engine failed)",
os.getpid(),
engine_data.get("name", "???"),
)
engine_data["inactive"] = True
return engines
-221
View File
@@ -1,221 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""`Adobe Stock`_ is a service that gives access to millions of royalty-free
assets. Assets types include photos, vectors, illustrations, templates, 3D
assets, videos, motion graphics templates and audio tracks.
.. Adobe Stock: https://stock.adobe.com/
Configuration
=============
The engine has the following mandatory setting:
- SearXNG's :ref:`engine categories`
- Adobe-Stock's :py:obj:`adobe_order`
- Adobe-Stock's :py:obj:`adobe_content_types`
.. code:: yaml
- name: adobe stock
engine: adobe_stock
shortcut: asi
categories: [images]
adobe_order: relevance
adobe_content_types: ["photo", "illustration", "zip_vector", "template", "3d", "image"]
- name: adobe stock video
engine: adobe_stock
network: adobe stock
shortcut: asi
categories: [videos]
adobe_order: relevance
adobe_content_types: ["video"]
Implementation
==============
"""
from datetime import datetime, timedelta
from urllib.parse import urlencode
import isodate
about = {
"website": "https://stock.adobe.com/",
"wikidata_id": "Q5977430",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
categories = []
paging = True
results_per_page = 10
base_url = "https://stock.adobe.com"
adobe_order: str = ""
"""Sort order, can be one of:
- ``relevance`` or
- ``featured`` or
- ``creation`` (most recent) or
- ``nb_downloads`` (number of downloads)
"""
ADOBE_VALID_TYPES = ["photo", "illustration", "zip_vector", "video", "template", "3d", "audio", "image"]
adobe_content_types: list = []
"""A list of of content types. The following content types are offered:
- Images: ``image``
- Videos: ``video``
- Templates: ``template``
- 3D: ``3d``
- Audio ``audio``
Additional subcategories:
- Photos: ``photo``
- Illustrations: ``illustration``
- Vectors: ``zip_vector`` (Vectors),
"""
# Do we need support for "free_collection" and "include_stock_enterprise"?
def init(_):
if not categories:
raise ValueError("adobe_stock engine: categories is unset")
# adobe_order
if not adobe_order:
raise ValueError("adobe_stock engine: adobe_order is unset")
if adobe_order not in ["relevance", "featured", "creation", "nb_downloads"]:
raise ValueError(f"unsupported adobe_order: {adobe_order}")
# adobe_content_types
if not adobe_content_types:
raise ValueError("adobe_stock engine: adobe_content_types is unset")
if isinstance(adobe_content_types, list):
for t in adobe_content_types:
if t not in ADOBE_VALID_TYPES:
raise ValueError("adobe_stock engine: adobe_content_types: '%s' is invalid" % t)
else:
raise ValueError(
"adobe_stock engine: adobe_content_types must be a list of strings not %s" % type(adobe_content_types)
)
def request(query, params):
args = {
"k": query,
"limit": results_per_page,
"order": adobe_order,
"search_page": params["pageno"],
"search_type": "pagination",
}
for content_type in ADOBE_VALID_TYPES:
args[f"filters[content_type:{content_type}]"] = 1 if content_type in adobe_content_types else 0
params["url"] = f"{base_url}/de/Ajax/Search?{urlencode(args)}"
# headers required to bypass bot-detection
if params["searxng_locale"] == "all":
params["headers"]["Accept-Language"] = "en-US,en;q=0.5"
return params
def parse_image_item(item):
return {
"template": "images.html",
"url": item["content_url"],
"title": item["title"],
"content": item["asset_type"],
"img_src": item["content_thumb_extra_large_url"],
"thumbnail_src": item["thumbnail_url"],
"resolution": f"{item['content_original_width']}x{item['content_original_height']}",
"img_format": item["format"],
"author": item["author"],
}
def parse_video_item(item):
# in video items, the title is more or less a "content description", we try
# to reduce the length of the title ..
title = item["title"]
content = ""
if "." in title.strip()[:-1]:
content = title
title = title.split(".", 1)[0]
elif "," in title:
content = title
title = title.split(",", 1)[0]
elif len(title) > 50:
content = title
title = ""
for w in content.split(" "):
title += f" {w}"
if len(title) > 50:
title = title.strip() + "\u2026"
break
return {
"template": "videos.html",
"url": item["content_url"],
"title": title,
"content": content,
# https://en.wikipedia.org/wiki/ISO_8601#Durations
"length": isodate.parse_duration(item["time_duration"]),
"publishedDate": datetime.fromisoformat(item["creation_date"]),
"thumbnail": item["thumbnail_url"],
"iframe_src": item["video_small_preview_url"],
"metadata": item["asset_type"],
}
def parse_audio_item(item):
audio_data = item["audio_data"]
content = audio_data.get("description") or ""
if audio_data.get("album"):
content = audio_data["album"] + " - " + content
return {
"url": item["content_url"],
"title": item["title"],
"content": content,
# "thumbnail": base_url + item["thumbnail_url"],
"iframe_src": audio_data["preview"]["url"],
"publishedDate": datetime.fromisoformat(audio_data["release_date"]) if audio_data["release_date"] else None,
"length": timedelta(seconds=round(audio_data["duration"] / 1000)) if audio_data["duration"] else None,
"author": item.get("artist_name"),
}
def response(resp):
results = []
json_resp = resp.json()
if isinstance(json_resp["items"], list):
return None
for item in json_resp["items"].values():
if item["asset_type"].lower() in ["image", "premium-image", "illustration", "vector"]:
result = parse_image_item(item)
elif item["asset_type"].lower() == "video":
result = parse_video_item(item)
elif item["asset_type"].lower() == "audio":
result = parse_audio_item(item)
else:
logger.error("no handle for %s --> %s", item["asset_type"], item)
continue
results.append(result)
return results
+1 -2
View File
@@ -12,7 +12,6 @@ servers and for Docker images.
import re
from urllib.parse import urlencode
from lxml import html
from dateutil import parser
from searx.utils import eval_xpath, eval_xpath_list, extract_text
@@ -57,7 +56,7 @@ def request(query, params):
def response(resp):
results = []
doc = html.fromstring(resp.text)
doc = resp.html()
for result in eval_xpath_list(doc, "//table/tbody/tr"):
if len(result.xpath("./td")) < 9:
+2 -3
View File
@@ -39,7 +39,6 @@ import random
import typing as t
from urllib.parse import urlencode
from lxml import html
from lxml.etree import ElementBase
from searx.data import ENGINE_TRAITS
@@ -143,7 +142,7 @@ def request(query: str, params: "OnlineParams") -> None:
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
dom = html.fromstring(resp.text)
dom = resp.html()
# Each result is a div with class "flex" inside "js-aarecord-list-outer"
# container. The "flex" filter excludes non-result div such as section
@@ -255,7 +254,7 @@ def fetch_traits(engine_traits: EngineTraits) -> None:
if not resp.ok:
raise RuntimeError("Response from Anna's Archive is not OK.")
dom = html.fromstring(resp.text)
dom = resp.html()
# supported language codes
lang_map: dict[str, str] = {}
+1 -2
View File
@@ -14,7 +14,6 @@ list in ``settings.yml``:
"""
from urllib.parse import urlencode
from lxml import html
from searx.result_types import EngineResults, MainResult
from searx.utils import eval_xpath, eval_xpath_list, extract_text
@@ -62,7 +61,7 @@ def request(query, params):
def response(resp) -> EngineResults:
res = EngineResults()
doc = html.fromstring(resp.text)
doc = resp.html()
for result in eval_xpath_list(doc, "//div[@class='article']"):
+2 -3
View File
@@ -4,7 +4,6 @@
# pylint: disable=invalid-name
from urllib.parse import urlencode
from lxml import html
from searx.utils import (
eval_xpath_list,
@@ -22,7 +21,7 @@ about = {
}
# engine dependent config
categories = ['files', 'apps']
categories = ['it', 'apps']
paging = True
time_range_support = False
@@ -43,7 +42,7 @@ def request(query, params):
def response(resp):
results = []
dom = html.fromstring(resp.text)
dom = resp.html()
# parse results
for result in eval_xpath_list(dom, "//div[@id='content']//div[@class='listWidget']/div/div[@class='appRow']"):
+1 -1
View File
@@ -17,7 +17,7 @@ about = {
"results": 'JSON',
}
categories = ['files', 'apps']
categories = ['it', 'apps']
safesearch = True
search_url = 'https://itunes.apple.com/search?{query}'
+1 -2
View File
@@ -37,8 +37,7 @@ def obtain_token():
)
token['value'] = loads(actual_token.text)['authInfo']['access_token']
token['last_updated'] = update_time
# pylint: disable=bare-except
except:
except: # pylint: disable=bare-except
pass
return token

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