1 Commits

Author SHA1 Message Date
dependabot[bot]
45c18a4c78 [upd] pypi: Bump redis from 5.0.8 to 5.2.1
Bumps [redis](https://github.com/redis/redis-py) from 5.0.8 to 5.2.1.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.0.8...v5.2.1)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-28 09:43:24 +00:00
488 changed files with 20687 additions and 54440 deletions

View File

@@ -3,8 +3,7 @@
"dockerfile": "Dockerfile"
},
"features": {
"ghcr.io/devcontainers/features/github-cli": {},
"ghcr.io/devcontainers/features/docker-in-docker": {}
"ghcr.io/devcontainers/features/github-cli": {}
},
"customizations": {
"vscode": {

View File

@@ -16,17 +16,11 @@ max_line_length = 119
[*.html]
indent_size = 4
[*.css]
indent_size = 2
[*.less]
indent_size = 2
[*.js]
indent_size = 2
[*.json]
indent_size = 2
indent_size = 4
insert_final_newline = ignore
# Minified JavaScript files shouldn't be changed

View File

@@ -12,11 +12,11 @@ updates:
prefix: "[upd] pypi:"
- package-ecosystem: "npm"
directory: "/client/simple"
directory: "/searx/static/themes/simple"
schedule:
interval: "weekly"
day: "friday"
open-pull-requests-limit: 5
target-branch: "master"
commit-message:
prefix: "[upd] web-client (simple):"
prefix: "[upd] npm:"

View File

@@ -7,7 +7,7 @@ on: # yamllint disable-line rule:truthy
jobs:
checker:
name: Checker
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -12,10 +12,10 @@ permissions:
jobs:
python:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
strategy:
matrix:
os: [ubuntu-24.04]
os: [ubuntu-20.04]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout
@@ -23,17 +23,32 @@ jobs:
- name: Install Ubuntu packages
run: |
sudo ./utils/searxng.sh install packages
sudo apt install firefox
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: 'x64'
- name: Cache Python dependencies
id: cache-python
uses: actions/cache@v4
with:
path: |
./local
./.nvm
./node_modules
key: python-${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('requirements*.txt', 'setup.py') }}
- name: Install Python dependencies
if: steps.cache-python.outputs.cache-hit != 'true'
run: |
make V=1 install
make V=1 gecko.driver
- name: Run tests
run: make V=1 ci.test
themes:
name: Themes
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -44,12 +59,23 @@ jobs:
with:
python-version: '3.12'
architecture: 'x64'
- name: Cache Python dependencies
id: cache-python
uses: actions/cache@v4
with:
path: |
./local
./.nvm
./node_modules
key: python-ubuntu-20.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
- name: Install node dependencies
run: make V=1 node.env
- name: Build themes
run: make themes.all
run: make V=1 themes.all
documentation:
name: Documentation
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
permissions:
contents: write # for JamesIves/github-pages-deploy-action to push changes in repo
steps:
@@ -73,7 +99,7 @@ jobs:
./local
./.nvm
./node_modules
key: python-ubuntu-24.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
key: python-ubuntu-20.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
- name: Build documentation
run: |
make V=1 docs.clean docs.html
@@ -90,7 +116,7 @@ jobs:
babel:
name: Update translations branch
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
if: ${{ github.repository_owner == 'searxng' && github.ref == 'refs/heads/master' }}
needs:
- python
@@ -140,7 +166,7 @@ jobs:
- documentation
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- name: Checkout
if: env.DOCKERHUB_USERNAME != null

View File

@@ -7,7 +7,7 @@ on: # yamllint disable-line rule:truthy
jobs:
dockers:
name: Trivy ${{ matrix.image }}
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@@ -7,7 +7,7 @@ on: # yamllint disable-line rule:truthy
jobs:
babel:
name: "create PR for additions from weblate"
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
if: ${{ github.repository_owner == 'searxng' && github.ref == 'refs/heads/master' }}
steps:
- name: Checkout
@@ -28,7 +28,7 @@ jobs:
./local
./.nvm
./node_modules
key: python-ubuntu-24.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
key: python-ubuntu-20.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
- name: weblate & git setup
env:
WEBLATE_CONFIG: ${{ secrets.WEBLATE_CONFIG }}

2
.nvmrc
View File

@@ -1 +1 @@
v23.5
v20.10

View File

@@ -1,4 +1,2 @@
nodejs 23.5.0
python 3.13.1
shellcheck 0.10.0
sqlite 3.47.2
python 3.12.0
shellcheck 0.9.0

View File

@@ -173,7 +173,4 @@ features or generally made searx better:
- Austin Olacsi `<https://github.com/Austin-Olacsi>`
- @micsthepick
- Daniel Kukula `<https://github.com/dkuku>`
- Patrick Evans `https://github.com/holysoles`
- Daniel Mowitz `<https://daniel.mowitz.rocks>`
- `Bearz314 <https://github.com/bearz314>`_
- Tommaso Colella `<https://github.com/gioleppe>`
- Patrick Evans `https://github.com/holysoles`

View File

@@ -12,7 +12,6 @@ RUN addgroup -g ${SEARXNG_GID} searxng && \
ENV INSTANCE_NAME=searxng \
AUTOCOMPLETE= \
BASE_URL= \
BIND_ADDRESS=[::]:8080 \
MORTY_KEY= \
MORTY_URL= \
SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml \
@@ -63,8 +62,6 @@ RUN su searxng -c "/usr/bin/python3 -m compileall -q searx" \
-o -name '*.svg' -o -name '*.ttf' -o -name '*.eot' \) \
-type f -exec gzip -9 -k {} \+ -exec brotli --best {} \+
HEALTHCHECK CMD wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
# Keep these arguments at the end to prevent redundant layer rebuilds
ARG LABEL_DATE=
ARG GIT_URL=unknown

View File

@@ -50,8 +50,8 @@ search.checker.%: install
$(Q)./manage pyenv.cmd searxng-checker -v "$(subst _, ,$(patsubst search.checker.%,%,$@))"
PHONY += test ci.test test.shell
ci.test: test.yamllint test.black test.types.ci test.pylint test.unit test.robot test.rst test.shell test.pybabel
test: test.yamllint test.black test.types.dev test.pylint test.unit test.robot test.rst test.shell
ci.test: test.yamllint test.black test.pyright test.pylint test.unit test.robot test.rst test.pybabel
test: test.yamllint test.black test.pyright test.pylint test.unit test.robot test.rst test.shell
test.shell:
$(Q)shellcheck -x -s dash \
dockerfiles/docker-entrypoint.sh
@@ -75,7 +75,7 @@ test.shell:
# wrap ./manage script
MANAGE += weblate.translations.commit weblate.push.translations
MANAGE += data.all data.traits data.useragents data.locales data.currencies
MANAGE += data.all data.traits data.useragents data.locales
MANAGE += docs.html docs.live docs.gh-pages docs.prebuild docs.clean
MANAGE += docker.build docker.push docker.buildx
MANAGE += gecko.driver
@@ -83,8 +83,8 @@ MANAGE += node.env node.env.dev node.clean
MANAGE += py.build py.clean
MANAGE += pyenv pyenv.install pyenv.uninstall
MANAGE += format.python
MANAGE += test.yamllint test.pylint test.black test.pybabel test.unit test.coverage test.robot test.rst test.clean test.themes test.types.dev test.types.ci
MANAGE += themes.all themes.fix themes.test
MANAGE += test.yamllint test.pylint test.pyright test.black test.pybabel test.unit test.coverage test.robot test.rst test.clean
MANAGE += themes.all themes.simple themes.simple.test pygments.less
MANAGE += static.build.commit static.build.drop static.build.restore
MANAGE += nvm.install nvm.clean nvm.status nvm.nodejs

View File

@@ -2,7 +2,7 @@
----
.. figure:: https://raw.githubusercontent.com/searxng/searxng/master/client/simple/src/brand/searxng.svg
.. figure:: https://raw.githubusercontent.com/searxng/searxng/master/src/brand/searxng.svg
:target: https://docs.searxng.org/
:alt: SearXNG
:width: 100%
@@ -34,7 +34,7 @@ A user_, admin_ and developer_ handbook is available on the homepage_.
.. _homepage: https://docs.searxng.org/
.. _metasearch engine: https://en.wikipedia.org/wiki/Metasearch_engine
.. |SearXNG logo| image:: https://raw.githubusercontent.com/searxng/searxng/master/client/simple/src/brand/searxng-wordmark.svg
.. |SearXNG logo| image:: https://raw.githubusercontent.com/searxng/searxng/master/src/brand/searxng-wordmark.svg
:target: https://docs.searxng.org/
:width: 5%

View File

@@ -1,3 +0,0 @@
dist
node_modules
.stylelintcache

View File

@@ -1,17 +0,0 @@
{
"formatter": "unix",
"plugins": [ "stylelint-prettier" ],
"extends": [ "stylelint-config-standard-less" ],
"rules": {
"prettier/prettier": true,
"declaration-empty-line-before": null,
"no-invalid-position-at-import-rule": null,
"property-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"selector-attribute-quotes": null,
"shorthand-property-no-redundant-values": null,
"at-rule-no-vendor-prefix": null,
"selector-id-pattern": null,
"selector-class-pattern": null
}
}

View File

@@ -1,24 +0,0 @@
=====================
MEMO vite development
=====================
Local install::
# in folder ./client/simple/
$ npm install
Start development server::
$ ./manage vite.simple.dev
# in folder ./client/simple/
$ npm exec -- vite
Fix source code::
# in folder ./client/simple/
$ npm run fix
Fix & Build::
$ ./manage vite.simple.build

View File

@@ -1,34 +0,0 @@
import globals from "globals";
import pluginJs from "@eslint/js";
/** @type {import('eslint').Linter.Config[]} */
export default [
pluginJs.configs.recommended,
// global "ignores"
// https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores
{
ignores: ["node_modules/", "dist/"]
},
{
files: [
"**/*.js",
],
linterOptions: {
reportUnusedDisableDirectives: "error",
// noInlineConfig: true
},
languageOptions: {
sourceType: "module",
globals: {
...globals.browser,
}
},
rules: {
indent: ["error", 2],
},
},
];

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +0,0 @@
{
"name": "simple",
"version": "1.0.0",
"type": "module",
"scripts": {
"clean": "rm -Rf node_modules",
"build": "node theme_icons.js && vite build",
"fix": "eslint --fix && stylelint --fix strict 'src/**/*.{css,scss,sass,less,styl,vue,svelte}'",
"icons.html": "node theme_icons.js"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2",
"edge.js": "^6.2.1",
"eslint": "^9.24.0",
"filemanager-webpack-plugin": "^8.0.0",
"globals": "^16.0.0",
"ionicons": "^7.4.0",
"leaflet": "^1.9.4",
"less": "^4.3.0",
"less-loader": "^12.2.0",
"normalize.css": "^8.0.1",
"sharp": "^0.34.1",
"style-loader": "^4.0.0",
"stylelint": "^16.17.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-standard-less": "^3.0.1",
"stylelint-prettier": "^5.0.3",
"svgo": "^3.3.2",
"swiped-events": "^1.2.0",
"vite": "^6.2.6",
"vite-plugin-static-copy": "^2.3.0",
"vite-plugin-stylelint": "^6.0.0",
"webpack": "^5.99.5",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"autocomplete-js": "^2.7.1"
}
}

View File

@@ -1,184 +0,0 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
import "../../../node_modules/swiped-events/src/swiped-events.js";
(function (w, d, searxng) {
'use strict';
if (searxng.endpoint !== 'results') {
return;
}
searxng.ready(function () {
d.querySelectorAll('#urls img').forEach(
img =>
img.addEventListener(
'error', () => {
// console.log("ERROR can't load: " + img.src);
img.src = window.searxng.settings.theme_static_path + "/img/img_load_error.svg";
},
{once: true}
));
if (d.querySelector('#search_url button#copy_url')) {
d.querySelector('#search_url button#copy_url').style.display = "block";
}
searxng.on('.btn-collapse', 'click', function () {
var btnLabelCollapsed = this.getAttribute('data-btn-text-collapsed');
var btnLabelNotCollapsed = this.getAttribute('data-btn-text-not-collapsed');
var target = this.getAttribute('data-target');
var targetElement = d.querySelector(target);
var html = this.innerHTML;
if (this.classList.contains('collapsed')) {
html = html.replace(btnLabelCollapsed, btnLabelNotCollapsed);
} else {
html = html.replace(btnLabelNotCollapsed, btnLabelCollapsed);
}
this.innerHTML = html;
this.classList.toggle('collapsed');
targetElement.classList.toggle('invisible');
});
searxng.on('.media-loader', 'click', function () {
var target = this.getAttribute('data-target');
var iframe_load = d.querySelector(target + ' > iframe');
var srctest = iframe_load.getAttribute('src');
if (srctest === null || srctest === undefined || srctest === false) {
iframe_load.setAttribute('src', iframe_load.getAttribute('data-src'));
}
});
searxng.on('#copy_url', 'click', function () {
var target = this.parentElement.querySelector('pre');
navigator.clipboard.writeText(target.innerText);
this.innerText = this.dataset.copiedText;
});
// searxng.selectImage (gallery)
// -----------------------------
// setTimeout() ID, needed to cancel *last* loadImage
let imgTimeoutID;
// progress spinner, while an image is loading
const imgLoaderSpinner = d.createElement('div');
imgLoaderSpinner.classList.add('loader');
// singleton image object, which is used for all loading processes of a
// detailed image
const imgLoader = new Image();
const loadImage = (imgSrc, onSuccess) => {
// if defered image load exists, stop defered task.
if (imgTimeoutID) clearTimeout(imgTimeoutID);
// defer load of the detail image for 1 sec
imgTimeoutID = setTimeout(() => {
imgLoader.src = imgSrc;
}, 1000);
// set handlers in the on-properties
imgLoader.onload = () => {
onSuccess();
imgLoaderSpinner.remove();
};
imgLoader.onerror = () => {
imgLoaderSpinner.remove();
};
};
searxng.selectImage = (resultElement) => {
// add a class that can be evaluated in the CSS and indicates that the
// detail view is open
d.getElementById('results').classList.add('image-detail-open');
// add a hash to the browser history so that pressing back doesn't return
// to the previous page this allows us to dismiss the image details on
// pressing the back button on mobile devices
window.location.hash = '#image-viewer';
searxng.scrollPageToSelected();
// if there is none element given by the caller, stop here
if (!resultElement) return;
// find <img> object in the element, if there is none, stop here.
const img = resultElement.querySelector('.result-images-source img');
if (!img) return;
// <img src="" data-src="http://example.org/image.jpg">
const src = img.getAttribute('data-src');
// already loaded high-res image or no high-res image available
if (!src) return;
// use the image thumbnail until the image is fully loaded
const thumbnail = resultElement.querySelector('.image_thumbnail');
img.src = thumbnail.src;
// show a progress spinner
const detailElement = resultElement.querySelector('.detail');
detailElement.appendChild(imgLoaderSpinner);
// load full size image in background
loadImage(src, () => {
// after the singelton loadImage has loaded the detail image into the
// cache, it can be used in the origin <img> as src property.
img.src = src;
img.removeAttribute('data-src');
});
};
searxng.closeDetail = function () {
d.getElementById('results').classList.remove('image-detail-open');
// remove #image-viewer hash from url by navigating back
if (window.location.hash == '#image-viewer') window.history.back();
searxng.scrollPageToSelected();
};
searxng.on('.result-detail-close', 'click', e => {
e.preventDefault();
searxng.closeDetail();
});
searxng.on('.result-detail-previous', 'click', e => {
e.preventDefault();
searxng.selectPrevious(false);
});
searxng.on('.result-detail-next', 'click', e => {
e.preventDefault();
searxng.selectNext(false);
});
// listen for the back button to be pressed and dismiss the image details when called
window.addEventListener('hashchange', () => {
if (window.location.hash != '#image-viewer') searxng.closeDetail();
});
d.querySelectorAll('.swipe-horizontal').forEach(
obj => {
obj.addEventListener('swiped-left', function () {
searxng.selectNext(false);
});
obj.addEventListener('swiped-right', function () {
searxng.selectPrevious(false);
});
}
);
w.addEventListener('scroll', function () {
var e = d.getElementById('backToTop'),
scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
results = d.getElementById('results');
if (e !== null) {
if (scrollTop >= 100) {
results.classList.add('scrolling');
} else {
results.classList.remove('scrolling');
}
}
}, true);
});
})(window, document, window.searxng);

View File

@@ -1 +0,0 @@
import "./head/00_init.js";

View File

@@ -1,7 +0,0 @@
import "./main/00_toolkit.js";
import "./main/infinite_scroll.js";
import "./main/keyboard.js";
import "./main/mapresult.js";
import "./main/preferences.js";
import "./main/results.js";
import "./main/search.js";

View File

@@ -1,35 +0,0 @@
/*
Layout of the KeyValue result class
*/
#main_results .result-keyvalue {
caption {
padding: 0.8rem 0.5rem;
font-style: italic;
caption-side: bottom;
background-color: var(--color-result-keyvalue-table);
}
.col-key {
width: 25%;
}
table {
word-break: break-word;
table-layout: fixed;
width: 100%;
background-color: var(--color-result-keyvalue-table);
}
tr.odd {
background-color: var(--color-result-keyvalue-odd);
}
tr.even {
background-color: var(--color-result-keyvalue-even);
}
th,
td {
padding: 0.3rem 0.5rem;
}
}

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path fill="#58f" d="M11 20.85a.92.92 0 0 1-1.1.93A10 10 0 0 1 2.06 13c-.06-.55.4-1 .95-1h3a1 1 0 0 1 1 1 3 3 0 0 0 3 3 1 1 0 0 1 1 1v3.85Zm6-1.92c0 .77.83 1.23 1.42.74a10 10 0 0 0 2.03-2.32c.39-.61-.09-1.35-.81-1.35H18a1 1 0 0 0-1 1v1.93ZM12 2a10 10 0 0 1 6.65 2.53c.61.55.17 1.47-.65 1.47h-.15A2.85 2.85 0 0 0 15 8.85c0 .33-.18.62-.47.77l-.08.04a1 1 0 0 1-.9 0l-.08-.04a.85.85 0 0 1-.47-.77A2.85 2.85 0 0 0 10.15 6H10a1 1 0 0 1-1-1V3.2c0-.44.28-.84.7-.94C10.45 2.1 11.22 2 12 2Z"/>
<path fill="#58f" d="M3.42 10c-.63 0-1.1-.58-.9-1.18.6-1.8 1.7-3.36 3.12-4.53C6.2 3.82 7 4.26 7 5a3 3 0 0 0 3 3h.15c.47 0 .85.38.85.85 0 1.09.61 2.07 1.58 2.56l.08.04a3 3 0 0 0 2.68 0l.08-.04A2.85 2.85 0 0 0 17 8.85c0-.47.38-.85.85-.85h2.66c.4 0 .77.23.9.6a9.98 9.98 0 0 1 .52 4.6.94.94 0 0 1-.95.8H18a3 3 0 0 0-3 3v3.8c0 .44-.28.84-.7.94l-.2.04a.92.92 0 0 1-1.1-.93V17a3 3 0 0 0-3-3 1 1 0 0 1-1-1 3 3 0 0 0-3-3H3.42Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 989 B

View File

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

Before

Width:  |  Height:  |  Size: 648 B

View File

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

Before

Width:  |  Height:  |  Size: 787 B

View File

@@ -1,2 +0,0 @@
leaflet.css -diff
leaflet.js -diff

View File

@@ -1,84 +0,0 @@
/**
* Generate icons.html for the jinja templates of the simple theme.
*/
import { argv } from "node:process";
import { dirname, resolve } from "node:path";
import { jinja_svg_sets } from "./tools/jinja_svg_catalog.js";
const HERE = dirname(argv[1]) + "/";
const dest = resolve(HERE, "../../searx/templates/simple/icons.html");
/** @type import("./tools/jinja_svg_catalog.js").JinjaMacro[] */
const searxng_jinja_macros = [
{ name: "icon", class: "sxng-icon-set" },
{ name: "icon_small", class: "sxng-icon-set-small" },
{ name: "icon_big", class: "sxng-icon-set-big" },
];
const sxng_icon_opts ={
multipass: true,
plugins: [
{ name: "removeTitle" },
{ name: "removeXMLNS" },
{ name: "addAttributesToSVGElement",
params: {
attributes: [
{
"aria-hidden": "true",
}]}}]
};
/** @type import("./tools/jinja_svg_catalog.js").IconSet */
const simple_icons = [
{
base: resolve(HERE, "node_modules/ionicons/dist/svg"),
set: {
"alert": "alert-outline.svg",
"appstore": "apps-outline.svg",
"book": "book-outline.svg",
"close": "close-outline.svg",
"download": "download-outline.svg",
"ellipsis-vertical": "ellipsis-vertical-outline.svg",
"file-tray-full": "file-tray-full-outline.svg",
"film": "film-outline.svg",
"globe": "globe-outline.svg",
"heart": "heart-outline.svg",
"image": "image-outline.svg",
"layers": "layers-outline.svg",
"leecher": "arrow-down.svg",
"location": "location-outline.svg",
"magnet": "magnet-outline.svg",
"musical-notes": "musical-notes-outline.svg",
"navigate-down": "chevron-down-outline.svg",
"navigate-left": "chevron-back-outline.svg",
"navigate-right": "chevron-forward-outline.svg",
"navigate-up": "chevron-up-outline.svg",
"people": "people-outline.svg",
"play": "play-outline.svg",
"radio": "radio-outline.svg",
"save": "save-outline.svg",
"school": "school-outline.svg",
"search": "search-outline.svg",
"seeder": "swap-vertical.svg",
"settings": "settings-outline.svg",
"tv": "tv-outline.svg",
},
svgo_opts: sxng_icon_opts,
},
// some of the ionicons are not suitable for a dark theme, we fixed the svg
// manually in src/svg/ionicons
// - https://github.com/searxng/searxng/pull/4284#issuecomment-2680550342
{
base: resolve(HERE, "src/svg/ionicons"),
set: {
"information-circle": "information-circle-outline.svg",
"newspaper": "newspaper-outline.svg",
},
svgo_opts: sxng_icon_opts,
}
];
jinja_svg_sets(dest, searxng_jinja_macros, simple_icons);

View File

@@ -1,78 +0,0 @@
import fs from "fs";
import path from "path";
import sharp from "sharp";
import { optimize as svgo } from "svgo";
/**
* @typedef {object} Src2Dest - Mapping of src to dest
* @property {string} src - Name of the source file.
* @property {string} dest - Name of the destination file.
*/
/**
* Convert a list of SVG files to PNG.
*
* @param {Src2Dest[]} items - Array of SVG files (src: SVG, dest:PNG) to convert.
*/
async function svg2png (items) {
items.forEach(
async (item) => {
try {
fs.mkdir(path.dirname(item.dest), { recursive: true }, (err) => {
if (err)
throw err;
});
const info = await sharp(item.src).png({
force: true,
compressionLevel: 9,
palette: true,
}).toFile(item.dest);
console.log(
`[svg2png] created ${item.dest} -- bytes: ${info.size}, w:${info.width}px, h:${info.height}px`
);
} catch (err) {
console.error(`ERROR: ${item.dest} -- ${err}`);
throw(err);
}
}
);
}
/**
* Optimize SVG images for WEB.
*
* @param {import('svgo').Config} svgo_opts - Options passed to svgo.
* @param {Src2Dest[]} items - Array of SVG files (src:SVG, dest:SVG) to optimize.
*/
async function svg2svg(svgo_opts, items) {
items.forEach(
async (item) => {
try {
fs.mkdir(path.dirname(item.dest), { recursive: true }, (err) => {
if (err)
throw err;
});
const raw = fs.readFileSync(item.src, "utf8");
const opt = svgo(raw, svgo_opts);
fs.writeFileSync(item.dest, opt.data);
console.log(
`[svg2svg] optimized: ${item.dest} -- src: ${item.src}`
);
} catch (err) {
console.error(`ERROR: optimize src: ${item.src} -- ${err}`);
throw(err);
}
}
);
}
export { svg2png, svg2svg };

View File

@@ -1,26 +0,0 @@
{{--
This is a EDGE https://edgejs.dev/ template to generate a HTML Jinja template
for the backend. Example output of this EDGE template:
- https://github.com/searxng/searxng/blob/master/searx/templates/simple/icons.html
--}}
{#
Catalog of SVG symbols that can be inserted into the HTML output of a Jinja
template. This file from:
client/simple/tools/icon_catalog.edge.html
#}
{%-
set catalog = {
@each((svg, name) in svg_catalog)
'{{{name}}}' : '{{{svg}}}',
@end
}
-%}
@each(macro in macros)
{% macro {{ macro.name }}(action, alt) -%}
{{ open_curly_brace }} catalog[action] | replace("{{__jinja_class_placeholder__}}", "{{ macro.class }}") | safe {{ close_curly_brace }}
{%- endmacro %}
@end

View File

@@ -1,130 +0,0 @@
import fs from "fs";
import { resolve, dirname } from "path";
import { Edge } from 'edge.js';
import { optimize as svgo } from "svgo";
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const __jinja_class_placeholder__ = "__jinja_class_placeholder__";
// -- types
/**
* @typedef {object} IconSet - A set of icons
* @property {object[]} set - Array of SVG icons, where property name is the
* name of the icon and value is the src of the SVG (relative to base).
* @property {string} base - Folder in which the SVG src files are located.
* @property {import("svgo").Config} svgo_opts - svgo options for this set.
*/
/**
* @typedef {object} IconSVG - Mapping of icon name to SVG source file.
* @property {string} name - Name of the icon isource file.
* @property {string} src - Name of the destination file.
* @property {import("svgo").Config} svgo_opts - Options passed to svgo.
*/
/**
* @typedef {object} JinjaMacro - Arguments to create a jinja macro
* @property {string} name - Name of the jinja macro.
* @property {string} class - SVG's class name (value of XML class attribute)
*/
// -- functions
/**
* Generate a jinja template with a catalog of SVG icons that can be
* used in in other HTML jinja templates.
*
* @param {string} dest - filename of the generate jinja template.
* @param {JinjaMacro} macros - Jinja macros to create.
* @param {IconSVG[]} items - Array of SVG items.
*/
function jinja_svg_catalog(dest, macros, items) {
const svg_catalog = {};
const edge_template = resolve(__dirname, "jinja_svg_catalog.html.edge");
items.forEach(
(item) => {
/** @type {import("svgo").Config} */
const svgo_opts = JSON.parse(JSON.stringify(item.svgo_opts));
svgo_opts.plugins.push({
name: "addAttributesToSVGElement",
params: {
attributes: [{ "class": __jinja_class_placeholder__, }]
}}
);
try {
const raw = fs.readFileSync(item.src, "utf8");
const opt = svgo(raw, svgo_opts);
svg_catalog[item.name] = opt.data;
} catch (err) {
console.error(`ERROR: jinja_svg_catalog processing ${item.name} src: ${item.src} -- ${err}`);
throw(err);
}
}
);
fs.mkdir(dirname(dest), { recursive: true }, (err) => {
if (err) throw err;
});
const ctx = {
svg_catalog: svg_catalog,
macros: macros,
edge_template: edge_template,
__jinja_class_placeholder__: __jinja_class_placeholder__,
// see https://github.com/edge-js/edge/issues/162
open_curly_brace : "{{",
close_curly_brace : "}}"
};
const jinjatmpl = Edge.create().renderRawSync(
fs.readFileSync(edge_template, "utf-8"),
ctx
);
fs.writeFileSync(dest, jinjatmpl);
console.log(`[jinja_svg_catalog] created: ${dest}`);
}
/**
* Calls jinja_svg_catalog for a collection of icon sets where each set has its
* own parameters.
*
* @param {string} dest - filename of the generate jinja template.
* @param {JinjaMacro} macros - Jinja macros to create.
* @param {IconSet[]} sets - Array of SVG sets.
*/
function jinja_svg_sets(dest, macros, sets) {
/** @type IconSVG[] */
const items = [];
const all = [];
for (const obj of sets) {
for (const [name, file] of Object.entries(obj.set)) {
if (all.includes(name)) {
throw new Error(`ERROR: ${name} has already been defined`);
}
items.push({
name: name,
src: resolve(obj.base, file),
svgo_opts: obj.svgo_opts,
});
}
jinja_svg_catalog(dest, macros, items);
}
}
// -- exports
export {
jinja_svg_sets,
jinja_svg_catalog,
};

View File

@@ -1,41 +0,0 @@
/**
* Custom vite plugins to build the web-client components of the simple theme.
*
* HINT:
*
* This is an inital implementation for the migration of the build process
* from grunt to vite. For fully support (vite: build & serve) more work is
* needed.
*/
import { svg2png } from "./img.js";
import { svg2svg } from "./img.js";
/**
* Vite plugin to convert a list of SVG files to PNG.
*
* @param {import('./img.js').Src2Dest} items - Array of SVG files (src: SVG, dest:PNG) to convert.
*/
function plg_svg2png(items) {
return {
name: 'searxng-simple-svg2png',
apply: 'build', // or 'serve'
async writeBundle() { svg2png(items); },
};
}
/**
* Vite plugin to optimize SVG images for WEB.
*
* @param {import('svgo').Config} svgo_opts - Options passed to svgo.
* @param {import('./img.js').Src2Dest} items - Array of SVG files (src:SVG, dest:SVG) to optimize.
*/
function plg_svg2svg(svgo_opts, items) {
return {
name: 'searxng-simple-svg2png',
apply: 'build', // or 'serve'
async writeBundle() { svg2svg(items, svgo_opts); },
};
}
export { plg_svg2png, plg_svg2svg };

View File

@@ -1,185 +0,0 @@
/**
* CONFIG: https://vite.dev/config/
*/
import { resolve } from "node:path";
import { defineConfig } from "vite";
import stylelint from "vite-plugin-stylelint";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { plg_svg2png } from "./tools/plg.js";
import { plg_svg2svg } from "./tools/plg.js";
const ROOT = "../.."; // root of the git reposetory
const PATH = {
dist: resolve(ROOT, "searx/static/themes/simple"),
// dist: resolve(ROOT, "client/simple/dist"),
src: "src",
modules: "node_modules",
brand: "src/brand",
static: resolve(ROOT, "client/simple/static"),
leaflet: resolve(ROOT, "client/simple/node_modules/leaflet/dist"),
templates: resolve(ROOT, "searx/templates/simple"),
};
const svg2svg_opts = {
plugins: [
{ name: "preset-default" },
"sortAttrs",
"convertStyleToAttrs",
]
};
const svg2svg_favicon_opts = {
plugins: [
{ name: "preset-default" },
"sortAttrs",
]
};
export default defineConfig({
root: PATH.src,
mode: "production",
// mode: "development",
// FIXME: missing CCS sourcemaps!!
// see: https://github.com/vitejs/vite/discussions/13845#discussioncomment-11992084
//
// what I have tried so far (see config below):
//
// - build.sourcemap
// - esbuild.sourcemap
// - css.preprocessorOptions.less.sourceMap
css: {
devSourcemap: true,
preprocessorOptions: {
less: {
// FIXME: missing CCS sourcemaps!!
sourceMap: {
outputSourceFiles: true,
sourceMapURL: (name) => { const s = name.split('/'); return s[s.length - 1] + '.map'; },
},
// env: 'development',
// relativeUrls: true,
// javascriptEnabled: true,
},
},
}, // end: css
esbuild : {
// FIXME: missing CCS sourcemaps!!
sourcemap: true
},
build: {
manifest: "manifest.json",
emptyOutDir: true,
assetsDir: "",
outDir: PATH.dist,
// FIXME: missing CCS sourcemaps!!
sourcemap: true,
// https://vite.dev/config/build-options.html#build-cssminify
cssMinify: true,
// cssMinify: "esbuild",
minify: "esbuild",
rollupOptions: {
input: {
// build CSS files
"css/searxng.min.css": PATH.src + "/less/style-ltr.less",
"css/searxng-rtl.min.css": PATH.src + "/less/style-rtl.less",
"css/rss.min.css": PATH.src + "/less/rss.less",
// build JS files
"js/searxng.head.min": PATH.src + "/js/searxng.head.js",
"js/searxng.min": PATH.src + "/js/searxng.js",
},
// file naming conventions / pathnames are relative to outDir (PATH.dist)
output: {
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
assetFileNames: "[name].[ext]",
// Vite does not support "rollupOptions.output.sourcemap".
// Please use "build.sourcemap" instead.
// sourcemap: true,
},
},
}, // end: build
plugins: [
stylelint({
build: true,
emitWarningAsError: true,
fix: true,
}),
// Leaflet
viteStaticCopy({
targets: [
{ src: PATH.leaflet + "/leaflet.{js,js.map}", dest: PATH.dist + "/js" },
{ src: PATH.leaflet + "/images/*.png", dest: PATH.dist + "/css/images/" },
{ src: PATH.leaflet + "/*.{css,css.map}", dest: PATH.dist + "/css" },
{ src: PATH.static + "/**/*", dest: PATH.dist },
]
}),
// -- svg images
plg_svg2svg(
[
{ src: PATH.src + "/svg/empty_favicon.svg", dest: PATH.dist + "/img/empty_favicon.svg" },
{ src: PATH.src + "/svg/select-dark.svg", dest: PATH.dist + "/img/select-dark.svg" },
{ src: PATH.src + "/svg/select-light.svg", dest: PATH.dist + "/img/select-light.svg" },
],
svg2svg_opts,
),
// SearXNG brand (static)
plg_svg2png(
[
{ src: PATH.brand + "/searxng-wordmark.svg", dest: PATH.dist + "/img/favicon.png" },
{ src: PATH.brand + "/searxng.svg", dest: PATH.dist + "/img/searxng.png" },
],
),
// -- svg
plg_svg2svg(
[
{ src: PATH.brand + "/searxng.svg", dest: PATH.dist + "/img/searxng.svg" },
{ src: PATH.brand + "/img_load_error.svg", dest: PATH.dist + "/img/img_load_error.svg" },
],
svg2svg_opts,
),
// -- favicon
plg_svg2svg(
[ { src: PATH.brand + "/searxng-wordmark.svg", dest: PATH.dist + "/img/favicon.svg" } ],
svg2svg_favicon_opts,
),
// -- simple templates
plg_svg2svg(
[
{ src: PATH.brand + "/searxng-wordmark.svg", dest: PATH.templates + "/searxng-wordmark.min.svg" },
],
svg2svg_opts
),
] // end: plugins
});

View File

@@ -14,6 +14,8 @@ Environment variables:
BASE_URL settings.yml : server.base_url
MORTY_URL settings.yml : result_proxy.url
MORTY_KEY settings.yml : result_proxy.key
BIND_ADDRESS uwsgi bind to the specified TCP socket using HTTP protocol.
Default value: ${DEFAULT_BIND_ADDRESS}
Volume:
/etc/searxng the docker entry point copies settings.yml and uwsgi.ini in
this directory (see the -f command line option)"
@@ -21,6 +23,9 @@ Volume:
EOF
}
export DEFAULT_BIND_ADDRESS="0.0.0.0:8080"
export BIND_ADDRESS="${BIND_ADDRESS:-${DEFAULT_BIND_ADDRESS}}"
# Parse command line
FORCE_CONF_UPDATE=0
DRY_RUN=0
@@ -168,8 +173,6 @@ fi
unset MORTY_KEY
printf 'Listen on %s\n' "${BIND_ADDRESS}"
# Start uwsgi
# TODO: "--http-socket" will be removed in the future (see uwsgi.ini.new config file): https://github.com/searxng/searxng/pull/4578
exec uwsgi --http-socket "${BIND_ADDRESS}" "${UWSGI_SETTINGS_PATH}"
printf 'Listen on %s\n' "${BIND_ADDRESS}"
exec uwsgi --master --uid searxng --gid searxng --http-socket "${BIND_ADDRESS}" "${UWSGI_SETTINGS_PATH}"

View File

@@ -1,8 +1,4 @@
[uwsgi]
# Listening address
# default value: [::]:8080 (see Dockerfile)
http-socket = $(BIND_ADDRESS)
# Who will run the code
uid = searxng
gid = searxng
@@ -52,5 +48,7 @@ die-on-term
# uwsgi serves the static files
static-map = /static=/usr/local/searxng/searx/static
# expires set to one day
static-expires = /* 86400
static-gzip-all = True
offload-threads = %k

View File

@@ -1,13 +1,16 @@
.. _plugins admin:
.. _plugins generic:
===============
List of plugins
Plugins builtin
===============
Further reading ..
.. sidebar:: Further reading ..
- :ref:`SearXNG settings <settings plugins>`
- :ref:`dev plugin`
- :ref:`dev plugin`
Configuration defaults (at built time):
:DO: Default on
.. _configured plugins:
@@ -19,13 +22,18 @@ Further reading ..
:widths: 3 1 9
* - Name
- Active
- DO
- Description
{% for plg in plugins %}
JS & CSS dependencies
* - {{plg.info.name}}
- {{(plg.active and "yes") or "no"}}
- {{plg.info.description}}
{% for plgin in plugins %}
* - {{plgin.name}}
- {{(plgin.default_on and "y") or ""}}
- {{plgin.description}}
{% for dep in (plgin.js_dependencies + plgin.css_dependencies) %}
| ``{{dep}}`` {% endfor %}
{% endfor %}

View File

@@ -93,13 +93,15 @@ TOML_ configuration is created in the file ``/etc/searxng/favicons.toml``.
:py:obj:`cache.db_url <.FaviconCacheConfig.db_url>`:
The path to the (SQLite_) database file. The default path is in the `/tmp`_
folder, which is deleted on every reboot and is therefore unsuitable for a
production environment. The FHS_ provides the folder `/var/cache`_ for the
cache of applications, so a suitable storage location of SearXNG's caches is
folder ``/var/cache/searxng``.
production environment. The FHS_ provides the folder for the
application cache
In a standard installation (compare :ref:`create searxng user`), the folder
must be created and the user under which the SearXNG process is running must
be given write permission to this folder.
The FHS_ provides the folder `/var/cache`_ for the cache of applications, so a
suitable storage location of SearXNG's caches is folder ``/var/cache/searxng``.
In container systems, a volume should be mounted for this folder and in a
standard installation (compare :ref:`create searxng user`), the folder must be
created and the user under which the SearXNG process is running must be given
write permission to this folder.
.. code:: bash
@@ -107,10 +109,6 @@ TOML_ configuration is created in the file ``/etc/searxng/favicons.toml``.
$ sudo chown root:searxng /var/cache/searxng/
$ sudo chmod g+w /var/cache/searxng/
In container systems, a volume should be mounted for this folder. Check
whether the process in the container has read/write access to the mounted
folder.
:py:obj:`cache.LIMIT_TOTAL_BYTES <.FaviconCacheConfig.LIMIT_TOTAL_BYTES>`:
Maximum of bytes stored in the cache of all blobs. The limit is only reached
at each maintenance interval after which the oldest BLOBs are deleted; the

View File

@@ -13,7 +13,7 @@ Settings
:maxdepth: 2
settings
settings_engines
settings_engine
settings_brand
settings_general
settings_search
@@ -22,6 +22,6 @@ Settings
settings_redis
settings_outgoing
settings_categories_as_tabs
settings_plugins

View File

@@ -59,7 +59,7 @@ and can relied on the default configuration :origin:`searx/settings.yml` using:
use_default_settings: true
server:
secret_key: "ultrasecretkey" # change this!
bind_address: "[::]"
bind_address: "0.0.0.0"
``engines:``
With ``use_default_settings: true``, each settings can be override in a

View File

@@ -1,30 +1,14 @@
.. _settings engines:
.. _settings engine:
============
``engines:``
============
===========
``engine:``
===========
.. sidebar:: Further reading ..
- :ref:`configured engines`
- :ref:`engines-dev`
In the section ``engines:`` is a list of the engines that are to be made
available in the instance. Each list entry is in turn a key/value mapping.
.. code:: yaml
engines:
- name: dummy.online
engine: dummy
..
- name: dummy.offline
engine: dummy-offline
..
..
In the code example below a *full fledged* example of a YAML setup from a dummy
engine is shown. Most of the options have a default value or even are optional.
@@ -35,7 +19,7 @@ engine is shown. Most of the options have a default value or even are optional.
.. code:: yaml
- name: example
- name: example engine
engine: example
shortcut: demo
base_url: 'https://{language}.example.com/'

View File

@@ -43,7 +43,7 @@ Communication with search engines.
Global timeout of the requests made to others engines in seconds. A bigger
timeout will allow to wait for answers from slow engines, but in consequence
will slow SearXNG reactivity (the result page may take the time specified in the
timeout to load). Can be override by ``timeout`` in the :ref:`settings engines`.
timeout to load). Can be override by ``timeout`` in the :ref:`settings engine`.
``useragent_suffix`` :
Suffix to the user-agent SearXNG uses to send requests to others engines. If an
@@ -105,6 +105,6 @@ Communication with search engines.
``using_tor_proxy`` :
Using tor proxy (``true``) or not (``false``) for all engines. The default is
``false`` and can be overwritten in the :ref:`settings engines`
``false`` and can be overwritten in the :ref:`settings engine`

View File

@@ -1,77 +0,0 @@
.. _settings plugins:
============
``plugins:``
============
.. attention::
The ``enabled_plugins:`` section in SearXNG's settings no longer exists.
There is no longer a distinction between built-in and external plugin, all
plugins are registered via the settings in the ``plugins:`` section.
.. sidebar:: Further reading ..
- :ref:`plugins admin`
- :ref:`dev plugin`
In SearXNG, plugins can be registered in the :py:obj:`PluginStore
<searx.plugins.PluginStorage>` via a fully qualified class name.
A configuration (:py:obj:`PluginCfg <searx.plugins.PluginCfg>`) can be
transferred to the plugin, e.g. to activate it by default / *opt-in* or
*opt-out* from user's point of view.
Please note that some plugins, such as the :ref:`hostnames plugin` plugin,
require further configuration before they can be made available for selection.
built-in plugins
================
The built-in plugins are all located in the namespace `searx.plugins`.
.. code:: yaml
plugins:
searx.plugins.calculator.SXNGPlugin:
active: true
searx.plugins.hash_plugin.SXNGPlugin:
active: true
searx.plugins.self_info.SXNGPlugin:
active: true
searx.plugins.tracker_url_remover.SXNGPlugin:
active: true
searx.plugins.unit_converter.SXNGPlugin:
active: true
searx.plugins.ahmia_filter.SXNGPlugin:
active: true
searx.plugins.hostnames.SXNGPlugin:
active: true
searx.plugins.oa_doi_rewrite.SXNGPlugin:
active: false
searx.plugins.tor_check.SXNGPlugin:
active: false
.. _settings external_plugins:
external plugins
================
.. _Only show green hosted results:
https://github.com/return42/tgwf-searx-plugins/
SearXNG supports *external plugins* / there is no need to install one, SearXNG
runs out of the box.
- `Only show green hosted results`_
- ..

View File

@@ -12,7 +12,6 @@
favicon_resolver: ""
default_lang: ""
ban_time_on_fail: 5
max_page: 0
max_ban_time_on_fail: 120
suspended_times:
SearxEngineAccessDenied: 86400
@@ -34,21 +33,14 @@
``autocomplete``:
Existing autocomplete backends, leave blank to turn it off.
- ``360search``
- ``baidu``
- ``brave``
- ``dbpedia``
- ``duckduckgo``
- ``google``
- ``mwmbl``
- ``quark``
- ``qwant``
- ``seznam``
- ``sogou``
- ``stract``
- ``startpage``
- ``swisscows``
- ``qwant``
- ``wikipedia``
- ``yandex``
``favicon_resolver``:
To activate favicons in SearXNG's result list select a default
@@ -77,11 +69,6 @@
- fr
- fr-BE
``max_page``:
If engine supports paging, 0 means unlimited numbers of pages. The value
is only applied if the engine itself does not have a max value that is
lower than this one.
``ban_time_on_fail``:
Ban time in seconds after engine errors.

View File

@@ -44,7 +44,7 @@
``center_alignment`` : default ``false``
When enabled, the results are centered instead of being in the left (or RTL)
side of the screen. This setting only affects the *desktop layout*
(:origin:`min-width: @tablet <client/simple/src/less/definitions.less>`)
(:origin:`min-width: @tablet <searx/static/themes/simple/src/less/definitions.less>`)
.. cache_url:

View File

@@ -54,7 +54,7 @@ searx.engines.load_engines(searx.settings['engines'])
jinja_contexts = {
'searx': {
'engines': searx.engines.engines,
'plugins': searx.plugins.STORAGE,
'plugins': searx.plugins.plugins,
'version': {
'node': os.getenv('NODE_MINIMUM_VERSION')
},
@@ -127,11 +127,11 @@ extensions = [
"sphinx_tabs.tabs", # https://github.com/djungelorm/sphinx-tabs
'myst_parser', # https://www.sphinx-doc.org/en/master/usage/markdown.html
'notfound.extension', # https://github.com/readthedocs/sphinx-notfound-page
'sphinxcontrib.autodoc_pydantic', # https://github.com/mansenfranzen/autodoc_pydantic
]
# autodoc_typehints = "description"
autodoc_default_options = {
'member-order': 'bysource',
'member-order': 'groupwise',
}
myst_enable_extensions = [
@@ -143,10 +143,10 @@ suppress_warnings = ['myst.domains']
intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"babel" : ("https://babel.readthedocs.io/en/latest/", None),
"flask": ("https://flask.palletsprojects.com/en/stable/", None),
"flask": ("https://flask.palletsprojects.com/", None),
"flask_babel": ("https://python-babel.github.io/flask-babel/", None),
"werkzeug": ("https://werkzeug.palletsprojects.com/en/stable/", None),
"jinja": ("https://jinja.palletsprojects.com/en/stable/", None),
# "werkzeug": ("https://werkzeug.palletsprojects.com/", None),
"jinja": ("https://jinja.palletsprojects.com/", None),
"linuxdoc" : ("https://return42.github.io/linuxdoc/", None),
"sphinx" : ("https://www.sphinx-doc.org/en/master/", None),
"redis": ('https://redis.readthedocs.io/en/stable/', None),
@@ -161,7 +161,7 @@ issues_github_path = "searxng/searxng"
notfound_urls_prefix = '/'
sys.path.append(os.path.abspath('_themes'))
sys.path.insert(0, os.path.abspath("../"))
sys.path.insert(0, os.path.abspath("../utils/"))
html_theme_path = ['_themes']
html_theme = "searxng"
@@ -197,7 +197,7 @@ html_sidebars = {
],
}
singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
html_logo = "../client/simple/src/brand/searxng-wordmark.svg"
html_logo = "../src/brand/searxng-wordmark.svg"
html_title = "SearXNG Documentation ({})".format(VERSION_STRING)
html_show_sourcelink = True

View File

@@ -1,11 +0,0 @@
.. _builtin answerers:
==================
Built-in Answerers
==================
.. toctree::
:maxdepth: 1
random
statistics

View File

@@ -1,7 +0,0 @@
.. _dev answerers:
====================
Answerer Development
====================
.. automodule:: searx.answerers

View File

@@ -1,9 +0,0 @@
=========
Answerers
=========
.. toctree::
:maxdepth: 2
development
builtins

View File

@@ -1,8 +0,0 @@
.. _answerer.random:
======
Random
======
.. autoclass:: searx.answerers.random.SXNGAnswerer
:members:

View File

@@ -1,8 +0,0 @@
.. _answerer.statistics:
==========
Statistics
==========
.. autoclass:: searx.answerers.statistics.SXNGAnswerer
:members:

View File

@@ -14,7 +14,7 @@ Engine Overview
.. sidebar:: Further reading ..
- :ref:`configured engines`
- :ref:`settings engines`
- :ref:`settings engine`
SearXNG is a metasearch-engine_, so it uses different search engines to provide
better results.
@@ -63,7 +63,7 @@ Engine File
Engine ``settings.yml``
-----------------------
For a more detailed description, see :ref:`settings engines` in the :ref:`settings.yml`.
For a more detailed description, see :ref:`settings engine` in the :ref:`settings.yml`.
.. table:: Common options in the engine setup (``settings.yml``)
:width: 100%
@@ -237,18 +237,335 @@ following parameters can be used to specify a search request:
=================== =========== ==========================================================================
Making a Response
=================
.. _engine results:
.. _engine media types:
In the ``response`` function of the engine, the HTTP response (``resp``) is
parsed and a list of results is returned.
Result Types (``template``)
===========================
A engine can append result-items of different media-types and different
result-types to the result list. The list of the result items is render to HTML
by templates. For more details read section:
Each result item of an engine can be of different media-types. Currently the
following media-types are supported. To set another media-type as
:ref:`template default`, the parameter ``template`` must be set to the desired
type.
- :ref:`simple theme templates`
- :ref:`result types`
.. _template default:
``default``
-----------
.. table:: Parameter of the **default** media type:
:width: 100%
========================= =====================================================
result-parameter information
========================= =====================================================
url string, url of the result
title string, title of the result
content string, general result-text
publishedDate :py:class:`datetime.datetime`, time of publish
========================= =====================================================
.. _template images:
``images``
----------
.. list-table:: Parameter of the **images** media type
:header-rows: 2
:width: 100%
* - result-parameter
- Python type
- information
* - template
- :py:class:`str`
- is set to ``images.html``
* - url
- :py:class:`str`
- url to the result site
* - title
- :py:class:`str`
- title of the result
* - content
- :py:class:`str`
- description of the image
* - publishedDate
- :py:class:`datetime <datetime.datetime>`
- time of publish
* - img_src
- :py:class:`str`
- url to the result image
* - thumbnail_src
- :py:class:`str`
- url to a small-preview image
* - resolution
- :py:class:`str`
- the resolution of the image (e.g. ``1920 x 1080`` pixel)
* - img_format
- :py:class:`str`
- the format of the image (e.g. ``png``)
* - filesize
- :py:class:`str`
- size of bytes in :py:obj:`human readable <searx.humanize_bytes>` notation
(e.g. ``MB`` for 1024 \* 1024 Bytes filesize).
.. _template videos:
``videos``
----------
.. table:: Parameter of the **videos** media type:
:width: 100%
========================= =====================================================
result-parameter information
------------------------- -----------------------------------------------------
template is set to ``videos.html``
========================= =====================================================
url string, url of the result
title string, title of the result
content *(not implemented yet)*
publishedDate :py:class:`datetime.datetime`, time of publish
thumbnail string, url to a small-preview image
length :py:class:`datetime.timedelta`, duration of result
views string, view count in humanized number format
========================= =====================================================
.. _template torrent:
``torrent``
-----------
.. _magnetlink: https://en.wikipedia.org/wiki/Magnet_URI_scheme
.. table:: Parameter of the **torrent** media type:
:width: 100%
========================= =====================================================
result-parameter information
------------------------- -----------------------------------------------------
template is set to ``torrent.html``
========================= =====================================================
url string, url of the result
title string, title of the result
content string, general result-text
publishedDate :py:class:`datetime.datetime`,
time of publish *(not implemented yet)*
seed int, number of seeder
leech int, number of leecher
filesize int, size of file in bytes
files int, number of files
magnetlink string, magnetlink_ of the result
torrentfile string, torrentfile of the result
========================= =====================================================
.. _template map:
``map``
-------
.. table:: Parameter of the **map** media type:
:width: 100%
========================= =====================================================
result-parameter information
------------------------- -----------------------------------------------------
template is set to ``map.html``
========================= =====================================================
url string, url of the result
title string, title of the result
content string, general result-text
publishedDate :py:class:`datetime.datetime`, time of publish
latitude latitude of result (in decimal format)
longitude longitude of result (in decimal format)
boundingbox boundingbox of result (array of 4. values
``[lat-min, lat-max, lon-min, lon-max]``)
geojson geojson of result (https://geojson.org/)
osm.type type of osm-object (if OSM-Result)
osm.id id of osm-object (if OSM-Result)
address.name name of object
address.road street name of object
address.house_number house number of object
address.locality city, place of object
address.postcode postcode of object
address.country country of object
========================= =====================================================
.. _template paper:
``paper``
---------
.. _BibTeX format: https://www.bibtex.com/g/bibtex-format/
.. _BibTeX field types: https://en.wikipedia.org/wiki/BibTeX#Field_types
.. list-table:: Parameter of the **paper** media type /
see `BibTeX field types`_ and `BibTeX format`_
:header-rows: 2
:width: 100%
* - result-parameter
- Python type
- information
* - template
- :py:class:`str`
- is set to ``paper.html``
* - title
- :py:class:`str`
- title of the result
* - content
- :py:class:`str`
- abstract
* - comments
- :py:class:`str`
- free text display in italic below the content
* - tags
- :py:class:`List <list>`\ [\ :py:class:`str`\ ]
- free tag list
* - publishedDate
- :py:class:`datetime <datetime.datetime>`
- last publication date
* - type
- :py:class:`str`
- short description of medium type, e.g. *book*, *pdf* or *html* ...
* - authors
- :py:class:`List <list>`\ [\ :py:class:`str`\ ]
- list of authors of the work (authors with a "s")
* - editor
- :py:class:`str`
- list of editors of a book
* - publisher
- :py:class:`str`
- name of the publisher
* - journal
- :py:class:`str`
- name of the journal or magazine the article was
published in
* - volume
- :py:class:`str`
- volume number
* - pages
- :py:class:`str`
- page range where the article is
* - number
- :py:class:`str`
- number of the report or the issue number for a journal article
* - doi
- :py:class:`str`
- DOI number (like ``10.1038/d41586-018-07848-2``)
* - issn
- :py:class:`List <list>`\ [\ :py:class:`str`\ ]
- ISSN number like ``1476-4687``
* - isbn
- :py:class:`List <list>`\ [\ :py:class:`str`\ ]
- ISBN number like ``9780201896831``
* - pdf_url
- :py:class:`str`
- URL to the full article, the PDF version
* - html_url
- :py:class:`str`
- URL to full article, HTML version
.. _template packages:
``packages``
------------
.. list-table:: Parameter of the **packages** media type
:header-rows: 2
:width: 100%
* - result-parameter
- Python type
- information
* - template
- :py:class:`str`
- is set to ``packages.html``
* - title
- :py:class:`str`
- title of the result
* - content
- :py:class:`str`
- abstract
* - package_name
- :py:class:`str`
- the name of the package
* - version
- :py:class:`str`
- the current version of the package
* - maintainer
- :py:class:`str`
- the maintainer or author of the project
* - publishedDate
- :py:class:`datetime <datetime.datetime>`
- date of latest update or release
* - tags
- :py:class:`List <list>`\ [\ :py:class:`str`\ ]
- free tag list
* - popularity
- :py:class:`str`
- the popularity of the package, e.g. rating or download count
* - license_name
- :py:class:`str`
- the name of the license
* - license_url
- :py:class:`str`
- the web location of a license copy
* - homepage
- :py:class:`str`
- the url of the project's homepage
* - source_code_url
- :py:class:`str`
- the location of the project's source code
* - links
- :py:class:`dict`
- additional links in the form of ``{'link_name': 'http://example.com'}``

View File

@@ -19,14 +19,6 @@ Engine Implementations
engine_overview
ResultList and engines
======================
.. autoclass:: searx.result_types.ResultList
.. autoclass:: searx.result_types.EngineResults
Engine Types
============
@@ -53,7 +45,6 @@ Online Engines
demo/demo_online
xpath
mediawiki
json_engine
.. toctree::
:maxdepth: 1

View File

@@ -1,13 +0,0 @@
.. _json_engine engine:
============
JSON Engine
============
.. contents::
:depth: 2
:local:
:backlinks: entry
.. automodule:: searx.engines.json_engine
:members:

View File

@@ -1,13 +0,0 @@
.. _core engine:
====
CORE
====
.. contents::
:depth: 2
:local:
:backlinks: entry
.. automodule:: searx.engines.core
:members:

View File

@@ -1,8 +0,0 @@
.. _reuters engine:
=======
Reuters
=======
.. automodule:: searx.engines.reuters
:members:

View File

@@ -1,13 +0,0 @@
.. _soundcloud engine:
==========
Soundcloud
==========
.. contents::
:depth: 2
:local:
:backlinks: entry
.. automodule:: searx.engines.soundcloud
:members:

View File

@@ -1,7 +0,0 @@
.. _extended_types.:
==============
Extended Types
==============
.. automodule:: searx.extended_types

View File

@@ -8,13 +8,9 @@ Developer documentation
quickstart
rtm_asdf
contribution_guide
extended_types
engines/index
result_types/index
templates
search_api
plugins/index
answerers/index
plugins
translation
lxcdev
makefile

View File

@@ -180,13 +180,10 @@ sources of the theme need to be rebuild. You can do that by running::
$ make themes.all
..
ToDo: vite server is not implemented yet / will be done in a follow up PR
Alternatively to ``themes.all`` you can run *live builds* of the theme you are
modify (:ref:`make themes`)::
Alternatively to ``themes.all`` you can run *live builds* of the theme you are
modify (:ref:`make themes`)::
$ LIVE_THEME=simple make run
$ LIVE_THEME=simple make run
.. _make format.python:

106
docs/dev/plugins.rst Normal file
View File

@@ -0,0 +1,106 @@
.. _dev plugin:
=======
Plugins
=======
.. sidebar:: Further reading ..
- :ref:`plugins generic`
Plugins can extend or replace functionality of various components of searx.
Example plugin
==============
.. code:: python
name = 'Example plugin'
description = 'This plugin extends the suggestions with the word "example"'
default_on = False # disabled by default
# attach callback to the post search hook
# request: flask request object
# ctx: the whole local context of the post search hook
def post_search(request, search):
search.result_container.suggestions.add('example')
return True
External plugins
================
SearXNG supports *external plugins* / there is no need to install one, SearXNG
runs out of the box. But to demonstrate; in the example below we install the
SearXNG plugins from *The Green Web Foundation* `[ref]
<https://www.thegreenwebfoundation.org/news/searching-the-green-web-with-searx/>`__:
.. code:: bash
$ sudo utils/searxng.sh instance cmd bash -c
(searxng-pyenv)$ pip install git+https://github.com/return42/tgwf-searx-plugins
In the :ref:`settings.yml` activate the ``plugins:`` section and add module
``only_show_green_results`` from ``tgwf-searx-plugins``.
.. code:: yaml
plugins:
...
- only_show_green_results
...
Plugin entry points
===================
Entry points (hooks) define when a plugin runs. Right now only three hooks are
implemented. So feel free to implement a hook if it fits the behaviour of your
plugin. A plugin doesn't need to implement all the hooks.
.. py:function:: pre_search(request, search) -> bool
Runs BEFORE the search request.
`search.result_container` can be changed.
Return a boolean:
* True to continue the search
* False to stop the search
:param flask.request request:
:param searx.search.SearchWithPlugins search:
:return: False to stop the search
:rtype: bool
.. py:function:: post_search(request, search) -> None
Runs AFTER the search request.
:param flask.request request: Flask request.
:param searx.search.SearchWithPlugins search: Context.
.. py:function:: on_result(request, search, result) -> bool
Runs for each result of each engine.
`result` can be changed.
If `result["url"]` is defined, then `result["parsed_url"] = urlparse(result['url'])`
.. warning::
`result["url"]` can be changed, but `result["parsed_url"]` must be updated too.
Return a boolean:
* True to keep the result
* False to remove the result
:param flask.request request:
:param searx.search.SearchWithPlugins search:
:param typing.Dict result: Result, see - :ref:`engine results`
:return: True to keep the result
:rtype: bool

View File

@@ -1,15 +0,0 @@
.. _builtin plugins:
================
Built-in Plugins
================
.. toctree::
:maxdepth: 1
calculator
hash_plugin
hostnames
self_info
tor_check
unit_converter

View File

@@ -1,8 +0,0 @@
.. _plugins.calculator:
==========
Calculator
==========
.. automodule:: searx.plugins.calculator
:members:

View File

@@ -1,7 +0,0 @@
.. _dev plugin:
==================
Plugin Development
==================
.. automodule:: searx.plugins

View File

@@ -1,8 +0,0 @@
.. _hash_plugin plugin:
===========
Hash Values
===========
.. autoclass:: searx.plugins.hash_plugin.SXNGPlugin
:members:

View File

@@ -1,9 +0,0 @@
=======
Plugins
=======
.. toctree::
:maxdepth: 2
development
builtins

View File

@@ -1,8 +0,0 @@
.. _self_info plugin:
=========
Self-Info
=========
.. autoclass:: searx.plugins.self_info.SXNGPlugin
:members:

View File

@@ -6,8 +6,7 @@ Development Quickstart
.. _npm: https://www.npmjs.com/
.. _Node.js: https://nodejs.org/
.. _eslint: https://eslint.org/
.. _stylelint: https://stylelint.io/
.. sidebar:: further read
@@ -40,9 +39,10 @@ to our ":ref:`how to contribute`" guideline.
- :ref:`make themes`
If you implement themes, you will need to setup a :ref:`Node.js environment
<make node.env>`. Before you call *make run* (2.), you need to compile the
modified styles and JavaScript: ``make node.clean themes.all``. If eslint_ or
stylelint_ report some issues, try ``make themes.fix``.
<make node.env>`: ``make node.env``
Before you call *make run* (2.), you need to compile the modified styles and
JavaScript: ``make themes.all``
Alternatively you can also compile selective the theme you have modified,
e.g. the *simple* theme.

View File

@@ -1,10 +0,0 @@
.. _result_types.answer:
==============
Answer Results
==============
The :ref:`area answer results` is an area in which additional information can
be displayed.
.. automodule:: searx.result_types.answer

View File

@@ -1,5 +0,0 @@
======
Result
======
.. automodule:: searx.result_types._base

View File

@@ -1,34 +0,0 @@
.. _result_types.corrections:
==================
Correction Results
==================
.. hint::
There is still no typing for these result items. The templates can be used as
orientation until the final typing is complete.
The :ref:`area corrections results` shows the user alternative search terms.
A result of this type is a very simple dictionary with only one key/value pair
.. code:: python
{"correction" : "lorem ipsum .."}
From this simple dict another dict is build up:
.. code:: python
# use RawTextQuery to get the corrections URLs with the same bang
{"url" : "!bang lorem ipsum ..", "title": "lorem ipsum .." }
and used in the template :origin:`corrections.html
<searx/templates/simple/elements/corrections.html>`:
title : :py:class:`str`
Corrected search term.
url : :py:class:`str`
Not really an URL, its the value to insert in a HTML form for a SearXNG query.

View File

@@ -1,105 +0,0 @@
.. _result types:
============
Result Types
============
To understand the typification of the results, let's take a brief look at the
structure of SearXNG .. At its core, SearXNG is nothing more than an aggregator
that aggregates the results from various sources, renders them via templates and
displays them to the user.
The **sources** can be:
1. :ref:`engines <engine implementations>`
2. :ref:`plugins <dev plugin>`
3. :ref:`answerers <dev answerers>`
The sources provide the results, which are displayed in different **areas**
depending on the type of result. The areas are:
.. _area main results:
:ref:`area main results <main search results>`
It is the main area in which -- as is typical for search engines -- the
results that a search engine has found for the search term are displayed.
.. _area answer results:
:ref:`area answers <result_types.answer>`
This area displays short answers that could be found for the search term.
.. _area info box:
:ref:`area info box <result_types.infobox>`
An area in which additional information can be displayed, e.g. excerpts from
wikipedia or other sources such as maps.
.. _area suggestions results:
:ref:`area suggestions <result_types.suggestion>`
Suggestions for alternative search terms can be found in this area. These can
be clicked on and a search is carried out with these search terms.
.. _area corrections results:
:ref:`area corrections <result_types.corrections>`
Results in this area are like the suggestion of alternative search terms,
which usually result from spelling corrections
At this point it is important to note that all **sources** can contribute
results to all of the areas mentioned above.
In most cases, however, the :ref:`engines <engine implementations>` will fill
the *main results* and the :ref:`answerers <dev answerers>` will generally
provide the contributions for the *answer* area. Not necessary to mention here
but for a better understanding: the plugins can also filter out or change
results from the main results area (e.g. the URL of the link).
The result items are organized in the :py:obj:`results.ResultContainer` and
after all sources have delivered their results, this container is passed to the
templating to build a HTML output. The output is usually HTML, but it is also
possible to output the result lists as JSON or RSS feed. Thats quite all we need
to know before we dive into typification of result items.
.. hint::
Typification of result items: we are at the very first beginng!
The first thing we have to realize is that there is no typification of the
result items so far, we have to build it up first .. and that is quite a big
task, which we will only be able to accomplish gradually.
The foundation for the typeless results was laid back in 2013 in the very first
commit :commit:`ae9fb1d7d`, and the principle has not changed since then. At
the time, the approach was perfectly adequate, but we have since evolved and the
demands on SearXNG increase with every feature request.
**Motivation:** in the meantime, it has become very difficult to develop new
features that require structural changes and it is especially hard for newcomers
to find their way in this typeless world. As long as the results are only
simple key/value dictionaries, it is not even possible for the IDEs to support
the application developer in his work.
**Planning:** The procedure for subsequent typing will have to be based on the
circumstances ..
.. attention::
As long as there is no type defined for a kind of result the HTML template
specify what the properties of a type are.
In this sense, you will either find a type definition here in the
documentation or, if this does not yet exist, a description of the HTML
template.
.. toctree::
:maxdepth: 2
base_result
main_result
answer
correction
suggestion
infobox

View File

@@ -1,59 +0,0 @@
.. _result_types.infobox:
===============
Infobox Results
===============
.. hint::
There is still no typing for these result items. The templates can be used as
orientation until the final typing is complete.
The :ref:`area info box` is an area where addtional infos shown to the user.
Fields used in the :origin:`infobox.html
<searx/templates/simple/elements/infobox.html>`:
img_src: :py:class:`str`
URL of a image or thumbnail that is displayed in the infobox.
infobox: :py:class:`str`
Title of the info box.
content: :py:class:`str`
Text of the info box.
The infobox has additional subsections for *attributes*, *urls* and
*relatedTopics*:
attributes: :py:class:`List <list>`\ [\ :py:class:`dict`\ ]
A list of attributes. An *attribute* is a dictionary with keys:
- label :py:class:`str`: (mandatory)
- value :py:class:`str`: (mandatory)
- image :py:class:`List <list>`\ [\ :py:class:`dict`\ ] (optional)
A list of images. An *image* is a dictionary with keys:
- src :py:class:`str`: URL of an image/thumbnail (mandatory)
- alt :py:class:`str`: alternative text for the image (mandatory)
urls: :py:class:`List <list>`\ [\ :py:class:`dict`\ ]
A list of links. An *link* is a dictionary with keys:
- url :py:class:`str`: URL of the link (mandatory)
- title :py:class:`str`: Title of the link (mandatory)
relatedTopics: :py:class:`List <list>`\ [\ :py:class:`dict`\ ]
A list of topics. An *topic* is a dictionary with keys:
- name: :py:class:`str`: (mandatory)
- suggestions: :py:class:`List <list>`\ [\ :py:class:`dict`\ ] (optional)
A list of suggestions. A *suggestion* is simple dictionary with just one
key/value pair:
- suggestion: :py:class:`str`: suggested search term (mandatory)

View File

@@ -1,7 +0,0 @@
.. _result_types.keyvalue:
=================
Key-Value Results
=================
.. automodule:: searx.result_types.keyvalue

View File

@@ -1,4 +0,0 @@
.. _result_types.mainresult:
.. autoclass:: searx.result_types._base.MainResult
:members:

View File

@@ -1,32 +0,0 @@
.. _main search results:
===================
Main Search Results
===================
In the :ref:`area main results` the results that a search engine has found for
the search term are displayed.
There is still no typing for all items in the :ref:`main result list`. The
following types have been implemented so far ..
.. toctree::
:maxdepth: 2
main/mainresult
main/keyvalue
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`
- :ref:`template paper`
- :ref:`template packages`
- :ref:`template code`
- :ref:`template files`
- :ref:`template products`

View File

@@ -1,38 +0,0 @@
.. _result_types.suggestion:
==================
Suggestion Results
==================
.. hint::
There is still no typing for these result items. The templates can be used as
orientation until the final typing is complete.
The :ref:`area suggestions results` shows the user alternative search terms.
A result of this type is a very simple dictionary with only one key/value pair
.. code:: python
{"suggestion" : "lorem ipsum .."}
From this simple dict another dict is build up:
.. code:: python
{"url" : "!bang lorem ipsum ..", "title": "lorem ipsum" }
and used in the template :origin:`suggestions.html
<searx/templates/simple/elements/suggestions.html>`:
.. code:: python
# use RawTextQuery to get the suggestion URLs with the same bang
{"url" : "!bang lorem ipsum ..", "title": "lorem ipsum" }
title : :py:class:`str`
Suggested search term
url : :py:class:`str`
Not really an URL, its the value to insert in a HTML form for a SearXNG query.

View File

@@ -60,7 +60,6 @@ Scripts to update static data in :origin:`searx/data/`
.. automodule:: searxng_extra.update.update_engine_traits
:members:
.. _update_osm_keys_tags.py:
``update_osm_keys_tags.py``
===========================

View File

@@ -1,577 +0,0 @@
.. _simple theme templates:
======================
Simple Theme Templates
======================
The simple template is complex, it consists of many different elements and also
uses macros and include statements. The following is a rough overview that we
would like to give the developerat hand, details must still be taken from the
:origin:`sources <searx/templates/simple/>`.
A :ref:`result item <result types>` can be of different media types. The media
type of a result is defined by the :py:obj:`result_type.Result.template`. To
set another media-type as :ref:`template default`, the field ``template``
in the result item must be set to the desired type.
.. contents:: Contents
:depth: 2
:local:
:backlinks: entry
.. _result template macros:
Result template macros
======================
.. _macro result_header:
``result_header``
-----------------
Execpt ``image.html`` and some others this macro is used in nearly all result
types in the :ref:`main result list`.
Fields used in the template :origin:`macro result_header
<searx/templates/simple/macros.html>`:
url : :py:class:`str`
Link URL of the result item.
title : :py:class:`str`
Link title of the result item.
img_src, thumbnail : :py:class:`str`
URL of a image or thumbnail that is displayed in the result item.
.. _macro result_sub_header:
``result_sub_header``
---------------------
Execpt ``image.html`` and some others this macro is used in nearly all result
types in the :ref:`main result list`.
Fields used in the template :origin:`macro result_sub_header
<searx/templates/simple/macros.html>`:
publishedDate : :py:obj:`datetime.datetime`
The date on which the object was published.
length: :py:obj:`time.struct_time`
Playing duration in seconds.
views: :py:class:`str`
View count in humanized number format.
author : :py:class:`str`
Author of the title.
metadata : :py:class:`str`
Miscellaneous metadata.
.. _engine_data:
``engine_data_form``
--------------------
The ``engine_data_form`` macro is used in :origin:`results,html
<searx/templates/simple/results.html>` in a HTML ``<form/>`` element. The
intention of this macro is to pass data of a engine from one :py:obj:`response
<searx.engines.demo_online.response>` to the :py:obj:`searx.search.SearchQuery`
of the next :py:obj:`request <searx.engines.demo_online.request>`.
To pass data, engine's response handler can append result items of typ
``engine_data``. This is by example used to pass a token from the response to
the next request:
.. code:: python
def response(resp):
...
results.append({
'engine_data': token,
'key': 'next_page_token',
})
...
return results
def request(query, params):
page_token = params['engine_data'].get('next_page_token')
.. _main result list:
Main Result List
================
The **media types** of the **main result type** are the template files in
the :origin:`result_templates <searx/templates/simple/result_templates>`.
.. _template default:
``default.html``
----------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`default.html
<searx/templates/simple/result_templates/default.html>`:
content : :py:class:`str`
General text of the result item.
iframe_src : :py:class:`str`
URL of an embedded ``<iframe>`` / the frame is collapsible.
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``
---------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`videos.html
<searx/templates/simple/result_templates/videos.html>`:
iframe_src : :py:class:`str`
URL of an embedded ``<iframe>`` / the frame is collapsible.
The videos are displayed as small thumbnails in the main results list, there
is an additional button to collaps/open the embeded video.
content : :py:class:`str`
Description of the code fragment.
.. _template torrent:
``torrent.html``
----------------
.. _magnet link: https://en.wikipedia.org/wiki/Magnet_URI_scheme
.. _torrent file: https://en.wikipedia.org/wiki/Torrent_file
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`torrent.html
<searx/templates/simple/result_templates/torrent.html>`:
magnetlink:
URL of the `magnet link`_.
torrentfile
URL of the `torrent file`_.
seed : ``int``
Number of seeders.
leech : ``int``
Number of leecher
filesize : ``int``
Size in Bytes (rendered to human readable unit of measurement).
files : ``int``
Number of files.
.. _template map:
``map.html``
------------
.. _GeoJSON: https://en.wikipedia.org/wiki/GeoJSON
.. _Leaflet: https://github.com/Leaflet/Leaflet
.. _bbox: https://wiki.openstreetmap.org/wiki/Bounding_Box
.. _HTMLElement.dataset: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
.. _Nominatim: https://nominatim.org/release-docs/latest/
.. _Lookup: https://nominatim.org/release-docs/latest/api/Lookup/
.. _place_id is not a persistent id:
https://nominatim.org/release-docs/latest/api/Output/#place_id-is-not-a-persistent-id
.. _perma_id: https://wiki.openstreetmap.org/wiki/Permanent_ID
.. _country code: https://wiki.openstreetmap.org/wiki/Country_code
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`map.html
<searx/templates/simple/result_templates/map.html>`:
content : :py:class:`str`
Description of the item.
address_label : :py:class:`str`
Label of the address / default ``_('address')``.
geojson : GeoJSON_
Geometries mapped to HTMLElement.dataset_ (``data-map-geojson``) and used by
Leaflet_.
boundingbox : ``[ min-lon, min-lat, max-lon, max-lat]``
A bbox_ area defined by min longitude , min latitude , max longitude and max
latitude. The bounding box is mapped to HTMLElement.dataset_
(``data-map-boundingbox``) and is used by Leaflet_.
longitude, latitude : :py:class:`str`
Geographical coordinates, mapped to HTMLElement.dataset_ (``data-map-lon``,
``data-map-lat``) and is used by Leaflet_.
address : ``{...}``
A dicticonary with the address data:
.. code:: python
address = {
'name' : str, # name of object
'road' : str, # street name of object
'house_number' : str, # house number of object
'postcode' : str, # postcode of object
'country' : str, # country of object
'country_code' : str,
'locality' : str,
}
country_code : :py:class:`str`
`Country code`_ of the object.
locality : :py:class:`str`
The name of the city, town, township, village, borough, etc. in which this
object is located.
links : ``[link1, link2, ...]``
A list of links with labels:
.. code:: python
links.append({
'label' : str,
'url' : str,
'url_label' : str, # set by some engines but unused (oscar)
})
data : ``[data1, data2, ...]``
A list of additional data, shown in two columns and containing a label and
value.
.. code:: python
data.append({
'label' : str,
'value' : str,
'key' : str, # set by some engines but unused
})
type : :py:class:`str` # set by some engines but unused (oscar)
Tag label from :ref:`OSM_KEYS_TAGS['tags'] <update_osm_keys_tags.py>`.
type_icon : :py:class:`str` # set by some engines but unused (oscar)
Type's icon.
osm : ``{...}``
OSM-type and OSM-ID, can be used to Lookup_ OSM data (Nominatim_). There is
also a discussion about "`place_id is not a persistent id`_" and the
perma_id_.
.. code:: python
osm = {
'type': str,
'id': str,
}
type : :py:class:`str`
Type of osm-object (if OSM-Result).
id :
ID of osm-object (if OSM-Result).
.. hint::
The ``osm`` property is set by engine ``openstreetmap.py``, but it is not
used in the ``map.html`` template yet.
.. _template paper:
``paper.html``
--------------
.. _BibTeX format: https://www.bibtex.com/g/bibtex-format/
.. _BibTeX field types: https://en.wikipedia.org/wiki/BibTeX#Field_types
Displays result fields from:
- :ref:`macro result_header`
Additional fields used in the :origin:`paper.html
<searx/templates/simple/result_templates/paper.html>`:
content : :py:class:`str`
An abstract or excerpt from the document.
comments : :py:class:`str`
Free text display in italic below the content.
tags : :py:class:`List <list>`\ [\ :py:class:`str`\ ]
Free tag list.
type : :py:class:`str`
Short description of medium type, e.g. *book*, *pdf* or *html* ...
authors : :py:class:`List <list>`\ [\ :py:class:`str`\ ]
List of authors of the work (authors with a "s" suffix, the "author" is in the
:ref:`macro result_sub_header`).
editor : :py:class:`str`
Editor of the book/paper.
publisher : :py:class:`str`
Name of the publisher.
journal : :py:class:`str`
Name of the journal or magazine the article was published in.
volume : :py:class:`str`
Volume number.
pages : :py:class:`str`
Page range where the article is.
number : :py:class:`str`
Number of the report or the issue number for a journal article.
doi : :py:class:`str`
DOI number (like ``10.1038/d41586-018-07848-2``).
issn : :py:class:`List <list>`\ [\ :py:class:`str`\ ]
ISSN number like ``1476-4687``
isbn : :py:class:`List <list>`\ [\ :py:class:`str`\ ]
ISBN number like ``9780201896831``
pdf_url : :py:class:`str`
URL to the full article, the PDF version
html_url : :py:class:`str`
URL to full article, HTML version
.. _template packages:
``packages``
------------
Displays result fields from:
- :ref:`macro result_header`
Additional fields used in the :origin:`packages.html
<searx/templates/simple/result_templates/packages.html>`:
package_name : :py:class:`str`
The name of the package.
version : :py:class:`str`
The current version of the package.
maintainer : :py:class:`str`
The maintainer or author of the project.
publishedDate : :py:class:`datetime <datetime.datetime>`
Date of latest update or release.
tags : :py:class:`List <list>`\ [\ :py:class:`str`\ ]
Free tag list.
popularity : :py:class:`str`
The popularity of the package, e.g. rating or download count.
license_name : :py:class:`str`
The name of the license.
license_url : :py:class:`str`
The web location of a license copy.
homepage : :py:class:`str`
The url of the project's homepage.
source_code_url: :py:class:`str`
The location of the project's source code.
links : :py:class:`dict`
Additional links in the form of ``{'link_name': 'http://example.com'}``
.. _template code:
``code.html``
-------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`code.html
<searx/templates/simple/result_templates/code.html>`:
content : :py:class:`str`
Description of the code fragment.
codelines : ``[line1, line2, ...]``
Lines of the code fragment.
code_language : :py:class:`str`
Name of the code language, the value is passed to
:py:obj:`pygments.lexers.get_lexer_by_name`.
repository : :py:class:`str`
URL of the repository of the code fragment.
.. _template files:
``files.html``
--------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`code.html
<searx/templates/simple/result_templates/files.html>`:
filename, size, time: :py:class:`str`
Filename, Filesize and Date of the file.
mtype : ``audio`` | ``video`` | :py:class:`str`
Mimetype type of the file.
subtype : :py:class:`str`
Mimetype / subtype of the file.
abstract : :py:class:`str`
Abstract of the file.
author : :py:class:`str`
Name of the author of the file
embedded : :py:class:`str`
URL of an embedded media type (``audio`` or ``video``) / is collapsible.
.. _template products:
``products.html``
-----------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`products.html
<searx/templates/simple/result_templates/products.html>`:
content : :py:class:`str`
Description of the product.
price : :py:class:`str`
The price must include the currency.
shipping : :py:class:`str`
Shipping details.
source_country : :py:class:`str`
Place from which the shipment is made.
.. _template answer results:
Answer results
==============
See :ref:`result_types.answer`
Suggestion results
==================
See :ref:`result_types.suggestion`
Correction results
==================
See :ref:`result_types.corrections`
Infobox results
===============
See :ref:`result_types.infobox`

View File

@@ -2,9 +2,9 @@
Source-Code
===========
This is a partial documentation of our source code. We are not aiming to
document every item from the source code, but we will add documentation when
requested.
This is a partial documentation of our source code. We are not aiming to document
every item from the source code, but we will add documentation when requested.
.. toctree::
:maxdepth: 2

View File

@@ -1,8 +1,9 @@
.. _hostnames plugin:
=========
Hostnames
=========
================
Hostnames plugin
================
.. automodule:: searx.plugins.hostnames
:members:
:members:

View File

@@ -1,8 +1,9 @@
.. _tor check plugin:
=========
Tor check
=========
================
Tor check plugin
================
.. automodule:: searx.plugins.tor_check
:members:
:members:

View File

@@ -1,8 +1,9 @@
.. _unit converter plugin:
==============
Unit Converter
==============
=====================
Unit converter plugin
=====================
.. automodule:: searx.plugins.unit_converter
:members:

View File

@@ -8,7 +8,7 @@ Configured Engines
- :ref:`settings categories_as_tabs`
- :ref:`engines-dev`
- :ref:`settings engines`
- :ref:`settings engine`
- :ref:`general engine configuration`
.. jinja:: searx

27
manage
View File

@@ -35,9 +35,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/utils/lib_go.sh"
# shellcheck source=utils/lib_redis.sh
source "$(dirname "${BASH_SOURCE[0]}")/utils/lib_redis.sh"
# shellcheck source=utils/lib_sxng_vite.sh
source "$(dirname "${BASH_SOURCE[0]}")/utils/lib_sxng_vite.sh"
PATH="${REPO_ROOT}/node_modules/.bin:${PATH}"
# config
@@ -97,6 +94,8 @@ pyenv.:
OK : test if virtualenv is OK
format.:
python : format Python code source using black
pygments.:
less : build LESS files for pygments
EOF
go.help
node.help
@@ -105,7 +104,6 @@ EOF
test.help
themes.help
static.help
vite.help
cat <<EOF
environment ...
SEARXNG_REDIS_URL : ${SEARXNG_REDIS_URL}
@@ -255,6 +253,15 @@ gecko.driver() {
dump_return $?
}
pygments.less() {
build_msg PYGMENTS "searxng_extra/update/update_pygments.py"
if ! pyenv.cmd python searxng_extra/update/update_pygments.py; then
build_msg PYGMENTS "building LESS files for pygments failed"
return 1
fi
return 0
}
py.build() {
build_msg BUILD "python package ${PYDIST}"
pyenv.cmd python setup.py \
@@ -314,18 +321,6 @@ format.python() {
dump_return $?
}
docs.prebuild() {
build_msg DOCS "build ${DOCS_BUILD}/includes"
(
set -e
[ "$VERBOSE" = "1" ] && set -x
mkdir -p "${DOCS_BUILD}/includes"
./utils/searxng.sh searxng.doc.rst > "${DOCS_BUILD}/includes/searxng.rst"
pyenv.cmd searxng_extra/docs_prebuild
)
dump_return $?
}
# shellcheck disable=SC2119
main() {

View File

@@ -1,7 +1,7 @@
{
"dependencies": {
"eslint": "^9.17.0",
"pyright": "^1.1.391"
"eslint": "^9.0.0",
"pyright": "^1.1.329"
},
"scripts": {
"clean": "rm -Rf node_modules package-lock.json"

View File

@@ -1,23 +1,24 @@
mock==5.2.0
mock==5.1.0
nose2[coverage_plugin]==0.15.1
cov-core==1.15.0
black==24.3.0
pylint==3.3.6
pylint==3.3.3
splinter==0.21.0
selenium==4.31.0
selenium==4.27.1
Pallets-Sphinx-Themes==2.3.0
Sphinx==7.4.7
sphinx-issues==5.0.1
sphinx-issues==5.0.0
sphinx-jinja==2.0.2
sphinx-tabs==3.4.7
sphinxcontrib-programoutput==0.18
sphinx-autobuild==2024.10.3
sphinx-notfound-page==1.1.0
sphinx-notfound-page==1.0.4
myst-parser==3.0.1
linuxdoc==20240924
aiounittest==1.5.0
yamllint==1.37.0
aiounittest==1.4.2
yamllint==1.35.1
wlc==1.15
coloredlogs==15.0.1
docutils>=0.21.2
parameterized==0.9.0
autodoc_pydantic==2.2.0

View File

@@ -1,21 +1,21 @@
certifi==2025.1.31
babel==2.17.0
certifi==2024.12.14
babel==2.16.0
flask-babel==4.0.0
flask==3.1.0
jinja2==3.1.6
lxml==5.3.2
pygments==2.19.1
jinja2==3.1.5
lxml==5.3.0
pygments==2.18.0
python-dateutil==2.9.0.post0
pyyaml==6.0.2
httpx[http2]==0.24.1
Brotli==1.1.0
uvloop==0.21.0
httpx-socks[asyncio]==0.7.7
setproctitle==1.3.5
redis==5.0.8
setproctitle==1.3.4
redis==5.2.1
markdown-it-py==3.0.0
fasttext-predict==0.9.2.4
tomli==2.2.1; python_version < '3.11'
tomli==2.0.2; python_version < '3.11'
msgspec==0.19.0
typer-slim==0.15.2
typer-slim==0.15.1
isodate==0.7.2

View File

@@ -9,7 +9,8 @@ import logging
import searx.unixthreadname
import searx.settings_loader
from searx.settings_defaults import SCHEMA, apply_schema
from searx.settings_defaults import settings_set_defaults
# Debug
LOG_FORMAT_DEBUG = '%(levelname)-7s %(name)-30.30s: %(message)s'
@@ -20,52 +21,14 @@ LOG_LEVEL_PROD = logging.WARNING
searx_dir = abspath(dirname(__file__))
searx_parent_dir = abspath(dirname(dirname(__file__)))
settings, settings_load_message = searx.settings_loader.load_settings()
settings = {}
searx_debug = False
logger = logging.getLogger('searx')
if settings is not None:
settings = settings_set_defaults(settings)
_unset = object()
def init_settings():
"""Initialize global ``settings`` and ``searx_debug`` variables and
``logger`` from ``SEARXNG_SETTINGS_PATH``.
"""
global settings, searx_debug # pylint: disable=global-variable-not-assigned
cfg, msg = searx.settings_loader.load_settings(load_user_settings=True)
cfg = cfg or {}
apply_schema(cfg, SCHEMA, [])
settings.clear()
settings.update(cfg)
searx_debug = settings['general']['debug']
if searx_debug:
_logging_config_debug()
else:
logging.basicConfig(level=LOG_LEVEL_PROD, format=LOG_FORMAT_PROD)
logging.root.setLevel(level=LOG_LEVEL_PROD)
logging.getLogger('werkzeug').setLevel(level=LOG_LEVEL_PROD)
logger.info(msg)
# log max_request_timeout
max_request_timeout = settings['outgoing']['max_request_timeout']
if max_request_timeout is None:
logger.info('max_request_timeout=%s', repr(max_request_timeout))
else:
logger.info('max_request_timeout=%i second(s)', max_request_timeout)
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 and link_token / "
"see https://docs.searxng.org/admin/searx.limiter.html"
)
def get_setting(name, default=_unset):
"""Returns the value to which ``name`` point. If there is no such name in the
settings and the ``default`` is unset, a :py:obj:`KeyError` is raised.
@@ -87,20 +50,20 @@ def get_setting(name, default=_unset):
return value
def _is_color_terminal():
def is_color_terminal():
if os.getenv('TERM') in ('dumb', 'unknown'):
return False
return sys.stdout.isatty()
def _logging_config_debug():
def logging_config_debug():
try:
import coloredlogs # pylint: disable=import-outside-toplevel
except ImportError:
coloredlogs = None
log_level = os.environ.get('SEARXNG_DEBUG_LOG_LEVEL', 'DEBUG')
if coloredlogs and _is_color_terminal():
if coloredlogs and is_color_terminal():
level_styles = {
'spam': {'color': 'green', 'faint': True},
'debug': {},
@@ -124,4 +87,26 @@ def _logging_config_debug():
logging.basicConfig(level=logging.getLevelName(log_level), format=LOG_FORMAT_DEBUG)
init_settings()
searx_debug = settings['general']['debug']
if searx_debug:
logging_config_debug()
else:
logging.basicConfig(level=LOG_LEVEL_PROD, format=LOG_FORMAT_PROD)
logging.root.setLevel(level=LOG_LEVEL_PROD)
logging.getLogger('werkzeug').setLevel(level=LOG_LEVEL_PROD)
logger = logging.getLogger('searx')
logger.info(settings_load_message)
# log max_request_timeout
max_request_timeout = settings['outgoing']['max_request_timeout']
if max_request_timeout is None:
logger.info('max_request_timeout=%s', repr(max_request_timeout))
else:
logger.info('max_request_timeout=%i second(s)', max_request_timeout)
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 and link_token / "
"see https://docs.searxng.org/admin/searx.limiter.html"
)

View File

@@ -1,49 +1,51 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""The *answerers* give instant answers related to the search query, they
usually provide answers of type :py:obj:`Answer <searx.result_types.Answer>`.
# pylint: disable=missing-module-docstring
Here is an example of a very simple answerer that adds a "Hello" into the answer
area:
import sys
from os import listdir
from os.path import realpath, dirname, join, isdir
from collections import defaultdict
.. code::
from searx.utils import load_module
from flask_babel import gettext as _
from searx.answerers import Answerer
from searx.result_types import Answer
class MyAnswerer(Answerer):
keywords = [ "hello", "hello world" ]
def info(self):
return AnswererInfo(name=_("Hello"), description=_("lorem .."), keywords=self.keywords)
def answer(self, request, search):
return [ Answer(answer="Hello") ]
----
.. autoclass:: Answerer
:members:
.. autoclass:: AnswererInfo
:members:
.. autoclass:: AnswerStorage
:members:
.. autoclass:: searx.answerers._core.ModuleAnswerer
:members:
:show-inheritance:
"""
from __future__ import annotations
__all__ = ["AnswererInfo", "Answerer", "AnswerStorage"]
answerers_dir = dirname(realpath(__file__))
from ._core import AnswererInfo, Answerer, AnswerStorage
def load_answerers():
answerers = [] # pylint: disable=redefined-outer-name
STORAGE: AnswerStorage = AnswerStorage()
STORAGE.load_builtins()
for filename in listdir(answerers_dir):
if not isdir(join(answerers_dir, filename)) or filename.startswith('_'):
continue
module = load_module('answerer.py', join(answerers_dir, filename))
if not hasattr(module, 'keywords') or not isinstance(module.keywords, tuple) or not module.keywords:
sys.exit(2)
answerers.append(module)
return answerers
def get_answerers_by_keywords(answerers): # pylint:disable=redefined-outer-name
by_keyword = defaultdict(list)
for answerer in answerers:
for keyword in answerer.keywords:
for keyword in answerer.keywords:
by_keyword[keyword].append(answerer.answer)
return by_keyword
def ask(query):
results = []
query_parts = list(filter(None, query.query.split()))
if not query_parts or query_parts[0] not in answerers_by_keywords:
return results
for answerer in answerers_by_keywords[query_parts[0]]:
result = answerer(query)
if result:
results.append(result)
return results
answerers = load_answerers()
answerers_by_keywords = get_answerers_by_keywords(answerers)

View File

@@ -1,169 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=too-few-public-methods, missing-module-docstring
from __future__ import annotations
import abc
import importlib
import logging
import pathlib
import warnings
from dataclasses import dataclass
from searx.utils import load_module
from searx.result_types.answer import BaseAnswer
_default = pathlib.Path(__file__).parent
log: logging.Logger = logging.getLogger("searx.answerers")
@dataclass
class AnswererInfo:
"""Object that holds informations about an answerer, these infos are shown
to the user in the Preferences menu.
To be able to translate the information into other languages, the text must
be written in English and translated with :py:obj:`flask_babel.gettext`.
"""
name: str
"""Name of the *answerer*."""
description: str
"""Short description of the *answerer*."""
examples: list[str]
"""List of short examples of the usage / of query terms."""
keywords: list[str]
"""See :py:obj:`Answerer.keywords`"""
class Answerer(abc.ABC):
"""Abstract base class of answerers."""
keywords: list[str]
"""Keywords to which the answerer has *answers*."""
@abc.abstractmethod
def answer(self, query: str) -> list[BaseAnswer]:
"""Function that returns a list of answers to the question/query."""
@abc.abstractmethod
def info(self) -> AnswererInfo:
"""Informations about the *answerer*, see :py:obj:`AnswererInfo`."""
class ModuleAnswerer(Answerer):
"""A wrapper class for legacy *answerers* where the names (keywords, answer,
info) are implemented on the module level (not in a class).
.. note::
For internal use only!
"""
def __init__(self, mod):
for name in ["keywords", "self_info", "answer"]:
if not getattr(mod, name, None):
raise SystemExit(2)
if not isinstance(mod.keywords, tuple):
raise SystemExit(2)
self.module = mod
self.keywords = mod.keywords # type: ignore
def answer(self, query: str) -> list[BaseAnswer]:
return self.module.answer(query)
def info(self) -> AnswererInfo:
kwargs = self.module.self_info()
kwargs["keywords"] = self.keywords
return AnswererInfo(**kwargs)
class AnswerStorage(dict):
"""A storage for managing the *answerers* of SearXNG. With the
:py:obj:`AnswerStorage.ask`” method, a caller can ask questions to all
*answerers* and receives a list of the results."""
answerer_list: set[Answerer]
"""The list of :py:obj:`Answerer` in this storage."""
def __init__(self):
super().__init__()
self.answerer_list = set()
def load_builtins(self):
"""Loads ``answerer.py`` modules from the python packages in
:origin:`searx/answerers`. The python modules are wrapped by
:py:obj:`ModuleAnswerer`."""
for f in _default.iterdir():
if f.name.startswith("_"):
continue
if f.is_file() and f.suffix == ".py":
self.register_by_fqn(f"searx.answerers.{f.stem}.SXNGAnswerer")
continue
# for backward compatibility (if a fork has additional answerers)
if f.is_dir() and (f / "answerer.py").exists():
warnings.warn(
f"answerer module {f} is deprecated / migrate to searx.answerers.Answerer", DeprecationWarning
)
mod = load_module("answerer.py", str(f))
self.register(ModuleAnswerer(mod))
def register_by_fqn(self, fqn: str):
"""Register a :py:obj:`Answerer` via its fully qualified class namen(FQN)."""
mod_name, _, obj_name = fqn.rpartition('.')
mod = importlib.import_module(mod_name)
code_obj = getattr(mod, obj_name, None)
if code_obj is None:
msg = f"answerer {fqn} is not implemented"
log.critical(msg)
raise ValueError(msg)
self.register(code_obj())
def register(self, answerer: Answerer):
"""Register a :py:obj:`Answerer`."""
self.answerer_list.add(answerer)
for _kw in answerer.keywords:
self[_kw] = self.get(_kw, [])
self[_kw].append(answerer)
def ask(self, query: str) -> list[BaseAnswer]:
"""An answerer is identified via keywords, if there is a keyword at the
first position in the ``query`` for which there is one or more
answerers, then these are called, whereby the entire ``query`` is passed
as argument to the answerer function."""
results = []
keyword = None
for keyword in query.split():
if keyword:
break
if not keyword or keyword not in self:
return results
for answerer in self[keyword]:
for answer in answerer.answer(query):
# In case of *answers* prefix ``answerer:`` is set, see searx.result_types.Result
answer.engine = f"answerer: {keyword}"
results.append(answer)
return results
@property
def info(self) -> list[AnswererInfo]:
return [a.info() for a in self.answerer_list]

View File

@@ -1,80 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring
from __future__ import annotations
import hashlib
import random
import string
import uuid
from flask_babel import gettext
from searx.result_types import Answer
from searx.result_types.answer import BaseAnswer
from . import Answerer, AnswererInfo
def random_characters():
random_string_letters = string.ascii_lowercase + string.digits + string.ascii_uppercase
return [random.choice(random_string_letters) for _ in range(random.randint(8, 32))]
def random_string():
return ''.join(random_characters())
def random_float():
return str(random.random())
def random_int():
random_int_max = 2**31
return str(random.randint(-random_int_max, random_int_max))
def random_sha256():
m = hashlib.sha256()
m.update(''.join(random_characters()).encode())
return str(m.hexdigest())
def random_uuid():
return str(uuid.uuid4())
def random_color():
color = "%06x" % random.randint(0, 0xFFFFFF)
return f"#{color.upper()}"
class SXNGAnswerer(Answerer):
"""Random value generator"""
keywords = ["random"]
random_types = {
"string": random_string,
"int": random_int,
"float": random_float,
"sha256": random_sha256,
"uuid": random_uuid,
"color": random_color,
}
def info(self):
return AnswererInfo(
name=gettext(self.__doc__),
description=gettext("Generate different random values"),
keywords=self.keywords,
examples=[f"random {x}" for x in self.random_types],
)
def answer(self, query: str) -> list[BaseAnswer]:
parts = query.split()
if len(parts) != 2 or parts[1] not in self.random_types:
return []
return [Answer(answer=self.random_types[parts[1]]())]

View File

@@ -0,0 +1,2 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring

View File

@@ -0,0 +1,79 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring
import hashlib
import random
import string
import uuid
from flask_babel import gettext
# required answerer attribute
# specifies which search query keywords triggers this answerer
keywords = ('random',)
random_int_max = 2**31
random_string_letters = string.ascii_lowercase + string.digits + string.ascii_uppercase
def random_characters():
return [random.choice(random_string_letters) for _ in range(random.randint(8, 32))]
def random_string():
return ''.join(random_characters())
def random_float():
return str(random.random())
def random_int():
return str(random.randint(-random_int_max, random_int_max))
def random_sha256():
m = hashlib.sha256()
m.update(''.join(random_characters()).encode())
return str(m.hexdigest())
def random_uuid():
return str(uuid.uuid4())
def random_color():
color = "%06x" % random.randint(0, 0xFFFFFF)
return f"#{color.upper()}"
random_types = {
'string': random_string,
'int': random_int,
'float': random_float,
'sha256': random_sha256,
'uuid': random_uuid,
'color': random_color,
}
# required answerer function
# can return a list of results (any result type) for a given query
def answer(query):
parts = query.query.split()
if len(parts) != 2:
return []
if parts[1] not in random_types:
return []
return [{'answer': random_types[parts[1]]()}]
# required answerer function
# returns information about the answerer
def self_info():
return {
'name': gettext('Random value generator'),
'description': gettext('Generate different random values'),
'examples': ['random {}'.format(x) for x in random_types],
}

View File

@@ -1,64 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring
from __future__ import annotations
from functools import reduce
from operator import mul
import babel
import babel.numbers
from flask_babel import gettext
from searx.extended_types import sxng_request
from searx.result_types import Answer
from searx.result_types.answer import BaseAnswer
from . import Answerer, AnswererInfo
kw2func = [
("min", min),
("max", max),
("avg", lambda args: sum(args) / len(args)),
("sum", sum),
("prod", lambda args: reduce(mul, args, 1)),
]
class SXNGAnswerer(Answerer):
"""Statistics functions"""
keywords = [kw for kw, _ in kw2func]
def info(self):
return AnswererInfo(
name=gettext(self.__doc__),
description=gettext("Compute {func} of the arguments".format(func='/'.join(self.keywords))),
keywords=self.keywords,
examples=["avg 123 548 2.04 24.2"],
)
def answer(self, query: str) -> list[BaseAnswer]:
results = []
parts = query.split()
if len(parts) < 2:
return results
ui_locale = babel.Locale.parse(sxng_request.preferences.get_value('locale'), sep='-')
try:
args = [babel.numbers.parse_decimal(num, ui_locale, numbering_system="latn") for num in parts[1:]]
except: # pylint: disable=bare-except
# seems one of the args is not a float type, can't be converted to float
return results
for k, func in kw2func:
if k == parts[0]:
res = func(args)
res = babel.numbers.format_decimal(res, locale=ui_locale)
f_str = ', '.join(babel.numbers.format_decimal(arg, locale=ui_locale) for arg in args)
results.append(Answer(answer=f"[{ui_locale}] {k}({f_str}) = {res} "))
break
return results

View File

@@ -0,0 +1,2 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring

View File

@@ -0,0 +1,53 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring
from functools import reduce
from operator import mul
from flask_babel import gettext
keywords = ('min', 'max', 'avg', 'sum', 'prod')
# required answerer function
# can return a list of results (any result type) for a given query
def answer(query):
parts = query.query.split()
if len(parts) < 2:
return []
try:
args = list(map(float, parts[1:]))
except: # pylint: disable=bare-except
return []
func = parts[0]
_answer = None
if func == 'min':
_answer = min(args)
elif func == 'max':
_answer = max(args)
elif func == 'avg':
_answer = sum(args) / len(args)
elif func == 'sum':
_answer = sum(args)
elif func == 'prod':
_answer = reduce(mul, args, 1)
if _answer is None:
return []
return [{'answer': str(_answer)}]
# required answerer function
# returns information about the answerer
def self_info():
return {
'name': gettext('Statistics functions'),
'description': gettext('Compute {functions} of the arguments').format(functions='/'.join(keywords)),
'examples': ['avg 123 548 2.04 24.2'],
}

View File

@@ -8,11 +8,9 @@ import json
import html
from urllib.parse import urlencode, quote_plus
import lxml.etree
import lxml.html
import lxml
from httpx import HTTPError
from searx.extended_types import SXNG_Response
from searx import settings
from searx.engines import (
engines,
@@ -20,7 +18,6 @@ from searx.engines import (
)
from searx.network import get as http_get, post as http_post
from searx.exceptions import SearxEngineResponseException
from searx.utils import extr
def update_kwargs(**kwargs):
@@ -29,31 +26,16 @@ def update_kwargs(**kwargs):
kwargs['raise_for_httperror'] = True
def get(*args, **kwargs) -> SXNG_Response:
def get(*args, **kwargs):
update_kwargs(**kwargs)
return http_get(*args, **kwargs)
def post(*args, **kwargs) -> SXNG_Response:
def post(*args, **kwargs):
update_kwargs(**kwargs)
return http_post(*args, **kwargs)
def baidu(query, _lang):
# baidu search autocompleter
base_url = "https://www.baidu.com/sugrec?"
response = get(base_url + urlencode({'ie': 'utf-8', 'json': 1, 'prod': 'pc', 'wd': query}))
results = []
if response.ok:
data = response.json()
if 'g' in data:
for item in data['g']:
results.append(item['q'])
return results
def brave(query, _lang):
# brave search autocompleter
url = 'https://search.brave.com/api/suggest?'
@@ -129,7 +111,7 @@ def google_complete(query, sxng_locale):
)
results = []
resp = get(url.format(subdomain=google_info['subdomain'], args=args))
if resp and resp.ok:
if resp.ok:
json_txt = resp.text[resp.text.find('[') : resp.text.find(']', -3) + 1]
data = json.loads(json_txt)
for item in data[0]:
@@ -149,35 +131,6 @@ def mwmbl(query, _lang):
return [result for result in results if not result.startswith("go: ") and not result.startswith("search: ")]
def qihu360search(query, _lang):
# 360Search search autocompleter
url = f"https://sug.so.360.cn/suggest?{urlencode({'format': 'json', 'word': query})}"
response = get(url)
results = []
if response.ok:
data = response.json()
if 'result' in data:
for item in data['result']:
results.append(item['word'])
return results
def quark(query, _lang):
# Quark search autocompleter
url = f"https://sugs.m.sm.cn/web?{urlencode({'q': query})}"
response = get(url)
results = []
if response.ok:
data = response.json()
for item in data.get('r', []):
results.append(item['w'])
return results
def seznam(query, _lang):
# seznam search autocompleter
url = 'https://suggest.seznam.cz/fulltext/cs?{query}'
@@ -201,23 +154,6 @@ def seznam(query, _lang):
]
def sogou(query, _lang):
# Sogou search autocompleter
base_url = "https://sor.html5.qq.com/api/getsug?"
response = get(base_url + urlencode({'m': 'searxng', 'key': query}))
if response.ok:
raw_json = extr(response.text, "[", "]", default="")
try:
data = json.loads(f"[{raw_json}]]")
return data[1]
except json.JSONDecodeError:
return []
return []
def stract(query, _lang):
# stract autocompleter (beta)
url = f"https://stract.com/beta/api/autosuggest?q={quote_plus(query)}"
@@ -230,6 +166,15 @@ def stract(query, _lang):
return [html.unescape(suggestion['raw']) for suggestion in resp.json()]
def startpage(query, sxng_locale):
"""Autocomplete from Startpage. Supports Startpage's languages"""
lui = engines['startpage'].traits.get_language(sxng_locale, 'english')
url = 'https://startpage.com/suggestions?{query}'
resp = get(url.format(query=urlencode({'q': query, 'segment': 'startpage.udog', 'lui': lui})))
data = resp.json()
return [e['text'] for e in data.get('suggestions', []) if 'text' in e]
def swisscows(query, _lang):
# swisscows autocompleter
url = 'https://swisscows.ch/api/suggest?{query}&itemsCount=5'
@@ -260,7 +205,7 @@ def wikipedia(query, sxng_locale):
results = []
eng_traits = engines['wikipedia'].traits
wiki_lang = eng_traits.get_language(sxng_locale, 'en')
wiki_netloc = eng_traits.custom['wiki_netloc'].get(wiki_lang, 'en.wikipedia.org') # type: ignore
wiki_netloc = eng_traits.custom['wiki_netloc'].get(wiki_lang, 'en.wikipedia.org')
url = 'https://{wiki_netloc}/w/api.php?{args}'
args = urlencode(
@@ -293,20 +238,17 @@ def yandex(query, _lang):
backends = {
'360search': qihu360search,
'baidu': baidu,
'brave': brave,
'dbpedia': dbpedia,
'duckduckgo': duckduckgo,
'google': google_complete,
'mwmbl': mwmbl,
'quark': quark,
'qwant': qwant,
'seznam': seznam,
'sogou': sogou,
'startpage': startpage,
'stract': stract,
'swisscows': swisscows,
'qwant': qwant,
'wikipedia': wikipedia,
'brave': brave,
'yandex': yandex,
}

View File

@@ -8,20 +8,17 @@ from ipaddress import (
IPv4Address,
IPv6Address,
ip_network,
ip_address,
)
import flask
import werkzeug
from searx import logger
from searx.extended_types import SXNG_Request
from . import config
logger = logger.getChild('botdetection')
def dump_request(request: SXNG_Request):
def dump_request(request: flask.Request):
return (
request.path
+ " || X-Forwarded-For: %s" % request.headers.get('X-Forwarded-For')
@@ -69,7 +66,7 @@ def _log_error_only_once(err_msg):
_logged_errors.append(err_msg)
def get_real_ip(request: SXNG_Request) -> str:
def get_real_ip(request: flask.Request) -> str:
"""Returns real IP of the request. Since not all proxies set all the HTTP
headers and incoming headers can be faked it may happen that the IP cannot
be determined correctly.
@@ -126,9 +123,6 @@ def get_real_ip(request: SXNG_Request) -> str:
if real_ip and remote_addr and real_ip != remote_addr:
logger.warning("IP from WSGI environment (%s) is not equal to IP from X-Real-IP (%s)", remote_addr, real_ip)
request_ip = ip_address(forwarded_for or real_ip or remote_addr or '0.0.0.0')
if request_ip.version == 6 and request_ip.ipv4_mapped:
request_ip = request_ip.ipv4_mapped
request_ip = forwarded_for or real_ip or remote_addr or '0.0.0.0'
# logger.debug("get_real_ip() -> %s", request_ip)
return str(request_ip)
return request_ip

View File

@@ -12,6 +12,7 @@ Accept_ header ..
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
"""
# pylint: disable=unused-argument
from __future__ import annotations
from ipaddress import (
@@ -19,18 +20,17 @@ from ipaddress import (
IPv6Network,
)
import flask
import werkzeug
from searx.extended_types import SXNG_Request
from . import config
from ._helpers import too_many_requests
def filter_request(
network: IPv4Network | IPv6Network,
request: SXNG_Request,
cfg: config.Config, # pylint: disable=unused-argument
request: flask.Request,
cfg: config.Config,
) -> werkzeug.Response | None:
if 'text/html' not in request.accept_mimetypes:

View File

@@ -13,6 +13,7 @@ bot if the Accept-Encoding_ header ..
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
"""
# pylint: disable=unused-argument
from __future__ import annotations
from ipaddress import (
@@ -20,18 +21,17 @@ from ipaddress import (
IPv6Network,
)
import flask
import werkzeug
from searx.extended_types import SXNG_Request
from . import config
from ._helpers import too_many_requests
def filter_request(
network: IPv4Network | IPv6Network,
request: SXNG_Request,
cfg: config.Config, # pylint: disable=unused-argument
request: flask.Request,
cfg: config.Config,
) -> werkzeug.Response | None:
accept_list = [l.strip() for l in request.headers.get('Accept-Encoding', '').split(',')]

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