summaryrefslogtreecommitdiff
path: root/searx/plugins/calculator.py
diff options
context:
space:
mode:
authorMarkus Heiser <markus.heiser@darmarit.de>2025-03-20 07:47:38 +0100
committerMarkus Heiser <markus.heiser@darmarIT.de>2025-03-29 10:16:43 +0100
commit50f92779bd9f2762d92a24a63851f4069f88297d (patch)
tree54e9a3d7b2839bf3feb30339c3e3bd63196fd1da /searx/plugins/calculator.py
parentd36da0a6c34761258c43424a7d948f0c554ef5c9 (diff)
[refactor] migrate plugins from "module" to class SXNGPlugin
This patch brings two major changes: - ``Result.filter_urls(..)`` to pass a filter function for URL fields - The ``enabled_plugins:`` section in SearXNG's settings do no longer exists. To understand plugin development compile documentation: $ make docs.clean docs.live and read http://0.0.0.0:8000/dev/plugins/development.html There is no longer a distinction between built-in and external plugin, all plugins are registered via the settings in the ``plugins:`` section. In SearXNG, plugins can be registered via a fully qualified class name. A configuration (`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. 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 external plugins ================ SearXNG supports *external plugins* / there is no need to install one, SearXNG runs out of the box. - Only show green hosted results: https://github.com/return42/tgwf-searx-plugins/ To get a developer installation in a SearXNG developer environment: .. code:: sh $ git clone git@github.com:return42/tgwf-searx-plugins.git $ ./manage pyenv.cmd python -m \ pip install -e tgwf-searx-plugins To register the plugin in SearXNG add ``only_show_green_results.SXNGPlugin`` to the ``plugins:``: .. code:: yaml plugins: # ... only_show_green_results.SXNGPlugin: active: false Result.filter_urls(..) ====================== The ``Result.filter_urls(..)`` can be used to filter and/or modify URL fields. In the following example, the filter function ``my_url_filter``: .. code:: python def my_url_filter(result, field_name, url_src) -> bool | str: if "google" in url_src: return False # remove URL field from result if "facebook" in url_src: new_url = url_src.replace("facebook", "fb-dummy") return new_url # return modified URL return True # leave URL in field unchanged is applied to all URL fields in the :py:obj:`Plugin.on_result` hook: .. code:: python class MyUrlFilter(Plugin): ... def on_result(self, request, search, result) -> bool: result.filter_urls(my_url_filter) return True Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
Diffstat (limited to 'searx/plugins/calculator.py')
-rw-r--r--searx/plugins/calculator.py126
1 files changed, 72 insertions, 54 deletions
diff --git a/searx/plugins/calculator.py b/searx/plugins/calculator.py
index 11caa272f..0b6a0838e 100644
--- a/searx/plugins/calculator.py
+++ b/searx/plugins/calculator.py
@@ -1,9 +1,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
-"""Calculate mathematical expressions using :py:obj`ast.parse` (mode="eval").
+"""Calculate mathematical expressions using :py:obj:`ast.parse` (mode="eval").
"""
from __future__ import annotations
-from typing import Callable
+import typing
import ast
import re
@@ -15,14 +15,78 @@ import babel.numbers
from flask_babel import gettext
from searx.result_types import EngineResults
+from searx.plugins import Plugin, PluginInfo
-name = "Basic Calculator"
-description = gettext("Calculate mathematical expressions via the search bar")
-default_on = True
-preference_section = 'general'
-plugin_id = 'calculator'
+if typing.TYPE_CHECKING:
+ from searx.search import SearchWithPlugins
+ from searx.extended_types import SXNG_Request
+ from searx.plugins import PluginCfg
-operators: dict[type, Callable] = {
+
+class SXNGPlugin(Plugin):
+ """Plugin converts strings to different hash digests. The results are
+ displayed in area for the "answers".
+ """
+
+ id = "calculator"
+
+ def __init__(self, plg_cfg: "PluginCfg") -> None:
+ super().__init__(plg_cfg)
+
+ self.info = PluginInfo(
+ id=self.id,
+ name=gettext("Basic Calculator"),
+ description=gettext("Calculate mathematical expressions via the search bar"),
+ preference_section="general",
+ )
+
+ def post_search(self, request: "SXNG_Request", search: "SearchWithPlugins") -> EngineResults:
+ results = EngineResults()
+
+ # only show the result of the expression on the first page
+ if search.search_query.pageno > 1:
+ return results
+
+ query = search.search_query.query
+ # in order to avoid DoS attacks with long expressions, ignore long expressions
+ if len(query) > 100:
+ return results
+
+ # replace commonly used math operators with their proper Python operator
+ query = query.replace("x", "*").replace(":", "/")
+
+ # use UI language
+ ui_locale = babel.Locale.parse(request.preferences.get_value("locale"), sep="-")
+
+ # parse the number system in a localized way
+ def _decimal(match: re.Match) -> str:
+ val = match.string[match.start() : match.end()]
+ val = babel.numbers.parse_decimal(val, ui_locale, numbering_system="latn")
+ return str(val)
+
+ decimal = ui_locale.number_symbols["latn"]["decimal"]
+ group = ui_locale.number_symbols["latn"]["group"]
+ query = re.sub(f"[0-9]+[{decimal}|{group}][0-9]+[{decimal}|{group}]?[0-9]?", _decimal, query)
+
+ # only numbers and math operators are accepted
+ if any(str.isalpha(c) for c in query):
+ return results
+
+ # in python, powers are calculated via **
+ query_py_formatted = query.replace("^", "**")
+
+ # Prevent the runtime from being longer than 50 ms
+ res = timeout_func(0.05, _eval_expr, query_py_formatted)
+ if res is None or res == "":
+ return results
+
+ res = babel.numbers.format_decimal(res, locale=ui_locale)
+ results.add(results.types.Answer(answer=f"{search.search_query.query} = {res}"))
+
+ return results
+
+
+operators: dict[type, typing.Callable] = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
@@ -92,49 +156,3 @@ def timeout_func(timeout, func, *args, **kwargs):
p.join()
p.close()
return ret_val
-
-
-def post_search(request, search) -> EngineResults:
- results = EngineResults()
-
- # only show the result of the expression on the first page
- if search.search_query.pageno > 1:
- return results
-
- query = search.search_query.query
- # in order to avoid DoS attacks with long expressions, ignore long expressions
- if len(query) > 100:
- return results
-
- # replace commonly used math operators with their proper Python operator
- query = query.replace("x", "*").replace(":", "/")
-
- # use UI language
- ui_locale = babel.Locale.parse(request.preferences.get_value('locale'), sep='-')
-
- # parse the number system in a localized way
- def _decimal(match: re.Match) -> str:
- val = match.string[match.start() : match.end()]
- val = babel.numbers.parse_decimal(val, ui_locale, numbering_system="latn")
- return str(val)
-
- decimal = ui_locale.number_symbols["latn"]["decimal"]
- group = ui_locale.number_symbols["latn"]["group"]
- query = re.sub(f"[0-9]+[{decimal}|{group}][0-9]+[{decimal}|{group}]?[0-9]?", _decimal, query)
-
- # only numbers and math operators are accepted
- if any(str.isalpha(c) for c in query):
- return results
-
- # in python, powers are calculated via **
- query_py_formatted = query.replace("^", "**")
-
- # Prevent the runtime from being longer than 50 ms
- res = timeout_func(0.05, _eval_expr, query_py_formatted)
- if res is None or res == "":
- return results
-
- res = babel.numbers.format_decimal(res, locale=ui_locale)
- results.add(results.types.Answer(answer=f"{search.search_query.query} = {res}"))
-
- return results