1 Commits

Author SHA1 Message Date
searxng-bot
872e838fa4 [data] update searx.data - update_engine_descriptions.py 2026-06-29 01:01:55 +00:00
67 changed files with 6654 additions and 4639 deletions

View File

@@ -84,7 +84,7 @@ jobs:
fetch-depth: "0"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |
@@ -96,7 +96,7 @@ jobs:
run: echo "date=$(date +'%Y%m%d')" >>$GITHUB_OUTPUT
- name: Setup cache container
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "container-${{ matrix.arch }}-${{ steps.date.outputs.date }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |
@@ -106,10 +106,10 @@ jobs:
- if: ${{ matrix.emulation }}
name: Setup QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Login to GHCR
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
@@ -147,10 +147,10 @@ jobs:
- if: ${{ matrix.emulation }}
name: Setup QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Login to GHCR
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
@@ -180,14 +180,14 @@ jobs:
persist-credentials: "false"
- name: Login to GHCR
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- name: Login to Docker Hub
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "docker.io"
username: "${{ secrets.DOCKER_USER }}"

View File

@@ -33,6 +33,7 @@ jobs:
- update_engine_traits.py
- update_wikidata_units.py
- update_engine_descriptions.py
- update_gsa_useragents.py
permissions:
contents: write
@@ -50,7 +51,7 @@ jobs:
persist-credentials: "false"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |

View File

@@ -43,7 +43,7 @@ jobs:
fetch-depth: "0"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |

View File

@@ -44,7 +44,7 @@ jobs:
persist-credentials: "false"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ matrix.python-version }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |
@@ -77,13 +77,13 @@ jobs:
node-version-file: "./.nvmrc"
- name: Setup cache Node.js
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "nodejs-${{ runner.arch }}-${{ hashFiles('./.nvmrc', './package.json') }}"
path: "./client/simple/node_modules/"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |

View File

@@ -46,7 +46,7 @@ jobs:
fetch-depth: "0"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |
@@ -94,7 +94,7 @@ jobs:
fetch-depth: "0"
- name: Setup cache Python
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
restore-keys: |

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: "false"
- name: Sync GHCS from Docker Scout
uses: docker/scout-action@ce97ec1bb85613e8eb35d086fad0c77a6cedf983 # v1.23.0
uses: docker/scout-action@7520205ff60037fdc436b40b6a1d1e55a839ec2d # v1.22.0
with:
organization: "searxng"
dockerhub-user: "${{ secrets.DOCKER_USER }}"
@@ -41,6 +41,6 @@ jobs:
write-comment: "false"
- name: Upload SARIFs
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: "./scout.sarif"

View File

@@ -63,7 +63,7 @@ format: format.python format.shell
# wrap ./manage script
MANAGE += weblate.translations.commit weblate.push.translations
MANAGE += data.all data.traits data.useragents data.locales data.currencies
MANAGE += data.all data.traits data.useragents data.gsa_useragents data.locales data.currencies
MANAGE += docs.html docs.live docs.gh-pages docs.prebuild docs.clean
MANAGE += podman.build
MANAGE += docker.build docker.buildx

File diff suppressed because it is too large Load Diff

View File

@@ -29,21 +29,21 @@
"swiped-events": "1.2.0"
},
"devDependencies": {
"@biomejs/biome": "2.5.2",
"@types/node": "^26.1.0",
"browserslist": "^4.28.4",
"@biomejs/biome": "2.5.0",
"@types/node": "^26.0.0",
"browserslist": "^4.28.2",
"browserslist-to-esbuild": "^2.1.1",
"edge.js": "^6.5.1",
"less": "^4.6.7",
"less": "^4.6.6",
"mathjs": "^15.2.0",
"sharp": "~0.35.3",
"sharp": "~0.35.1",
"sort-package-json": "^4.0.0",
"stylelint": "^17.14.0",
"stylelint": "^17.13.0",
"stylelint-config-standard-less": "^4.1.0",
"stylelint-prettier": "^5.0.3",
"svgo": "^4.0.1",
"typescript": "~6.0.3",
"vite": "^8.1.3",
"vite": "^8.0.16",
"vite-bundle-analyzer": "^1.3.8"
}
}

View File

@@ -1,4 +1,4 @@
FROM docker.io/searxng/base:searxng-builder AS builder
FROM ghcr.io/searxng/base:searxng-builder AS builder
COPY ./requirements.txt ./requirements-server.txt ./

View File

@@ -2,7 +2,7 @@ ARG CONTAINER_IMAGE_ORGANIZATION="searxng"
ARG CONTAINER_IMAGE_NAME="searxng"
FROM localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:builder AS builder
FROM docker.io/searxng/base:searxng AS dist
FROM ghcr.io/searxng/base:searxng AS dist
COPY --chown=977:977 --from=builder /usr/local/searxng/.venv/ ./.venv/
COPY --chown=977:977 --from=builder /usr/local/searxng/searx/ ./searx/

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 CREATED SIZE
localhost/searxng/searxng 2025.8.1-3d96414 ... About a minute ago 183 MB
localhost/searxng/searxng latest ... About a minute ago 183 MB
localhost/searxng/searxng builder ... About a minute ago 524 MB
ghcr.io/searxng/base searxng-builder ... 2 days ago 378 MB
ghcr.io/searxng/base searxng ... 2 days ago 42.2 MB
Migrate from ``searxng-docker``
===============================

View File

@@ -4,32 +4,31 @@
Search API
==========
SearXNG supports querying via a simple HTTP API. Two endpoints, ``/`` and
``/search``, are supported for both GET and POST methods. The ``GET`` method
expects parameters as URL query parameters, while the POST method expects
parameters as form data (``application/x-www-form-urlencoded``).
SearXNG supports querying via a simple HTTP API.
Two endpoints, ``/`` and ``/search``, are supported for both GET and POST methods.
The GET method expects parameters as URL query parameters, while the POST method expects parameters as form data.
If you want to consume the results as JSON, CSV, or RSS, you need to set the
``format`` parameter accordingly. Supported formats are defined in
``settings.yml``, under the :ref:`settings search` section. Requesting an
unset format will return a 403 Forbidden error. Be aware that many public
instances have these formats disabled.
``format`` parameter accordingly. Supported formats are defined in ``settings.yml``, under the ``search`` section.
Requesting an unset format will return a 403 Forbidden error. Be aware that many public instances have these formats disabled.
Endpoints:
.. code::
``GET /``
``GET /search``
GET /
GET /search
POST /
POST /search
``POST /``
``POST /search``
example cURL calls:
.. code:: bash
.. code-block:: bash
curl 'https://searx.example.org/search?q=searxng&format=json'
curl -X POST 'https://searx.example.org/search' -d 'q=searxng&format=csv'
curl -L -X POST -d 'q=searxng&format=json' 'https://searx.example.org/'
Parameters
@@ -54,27 +53,90 @@ Parameters
Comma separated list, specifies the active search categories (see
:ref:`configured engines`)
``engines`` : optional
Comma separated list, specifies the active search engines (see
:ref:`configured engines`).
``language`` : default from :ref:`settings search`
Code of the language.
``pageno`` : default ``1``
Search page number.
``time_range`` : optional : [ ``day``, ``month``, ``year`` ]
``time_range`` : optional
[ ``day``, ``month``, ``year`` ]
Time range of search for engines which support it. See if an engine supports
time range search in the preferences page of an instance.
``format`` : optional : [ ``json``, ``csv``, ``rss`` ]
``format`` : optional
[ ``json``, ``csv``, ``rss`` ]
Output format of results. Format needs to be activated in :ref:`settings
search`.
``safesearch`` : default from :ref:`settings search` : [ ``0``, ``1``, ``2`` ]
``results_on_new_tab`` : default ``0``
[ ``0``, ``1`` ]
Open search results on new tab.
``image_proxy`` : default from :ref:`settings server`
[ ``True``, ``False`` ]
Proxy image results through SearXNG.
``autocomplete`` : default from :ref:`settings search`
[ ``google``, ``dbpedia``, ``duckduckgo``, ``mwmbl``, ``startpage``,
``privacywall``, ``wikipedia``, ``swisscows``, ``qwant`` ]
Service which completes words as you type.
``safesearch`` : default from :ref:`settings search`
[ ``0``, ``1``, ``2`` ]
Filter search results of engines which support safe search. See if an engine
supports safe search in the preferences page of an instance.
``theme`` : default ``simple`` : [ ``simple`` ]
``theme`` : default ``simple``
[ ``simple`` ]
Theme of instance.
Please note, available themes depend on an instance. It is possible that an
instance administrator deleted, created or renamed themes on their instance.
See the available options in the preferences page of the instance.
``enabled_plugins`` : optional
List of enabled plugins.
:default:
``Hash_plugin``, ``Self_Information``,
``Tracker_URL_remover``, ``Ahmia_blacklist``
:values:
.. enabled by default
``Hash_plugin``, ``Self_Information``,
``Tracker_URL_remover``, ``Ahmia_blacklist``,
.. disabled by default
``Hostnames_plugin``, ``Open_Access_DOI_rewrite``,
``Vim-like_hotkeys``, ``Tor_check_plugin``
``disabled_plugins``: optional
List of disabled plugins.
:default:
``Hostnames_plugin``, ``Open_Access_DOI_rewrite``,
``Vim-like_hotkeys``, ``Tor_check_plugin``
:values:
see values from ``enabled_plugins``
``enabled_engines`` : optional : *all* :origin:`engines <searx/engines>`
List of enabled engines.
``disabled_engines`` : optional : *all* :origin:`engines <searx/engines>`
List of disabled engines.

View File

@@ -18,11 +18,11 @@ myst-parser==5.0.0
linuxdoc==20260504
aiounittest==1.5.0
yamllint==1.38.0
wlc==2.1.0
wlc==2.0.0
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.7.8
basedpyright==1.39.9
granian[reload]==2.7.6
basedpyright==1.39.8
types-lxml==2026.2.16

View File

@@ -1,2 +1,2 @@
granian==2.7.8
granian[pname]==2.7.8
granian==2.7.6
granian[pname]==2.7.6

View File

@@ -13,7 +13,7 @@ sniffio==1.3.1
valkey==6.1.1
markdown-it-py==4.2.0
msgspec==0.21.1
typer==0.26.8
typer==0.26.7
isodate==0.7.2
whitenoise==6.12.0
typing-extensions==4.16.0
typing-extensions==4.15.0

View File

@@ -6,7 +6,7 @@ make data.all
"""
# pylint: disable=invalid-name
__all__ = ["ahmia_blacklist_loader", "data_dir", "get_cache"]
__all__ = ["ahmia_blacklist_loader", "gsa_useragents_loader", "data_dir", "get_cache"]
import json
import typing as t
@@ -63,6 +63,7 @@ lazy_globals = {
"ENGINE_TRAITS": None,
"LOCALES": None,
"TRACKER_PATTERNS": TrackerPatternsDB(),
"GSA_USER_AGENTS": None,
}
data_json_files = {
@@ -105,3 +106,24 @@ def ahmia_blacklist_loader() -> list[str]:
"""
with open(data_dir / 'ahmia_blacklist.txt', encoding='utf-8') as f:
return f.read().split()
def gsa_useragents_loader() -> list[str]:
"""Load data from `gsa_useragents.txt` and return a list of user agents
suitable for Google. The user agents are fetched by::
searxng_extra/update/update_gsa_useragents.py
This function is used by :py:mod:`searx.engines.google`.
"""
data = lazy_globals["GSA_USER_AGENTS"]
if data is not None:
return data
log.debug("init searx.data.%s", "GSA_USER_AGENTS")
with open(data_dir / 'gsa_useragents.txt', encoding='utf-8') as f:
lazy_globals["GSA_USER_AGENTS"] = f.read().splitlines()
return lazy_globals["GSA_USER_AGENTS"]

File diff suppressed because it is too large Load Diff

View File

@@ -773,7 +773,7 @@
"nl": "Boliviaanse boliviano",
"oc": "Boliviano",
"pa": "ਬੋਲੀਵੀਆਨੋ",
"pl": "boliwiano",
"pl": "boliviano",
"pt": "Boliviano",
"ro": "boliviano",
"ru": "боливиано",
@@ -1107,7 +1107,7 @@
"fi": "Kongon frangi",
"fr": "franc congolais",
"ga": "franc an Chongó",
"gl": "franco congolés",
"gl": "Franco congolés",
"he": "פרנק קונגולזי",
"hr": "Kongoanski franak",
"hu": "kongói frank",
@@ -1636,7 +1636,7 @@
"fi": "Algerian dinaari",
"fr": "dinar algérien",
"ga": "dinar na hAilgéire",
"gl": "dinar alxeriano",
"gl": "Dinar alxeriano",
"he": "דינר אלג'ירי",
"hr": "Alžirski dinar",
"hu": "algériai dinár",
@@ -1651,7 +1651,7 @@
"pap": "dinar argelino",
"pl": "dinar algierski",
"pt": "dinar argelino",
"ro": "dinar algerian",
"ro": "Dinar algerian",
"ru": "алжирский динар",
"sk": "Alžírský dinár",
"sl": "alžirski dinar",
@@ -1786,7 +1786,7 @@
"cy": "Ewro",
"da": "Euro",
"de": "Euro",
"en": "euro",
"en": "Euro",
"eo": "eŭro",
"es": "Euro",
"et": "Euro",
@@ -1803,7 +1803,7 @@
"it": "Euro",
"ja": "ユーロ",
"ko": "유로",
"lt": "euras",
"lt": "Euras",
"lv": "eiro",
"ml": "യൂറോ",
"ms": "Euro",
@@ -2759,7 +2759,7 @@
"pa": "ਜਪਾਨੀ ਯੈੱਨ",
"pl": "jen",
"pt": "iene",
"ro": "yen",
"ro": "yeni",
"ru": "японская иена",
"sk": "jen",
"sl": "japonski jen",
@@ -3337,7 +3337,6 @@
"fi": "Libyan dinaari",
"fr": "dinar libyen",
"ga": "dinar na Libia",
"gl": "dinar libio",
"he": "דינר לובי ",
"hr": "Libijski dinar",
"hu": "líbiai dinár",
@@ -3540,7 +3539,6 @@
"ja": "チャット",
"ko": "미얀마 짯",
"lt": "Kijatas",
"lv": "Kjats",
"ml": "ബർമ്മീസ് ക്യാറ്റ്",
"nl": "Myanmarese kyat",
"oc": "Kyat",
@@ -4312,7 +4310,7 @@
"ar": "بيسو فلبيني",
"bg": "Филипинско песо",
"ca": "peso filipí",
"cs": "filipínské peso",
"cs": "Filipínské peso",
"de": "philippinischer Peso",
"en": "Philippine peso",
"eo": "filipina peso",
@@ -4616,7 +4614,7 @@
"fi": "Serbian dinaari",
"fr": "dinar serbe",
"ga": "Dinar na Seirbia",
"gl": "dinar serbio",
"gl": "Dinar serbio",
"he": "דינר סרבי",
"hr": "srpski dinar",
"hu": "szerb dinár",
@@ -5333,7 +5331,7 @@
"pa": "ਤਾਜਿਕਿਸਤਾਨੀ ਸੋਮੋਨੀ",
"pl": "Somoni",
"pt": "Somoni",
"ro": "somoni tadjic",
"ro": "Somoni tadjic",
"ru": "таджикский сомони",
"sk": "tadžický som",
"sl": "tadžikistanski somoni",
@@ -5396,7 +5394,6 @@
"fi": "Tunisian dinaari",
"fr": "dinar tunisien",
"ga": "dinar na Túinéise",
"gl": "dinar tunisiano",
"he": "דינר תוניסאי",
"hr": "tuniski dinar",
"hu": "tunéziai dinár",
@@ -5769,7 +5766,7 @@
"fi": "Uruguayn peso",
"fr": "peso uruguayen",
"ga": "peso Uragua",
"gl": "peso uruguaio",
"gl": "Peso uruguaio",
"he": "פסו של אורוגוואי",
"hr": "Urugvajski pezo",
"hu": "uruguayi peso",
@@ -6150,7 +6147,7 @@
"oc": "Drechs de tiratge Especials",
"pl": "specjalne prawa ciągnienia",
"pt": "direitos especiais de saque",
"ro": "drepturi speciale de tragere",
"ro": "Drepturi speciale de tragere",
"ru": "специальные права заимствования",
"sk": "Zvláštne práva čerpania",
"sl": "posebne pravice črpanja",
@@ -6228,7 +6225,6 @@
"ja": "CFPフラン",
"ko": "CFP 프랑",
"lt": "CFP frankas",
"lv": "Klusā okeāna franks",
"ms": "Franc CFP",
"nl": "CFP-frank",
"oc": "Franc CFP",
@@ -7059,7 +7055,6 @@
"bolivjano": "BOB",
"bolivya bolivianosu": "BOB",
"bolivya bolivyanosu": "BOB",
"boliwiano": "BOB",
"bolívar digital": "VED",
"bolívar soberano": "VES",
"bolívar sobirà": "VES",
@@ -8430,7 +8425,6 @@
"drame arménio": "AMD",
"dramm": "AMD",
"drechs de tiratge especials": "XDR",
"drept special de tragere": "XDR",
"drepturi speciale de tragere": "XDR",
"drets especials de gir": "XDR",
"droits de tirage speciaux": "XDR",
@@ -9524,8 +9518,6 @@
"kíp lào": "LAK",
"kīp": "LAK",
"kjat": "MMK",
"kjats": "MMK",
"klusā okeāna franks": "XPF",
"km": "BAM",
"kmf": "KMF",
"koeweitse dinar": "KWD",
@@ -10031,7 +10023,6 @@
"lire sterline": "GBP",
"lire turque": "TRY",
"lisente": "LSL",
"list of syrian coins": "SYP",
"liura de gibartar": "GIP",
"liura egipciana": "EGP",
"liura esterlina": "GBP",
@@ -11322,6 +11313,7 @@
"riel camboxano": "KHR",
"riel camboyano": "KHR",
"riel campuchia": "KHR",
"riel kambodżański": "KHR",
"riel kamboja": "KHR",
"riel na cambóide": "KHR",
"rietumāfrikas franks": "XOF",
@@ -12265,7 +12257,6 @@
"thaise baht": "THB",
"thajský baht": "THB",
"thb": "THB",
"the australian dollar": "AUD",
"thebe": "BWP",
"third belarusian ruble": "BYN",
"tical": "THB",
@@ -12749,7 +12740,6 @@
"yen": "JPY",
"yen giapponese": "JPY",
"yen japones": "JPY",
"yen japonez": "JPY",
"yen japonés": "JPY",
"yeni": "JPY",
"yeni i̇srail şekeli": "ILS",
@@ -12816,6 +12806,7 @@
"zimbabwe zig": "ZWG",
"zimbabwean dollar": "ZWL",
"zimbabwean gold": "ZWG",
"zimbabwean zig": "ZWG",
"zimbabwen kulta": "ZWG",
"zimbabwiansky zlatý": "ZWG",
"zimbabwský dolar": "ZWL",
@@ -12974,7 +12965,6 @@
"FKP",
"EGP"
],
"£S": "SYP",
"£e": "EGP",
"£s": "SYP",
"¥": [
@@ -14969,8 +14959,6 @@
"فورنت مجري": "HUF",
"فورينت مجري": "HUF",
"فِرَنْكٌ رُوَنْدِيٌّ": "RWF",
"قائمة النقود المعدنية السورية": "SYP",
"قائمة عملات سوريا المعدنية": "SYP",
"ك": "KWD",
"كتزال غواتيمالي": "GTQ",
"كرونة آيسلندية": "ISK",

File diff suppressed because it is too large Load Diff

View File

@@ -3476,598 +3476,6 @@
"zu-ZA": "ZA"
}
},
"google cse": {
"all_locale": "ZZ",
"custom": {
"supported_domains": {
"AD": "www.google.ad",
"AE": "www.google.ae",
"AF": "www.google.com.af",
"AG": "www.google.com.ag",
"AL": "www.google.al",
"AM": "www.google.am",
"AO": "www.google.co.ao",
"AR": "www.google.com.ar",
"AS": "www.google.as",
"AT": "www.google.at",
"AU": "www.google.com.au",
"AZ": "www.google.az",
"BA": "www.google.ba",
"BD": "www.google.com.bd",
"BE": "www.google.be",
"BF": "www.google.bf",
"BG": "www.google.bg",
"BH": "www.google.com.bh",
"BI": "www.google.bi",
"BJ": "www.google.bj",
"BN": "www.google.com.bn",
"BO": "www.google.com.bo",
"BR": "www.google.com.br",
"BS": "www.google.bs",
"BT": "www.google.bt",
"BW": "www.google.co.bw",
"BY": "www.google.by",
"BZ": "www.google.com.bz",
"CA": "www.google.ca",
"CAT": "www.google.cat",
"CD": "www.google.cd",
"CF": "www.google.cf",
"CG": "www.google.cg",
"CH": "www.google.ch",
"CI": "www.google.ci",
"CK": "www.google.co.ck",
"CL": "www.google.cl",
"CM": "www.google.cm",
"CN": "www.google.com.hk",
"CO": "www.google.com.co",
"CR": "www.google.co.cr",
"CU": "www.google.com.cu",
"CV": "www.google.cv",
"CY": "www.google.com.cy",
"CZ": "www.google.cz",
"DE": "www.google.de",
"DJ": "www.google.dj",
"DK": "www.google.dk",
"DM": "www.google.dm",
"DO": "www.google.com.do",
"DZ": "www.google.dz",
"EC": "www.google.com.ec",
"EE": "www.google.ee",
"EG": "www.google.com.eg",
"ES": "www.google.es",
"ET": "www.google.com.et",
"FI": "www.google.fi",
"FJ": "www.google.com.fj",
"FM": "www.google.fm",
"FR": "www.google.fr",
"GA": "www.google.ga",
"GE": "www.google.ge",
"GG": "www.google.gg",
"GH": "www.google.com.gh",
"GI": "www.google.com.gi",
"GL": "www.google.gl",
"GM": "www.google.gm",
"GR": "www.google.gr",
"GT": "www.google.com.gt",
"GY": "www.google.gy",
"HK": "www.google.com.hk",
"HN": "www.google.hn",
"HR": "www.google.hr",
"HT": "www.google.ht",
"HU": "www.google.hu",
"ID": "www.google.co.id",
"IE": "www.google.ie",
"IL": "www.google.co.il",
"IM": "www.google.im",
"IN": "www.google.co.in",
"IQ": "www.google.iq",
"IS": "www.google.is",
"IT": "www.google.it",
"JE": "www.google.je",
"JM": "www.google.com.jm",
"JO": "www.google.jo",
"JP": "www.google.co.jp",
"KE": "www.google.co.ke",
"KG": "www.google.kg",
"KH": "www.google.com.kh",
"KI": "www.google.ki",
"KR": "www.google.co.kr",
"KW": "www.google.com.kw",
"KZ": "www.google.kz",
"LA": "www.google.la",
"LB": "www.google.com.lb",
"LI": "www.google.li",
"LK": "www.google.lk",
"LS": "www.google.co.ls",
"LT": "www.google.lt",
"LU": "www.google.lu",
"LV": "www.google.lv",
"LY": "www.google.com.ly",
"MA": "www.google.co.ma",
"MD": "www.google.md",
"ME": "www.google.me",
"MG": "www.google.mg",
"MK": "www.google.mk",
"ML": "www.google.ml",
"MM": "www.google.com.mm",
"MN": "www.google.mn",
"MT": "www.google.com.mt",
"MU": "www.google.mu",
"MV": "www.google.mv",
"MW": "www.google.mw",
"MX": "www.google.com.mx",
"MY": "www.google.com.my",
"MZ": "www.google.co.mz",
"NA": "www.google.com.na",
"NE": "www.google.ne",
"NG": "www.google.com.ng",
"NI": "www.google.com.ni",
"NL": "www.google.nl",
"NO": "www.google.no",
"NP": "www.google.com.np",
"NR": "www.google.nr",
"NU": "www.google.nu",
"NZ": "www.google.co.nz",
"OM": "www.google.com.om",
"PA": "www.google.com.pa",
"PE": "www.google.com.pe",
"PG": "www.google.com.pg",
"PH": "www.google.com.ph",
"PK": "www.google.com.pk",
"PL": "www.google.pl",
"PN": "www.google.pn",
"PR": "www.google.com.pr",
"PS": "www.google.ps",
"PT": "www.google.pt",
"PY": "www.google.com.py",
"QA": "www.google.com.qa",
"RO": "www.google.ro",
"RS": "www.google.rs",
"RU": "www.google.ru",
"RW": "www.google.rw",
"SA": "www.google.com.sa",
"SB": "www.google.com.sb",
"SC": "www.google.sc",
"SE": "www.google.se",
"SG": "www.google.com.sg",
"SH": "www.google.sh",
"SI": "www.google.si",
"SK": "www.google.sk",
"SL": "www.google.com.sl",
"SM": "www.google.sm",
"SN": "www.google.sn",
"SO": "www.google.so",
"SR": "www.google.sr",
"ST": "www.google.st",
"SV": "www.google.com.sv",
"TD": "www.google.td",
"TG": "www.google.tg",
"TH": "www.google.co.th",
"TJ": "www.google.com.tj",
"TL": "www.google.tl",
"TM": "www.google.tm",
"TN": "www.google.tn",
"TO": "www.google.to",
"TR": "www.google.com.tr",
"TT": "www.google.tt",
"TW": "www.google.com.tw",
"TZ": "www.google.co.tz",
"UA": "www.google.com.ua",
"UG": "www.google.co.ug",
"UK": "www.google.co.uk",
"UY": "www.google.com.uy",
"UZ": "www.google.co.uz",
"VC": "www.google.com.vc",
"VE": "www.google.co.ve",
"VI": "www.google.co.vi",
"VN": "www.google.com.vn",
"VU": "www.google.vu",
"WS": "www.google.ws",
"ZA": "www.google.co.za",
"ZM": "www.google.co.zm",
"ZW": "www.google.co.zw"
}
},
"data_type": "traits_v1",
"languages": {
"af": "lang_af",
"ak": "lang_ak",
"am": "lang_am",
"ar": "lang_ar",
"az": "lang_az",
"be": "lang_be",
"bem": "lang_bem",
"bg": "lang_bg",
"bn": "lang_bn",
"br": "lang_br",
"bs": "lang_bs",
"ca": "lang_ca",
"ceb": "lang_ceb",
"chr": "lang_chr",
"ckb": "lang_ckb",
"co": "lang_co",
"cs": "lang_cs",
"cy": "lang_cy",
"da": "lang_da",
"de": "lang_de",
"ee": "lang_ee",
"el": "lang_el",
"en": "lang_en",
"eo": "lang_eo",
"es": "lang_es",
"et": "lang_et",
"eu": "lang_eu",
"fa": "lang_fa",
"fi": "lang_fi",
"fil": "lang_fil",
"fo": "lang_fo",
"fr": "lang_fr",
"fy": "lang_fy",
"ga": "lang_ga",
"gaa": "lang_gaa",
"gd": "lang_gd",
"gl": "lang_gl",
"gn": "lang_gn",
"gu": "lang_gu",
"ha": "lang_ha",
"haw": "lang_haw",
"he": "lang_iw",
"hi": "lang_hi",
"hr": "lang_hr",
"ht": "lang_ht",
"hu": "lang_hu",
"hy": "lang_hy",
"ia": "lang_ia",
"id": "lang_id",
"ig": "lang_ig",
"is": "lang_is",
"it": "lang_it",
"ja": "lang_ja",
"jv": "lang_jw",
"ka": "lang_ka",
"kk": "lang_kk",
"km": "lang_km",
"kn": "lang_kn",
"ko": "lang_ko",
"ky": "lang_ky",
"la": "lang_la",
"lg": "lang_lg",
"ln": "lang_ln",
"lo": "lang_lo",
"lt": "lang_lt",
"lv": "lang_lv",
"mfe": "lang_mfe",
"mg": "lang_mg",
"mi": "lang_mi",
"mk": "lang_mk",
"ml": "lang_ml",
"mn": "lang_mn",
"mr": "lang_mr",
"ms": "lang_ms",
"mt": "lang_mt",
"my": "lang_my",
"nb": "lang_no",
"ne": "lang_ne",
"nl": "lang_nl",
"nn": "lang_nn",
"nso": "lang_nso",
"ny": "lang_ny",
"nyn": "lang_nyn",
"oc": "lang_oc",
"om": "lang_om",
"or": "lang_or",
"pa": "lang_pa",
"pcm": "lang_pcm",
"pl": "lang_pl",
"ps": "lang_ps",
"pt": "lang_pt-BR",
"qu": "lang_qu",
"rm": "lang_rm",
"rn": "lang_rn",
"ro": "lang_ro",
"ru": "lang_ru",
"rw": "lang_rw",
"sd": "lang_sd",
"si": "lang_si",
"sk": "lang_sk",
"sl": "lang_sl",
"sn": "lang_sn",
"so": "lang_so",
"sq": "lang_sq",
"sr": "lang_sr",
"sr_Latn": "lang_sr-ME",
"st": "lang_st",
"su": "lang_su",
"sv": "lang_sv",
"sw": "lang_sw",
"ta": "lang_ta",
"te": "lang_te",
"tg": "lang_tg",
"th": "lang_th",
"ti": "lang_ti",
"tk": "lang_tk",
"tn": "lang_tn",
"to": "lang_to",
"tr": "lang_tr",
"tt": "lang_tt",
"ug": "lang_ug",
"uk": "lang_uk",
"ur": "lang_ur",
"uz": "lang_uz",
"vi": "lang_vi",
"wo": "lang_wo",
"xh": "lang_xh",
"yi": "lang_yi",
"yo": "lang_yo",
"zh": "lang_zh-CN",
"zh_Hans": "lang_zh-CN",
"zh_Hant": "lang_zh-TW",
"zu": "lang_zu"
},
"regions": {
"af-ZA": "ZA",
"ak-GH": "GH",
"ar-AE": "AE",
"ar-BH": "BH",
"ar-DJ": "DJ",
"ar-DZ": "DZ",
"ar-EG": "EG",
"ar-IL": "IL",
"ar-IQ": "IQ",
"ar-JO": "JO",
"ar-KW": "KW",
"ar-LB": "LB",
"ar-LY": "LY",
"ar-MA": "MA",
"ar-OM": "OM",
"ar-PS": "PS",
"ar-QA": "QA",
"ar-SA": "SA",
"ar-SO": "SO",
"ar-TD": "TD",
"ar-TN": "TN",
"be-BY": "BY",
"bg-BG": "BG",
"bn-IN": "IN",
"bs-BA": "BA",
"ca-AD": "AD",
"ca-ES": "ES",
"ceb-PH": "PH",
"ckb-IQ": "IQ",
"cs-CZ": "CZ",
"cy-GB": "GB",
"da-DK": "DK",
"de-AT": "AT",
"de-BE": "BE",
"de-CH": "CH",
"de-DE": "DE",
"de-LI": "LI",
"de-LU": "LU",
"ee-GH": "GH",
"el-CY": "CY",
"el-GR": "GR",
"en-AG": "AG",
"en-AS": "AS",
"en-AU": "AU",
"en-BI": "BI",
"en-BS": "BS",
"en-BW": "BW",
"en-BZ": "BZ",
"en-CA": "CA",
"en-CK": "CK",
"en-CM": "CM",
"en-DM": "DM",
"en-FJ": "FJ",
"en-FM": "FM",
"en-GB": "GB",
"en-GG": "GG",
"en-GH": "GH",
"en-GI": "GI",
"en-GM": "GM",
"en-GY": "GY",
"en-HK": "HK",
"en-IE": "IE",
"en-IM": "IM",
"en-IN": "IN",
"en-JE": "JE",
"en-JM": "JM",
"en-KE": "KE",
"en-KI": "KI",
"en-LS": "LS",
"en-MG": "MG",
"en-MT": "MT",
"en-MU": "MU",
"en-MW": "MW",
"en-NA": "NA",
"en-NG": "NG",
"en-NR": "NR",
"en-NU": "NU",
"en-NZ": "NZ",
"en-PG": "PG",
"en-PH": "PH",
"en-PK": "PK",
"en-PN": "PN",
"en-PR": "PR",
"en-RW": "RW",
"en-SB": "SB",
"en-SC": "SC",
"en-SG": "SG",
"en-SH": "SH",
"en-SL": "SL",
"en-TO": "TO",
"en-TT": "TT",
"en-TZ": "TZ",
"en-UG": "UG",
"en-US": "US",
"en-VC": "VC",
"en-VI": "VI",
"en-VU": "VU",
"en-WS": "WS",
"en-ZA": "ZA",
"en-ZM": "ZM",
"en-ZW": "ZW",
"es-AR": "AR",
"es-BO": "BO",
"es-CL": "CL",
"es-CO": "CO",
"es-CR": "CR",
"es-CU": "CU",
"es-DO": "DO",
"es-EC": "EC",
"es-ES": "ES",
"es-GT": "GT",
"es-HN": "HN",
"es-MX": "MX",
"es-NI": "NI",
"es-PA": "PA",
"es-PE": "PE",
"es-PR": "PR",
"es-PY": "PY",
"es-SV": "SV",
"es-US": "US",
"es-UY": "UY",
"es-VE": "VE",
"et-EE": "EE",
"eu-ES": "ES",
"fa-AF": "AF",
"fi-FI": "FI",
"fil-PH": "PH",
"fr-BE": "BE",
"fr-BF": "BF",
"fr-BI": "BI",
"fr-BJ": "BJ",
"fr-CA": "CA",
"fr-CD": "CD",
"fr-CF": "CF",
"fr-CG": "CG",
"fr-CH": "CH",
"fr-CI": "CI",
"fr-CM": "CM",
"fr-DJ": "DJ",
"fr-DZ": "DZ",
"fr-FR": "FR",
"fr-GA": "GA",
"fr-HT": "HT",
"fr-LU": "LU",
"fr-MA": "MA",
"fr-MG": "MG",
"fr-ML": "ML",
"fr-MU": "MU",
"fr-NE": "NE",
"fr-RW": "RW",
"fr-SC": "SC",
"fr-SN": "SN",
"fr-TD": "TD",
"fr-TG": "TG",
"fr-TN": "TN",
"fr-VU": "VU",
"fy-NL": "NL",
"ga-GB": "GB",
"ga-IE": "IE",
"gaa-GH": "GH",
"gd-GB": "GB",
"gl-ES": "ES",
"gn-PY": "PY",
"gu-IN": "IN",
"haw-US": "US",
"he-IL": "IL",
"hi-IN": "IN",
"hr-BA": "BA",
"hr-HR": "HR",
"ht-HT": "HT",
"hu-HU": "HU",
"hy-AM": "AM",
"id-ID": "ID",
"is-IS": "IS",
"it-CH": "CH",
"it-IT": "IT",
"it-SM": "SM",
"ja-JP": "JP",
"kk-KZ": "KZ",
"kn-IN": "IN",
"ko-KR": "KR",
"ky-KG": "KG",
"ln-CD": "CD",
"lt-LT": "LT",
"lv-LV": "LV",
"mg-MG": "MG",
"mi-NZ": "NZ",
"ml-IN": "IN",
"mr-IN": "IN",
"ms-SG": "SG",
"mt-MT": "MT",
"nb-NO": "NO",
"ne-IN": "IN",
"nl-BE": "BE",
"nl-NL": "NL",
"nl-SR": "SR",
"nn-NO": "NO",
"nso-ZA": "ZA",
"ny-MW": "MW",
"oc-ES": "ES",
"or-IN": "IN",
"pa-IN": "IN",
"pl-PL": "PL",
"ps-AF": "AF",
"pt-AO": "AO",
"pt-BR": "BR",
"pt-CV": "CV",
"pt-MZ": "MZ",
"pt-PT": "PT",
"pt-ST": "ST",
"pt-TL": "TL",
"qu-BO": "BO",
"qu-EC": "EC",
"qu-PE": "PE",
"rm-CH": "CH",
"rn-BI": "BI",
"ro-MD": "MD",
"ro-RO": "RO",
"ru-BY": "BY",
"ru-KG": "KG",
"ru-KZ": "KZ",
"ru-RU": "RU",
"ru-UA": "UA",
"rw-RW": "RW",
"sd-IN": "IN",
"sk-SK": "SK",
"sl-SI": "SI",
"sn-ZW": "ZW",
"so-SO": "SO",
"sr-BA": "BA",
"sr-RS": "RS",
"st-LS": "LS",
"st-ZA": "ZA",
"sv-FI": "FI",
"sv-SE": "SE",
"sw-CD": "CD",
"sw-KE": "KE",
"sw-TZ": "TZ",
"sw-UG": "UG",
"ta-IN": "IN",
"ta-SG": "SG",
"te-IN": "IN",
"th-TH": "TH",
"tn-BW": "BW",
"tn-ZA": "ZA",
"to-TO": "TO",
"tr-CY": "CY",
"tr-TR": "TR",
"tt-RU": "RU",
"uk-UA": "UA",
"ur-IN": "IN",
"ur-PK": "PK",
"vi-VN": "VN",
"wo-SN": "SN",
"xh-ZA": "ZA",
"yo-NG": "NG",
"zh-CN": "HK",
"zh-HK": "HK",
"zh-SG": "SG",
"zh-TW": "TW",
"zu-ZA": "ZA"
}
},
"google images": {
"all_locale": "ZZ",
"custom": {
@@ -7136,103 +6544,6 @@
"to-TO": "to"
}
},
"neosearch": {
"all_locale": null,
"custom": {
"countrycodes": [
"AE",
"AM",
"AR",
"AT",
"AU",
"AZ",
"BE",
"BG",
"BH",
"BO",
"BR",
"BY",
"CA",
"CH",
"CL",
"CN",
"CO",
"CY",
"CZ",
"DE",
"DK",
"DZ",
"EC",
"EE",
"EG",
"ES",
"FI",
"FR",
"GB",
"GE",
"GH",
"GR",
"HK",
"HR",
"HU",
"ID",
"IE",
"IL",
"IN",
"IT",
"JO",
"JP",
"KE",
"KG",
"KR",
"KW",
"KZ",
"LB",
"LT",
"LU",
"LV",
"MA",
"MD",
"MT",
"MX",
"MY",
"NG",
"NL",
"NO",
"NZ",
"OM",
"PE",
"PH",
"PL",
"PT",
"PY",
"QA",
"RO",
"RU",
"SA",
"SE",
"SG",
"SI",
"SK",
"TH",
"TJ",
"TM",
"TN",
"TR",
"TW",
"UA",
"US",
"UY",
"UZ",
"VE",
"VN",
"ZA"
]
},
"data_type": "traits_v1",
"languages": {},
"regions": {}
},
"odysee": {
"all_locale": null,
"custom": {},
@@ -10274,4 +9585,4 @@
},
"regions": {}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
],
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
"versions": [
"152.0",
"151.0"
"151.0",
"150.0"
]
}

View File

@@ -3272,7 +3272,7 @@
"Q128822": {
"si_name": "Q182429",
"symbol": "kn",
"to_si_factor": 0.514
"to_si_factor": 0.5144444444444445
},
"Q12912288": {
"si_name": null,

View File

@@ -361,43 +361,37 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
https: socks5://proxy:port
"""
def setup(self, engine_settings: dict[str, t.Any]) -> bool | None: # pylint: disable=unused-argument
def setup(self, engine_settings: dict[str, t.Any]) -> bool: # pylint: disable=unused-argument
"""Dynamic setup of the engine settings.
With this method, the engine's setup is carried out. For example, to
check or dynamically adapt the values handed over in the parameter
``engine_settings``.
``engine_settings``. The return value (True/False) indicates whether
the setup was successful and the engine can be built or rejected.
Whether the initialization was successful can be indicated by the return
value ``True`` or even ``False``.
- If no return value (``None`` ) is given from this method , this is
equivalent to ``True``.
- If an exception is thrown as part of the initialization, this is
equivalent to ``False``.
The method is optional and is called **synchronously** as part of the
The method is optional and is called synchronously as part of the
initialization of the service and is therefore only suitable for simple
(local) exams/changes at the engine setting.
The :py:obj:`Engine.init` method must be used for longer tasks in which
values of a remote must be determined, for example.
(local) exams/changes at the engine setting. The :py:obj:`Engine.init`
method must be used for longer tasks in which values of a remote must be
determined, for example.
"""
return True
def init(self, engine_settings: dict[str, t.Any]) -> bool | None: # pylint: disable=unused-argument
"""Initialization of the engine.
The method is optional and called **asynchronous** (in a thread). The
method is comparable to :py:obj:`Engine.setup`, it is suitable, for
caching data that first needs to be requested from a remote.
The method is optional and asynchronous (in a thread). It is suitable,
for example, for setting up a cache (for the engine) or for querying
values (required by the engine) from a remote.
The method is optional and runs **asynchronously** (in a thread), it is
comparable to :py:obj:`Engine.setup`. For instance, it is suitable for
caching data that first needs to be requested from a remote source.
Whether the initialization was successful can be indicated by the return
value ``True`` or even ``False``.
The evaluation of the return value is analogous to :py:obj:`Engine.setup`.
- If no return value is given from this init method (``None``), this is
equivalent to ``True``.
- If an exception is thrown as part of the initialization, this is
equivalent to ``False``.
"""
return True

View File

@@ -269,27 +269,21 @@ def is_engine_active(engine: "Engine | types.ModuleType"):
def call_engine_setup(engine: "Engine | types.ModuleType", engine_data: dict[str, t.Any]) -> bool:
setup_ok: bool | None = False
setup_ok = False
setup_func = getattr(engine, "setup", None)
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}")
setup_ok = False
# The evaluation of the return value is analogous to Engine.init
if setup_ok is None:
setup_ok = True
logger.exception('exception : {0}'.format(e))
if not setup_ok:
logger.error(f"(PID {os.getpid()}) {engine.name}: engine setup was not successful")
logger.error("%s: Engine setup was not successful, engine is set to inactive.", engine.name)
return setup_ok
@@ -317,16 +311,14 @@ def load_engines(engine_list: list[dict[str, t.Any]]):
for engine_data in engine_list:
if engine_data.get("inactive") is True:
continue
engine = load_engine(engine_data)
if engine:
register_engine(engine)
else:
# 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)"
f"(PID {os.getpid()}) loading engine %s failed: set engine to inactive!", engine_data.get("name", "???")
)
engine_data["inactive"] = True
return engines

View File

@@ -7,17 +7,13 @@
# There exits a https://github.com/ohblue/baidu-serp-api/
# but we don't use it here (may we can learn from).
import typing as t
from urllib.parse import urlencode
from datetime import datetime
from html import unescape
import time
import json
from searx.exceptions import SearxEngineAPIException, SearxEngineCaptchaException, SearxEngineAccessDeniedException
from searx.enginelib import EngineCache
from searx.network import get as http_get
from searx.exceptions import SearxEngineAPIException, SearxEngineCaptchaException
from searx.utils import html_to_text
about = {
@@ -39,31 +35,6 @@ baidu_category = 'general'
time_range_support = True
time_range_dict = {"day": 86400, "week": 604800, "month": 2592000, "year": 31536000}
image_base_url = "https://image.baidu.com/"
COOKIE_CACHE_KEY = "cookie"
COOKIE_CACHE_EXPIRATION_SECONDS = 3600
CACHE: EngineCache
"""Stores cookies from Baidu image search warmup."""
def setup(engine_settings: dict[str, t.Any]) -> bool:
global CACHE # pylint: disable=global-statement
CACHE = EngineCache(engine_settings["name"])
return True
def get_image_cookies(headers: dict[str, str]) -> dict[str, str]:
cookies: dict[str, str] | None = CACHE.get(COOKIE_CACHE_KEY)
if cookies:
return cookies
warmup = http_get(image_base_url, headers=headers, timeout=10)
cookies = dict(warmup.cookies.items())
CACHE.set(key=COOKIE_CACHE_KEY, value=cookies, expire=COOKIE_CACHE_EXPIRATION_SECONDS)
return cookies
def init(_):
if baidu_category not in ('general', 'images', 'it'):
@@ -117,9 +88,6 @@ def request(query, params):
if baidu_category == 'it':
query_params["paramList"] += f",timestamp_range={past}-{now}"
if baidu_category == 'images':
params["cookies"] = get_image_cookies(params["headers"])
params["url"] = f"{query_url}?{urlencode(query_params)}"
params["allow_redirects"] = False
return params
@@ -135,8 +103,6 @@ def response(resp):
# baidu's JSON encoder wrongly quotes / and ' characters by \\ and \'
text = text.replace(r"\/", "/").replace(r"\'", "'")
data = json.loads(text, strict=False)
if data.get("antiFlag") == 1:
raise SearxEngineAccessDeniedException(data.get("message", "Forbid spider access"))
parsers = {'general': parse_general, 'images': parse_images, 'it': parse_it}
return parsers[baidu_category](data)

View File

@@ -8,7 +8,6 @@ import random
import string
from urllib.parse import urlencode
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from searx import utils
@@ -40,32 +39,6 @@ cookie = {
"home_feed_column": "4",
}
_CN_TZ = ZoneInfo("Asia/Shanghai")
# Calendar-day time filter (Asia/Shanghai); dict values are days to subtract from today.
time_range_support = True
time_range_dict = {"day": 0, "week": 6, "month": 29, "year": 364}
def _pubtime_range(time_range: str) -> tuple[int, int]:
"""Return ``(pubtime_begin_s, pubtime_end_s)`` for Bilibili's search API.
Time ranges follow Bilibili's website semantics: they are counted in
**calendar days** in China Standard Time (``Asia/Shanghai``), not as
sliding 24-hour windows. For example, ``day`` means from 00:00:00 to
23:59:59 of the current local day; ``week`` spans from 00:00:00 on the
calendar day six days ago through the end of today, and so on.
The returned Unix timestamps (seconds) map to Bilibili's
``pubtime_begin_s`` and ``pubtime_end_s`` query parameters.
"""
now = datetime.now(_CN_TZ)
pubtime_end_s = int(now.replace(hour=23, minute=59, second=59, microsecond=0).timestamp())
begin_day = now - timedelta(days=time_range_dict[time_range])
pubtime_begin_s = int(begin_day.replace(hour=0, minute=0, second=0, microsecond=0).timestamp())
return pubtime_begin_s, pubtime_end_s
def request(query, params):
query_params = {
@@ -77,11 +50,6 @@ def request(query, params):
"search_type": "video",
}
if params.get("time_range") in time_range_dict:
pubtime_begin_s, pubtime_end_s = _pubtime_range(params["time_range"])
query_params["pubtime_begin_s"] = pubtime_begin_s
query_params["pubtime_end_s"] = pubtime_end_s
params["url"] = f"{base_url}?{urlencode(query_params)}"
params["headers"]["Referer"] = "https://www.bilibili.com/"
params["headers"]["Accept"] = "application/json, text/javascript, */*; q=0.01"

View File

@@ -32,6 +32,7 @@ from searx.utils import (
eval_xpath_getindex,
eval_xpath_list,
extract_text,
gen_gsa_useragent,
)
if t.TYPE_CHECKING:
@@ -196,7 +197,7 @@ def get_google_info(params: "OnlineParams", eng_traits: EngineTraits) -> dict[st
# https://developers.google.com/custom-search/docs/xml_results_appendices#interfaceLanguages
# https://github.com/searxng/searxng/issues/2515#issuecomment-1607150817
ret_val["params"]["hl"] = f"{lang_code}"
ret_val["params"]["hl"] = f"{lang_code}-{country}"
# lr parameter:
# The lr (language restrict) parameter restricts search results to
@@ -267,6 +268,7 @@ def get_google_info(params: "OnlineParams", eng_traits: EngineTraits) -> dict[st
# HTTP headers
ret_val["headers"]["Accept"] = "*/*"
ret_val["headers"]["User-Agent"] = gen_gsa_useragent()
# Cookies

View File

@@ -1,158 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Google Custom Search Engine"""
import datetime
import typing as t
from json import loads
from urllib.parse import urlencode
from searx.enginelib import EngineCache
from searx.exceptions import SearxEngineAPIException, SearxEngineTooManyRequestsException
from searx.network import get
from searx.result_types import EngineResults
from searx.engines.google import fetch_traits # pylint: disable=unused-import
from searx.engines.google import filter_mapping, get_google_info
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://www.google.com",
"wikidata_id": "Q2233943",
"official_api_documentation": "https://developers.google.com/custom-search/docs/element",
"use_official_api": False,
"require_api_key": False,
"results": "JSONP",
"description": "Platform for creating custom search engines based on Google Search.",
}
categories = ["general", "web"]
paging = True
max_page = 5
page_size = 20
time_range_support = True
language_support = True
safesearch = True
CX = "partner-pub-8993703457585266:4862972284" # blackle.com
CACHE: EngineCache
def setup(engine_settings: dict[str, t.Any]) -> bool:
global CACHE # pylint: disable=global-statement
CACHE = EngineCache(engine_settings["name"])
return True
def _cse_token() -> dict[str, str]:
token: dict[str, str] = CACHE.get(CX)
if token:
return token
resp = get(f"https://www.google.com/cse/cse.js?cx={CX}", timeout=10)
if not resp.ok:
raise SearxEngineAPIException("failed to obtain cse token")
end = resp.text.rfind("});")
start = resp.text.rfind("({")
opts: dict[str, str] = loads(resp.text[start + 1 : end + 1])
cse_tok = opts.get("cse_token")
if not cse_tok:
raise SearxEngineAPIException("failed to obtain cse token")
exp = opts.get("exp")
token = {
"cse_tok": cse_tok,
"cselibv": opts.get("cselibVersion", ""),
"exp": ",".join(exp) if exp else "",
}
CACHE.set(CX, token, expire=3600)
return token
def _get_start_and_end_date_str(time_range: str) -> tuple[str, str]:
time_range_map = {"day": 1, "week": 7, "month": 30, "year": 365}
end_date = datetime.datetime.now()
start_date = end_date - datetime.timedelta(days=time_range_map[time_range])
return start_date.strftime("%Y%m%d"), end_date.strftime("%Y%m%d")
def request(query: str, params: "OnlineParams") -> None:
token = _cse_token()
google_info = get_google_info(params, traits)
info: dict[str, str] = google_info["params"]
hl = info["hl"]
if params.get("searxng_locale", "all") == "all" or "ZZ" in hl:
hl = "en"
args = {
"rsz": "filtered_cse",
"num": str(page_size),
"hl": hl,
"cselibv": token["cselibv"],
"cx": CX,
"q": query,
"safe": filter_mapping[params["safesearch"]],
"cse_tok": token["cse_tok"],
"callback": "_",
"rurl": "",
}
if params["time_range"]:
start_date, end_date = _get_start_and_end_date_str(params["time_range"])
args["sort"] = f"date:r:{start_date}:{end_date}"
if info.get("lr"):
args["lr"] = info["lr"]
if info.get("cr"):
args["cr"] = info["cr"]
if google_info["country"] not in (None, "ZZ"):
args["gl"] = google_info["country"]
if token["exp"]:
args["exp"] = token["exp"]
start = (params["pageno"] - 1) * page_size
if start:
args["start"] = str(start)
params["url"] = "https://cse.google.com/cse/element/v1?" + urlencode(args)
params["cookies"] = google_info["cookies"]
params["headers"].update(google_info["headers"])
params["headers"]["Referer"] = "https://cse.google.com/"
def response(resp: "SXNG_Response") -> EngineResults:
json_resp = resp.text[resp.text.find("{") : resp.text.rfind("}") + 1]
data = loads(json_resp)
# not the real types, but a sufficient approximation
item: dict[str, str]
error: dict[str, str | int]
if error := data.get("error"):
message = error.get("message", "unknown error")
if error.get("code") == 429:
raise SearxEngineTooManyRequestsException(message=f"google cse: {message}")
raise SearxEngineAPIException(f"google cse: {message}")
results = EngineResults()
for item in data.get("results", []):
url = item.get("unescapedUrl")
if not url:
continue
results.add(
results.types.MainResult(
url=url,
title=item.get("titleNoFormatting", ""),
content=item.get("contentNoFormatting", ""),
thumbnail=item.get("richSnippet", {}).get("cseThumbnail", {}).get("src", ""), # type: ignore
)
)
return results

View File

@@ -13,13 +13,9 @@ It seems to use Bing internally, as the image thumbnails are loaded from Bing.
from urllib.parse import urlencode
import typing as t
from lxml import html
from searx.enginelib import EngineCache
from searx.network import get
from searx.exceptions import SearxEngineAPIException, SearxEngineAccessDeniedException
from searx.exceptions import SearxEngineAccessDeniedException
from searx.result_types import EngineResults
from searx.utils import eval_xpath, extract_text, gen_useragent
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
@@ -42,45 +38,14 @@ heexy_categ = "web"
"""Category to search in. Can be either "web" or "image"."""
base_url = "https://heexy.org"
api_url = "https://seapi.heexy.org"
base_url = "https://seapi.heexy.org"
safe_search_map = {0: "off", 1: "on", 2: "on"}
CACHE: EngineCache
"""Cache for storing the ``X-Data-Cacheft`` token (acts like an API key)."""
def setup(engine_settings: dict[str, t.Any]) -> bool:
global CACHE # pylint: disable=global-statement
def init(_):
if heexy_categ not in ("web", "image"):
raise ValueError("invalid search category: %s" % heexy_categ)
CACHE = EngineCache(engine_settings["name"])
return True
def _get_api_token(query: str) -> str:
"""The API token is independent of the search query. We just need any query
to obtain it initially, and don't hardcode it here to decrease chances of
getting blocked. The token must be passed as ``X-Data-Cacheft`` header."""
cached_token: str = CACHE.get("token")
if cached_token:
return cached_token
resp = get(f"{base_url}/search?q={query}", headers={"User-Agent": gen_useragent()})
if not resp.ok:
raise SearxEngineAPIException("failed to obtain request token: invalid response code")
doc = html.fromstring(resp.text)
token = extract_text(eval_xpath(doc, "//html/@data-cacheft"))
if not token:
raise SearxEngineAPIException("failed to obtain request token: no token found")
CACHE.set("token", token)
return token
def request(query: str, params: "OnlineParams") -> None:
args = {
@@ -91,9 +56,8 @@ def request(query: str, params: "OnlineParams") -> None:
if params["searxng_locale"] != "all":
args["lang"] = params["searxng_locale"].split("-")[0]
params["url"] = f"{api_url}/search/{heexy_categ}?{urlencode(args)}"
params["headers"]["X-Data-Cacheft"] = _get_api_token(query)
params["headers"]["Origin"] = api_url
params["url"] = f"{base_url}/search/{heexy_categ}?{urlencode(args)}"
params["headers"]["Origin"] = base_url
def response(resp: "SXNG_Response"):

View File

@@ -1,62 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Magnific_ is a database for images.
.. _Magnific: https://www.magnific.com
"""
from urllib.parse import urlencode
import typing as t
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://www.magnific.com",
"wikidata_id": "Q104211654",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://www.magnific.com"
categories = ["images"]
paging = True
free_images_only = True
"""
Whether to only load images that may be used for free, without a Magnific account.
"""
def request(query: str, params: "OnlineParams") -> None:
args = {"term": query, "filters[ai-generated][excluded]": 1, "page": params["pageno"], "locale": "en"}
if free_images_only:
args["filters[license]"] = "free"
params["headers"]["Referer"] = f"{base_url}/search"
params["url"] = f"{base_url}/api/regular/search?{urlencode(args)}"
def response(resp: "SXNG_Response"):
res = EngineResults()
result: dict[str, t.Any] # TBH: dict[str, t.Any]
for result in resp.json()["items"]:
res.add(
res.types.Image(
title=result["name"],
url=result["url"],
thumbnail_src=result["preview"]["url"],
img_src=result["preview"]["url"],
resolution=f"{result['preview']['width']}x{result['preview']['height']}",
)
)
return res

View File

@@ -99,35 +99,23 @@ def parse_general(data):
dom = html.fromstring(data)
for item in eval_xpath_list(dom, "//div[contains(@class, 'fds-web-normal-doc-root')]"):
thumbnail = extract_text(
eval_xpath(
item,
".//div[contains(@class, 'sds-comps-image') and not(contains(@class, 'sds-comps-image-circle'))]/img/@src",
)
)
title = extract_text(eval_xpath(item, ".//span[contains(@class, 'sds-comps-text-type-headline1')]"))
url = None
for item in eval_xpath_list(dom, "//ul[contains(@class, 'lst_total')]/li[contains(@class, 'bx')]"):
thumbnail = None
try:
url = eval_xpath_getindex(
item, ".//a[starts-with(@href, 'http') and not(contains(@href, 'keep.naver.com'))]/@href", 0
)
thumbnail = eval_xpath_getindex(item, ".//div[contains(@class, 'thumb_single')]//img/@data-lazysrc", 0)
except (ValueError, TypeError, SearxEngineXPathException):
pass
content = extract_text(eval_xpath(item, ".//span[contains(@class, 'sds-comps-text-type-body1')]"))
if title and url:
results.add(
MainResult(
title=title,
url=url,
content=content or "",
thumbnail=thumbnail or "",
)
results.add(
MainResult(
title=extract_text(eval_xpath(item, ".//a[contains(@class, 'link_tit')]")),
url=eval_xpath_getindex(item, ".//a[contains(@class, 'link_tit')]/@href", 0),
content=extract_text(
eval_xpath(item, ".//div[contains(@class, 'total_dsc_wrap')]//a[contains(@class, 'api_txt_lines')]")
),
thumbnail=thumbnail,
)
)
return results
@@ -185,7 +173,7 @@ def parse_news(data):
title=title,
url=url,
content=content,
thumbnail=thumbnail or "",
thumbnail=thumbnail,
)
)
@@ -208,7 +196,7 @@ def parse_videos(data):
length = None
try:
length = parse_duration_string(extract_text(eval_xpath(item, ".//span[contains(@class, 'time')]")) or "")
length = parse_duration_string(extract_text(eval_xpath(item, ".//span[contains(@class, 'time')]")))
except (ValueError, TypeError):
pass

View File

@@ -1,66 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Neocities_ is open source software for creating blogs.
.. _Neocities : https://github.com/neocities/neocities
"""
from urllib.parse import urlencode
import typing as t
from lxml import html
from searx.utils import eval_xpath, eval_xpath_list, extract_text
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from extended_types import SXNG_Response
from search.processors import OnlineParams
# Engine metadata
about = {
"website": "https://neocities.org/",
"wikidata_id": "Q17071099",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
# Engine configuration
categories = ["general", "blogs"]
paging = True
# Search URL
base_url = "https://neocities.org"
results_xpath = "//div[@class='result-item']"
url_xpath = './/div[@class="result-url"]/a/@href'
title_xpath = './/h3[@class="result-title"]/a/text()'
content_xpath = './/p[@class="result-snippet"]//text()'
screenshot_xpath = './/a[@class="result-screenshot"]/img/@src'
def request(query: str, params: "OnlineParams") -> None:
query_params: dict[str, t.Any] = {"q": query}
if params['pageno'] > 1:
offset = (params["pageno"] - 1) * 100
query_params["start"] = offset
params["url"] = f"{base_url}/search?{urlencode(query_params)}"
def response(resp: "SXNG_Response") -> EngineResults:
results = EngineResults()
dom = html.fromstring(resp.text)
for result in eval_xpath_list(dom, results_xpath):
results.add(
results.types.MainResult(
url=extract_text(eval_xpath(result, url_xpath)),
title=extract_text(eval_xpath(result, title_xpath)) or "",
content=extract_text(eval_xpath(result, content_xpath)) or "",
thumbnail=base_url + (extract_text(eval_xpath(result, screenshot_xpath)) or ""),
)
)
return results

View File

@@ -1,92 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Neosearch_ aims to be a privacy-first alternative to Google.
.. _Neosearch: https://neosearch.org/About
"""
from json import loads
import typing as t
from urllib.parse import urlencode
from searx.extended_types import SXNG_Response
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.enginelib.traits import EngineTraits
from searx.search.processors import OnlineParams
traits: EngineTraits
about = {
"website": "https://neosearch.org",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
base_url = "https://neosearch.org"
categories = ["general"]
paging = False
def request(query: str, params: "OnlineParams"):
args = {"q": query, "generate": "auto"}
countrycode = params["searxng_locale"].split("-")[-1].upper()
if countrycode in traits.custom["countrycodes"]:
args["loc"] = countrycode
params["url"] = f"{base_url}/search?{urlencode(args)}"
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
# first line contains something like `{"location": "de"}`
# second line contains the actual results
json_resp = loads(resp.text.splitlines()[-1])
for lens in json_resp["lenses"]:
for category in lens["categories"]:
for result in category["links"]:
if not result["url"]:
continue
res.add(
res.types.MainResult(
url=result["url"],
title=result["title"],
content=result["snippet"] or result["description"],
)
)
for suggestion in json_resp.get("suggestions", []):
res.add(res.types.LegacyResult(suggestion=suggestion))
return res
def fetch_traits(engine_traits: "EngineTraits") -> None:
# pylint: disable=import-outside-toplevel
from searx.network import get
from searx.utils import extr, js_obj_str_to_python
from babel.core import get_global
resp = get(base_url)
locations_js_raw = extr(resp.text, "const LOCATIONS = ", ";")
if not locations_js_raw:
raise RuntimeError("failed to find locations in neosearch HTML response")
locations: list[dict[str, str]] = js_obj_str_to_python(locations_js_raw)
babel_reg_list = get_global("territory_languages").keys()
countrycodes: list[str] = []
for loc in locations:
_reg = loc["code"].upper()
if _reg not in babel_reg_list:
print(f"ERROR: region tag {_reg} is unknown by babel")
continue
countrycodes.append(_reg)
countrycodes.sort()
engine_traits.custom["countrycodes"] = countrycodes

View File

@@ -1,63 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Picjumbo_ provides free stock photos.
.. _Picjumbo: https://picjumbo.com
"""
from urllib.parse import urlparse, urlunparse
import typing as t
from lxml import html
from searx.result_types import EngineResults
from searx.utils import eval_xpath, eval_xpath_list, extract_text
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://picjumbo.com",
"wikidata_id": None,
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
base_url = "https://picjumbo.com"
categories = ["images"]
paging = True
def request(query: str, params: "OnlineParams") -> None:
params["url"] = f"{base_url}/search/{query}/page/{params['pageno']}"
def _get_max_res_url(url: str) -> str:
"""Get the maximum resolution of the image based on the thumbnail URL."""
parsed_url = urlparse(url)
max_res_url = parsed_url._replace(query="w=10000&quality=100")
return urlunparse(max_res_url)
def response(resp: "SXNG_Response"):
res = EngineResults()
doc = html.fromstring(resp.text)
for result in eval_xpath_list(doc, "//div[contains(@class, 'photo_query')]/div[contains(@class, 'photo_item')]"):
thumbnail = extract_text(eval_xpath(result, ".//img[contains(@class, 'image')]/@src")) or ""
res.add(
res.types.Image(
url=extract_text(eval_xpath(result, ".//a[contains(@class, 'image')]/@href")) or "",
title=extract_text(eval_xpath(result, ".//h3")) or "",
content=extract_text(eval_xpath(result, ".//meta[@itemprop='keywords']/@content")) or "",
thumbnail_src=thumbnail,
img_src=_get_max_res_url(thumbnail),
)
)
return res

View File

@@ -38,7 +38,6 @@ Implementations
"""
import random
import typing as t
from datetime import (
@@ -90,11 +89,6 @@ qwant_categ: str = None # pyright: ignore[reportAssignmentType]
safesearch = True
# tgp seems to be short for "test group" - its actual value doesn't matter, as
# long as it's sent and at the correct position in the query params and doesn't
# change too frequently
test_group_value = random.randint(1, 3)
# fmt: off
qwant_news_locales = [
"ca_ad", "ca_es", "ca_fr", "co_fr", "de_at", "de_ch", "de_de", "en_au",
@@ -120,24 +114,17 @@ def request(query: str, params: "OnlineParams") -> None:
results_per_page = 10
if qwant_categ == "images":
results_per_page = 50
args = {
"q": query,
"count": results_per_page,
"locale": q_locale,
"offset": (params["pageno"] - 1) * results_per_page,
"tgp": test_group_value,
"device": "desktop",
"safesearch": params["safesearch"],
"tgp": 1,
"display": True,
"llm": True,
}
# shuffle query parameters to be harder to fingerprint
args = list(args.items())
random.shuffle(args)
args = dict(args)
params["raise_for_httperror"] = False
params["url"] = f"{api_url}{qwant_categ}?{urlencode(args)}"
@@ -203,6 +190,7 @@ def response(resp: "SXNG_Response") -> EngineResults:
mainline_items: list[dict[str, t.Any]] = row.get("items", [])
for item in mainline_items:
title: str = item.get("title", "")
res_url: str = item.get("url", "")
pub_date: datetime | None = None

View File

@@ -1,96 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""SearchZee is a small, indie project, web and news results pulled from
independent search infrastructure."""
import typing as t
from urllib.parse import urlencode
from searx.exceptions import SearxEngineAPIException
from searx.extended_types import SXNG_Response
from searx.network import get
from searx.result_types import EngineResults
from searx.utils import extr, html_to_text
from searx.enginelib import EngineCache
if t.TYPE_CHECKING:
from searx.search.processors import OnlineParams
about = {
"website": "https://searchzee.com",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
"description": (
"SearchZee is a small, indie project, the web and news results"
" are pulled from an independent search infrastructure."
),
}
categories: list[str] = None # type: ignore[reportAssignmentType]
paging = True
SearchzeeCategType = t.Literal["web", "news"]
searchzee_categ: SearchzeeCategType = None # type: ignore[reportAssignmentType]
CACHE: EngineCache
"""Cache for storing the scraped API Token."""
base_url = "https://searchzee.com"
# only supports for news
time_range_map = {"day": "pd", "week": "pw", "month": "pm", "year": "py"}
def setup(engine_settings: dict[str, t.Any]) -> bool:
if searchzee_categ not in t.get_args(SearchzeeCategType):
raise ValueError("invalid category: %s" % searchzee_categ)
global CACHE # pylint: disable=global-statement
CACHE = EngineCache(engine_settings["name"]) # type: ignore[reportAny]
return True
def _obtain_api_token() -> str:
token: str | None = CACHE.get("token") # type: ignore[reportAny]
if token:
return token
token_resp = get(
f"{base_url}/app.js",
)
if not token_resp.ok:
raise SearxEngineAPIException("failed to obtain api key")
token = extr(token_resp.text, "const SEARCHZEE_API_TOKEN = \"", "\";")
CACHE.set("token", token, expire=3600)
return token
def request(query: str, params: "OnlineParams"):
params["headers"]["X-SearchZee-Token"] = _obtain_api_token()
args = {"q": query, "type": searchzee_categ, "offset": params["pageno"] - 1}
if params["time_range"]:
args["freshness"] = time_range_map[params["time_range"]]
params["url"] = f"{base_url}/api/search?{urlencode(args)}"
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
results: list[dict[str, str]] = resp.json()["results"] # type: ignore[reportAny]
for result in results:
res.add(
res.types.MainResult(
url=result["url"],
title=html_to_text(result["title"]),
content=html_to_text(result["summary"]),
thumbnail=result.get("thumbnail") or "",
)
)
return res

View File

@@ -1,62 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Shopify stock photos provides royalty-free images, intended for use with
Shopify.
"""
import typing as t
from urllib.parse import urlencode
from lxml import html
from searx.result_types import EngineResults
from searx.utils import eval_xpath, eval_xpath_list, extract_text
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://www.shopify.com/stock-photos",
"wikidata_id": None,
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
base_url = "https://www.shopify.com"
categories = ["images"]
paging = True
def request(query: str, params: "OnlineParams") -> None:
args = {"q": query, "page": params["pageno"]}
params["url"] = f"{base_url}/stock-photos/photos/search?{urlencode(args)}"
def _get_download_url(url: str) -> str:
"""Get the link to the full quality image."""
query_start = url.find("?")
return url[:query_start] + "/download?quality=premium"
def response(resp: "SXNG_Response"):
res = EngineResults()
doc = html.fromstring(resp.text)
for result in eval_xpath_list(doc, "//div[contains(@class, 'js-masonry-grid')]/div"):
url = base_url + (extract_text(eval_xpath(result, ".//a[contains(@class, 'photo-tile')]/@href")) or "")
res.add(
res.types.Image(
url=url,
title=extract_text(eval_xpath(result, ".//p[contains(@class, 'photo-tile__title')]")) or "",
thumbnail_src=extract_text(eval_xpath(result, ".//img[contains(@class, 'photo-card__image')]/@src"))
or "",
img_src=_get_download_url(url),
)
)
return res

View File

@@ -1,55 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Stocksnap_ is a search engine for CC0-licensed images.
.. _Stocksnap: https://stocksnap.io
"""
import typing as t
from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
about = {
"website": "https://stocksnap.io",
"wikidata_id": None,
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "JSON",
}
# otherwise all requests get blocked, probably HTTP2 fingerprinting
enable_http2 = False
base_url = "https://stocksnap.io"
cdn_url = "https://cdn.stocksnap.io"
categories = ["images"]
paging = True
def request(query: str, params: "OnlineParams") -> None:
params["url"] = f"{base_url}/api/search-photos/{query}/relevance/desc/{params['pageno']}"
def response(resp: "SXNG_Response"):
res = EngineResults()
result: dict[str, str] # TBH: dict[str, t.Any]
for result in resp.json()["results"]:
slug = "-".join(result['keywords'][:1]) + "-" + result["img_id"]
res.add(
res.types.Image(
title=result["tags"],
url=f"{base_url}/photo/{slug}",
thumbnail_src=f"{cdn_url}/img-thumbs/280h/{result['img_id']}.jpg",
img_src=f"{cdn_url}/img-thumbs/960w/{result['img_id']}.jpg",
img_format="JPEG",
resolution=f"{result['img_width']}x{result['img_height']}",
)
)
return res

View File

@@ -16,7 +16,6 @@ __all__ = [
import typing as t
import os
from searx import logger
from searx import engines
@@ -93,9 +92,7 @@ class ProcessorMap(dict[str, EngineProcessor]):
self[eng_proc.engine.name] = eng_proc
# logger.debug("registered engine processor: %s", eng_proc.engine.name)
else:
logger.error(
f"(PID {os.getpid()}) {eng_proc.engine.name}: can't register engines processor (init engine failed)"
)
logger.error("can't register engine processor: %s (init failed)", eng_proc.engine.name)
return eng_proc_ok

View File

@@ -150,26 +150,19 @@ class EngineProcessor(ABC):
threading.Thread(target=__init_processor_thread, daemon=True).start()
def init_engine(self) -> bool:
eng_setting = get_engine_from_settings(self.engine.name)
init_ok: bool | None = False
try:
init_ok = self.engine.init(eng_setting)
except Exception as e: # pylint: disable=broad-except
logger.exception(f"(PID {os.getpid()}) {self.engine.name}: engine INIT failed, exception: {e}")
except Exception: # pylint: disable=broad-except
logger.exception(
f"(PID {os.getpid()}) Init method of engine %s failed due to an exception.", self.engine.name
)
init_ok = False
# In older engines, None is returned from the init method, which is
# equivalent to indicating that the initialization was successful
# (compare: Engine.setup).
# equivalent to indicating that the initialization was successful.
if init_ok is None:
init_ok = True
if not init_ok:
logger.error(f"(PID {os.getpid()}) {self.engine.name}: engine init was not successful")
return init_ok
def handle_exception(

View File

@@ -1199,11 +1199,6 @@ engines:
- name: google
engine: google
shortcut: go
inactive: true
- name: google cse
engine: google_cse
shortcut: goc
- name: google images
engine: google_images
@@ -1216,7 +1211,6 @@ engines:
- name: google videos
engine: google_videos
shortcut: gov
inactive: true
- name: google scholar
engine: google_scholar
@@ -1591,11 +1585,6 @@ engines:
disabled: true
inactive: true
- name: magnific
engine: magnific
shortcut: mag
disabled: true
- name: marginalia
engine: marginalia
shortcut: mar
@@ -1697,12 +1686,6 @@ engines:
shortcut: mwm
disabled: true
- name: neocities
engine: neocities
shortcut: nc
disabled: true
inactive: true
- name: niconico
engine: niconico
shortcut: nico
@@ -1874,11 +1857,6 @@ engines:
engine: photon
shortcut: ph
- name: picjumbo
engine: picjumbo
shortcut: pj
disabled: true
- name: pinterest
engine: pinterest
shortcut: pin
@@ -2187,11 +2165,6 @@ engines:
engine: sepiasearch
shortcut: sep
- name: shopify stock
engine: shopify_stock
shortcut: shs
disabled: true
- name: sogou
engine: sogou
shortcut: sogou
@@ -2222,11 +2195,6 @@ engines:
api_site: 'stackoverflow'
categories: [it, q&a]
- name: stocksnap
engine: stocksnap
shortcut: sto
disabled: true
- name: askubuntu
engine: stackexchange
shortcut: ubuntu
@@ -2866,12 +2834,6 @@ engines:
shortcut: nvrv
disabled: true
- name: neosearch
engine: neosearch
shortcut: neo
disabled: true
inactive: true
- name: rubygems
shortcut: rbg
engine: xpath
@@ -3001,17 +2963,44 @@ engines:
results: HTML
- name: searchzee
engine: searchzee
engine: json_engine
search_url: https://searchzee.com/api/search?q={query}&type=web&offset={pageno}
paging: true
first_page_num: 0
results_query: results
url_query: url
title_query: title
content_query: summary
content_html_to_text: true
categories: general
searchzee_categ: web
shortcut: sz
disabled: true
inactive: true
about:
website: https://searchzee.com
use_official_api: false
require_api_key: false
results: JSON
- name: searchzee news
engine: searchzee
engine: json_engine
search_url: https://searchzee.com/api/search?q={query}&type=news&offset={pageno}{time_range}
paging: true
first_page_num: 0
time_range_support: true
time_range_url: "&freshness={time_range_val}"
time_range_map:
day: pd
week: pw
month: pm
year: py
results_query: results
url_query: url
title_query: title
content_query: summary
thumbnail_query: thumbnail
content_html_to_text: true
categories: news
searchzee_categ: news
shortcut: sznw
disabled: true
inactive: true
@@ -3296,36 +3285,6 @@ engines:
base_url: https://info.searchtoday.site
disabled: true
- name: sina
engine: json_engine
shortcut: sina
categories: news
paging: true
first_page_num: 1
search_url: https://search.sina.com.cn/api/search?q={query}&tp=mix&sort=0&page={pageno}&size=10&from=search_result
results_query: data/list
url_query: url
title_query: title
content_query: intro
thumbnail_query: thumb
title_html_to_text: true
time_range_map:
day: d
week: w
month: m
year: y
time_range_support: true
time_range_url: "&from=advanced_search&time={time_range_val}"
disabled: true
inactive: true
language: zh
about:
website: https://search.sina.com.cn
wikidata_id: Q938668
use_official_api: false
require_api_key: false
results: JSON
# - name: webcrawler
# engine: s1search
# shortcut: wc

View File

@@ -1 +1 @@
{"version":3,"file":"5Ako-qGW.min.js","names":[],"sources":["../../../../../client/simple/src/js/main/search.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { listen } from \"../toolkit.ts\";\nimport { getElement } from \"../util/getElement.ts\";\n\nconst searchForm: HTMLFormElement = getElement<HTMLFormElement>(\"search\");\nconst searchInput: HTMLInputElement = getElement<HTMLInputElement>(\"q\");\nconst searchReset: HTMLButtonElement = getElement<HTMLButtonElement>(\"clear_search\");\n\nconst isMobile: boolean = window.matchMedia(\"(max-width: 50em)\").matches;\nconst isResultsPage: boolean = document.querySelector(\"main\")?.id === \"main_results\";\n\nconst categoryButtons: HTMLButtonElement[] = Array.from(\n document.querySelectorAll<HTMLButtonElement>(\"#categories_container button.category\")\n);\n\nif (searchInput.value.length === 0) {\n searchReset.classList.add(\"empty\");\n}\n\n// focus search input on large screens\nif (!(isMobile || isResultsPage)) {\n searchInput.focus();\n}\n\n// On mobile, move cursor to the end of the input on focus\nif (isMobile) {\n listen(\"focus\", searchInput, () => {\n // Defer cursor move until the next frame to prevent a visual jump\n requestAnimationFrame(() => {\n const end = searchInput.value.length;\n searchInput.setSelectionRange(end, end);\n searchInput.scrollLeft = searchInput.scrollWidth;\n });\n });\n}\n\nlisten(\"input\", searchInput, () => {\n searchReset.classList.toggle(\"empty\", searchInput.value.length === 0);\n});\n\nlisten(\"click\", searchReset, (event: MouseEvent) => {\n event.preventDefault();\n searchInput.value = \"\";\n searchInput.focus();\n searchReset.classList.add(\"empty\");\n});\n\nfor (const button of categoryButtons) {\n listen(\"click\", button, (event: MouseEvent) => {\n if (event.shiftKey) {\n event.preventDefault();\n button.classList.toggle(\"selected\");\n return;\n }\n\n // deselect all other categories\n for (const categoryButton of categoryButtons) {\n categoryButton.classList.toggle(\"selected\", categoryButton === button);\n }\n });\n}\n\nif (document.querySelector(\"div.search_filters\")) {\n const safesearchElement = document.getElementById(\"safesearch\");\n if (safesearchElement) {\n listen(\"change\", safesearchElement, () => searchForm.submit());\n }\n\n const timeRangeElement = document.getElementById(\"time_range\");\n if (timeRangeElement) {\n listen(\"change\", timeRangeElement, () => searchForm.submit());\n }\n\n const languageElement = document.getElementById(\"language\");\n if (languageElement) {\n listen(\"change\", languageElement, () => searchForm.submit());\n }\n}\n\n// override searchForm submit event\nlisten(\"submit\", searchForm, (event: Event) => {\n event.preventDefault();\n\n if (categoryButtons.length > 0) {\n const searchCategories = getElement<HTMLInputElement>(\"selected-categories\");\n searchCategories.value = categoryButtons\n .filter((button) => button.classList.contains(\"selected\"))\n .map((button) => button.name.replace(\"category_\", \"\"))\n .join(\",\");\n }\n\n searchForm.submit();\n});\n"],"mappings":"yEAKA,IAAM,EAA8B,EAA4B,QAAQ,EAClE,EAAgC,EAA6B,GAAG,EAChE,EAAiC,EAA8B,cAAc,EAE7E,EAAoB,OAAO,WAAW,mBAAmB,CAAC,CAAC,QAC3D,EAAyB,SAAS,cAAc,MAAM,CAAC,EAAE,KAAO,eAEhE,EAAuC,MAAM,KACjD,SAAS,iBAAoC,uCAAuC,CACtF,EAEI,EAAY,MAAM,SAAW,GAC/B,EAAY,UAAU,IAAI,OAAO,EAI7B,GAAY,GAChB,EAAY,MAAM,EAIhB,GACF,EAAO,QAAS,MAAmB,CAEjC,0BAA4B,CAC1B,IAAM,EAAM,EAAY,MAAM,OAC9B,EAAY,kBAAkB,EAAK,CAAG,EACtC,EAAY,WAAa,EAAY,WACvC,CAAC,CACH,CAAC,EAGH,EAAO,QAAS,MAAmB,CACjC,EAAY,UAAU,OAAO,QAAS,EAAY,MAAM,SAAW,CAAC,CACtE,CAAC,EAED,EAAO,QAAS,EAAc,GAAsB,CAClD,EAAM,eAAe,EACrB,EAAY,MAAQ,GACpB,EAAY,MAAM,EAClB,EAAY,UAAU,IAAI,OAAO,CACnC,CAAC,EAED,IAAK,IAAM,KAAU,EACnB,EAAO,QAAS,EAAS,GAAsB,CAC7C,GAAI,EAAM,SAAU,CAClB,EAAM,eAAe,EACrB,EAAO,UAAU,OAAO,UAAU,EAClC,MACF,CAGA,IAAK,IAAM,KAAkB,EAC3B,EAAe,UAAU,OAAO,WAAY,IAAmB,CAAM,CAEzE,CAAC,EAGH,GAAI,SAAS,cAAc,oBAAoB,EAAG,CAChD,IAAM,EAAoB,SAAS,eAAe,YAAY,EAC1D,GACF,EAAO,SAAU,MAAyB,EAAW,OAAO,CAAC,EAG/D,IAAM,EAAmB,SAAS,eAAe,YAAY,EACzD,GACF,EAAO,SAAU,MAAwB,EAAW,OAAO,CAAC,EAG9D,IAAM,EAAkB,SAAS,eAAe,UAAU,EACtD,GACF,EAAO,SAAU,MAAuB,EAAW,OAAO,CAAC,CAE/D,CAGA,EAAO,SAAU,EAAa,GAAiB,CAG7C,GAFA,EAAM,eAAe,EAEjB,EAAgB,OAAS,EAAG,CAC9B,IAAM,EAAmB,EAA6B,qBAAqB,EAC3E,EAAiB,MAAQ,EACtB,OAAQ,GAAW,EAAO,UAAU,SAAS,UAAU,CAAC,CAAC,CACzD,IAAK,GAAW,EAAO,KAAK,QAAQ,YAAa,EAAE,CAAC,CAAC,CACrD,KAAK,GAAG,CACb,CAEA,EAAW,OAAO,CACpB,CAAC"}
{"version":3,"file":"5Ako-qGW.min.js","names":[],"sources":["../../../../../client/simple/src/js/main/search.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { listen } from \"../toolkit.ts\";\nimport { getElement } from \"../util/getElement.ts\";\n\nconst searchForm: HTMLFormElement = getElement<HTMLFormElement>(\"search\");\nconst searchInput: HTMLInputElement = getElement<HTMLInputElement>(\"q\");\nconst searchReset: HTMLButtonElement = getElement<HTMLButtonElement>(\"clear_search\");\n\nconst isMobile: boolean = window.matchMedia(\"(max-width: 50em)\").matches;\nconst isResultsPage: boolean = document.querySelector(\"main\")?.id === \"main_results\";\n\nconst categoryButtons: HTMLButtonElement[] = Array.from(\n document.querySelectorAll<HTMLButtonElement>(\"#categories_container button.category\")\n);\n\nif (searchInput.value.length === 0) {\n searchReset.classList.add(\"empty\");\n}\n\n// focus search input on large screens\nif (!(isMobile || isResultsPage)) {\n searchInput.focus();\n}\n\n// On mobile, move cursor to the end of the input on focus\nif (isMobile) {\n listen(\"focus\", searchInput, () => {\n // Defer cursor move until the next frame to prevent a visual jump\n requestAnimationFrame(() => {\n const end = searchInput.value.length;\n searchInput.setSelectionRange(end, end);\n searchInput.scrollLeft = searchInput.scrollWidth;\n });\n });\n}\n\nlisten(\"input\", searchInput, () => {\n searchReset.classList.toggle(\"empty\", searchInput.value.length === 0);\n});\n\nlisten(\"click\", searchReset, (event: MouseEvent) => {\n event.preventDefault();\n searchInput.value = \"\";\n searchInput.focus();\n searchReset.classList.add(\"empty\");\n});\n\nfor (const button of categoryButtons) {\n listen(\"click\", button, (event: MouseEvent) => {\n if (event.shiftKey) {\n event.preventDefault();\n button.classList.toggle(\"selected\");\n return;\n }\n\n // deselect all other categories\n for (const categoryButton of categoryButtons) {\n categoryButton.classList.toggle(\"selected\", categoryButton === button);\n }\n });\n}\n\nif (document.querySelector(\"div.search_filters\")) {\n const safesearchElement = document.getElementById(\"safesearch\");\n if (safesearchElement) {\n listen(\"change\", safesearchElement, () => searchForm.submit());\n }\n\n const timeRangeElement = document.getElementById(\"time_range\");\n if (timeRangeElement) {\n listen(\"change\", timeRangeElement, () => searchForm.submit());\n }\n\n const languageElement = document.getElementById(\"language\");\n if (languageElement) {\n listen(\"change\", languageElement, () => searchForm.submit());\n }\n}\n\n// override searchForm submit event\nlisten(\"submit\", searchForm, (event: Event) => {\n event.preventDefault();\n\n if (categoryButtons.length > 0) {\n const searchCategories = getElement<HTMLInputElement>(\"selected-categories\");\n searchCategories.value = categoryButtons\n .filter((button) => button.classList.contains(\"selected\"))\n .map((button) => button.name.replace(\"category_\", \"\"))\n .join(\",\");\n }\n\n searchForm.submit();\n});\n"],"mappings":"yEAKA,IAAM,EAA8B,EAA4B,QAAQ,EAClE,EAAgC,EAA6B,GAAG,EAChE,EAAiC,EAA8B,cAAc,EAE7E,EAAoB,OAAO,WAAW,mBAAmB,EAAE,QAC3D,EAAyB,SAAS,cAAc,MAAM,GAAG,KAAO,eAEhE,EAAuC,MAAM,KACjD,SAAS,iBAAoC,uCAAuC,CACtF,EAEI,EAAY,MAAM,SAAW,GAC/B,EAAY,UAAU,IAAI,OAAO,EAI7B,GAAY,GAChB,EAAY,MAAM,EAIhB,GACF,EAAO,QAAS,MAAmB,CAEjC,0BAA4B,CAC1B,IAAM,EAAM,EAAY,MAAM,OAC9B,EAAY,kBAAkB,EAAK,CAAG,EACtC,EAAY,WAAa,EAAY,WACvC,CAAC,CACH,CAAC,EAGH,EAAO,QAAS,MAAmB,CACjC,EAAY,UAAU,OAAO,QAAS,EAAY,MAAM,SAAW,CAAC,CACtE,CAAC,EAED,EAAO,QAAS,EAAc,GAAsB,CAClD,EAAM,eAAe,EACrB,EAAY,MAAQ,GACpB,EAAY,MAAM,EAClB,EAAY,UAAU,IAAI,OAAO,CACnC,CAAC,EAED,IAAK,IAAM,KAAU,EACnB,EAAO,QAAS,EAAS,GAAsB,CAC7C,GAAI,EAAM,SAAU,CAClB,EAAM,eAAe,EACrB,EAAO,UAAU,OAAO,UAAU,EAClC,MACF,CAGA,IAAK,IAAM,KAAkB,EAC3B,EAAe,UAAU,OAAO,WAAY,IAAmB,CAAM,CAEzE,CAAC,EAGH,GAAI,SAAS,cAAc,oBAAoB,EAAG,CAChD,IAAM,EAAoB,SAAS,eAAe,YAAY,EAC1D,GACF,EAAO,SAAU,MAAyB,EAAW,OAAO,CAAC,EAG/D,IAAM,EAAmB,SAAS,eAAe,YAAY,EACzD,GACF,EAAO,SAAU,MAAwB,EAAW,OAAO,CAAC,EAG9D,IAAM,EAAkB,SAAS,eAAe,UAAU,EACtD,GACF,EAAO,SAAU,MAAuB,EAAW,OAAO,CAAC,CAE/D,CAGA,EAAO,SAAU,EAAa,GAAiB,CAG7C,GAFA,EAAM,eAAe,EAEjB,EAAgB,OAAS,EAAG,CAC9B,IAAM,EAAmB,EAA6B,qBAAqB,EAC3E,EAAiB,MAAQ,EACtB,OAAQ,GAAW,EAAO,UAAU,SAAS,UAAU,CAAC,EACxD,IAAK,GAAW,EAAO,KAAK,QAAQ,YAAa,EAAE,CAAC,EACpD,KAAK,GAAG,CACb,CAEA,EAAW,OAAO,CACpB,CAAC"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"e2-9fzwE.min.js","names":[],"sources":["../../../../../client/simple/src/js/main/preferences.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { http, listen, settings } from \"../toolkit.ts\";\nimport { assertElement } from \"../util/assertElement.ts\";\n\nlet engineDescriptions: Record<string, [string, string]> | undefined;\n\nconst loadEngineDescriptions = async (): Promise<void> => {\n if (engineDescriptions) return;\n try {\n const res = await http(\"GET\", \"engine_descriptions.json\");\n engineDescriptions = await res.json();\n } catch (error) {\n console.error(\"Error fetching engineDescriptions:\", error);\n }\n if (!engineDescriptions) return;\n\n for (const [engine_name, [description, source]] of Object.entries(engineDescriptions)) {\n const elements = document.querySelectorAll<HTMLElement>(`[data-engine-name=\"${engine_name}\"] .engine-description`);\n const sourceText = ` (<i>${settings.translations?.Source}:&nbsp;${source}</i>)`;\n\n for (const element of elements) {\n element.innerHTML = description + sourceText;\n }\n }\n};\n\nconst toggleEngines = (enable: boolean, engineToggles: NodeListOf<HTMLInputElement>): void => {\n for (const engineToggle of engineToggles) {\n // check if element visible, so that only engines of the current category are modified\n if (engineToggle.offsetParent) {\n engineToggle.checked = !enable;\n }\n }\n};\n\nconst engineElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(\"[data-engine-name]\");\nfor (const engineElement of engineElements) {\n listen(\"mouseenter\", engineElement, loadEngineDescriptions);\n}\n\nconst engineToggles: NodeListOf<HTMLInputElement> = document.querySelectorAll<HTMLInputElement>(\n \"tbody input[type=checkbox][class~=checkbox-onoff]\"\n);\n\nconst enableAllEngines: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(\".enable-all-engines\");\nfor (const engine of enableAllEngines) {\n listen(\"click\", engine, () => toggleEngines(true, engineToggles));\n}\n\nconst disableAllEngines: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(\".disable-all-engines\");\nfor (const engine of disableAllEngines) {\n listen(\"click\", engine, () => toggleEngines(false, engineToggles));\n}\n\nlisten(\"click\", \"#copy-hash\", async function (this: HTMLElement) {\n const target = this.parentElement?.querySelector<HTMLPreElement>(\"pre\");\n assertElement(target);\n\n if (window.isSecureContext) {\n await navigator.clipboard.writeText(target.innerText);\n } else {\n const selection = window.getSelection();\n if (selection) {\n const range = document.createRange();\n range.selectNodeContents(target);\n selection.removeAllRanges();\n selection.addRange(range);\n document.execCommand(\"copy\");\n }\n }\n\n if (this.dataset.copiedText) {\n this.innerText = this.dataset.copiedText;\n }\n});\n"],"mappings":"4FAKA,IAAI,EAEE,EAAyB,SAA2B,CACpD,MACJ,IAAI,CAEF,EAAqB,MAAM,MADT,EAAK,MAAO,0BAA0B,EAAA,CACzB,KAAK,CACtC,OAAS,EAAO,CACd,QAAQ,MAAM,qCAAsC,CAAK,CAC3D,CACK,KAEL,IAAK,GAAM,CAAC,EAAa,CAAC,EAAa,MAAY,OAAO,QAAQ,CAAkB,EAAG,CACrF,IAAM,EAAW,SAAS,iBAA8B,sBAAsB,EAAY,uBAAuB,EAC3G,EAAa,QAAQ,EAAS,cAAc,OAAO,SAAS,EAAO,OAEzE,IAAK,IAAM,KAAW,EACpB,EAAQ,UAAY,EAAc,CAEtC,CAVA,CAWF,EAEM,GAAiB,EAAiB,IAAsD,CAC5F,IAAK,IAAM,KAAgB,EAErB,EAAa,eACf,EAAa,QAAU,CAAC,EAG9B,EAEM,EAA0C,SAAS,iBAA8B,oBAAoB,EAC3G,IAAK,IAAM,KAAiB,EAC1B,EAAO,aAAc,EAAe,CAAsB,EAG5D,IAAM,EAA8C,SAAS,iBAC3D,mDACF,EAEM,EAA4C,SAAS,iBAA8B,qBAAqB,EAC9G,IAAK,IAAM,KAAU,EACnB,EAAO,QAAS,MAAc,EAAc,GAAM,CAAa,CAAC,EAGlE,IAAM,EAA6C,SAAS,iBAA8B,sBAAsB,EAChH,IAAK,IAAM,KAAU,EACnB,EAAO,QAAS,MAAc,EAAc,GAAO,CAAa,CAAC,EAGnE,EAAO,QAAS,aAAc,gBAAmC,CAC/D,IAAM,EAAS,KAAK,eAAe,cAA8B,KAAK,EAGtE,GAFA,EAAc,CAAM,EAEhB,OAAO,gBACT,MAAM,UAAU,UAAU,UAAU,EAAO,SAAS,MAC/C,CACL,IAAM,EAAY,OAAO,aAAa,EACtC,GAAI,EAAW,CACb,IAAM,EAAQ,SAAS,YAAY,EACnC,EAAM,mBAAmB,CAAM,EAC/B,EAAU,gBAAgB,EAC1B,EAAU,SAAS,CAAK,EACxB,SAAS,YAAY,MAAM,CAC7B,CACF,CAEI,KAAK,QAAQ,aACf,KAAK,UAAY,KAAK,QAAQ,WAElC,CAAC"}
{"version":3,"file":"e2-9fzwE.min.js","names":[],"sources":["../../../../../client/simple/src/js/main/preferences.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { http, listen, settings } from \"../toolkit.ts\";\nimport { assertElement } from \"../util/assertElement.ts\";\n\nlet engineDescriptions: Record<string, [string, string]> | undefined;\n\nconst loadEngineDescriptions = async (): Promise<void> => {\n if (engineDescriptions) return;\n try {\n const res = await http(\"GET\", \"engine_descriptions.json\");\n engineDescriptions = await res.json();\n } catch (error) {\n console.error(\"Error fetching engineDescriptions:\", error);\n }\n if (!engineDescriptions) return;\n\n for (const [engine_name, [description, source]] of Object.entries(engineDescriptions)) {\n const elements = document.querySelectorAll<HTMLElement>(`[data-engine-name=\"${engine_name}\"] .engine-description`);\n const sourceText = ` (<i>${settings.translations?.Source}:&nbsp;${source}</i>)`;\n\n for (const element of elements) {\n element.innerHTML = description + sourceText;\n }\n }\n};\n\nconst toggleEngines = (enable: boolean, engineToggles: NodeListOf<HTMLInputElement>): void => {\n for (const engineToggle of engineToggles) {\n // check if element visible, so that only engines of the current category are modified\n if (engineToggle.offsetParent) {\n engineToggle.checked = !enable;\n }\n }\n};\n\nconst engineElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(\"[data-engine-name]\");\nfor (const engineElement of engineElements) {\n listen(\"mouseenter\", engineElement, loadEngineDescriptions);\n}\n\nconst engineToggles: NodeListOf<HTMLInputElement> = document.querySelectorAll<HTMLInputElement>(\n \"tbody input[type=checkbox][class~=checkbox-onoff]\"\n);\n\nconst enableAllEngines: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(\".enable-all-engines\");\nfor (const engine of enableAllEngines) {\n listen(\"click\", engine, () => toggleEngines(true, engineToggles));\n}\n\nconst disableAllEngines: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(\".disable-all-engines\");\nfor (const engine of disableAllEngines) {\n listen(\"click\", engine, () => toggleEngines(false, engineToggles));\n}\n\nlisten(\"click\", \"#copy-hash\", async function (this: HTMLElement) {\n const target = this.parentElement?.querySelector<HTMLPreElement>(\"pre\");\n assertElement(target);\n\n if (window.isSecureContext) {\n await navigator.clipboard.writeText(target.innerText);\n } else {\n const selection = window.getSelection();\n if (selection) {\n const range = document.createRange();\n range.selectNodeContents(target);\n selection.removeAllRanges();\n selection.addRange(range);\n document.execCommand(\"copy\");\n }\n }\n\n if (this.dataset.copiedText) {\n this.innerText = this.dataset.copiedText;\n }\n});\n"],"mappings":"4FAKA,IAAI,EAEE,EAAyB,SAA2B,CACpD,MACJ,IAAI,CAEF,EAAqB,MAAM,MADT,EAAK,MAAO,0BAA0B,GACzB,KAAK,CACtC,OAAS,EAAO,CACd,QAAQ,MAAM,qCAAsC,CAAK,CAC3D,CACK,KAEL,IAAK,GAAM,CAAC,EAAa,CAAC,EAAa,MAAY,OAAO,QAAQ,CAAkB,EAAG,CACrF,IAAM,EAAW,SAAS,iBAA8B,sBAAsB,EAAY,uBAAuB,EAC3G,EAAa,QAAQ,EAAS,cAAc,OAAO,SAAS,EAAO,OAEzE,IAAK,IAAM,KAAW,EACpB,EAAQ,UAAY,EAAc,CAEtC,CAVA,CAWF,EAEM,GAAiB,EAAiB,IAAsD,CAC5F,IAAK,IAAM,KAAgB,EAErB,EAAa,eACf,EAAa,QAAU,CAAC,EAG9B,EAEM,EAA0C,SAAS,iBAA8B,oBAAoB,EAC3G,IAAK,IAAM,KAAiB,EAC1B,EAAO,aAAc,EAAe,CAAsB,EAG5D,IAAM,EAA8C,SAAS,iBAC3D,mDACF,EAEM,EAA4C,SAAS,iBAA8B,qBAAqB,EAC9G,IAAK,IAAM,KAAU,EACnB,EAAO,QAAS,MAAc,EAAc,GAAM,CAAa,CAAC,EAGlE,IAAM,EAA6C,SAAS,iBAA8B,sBAAsB,EAChH,IAAK,IAAM,KAAU,EACnB,EAAO,QAAS,MAAc,EAAc,GAAO,CAAa,CAAC,EAGnE,EAAO,QAAS,aAAc,gBAAmC,CAC/D,IAAM,EAAS,KAAK,eAAe,cAA8B,KAAK,EAGtE,GAFA,EAAc,CAAM,EAEhB,OAAO,gBACT,MAAM,UAAU,UAAU,UAAU,EAAO,SAAS,MAC/C,CACL,IAAM,EAAY,OAAO,aAAa,EACtC,GAAI,EAAW,CACb,IAAM,EAAQ,SAAS,YAAY,EACnC,EAAM,mBAAmB,CAAM,EAC/B,EAAU,gBAAgB,EAC1B,EAAU,SAAS,CAAK,EACxB,SAAS,YAAY,MAAM,CAC7B,CACF,CAEI,KAAK,QAAQ,aACf,KAAK,UAAY,KAAK,QAAQ,WAElC,CAAC"}

View File

@@ -77,7 +77,7 @@
]
},
"src/js/plugin/Calculator.ts": {
"file": "chunk/C8c7HJzp.min.js",
"file": "chunk/CTP0QVHn.min.js",
"name": "calculator",
"src": "src/js/plugin/Calculator.ts",
"isDynamicEntry": true,
@@ -98,7 +98,7 @@
]
},
"src/js/plugin/MapView.ts": {
"file": "chunk/BnnvKC7b.min.js",
"file": "chunk/U6YV4Y8e.min.js",
"name": "mapview",
"src": "src/js/plugin/MapView.ts",
"isDynamicEntry": true,

View File

@@ -1,3 +1,3 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./chunk/BnnvKC7b.min.js","./sxng-mapview.min.css","./chunk/DpvWr1cn.min.js","./chunk/DK4yUVpy.min.js","./chunk/DcK-mo-Y.min.js","./chunk/C8c7HJzp.min.js","./chunk/C93hSkpT.min.js","./chunk/5Ako-qGW.min.js","./chunk/DvCYLbJr.min.js","./chunk/B8prKeWj.min.js","./chunk/e2-9fzwE.min.js"])))=>i.map(i=>d[i]);
var e=class{id;constructor(e){this.id=e,queueMicrotask(()=>this.invoke())}async invoke(){try{console.debug(`[PLUGIN] ${this.id}: Running...`);let e=await this.run();if(!e)return;console.debug(`[PLUGIN] ${this.id}: Running post-exec...`),await this.post(e)}catch(e){console.error(`[PLUGIN] ${this.id}:`,e)}finally{console.debug(`[PLUGIN] ${this.id}: Done.`)}}},t={index:`index`,results:`results`,preferences:`preferences`,unknown:`unknown`},n={closeDetail:void 0,scrollPageToSelected:void 0,selectImage:void 0,selectNext:void 0,selectPrevious:void 0},r=()=>{let e=document.querySelector(`meta[name="endpoint"]`)?.getAttribute(`content`);return e&&e in t?e:t.unknown},i=()=>{let e=document.querySelector(`script[client_settings]`)?.getAttribute(`client_settings`);if(!e)return{};try{return JSON.parse(atob(e))}catch(e){return console.error(`Failed to load client_settings:`,e),{}}},a=async(e,t,n)=>{let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??3e4),a=await fetch(t,{body:n?.body,method:e,signal:r.signal}).finally(()=>clearTimeout(i));if(!a.ok)throw Error(a.statusText);return a},o=(e,t,n,r)=>{if(typeof t!=`string`){t.addEventListener(e,n,r);return}document.addEventListener(e,e=>{for(let r of e.composedPath())if(r instanceof HTMLElement&&r.matches(t)){try{n.call(r,e)}catch(e){console.error(e)}break}},r)},s=(e,t)=>{for(let e of t?.on??[])if(!e)return;document.readyState===`loading`?o(`DOMContentLoaded`,document,e,{once:!0}):e()},c=r(),l=i(),u=(e,t)=>{d(t)&&e()},d=e=>{switch(e.on){case`global`:return!0;case`endpoint`:return!!e.where.includes(c)}},f=`modulepreload`,p=function(e,t){return new URL(e,t).href},m={},h=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,new URL(`../../../src/node/plugins/importAnalysisBuild.ts`,import.meta.url)).href}r=o(t.map(t=>{if(t=p(t,n),t=s(t),t in m)return;m[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:f,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};s(()=>{document.documentElement.classList.remove(`no-js`),document.documentElement.classList.add(`js`),o(`click`,`.close`,function(){this.parentNode?.classList.add(`invisible`)}),o(`click`,`.searxng_init_map`,async function(e){e.preventDefault(),this.classList.remove(`searxng_init_map`),u(()=>h(async()=>{let{default:e}=await import(`./chunk/BnnvKC7b.min.js`);return{default:e}},__vite__mapDeps([0,1]),import.meta.url).then(({default:e})=>new e(this)),{on:`endpoint`,where:[t.results]})}),l.plugins?.includes(`infiniteScroll`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/DpvWr1cn.min.js`);return{default:e}},__vite__mapDeps([2,3,4]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]}),l.plugins?.includes(`calculator`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/C8c7HJzp.min.js`);return{default:e}},__vite__mapDeps([5,4,3]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]})}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([6,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([7,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([8,3]),import.meta.url)},{on:[c===t.index]}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([6,3]),import.meta.url),h(()=>import(`./chunk/B8prKeWj.min.js`),__vite__mapDeps([9,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([7,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([8,3]),import.meta.url)},{on:[c===t.results]}),s(()=>{h(()=>import(`./chunk/e2-9fzwE.min.js`),__vite__mapDeps([10,3]),import.meta.url)},{on:[c===t.preferences]});export{e as a,l as i,o as n,n as r,a as t};
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./chunk/U6YV4Y8e.min.js","./sxng-mapview.min.css","./chunk/DpvWr1cn.min.js","./chunk/DK4yUVpy.min.js","./chunk/DcK-mo-Y.min.js","./chunk/CTP0QVHn.min.js","./chunk/C93hSkpT.min.js","./chunk/5Ako-qGW.min.js","./chunk/DvCYLbJr.min.js","./chunk/B8prKeWj.min.js","./chunk/e2-9fzwE.min.js"])))=>i.map(i=>d[i]);
var e=class{id;constructor(e){this.id=e,queueMicrotask(()=>this.invoke())}async invoke(){try{console.debug(`[PLUGIN] ${this.id}: Running...`);let e=await this.run();if(!e)return;console.debug(`[PLUGIN] ${this.id}: Running post-exec...`),await this.post(e)}catch(e){console.error(`[PLUGIN] ${this.id}:`,e)}finally{console.debug(`[PLUGIN] ${this.id}: Done.`)}}},t={index:`index`,results:`results`,preferences:`preferences`,unknown:`unknown`},n={closeDetail:void 0,scrollPageToSelected:void 0,selectImage:void 0,selectNext:void 0,selectPrevious:void 0},r=()=>{let e=document.querySelector(`meta[name="endpoint"]`)?.getAttribute(`content`);return e&&e in t?e:t.unknown},i=()=>{let e=document.querySelector(`script[client_settings]`)?.getAttribute(`client_settings`);if(!e)return{};try{return JSON.parse(atob(e))}catch(e){return console.error(`Failed to load client_settings:`,e),{}}},a=async(e,t,n)=>{let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??3e4),a=await fetch(t,{body:n?.body,method:e,signal:r.signal}).finally(()=>clearTimeout(i));if(!a.ok)throw Error(a.statusText);return a},o=(e,t,n,r)=>{if(typeof t!=`string`){t.addEventListener(e,n,r);return}document.addEventListener(e,e=>{for(let r of e.composedPath())if(r instanceof HTMLElement&&r.matches(t)){try{n.call(r,e)}catch(e){console.error(e)}break}},r)},s=(e,t)=>{for(let e of t?.on??[])if(!e)return;document.readyState===`loading`?o(`DOMContentLoaded`,document,e,{once:!0}):e()},c=r(),l=i(),u=(e,t)=>{d(t)&&e()},d=e=>{switch(e.on){case`global`:return!0;case`endpoint`:return!!e.where.includes(c)}},f=`modulepreload`,p=function(e,t){return new URL(e,t).href},m={},h=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=p(t,n),t in m)return;m[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:f,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};s(()=>{document.documentElement.classList.remove(`no-js`),document.documentElement.classList.add(`js`),o(`click`,`.close`,function(){this.parentNode?.classList.add(`invisible`)}),o(`click`,`.searxng_init_map`,async function(e){e.preventDefault(),this.classList.remove(`searxng_init_map`),u(()=>h(async()=>{let{default:e}=await import(`./chunk/U6YV4Y8e.min.js`);return{default:e}},__vite__mapDeps([0,1]),import.meta.url).then(({default:e})=>new e(this)),{on:`endpoint`,where:[t.results]})}),l.plugins?.includes(`infiniteScroll`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/DpvWr1cn.min.js`);return{default:e}},__vite__mapDeps([2,3,4]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]}),l.plugins?.includes(`calculator`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/CTP0QVHn.min.js`);return{default:e}},__vite__mapDeps([5,4,3]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]})}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([6,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([7,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([8,3]),import.meta.url)},{on:[c===t.index]}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([6,3]),import.meta.url),h(()=>import(`./chunk/B8prKeWj.min.js`),__vite__mapDeps([9,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([7,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([8,3]),import.meta.url)},{on:[c===t.results]}),s(()=>{h(()=>import(`./chunk/e2-9fzwE.min.js`),__vite__mapDeps([10,3]),import.meta.url)},{on:[c===t.preferences]});export{e as a,l as i,o as n,n as r,a as t};
//# sourceMappingURL=sxng-core.min.js.map

File diff suppressed because one or more lines are too long

View File

@@ -74,8 +74,6 @@ sxng_locales = (
('ja-JP', '日本語', '日本', 'Japanese', '\U0001f1ef\U0001f1f5'),
('ko', '한국어', '', 'Korean', '\U0001f310'),
('ko-KR', '한국어', '대한민국', 'Korean', '\U0001f1f0\U0001f1f7'),
('mi', 'Māori', '', 'Māori', '\U0001f310'),
('mi-NZ', 'Māori', 'Aotearoa', 'Māori', '\U0001f1f3\U0001f1ff'),
('nb', 'Norsk Bokmål', '', 'Norwegian Bokmål', '\U0001f310'),
('nb-NO', 'Norsk Bokmål', 'Norge', 'Norwegian Bokmål', '\U0001f1f3\U0001f1f4'),
('nl', 'Nederlands', '', 'Dutch', '\U0001f310'),

View File

@@ -28,23 +28,21 @@
# DZDevelopers <dzdevelopers@noreply.codeberg.org>, 2025.
# youtherthyf <youtherthyf@noreply.codeberg.org>, 2025.
# mustafa-phd <mustafa-phd@noreply.codeberg.org>, 2026.
# nebras <nebras@noreply.codeberg.org>, 2026.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-06-13 11:31+0000\n"
"PO-Revision-Date: 2026-06-28 18:07+0000\n"
"Last-Translator: nebras <nebras@noreply.codeberg.org>\n"
"Language-Team: Arabic <https://translate.codeberg.org/projects/searxng/"
"searxng/ar/>\n"
"PO-Revision-Date: 2026-05-27 12:07+0000\n"
"Last-Translator: mustafa-phd <mustafa-phd@noreply.codeberg.org>\n"
"Language: ar\n"
"Language-Team: Arabic "
"<https://translate.codeberg.org/projects/searxng/searxng/ar/>\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : "
"n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 2026.6.1\n"
"Generated-By: Babel 2.18.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@@ -1655,11 +1653,11 @@ msgstr "الدقة"
#: searx/templates/simple/result_templates/images.html:55
msgid "Image formats"
msgstr "صيغ الصور"
msgstr ""
#: searx/templates/simple/result_templates/images.html:56
msgid "original format"
msgstr "الصيغة الاصلية"
msgstr ""
#: searx/templates/simple/result_templates/images.html:63
msgid "Engine"
@@ -2404,3 +2402,4 @@ msgstr "إخفاء الفيديو"
#~ msgid "Format"
#~ msgstr "صيغة"

View File

@@ -25,22 +25,20 @@
# nhthinh <nhthinh@noreply.codeberg.org>, 2025.
# recreationalprogamer <recreationalprogamer@noreply.codeberg.org>, 2025.
# namhv05 <namhv05@noreply.codeberg.org>, 2026.
# Anonymous104 <anonymous104@noreply.codeberg.org>, 2026.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-06-13 11:31+0000\n"
"PO-Revision-Date: 2026-06-28 18:07+0000\n"
"Last-Translator: Anonymous104 <anonymous104@noreply.codeberg.org>\n"
"Language-Team: Vietnamese <https://translate.codeberg.org/projects/searxng/"
"searxng/vi/>\n"
"PO-Revision-Date: 2026-05-19 12:08+0000\n"
"Last-Translator: return42 <return42@noreply.codeberg.org>\n"
"Language: vi\n"
"Language-Team: Vietnamese "
"<https://translate.codeberg.org/projects/searxng/searxng/vi/>\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 2026.6.1\n"
"Generated-By: Babel 2.18.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@@ -1653,11 +1651,11 @@ msgstr "Độ phân giải"
#: searx/templates/simple/result_templates/images.html:55
msgid "Image formats"
msgstr "Các định dạng ảnh"
msgstr ""
#: searx/templates/simple/result_templates/images.html:56
msgid "original format"
msgstr "định dạng gốc"
msgstr ""
#: searx/templates/simple/result_templates/images.html:63
msgid "Engine"
@@ -2421,3 +2419,4 @@ msgstr "ẩn video"
#~ msgid "Format"
#~ msgstr "Định dạng"

View File

@@ -26,7 +26,7 @@ from lxml.etree import XPath, XPathError, XPathSyntaxError
from lxml.etree import ElementBase, _Element # pyright: ignore[reportPrivateUsage]
from searx import settings
from searx.data import USER_AGENTS
from searx.data import USER_AGENTS, gsa_useragents_loader
from searx.version import VERSION_TAG
from searx.exceptions import SearxXPathSyntaxException, SearxEngineXPathException
from searx import logger
@@ -82,6 +82,14 @@ def gen_useragent(os_string: str | None = None) -> str:
)
def gen_gsa_useragent() -> str:
"""Return a random "Google Go App" User Agent suitable for Google
See searx/data/gsa_useragents.txt
"""
return choice(gsa_useragents_loader()) + " NSTNWV"
class HTMLTextExtractor(HTMLParser):
"""Internal class to extract text from HTML"""

View File

@@ -131,8 +131,8 @@ def fetch_traits_map() -> EngineTraitsMap:
def filter_locales(traits_map: EngineTraitsMap) -> set[str]:
"""Filter language & region tags by a threshold."""
min_eng_per_region = 19
min_eng_per_lang = 24
min_eng_per_region = 18
min_eng_per_lang = 23
_: dict[str, int] = {}
for eng in traits_map.values():

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python
# SPDX-License-Identifier: AGPL-3.0-or-later
"""This script fetches user agents suitable for Google.
Output file: :origin:`searx/data/gsa_useragents.txt` (:origin:`CI Update data
... <.github/workflows/data-update.yml>`).
.. Source for user agents: https://github.com/intoli/user-agents/
"""
from gzip import decompress
from json import loads
from searx.data import data_dir
from searx.network import get as http_get
from searx.utils import searxng_useragent
DATA_FILE = data_dir / "gsa_useragents.txt"
URL = "https://raw.githubusercontent.com/intoli/user-agents/main/src/user-agents.json.gz"
def fetch_gsa_useragents() -> list[str]:
response = http_get(URL, timeout=3.0, headers={"User-Agent": searxng_useragent()})
response.raise_for_status()
suas: set[str] = set()
for ua in loads(decompress(response.content)):
if (
"Android" in ua["userAgent"]
and "Chrome" in ua["userAgent"]
and "Samsung" not in ua["userAgent"]
and "Android 10; K" not in ua["userAgent"]
):
suas.add(ua["userAgent"])
luas = list(suas)
luas.sort()
return luas
if __name__ == "__main__":
useragents = fetch_gsa_useragents()
with DATA_FILE.open("w", encoding="utf-8") as f:
f.write("\n".join(useragents))

View File

@@ -776,11 +776,8 @@ docs.live() {
pyenv.install
docs.prebuild
# shellcheck disable=SC2086
SPHINX_AUTOBUILD_DEBUG=1 PATH="${PY_ENV_BIN}:${PATH}" pyenv.cmd sphinx-autobuild \
${SPHINX_VERBOSE} ${SPHINXOPTS} \
--watch ./searx \
--re-ignore '(^|/)\.#|^(?!.*\.(rst|py|yml)$).*' \
--open-browser --host 0.0.0.0 \
PATH="${PY_ENV_BIN}:${PATH}" pyenv.cmd sphinx-autobuild \
${SPHINX_VERBOSE} ${SPHINXOPTS} --open-browser --host 0.0.0.0 \
-b html -c ./docs -d "${DOCS_BUILD}/.doctrees" ./docs "${DOCS_DIST}"
dump_return $?
}

View File

@@ -7,6 +7,7 @@ data.:
all : update searx/sxng_locales.py and searx/data/*
traits : update searx/data/engine_traits.json & searx/sxng_locales.py
useragents : update searx/data/useragents.json with the most recent versions of Firefox
gsa_useragents: update searx/data/gsa_useragents.txt with compatible useragents
locales : update searx/data/locales.json from babel
currencies : update searx/data/currencies.json from wikidata
EOF
@@ -19,6 +20,7 @@ data.all() {
pyenv.activate
data.traits
data.useragents
data.gsa_useragents
data.locales
build_msg DATA "update searx/data/osm_keys_tags.json"
@@ -53,6 +55,12 @@ data.useragents() {
dump_return $?
}
data.gsa_useragents() {
build_msg DATA "update searx/data/gsa_useragents.txt"
pyenv.cmd python searxng_extra/update/update_gsa_useragents.py
dump_return $?
}
data.locales() {
(
set -e