1 Commits

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

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