diff --git a/docs/admin/plugins.rst b/docs/admin/plugins.rst index d3d137fcf..be9778673 100644 --- a/docs/admin/plugins.rst +++ b/docs/admin/plugins.rst @@ -30,7 +30,6 @@ Configuration defaults (at built time): {% for plg in plugins %} * - {{plg.info.name}} - - {{(plg.default_on and "y") or ""}} - {{plg.info.description}} {% endfor %} diff --git a/docs/admin/settings/settings_plugins.rst b/docs/admin/settings/settings_plugins.rst index 991e7ee53..ae9976a88 100644 --- a/docs/admin/settings/settings_plugins.rst +++ b/docs/admin/settings/settings_plugins.rst @@ -12,13 +12,13 @@ Plugins The built-in plugins can be activated or deactivated via the settings -(:ref:`settings enabled_plugins`) and external plugins can be integrated into +(:ref:`settings plugins`) and external plugins can be integrated into SearXNG (:ref:`settings external_plugins`). -.. _settings enabled_plugins: +.. _settings plugins: -``enabled_plugins:`` (internal) +``plugins:`` (internal) =============================== In :ref:`plugins admin` you find a complete list of all plugins, the default @@ -26,14 +26,23 @@ configuration looks like: .. code:: yaml - enabled_plugins: - - 'Basic Calculator' - - 'Hash plugin' - - 'Self Information' - - 'Tracker URL remover' - - 'Unit converter plugin' - - 'Ahmia blacklist' + plugins: + - id: 'calculator' + default_on: true + - id: 'hash_plugin' + default_on: true + - id: 'self_info' + default_on: true + - id: 'tracker_url_remover' + default_on: true + - id: 'unit_converter' + default_on: true + - id: 'ahmia_filter' # activation depends on outgoing.using_tor_proxy + default_on: true +In order to disable a plugin by default, but still allow users to use it by enabling +it in their user settings, set ``default_on`` to ``false``. To completely disable a +plugin, you can set `inactive` to `true`. .. _settings external_plugins: @@ -56,9 +65,11 @@ In the :ref:`settings.yml` activate the ``plugins:`` section and add module .. code:: yaml plugins: - - only_show_green_results - # - mypackage.mymodule.MyPlugin - # - mypackage.mymodule.MyOtherPlugin + - id: only_show_green_results + default_on: true + # - fqn: mypackage.mymodule.MyPlugin + # - fqn: mypackage.mymodule.MyOtherPlugin + # default_on: false .. hint:: diff --git a/searx/plugins/__init__.py b/searx/plugins/__init__.py index 9aaf2f2db..63f9132f0 100644 --- a/searx/plugins/__init__.py +++ b/searx/plugins/__init__.py @@ -18,7 +18,6 @@ area: class MyPlugin(Plugin): id = "self_info" - default_on = True def __init__(self): super().__init__() diff --git a/searx/plugins/_core.py b/searx/plugins/_core.py index 70e5758ec..87c7558b0 100644 --- a/searx/plugins/_core.py +++ b/searx/plugins/_core.py @@ -75,9 +75,6 @@ class Plugin(abc.ABC): id: typing.ClassVar[str] """The ID (suffix) in the HTML form.""" - default_on: typing.ClassVar[bool] - """Plugin is enabled/disabled by default.""" - keywords: list[str] = [] """Keywords in the search query that activate the plugin. The *keyword* is the first word in a search query. If a plugin should be executed regardless @@ -94,9 +91,8 @@ class Plugin(abc.ABC): def __init__(self) -> None: super().__init__() - for attr in ["id", "default_on"]: - if getattr(self, attr, None) is None: - raise NotImplementedError(f"plugin {self} is missing attribute {attr}") + if getattr(self, "id", None) is None: + raise NotImplementedError(f"plugin {self} is missing attribute id") if not self.id: self.id = f"{self.__class__.__module__}.{self.__class__.__name__}" @@ -117,6 +113,25 @@ class Plugin(abc.ABC): return hash(self) == hash(other) + def is_enabled_by_default(self) -> bool: + """ + Check whether a plugin is enabled by default based on the instance's configuration + + This method may not be overriden in any plugin implementation! + """ + plugins = searx.get_setting('plugins', []) + + for plugin in plugins: + if isinstance(plugin, dict): + if plugin.get('id') == self.id: + return plugin.get('default_on', True) + + # legacy way of enabling plugins (list of strings) - TODO: remove in the future + legacy_enabled_plugins = searx.get_setting('enabled_plugins', []) + if not legacy_enabled_plugins: + return False + return self.info.name in legacy_enabled_plugins + def init(self, app: flask.Flask) -> bool: # pylint: disable=unused-argument """Initialization of the plugin, the return value decides whether this plugin is active or not. Initialization only takes place once, at the @@ -176,7 +191,7 @@ class ModulePlugin(Plugin): - `module.logger` --> :py:obj:`Plugin.log` """ - _required_attrs = (("name", str), ("description", str), ("default_on", bool)) + _required_attrs = (("name", str), ("description", str)) def __init__(self, mod: types.ModuleType): """In case of missing attributes in the module or wrong types are given, @@ -197,7 +212,6 @@ class ModulePlugin(Plugin): self.log.critical(msg) raise TypeError(msg) - self.default_on = mod.default_on self.info = PluginInfo( id=self.id, name=self.module.name, @@ -271,21 +285,40 @@ class PluginStorage: - the external plugins from :ref:`settings plugins`. """ - for f in _default.iterdir(): + plugins = searx.get_setting('plugins', []) - if f.name.startswith("_"): - continue + # only there for backwards compatibility - TODO: remove + if not plugins and searx.get_setting('enabled_plugins', []): + for f in _default.iterdir(): + if f.name.startswith("_"): + continue - if f.stem not in self.legacy_plugins: - self.register_by_fqn(f"searx.plugins.{f.stem}.SXNGPlugin") - continue + self.load_by_id(f.stem) - # for backward compatibility - mod = load_module(f.name, str(f.parent)) - self.register(ModulePlugin(mod)) + for plugin in plugins: + if isinstance(plugin, dict): + if plugin.get('inactive', False): + continue - for fqn in searx.get_setting("plugins"): # type: ignore - self.register_by_fqn(fqn) + if 'fqn' in plugin: + self.register_by_fqn(plugin['fqn']) + elif 'id' in plugin: + self.load_by_id(plugin['id']) + else: + log.debug('Invalid plugin configuration: %s', plugin) + + # legacy way of enabling plugins - TODO: remove in the future + elif isinstance(plugin, str): + self.register_by_fqn(plugin) + + def load_by_id(self, plugin_id): + if plugin_id not in self.legacy_plugins: + self.register_by_fqn(f"searx.plugins.{plugin_id}.SXNGPlugin") + return + + # for backward compatibility + mod = load_module(f"{plugin_id}.py", _default) + self.register(ModulePlugin(mod)) def register(self, plugin: Plugin): """Register a :py:obj:`Plugin`. In case of name collision (if two diff --git a/searx/plugins/ahmia_filter.py b/searx/plugins/ahmia_filter.py index 3a6d48eed..55984465b 100644 --- a/searx/plugins/ahmia_filter.py +++ b/searx/plugins/ahmia_filter.py @@ -12,7 +12,6 @@ from searx import get_setting name = "Ahmia blacklist" description = "Filter out onion results that appear in Ahmia's blacklist. (See https://ahmia.fi/blacklist)" -default_on = True preference_section = 'onions' ahmia_blacklist: list = [] diff --git a/searx/plugins/calculator.py b/searx/plugins/calculator.py index 11caa272f..3f561df44 100644 --- a/searx/plugins/calculator.py +++ b/searx/plugins/calculator.py @@ -18,7 +18,6 @@ from searx.result_types import EngineResults name = "Basic Calculator" description = gettext("Calculate mathematical expressions via the search bar") -default_on = True preference_section = 'general' plugin_id = 'calculator' diff --git a/searx/plugins/hash_plugin.py b/searx/plugins/hash_plugin.py index 940c895a1..a0f92912e 100644 --- a/searx/plugins/hash_plugin.py +++ b/searx/plugins/hash_plugin.py @@ -22,7 +22,6 @@ class SXNGPlugin(Plugin): """ id = "hash_plugin" - default_on = True keywords = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"] def __init__(self): diff --git a/searx/plugins/hostnames.py b/searx/plugins/hostnames.py index 5f88bcd40..c06e7c6e6 100644 --- a/searx/plugins/hostnames.py +++ b/searx/plugins/hostnames.py @@ -7,12 +7,13 @@ plugin"**, see :pull:`3463` & :pull:`3552`. The **Hostnames plugin** can be enabled by adding it to the -``enabled_plugins`` **list** in the ``setting.yml`` like so. +``plugins`` **list** in the ``setting.yml`` like so. .. code:: yaml - enabled_plugins: - - 'Hostnames plugin' + plugins: + - id: 'hostnames' + default_on: true ... - ``hostnames.replace``: A **mapping** of regular expressions to hostnames to be @@ -104,7 +105,6 @@ from searx.settings_loader import get_yaml_cfg name = gettext('Hostnames plugin') description = gettext('Rewrite hostnames, remove results or prioritize them based on the hostname') -default_on = False preference_section = 'general' plugin_id = 'hostnames' diff --git a/searx/plugins/oa_doi_rewrite.py b/searx/plugins/oa_doi_rewrite.py index be5a8d4a4..61f3eb322 100644 --- a/searx/plugins/oa_doi_rewrite.py +++ b/searx/plugins/oa_doi_rewrite.py @@ -14,7 +14,6 @@ regex = re.compile(r'10\.\d{4,9}/[^\s]+') name = gettext('Open Access DOI rewrite') description = gettext('Avoid paywalls by redirecting to open-access versions of publications when available') -default_on = False preference_section = 'general/doi_resolver' diff --git a/searx/plugins/self_info.py b/searx/plugins/self_info.py index f5498e480..d9c54d055 100644 --- a/searx/plugins/self_info.py +++ b/searx/plugins/self_info.py @@ -23,7 +23,6 @@ class SXNGPlugin(Plugin): """ id = "self_info" - default_on = True keywords = ["ip", "user-agent"] def __init__(self): diff --git a/searx/plugins/tor_check.py b/searx/plugins/tor_check.py index 95281eb42..2baafad19 100644 --- a/searx/plugins/tor_check.py +++ b/searx/plugins/tor_check.py @@ -8,9 +8,10 @@ Enable in ``settings.yml``: .. code:: yaml - enabled_plugins: + plugins: .. - - 'Tor check plugin' + - id: 'tor_check' + default_on: true """ @@ -24,8 +25,6 @@ from searx.network import get from searx.result_types import Answer -default_on = False - name = gettext("Tor check plugin") '''Translated name of the plugin''' diff --git a/searx/plugins/tracker_url_remover.py b/searx/plugins/tracker_url_remover.py index f33f7fdfd..78bba4e70 100644 --- a/searx/plugins/tracker_url_remover.py +++ b/searx/plugins/tracker_url_remover.py @@ -17,7 +17,6 @@ regexes = { name = gettext('Tracker URL remover') description = gettext('Remove trackers arguments from the returned URL') -default_on = True preference_section = 'privacy' diff --git a/searx/plugins/unit_converter.py b/searx/plugins/unit_converter.py index cdd8287fe..c35493cb2 100644 --- a/searx/plugins/unit_converter.py +++ b/searx/plugins/unit_converter.py @@ -14,7 +14,8 @@ Enable in ``settings.yml``: enabled_plugins: .. - - 'Unit converter plugin' + - name: 'Unit converter plugin' + default_on: true """ @@ -30,7 +31,6 @@ from searx.result_types import Answer name = "Unit converter plugin" description = gettext("Convert between units") -default_on = True plugin_id = "unit_converter" preference_section = "general" diff --git a/searx/preferences.py b/searx/preferences.py index 9f810ec72..6910e0208 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -316,7 +316,8 @@ class PluginsSetting(BooleanChoices): """Plugin settings""" def __init__(self, default_value, plugins: Iterable[searx.plugins.Plugin]): - super().__init__(default_value, {plugin.id: plugin.default_on for plugin in plugins}) + plugin_states = {plugin.id: plugin.is_enabled_by_default() for plugin in plugins} + super().__init__(default_value, plugin_states) def transform_form_items(self, items): return [item[len('plugin_') :] for item in items] diff --git a/searx/settings.yml b/searx/settings.yml index 23092b800..dc3135def 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -226,30 +226,43 @@ outgoing: # - 1.1.1.2 # - fe80::/126 +# Comment plugins out to completely disable them. +# Set 'default_on' to false in order to disable them by default, +# but allow users to manually enable them in the settings. +# see https://docs.searxng.org/admin/settings/settings_plugins.html +# +# You can set 'inactive' to 'true' in order to force disable a plugin +# that's enabled by default. +plugins: + - id: 'calculator' + default_on: true + - id: 'hash_plugin' + default_on: true + - id: 'self_info' + default_on: true + - id: 'tracker_url_remover' + default_on: true + - id: 'unit_converter' + default_on: true + - id: 'ahmia_filter' # activation depends on outgoing.using_tor_proxy + default_on: true +# +# These plugins are completely disabled if nothing is configured .. +# - id: 'hostnames' # see 'hostnames' configuration below +# default_on: false +# - id: 'oa_doi_rewrite' +# default_on: false +# - id: 'tor_check' +# default_on: false +# # External plugin configuration, for more details see # https://docs.searxng.org/admin/settings/settings_plugins.html -# -# plugins: -# - mypackage.mymodule.MyPlugin -# - mypackage.mymodule.MyOtherPlugin +# - fqn: mypackage.mymodule.MyPlugin +# default_on: true +# - fqn: mypackage.mymodule.MyOtherPlugin +# default_on: false # - ... -# Comment or un-comment plugin to activate / deactivate by default. -# https://docs.searxng.org/admin/settings/settings_plugins.html -# -# enabled_plugins: -# # these plugins are enabled if nothing is configured .. -# - 'Basic Calculator' -# - 'Hash plugin' -# - 'Self Information' -# - 'Tracker URL remover' -# - 'Unit converter plugin' -# - 'Ahmia blacklist' # activation depends on outgoing.using_tor_proxy -# # these plugins are disabled if nothing is configured .. -# - 'Hostnames plugin' # see 'hostnames' configuration below -# - 'Open Access DOI rewrite' -# - 'Tor check plugin' - # Configuration of the "Hostnames plugin": # # hostnames: diff --git a/searx/settings_defaults.py b/searx/settings_defaults.py index 1bafa749a..8fd705b46 100644 --- a/searx/settings_defaults.py +++ b/searx/settings_defaults.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-or-later -"""Implementation of the default settings. - -""" +"""Implementation of the default settings.""" import typing import numbers @@ -236,7 +234,7 @@ SCHEMA = { 'proxify_results': SettingsValue(bool, False), }, 'plugins': SettingsValue(list, []), - 'enabled_plugins': SettingsValue((None, list), None), + 'enabled_plugins': SettingsValue((None, list), None), # legacy - TODO: remove 'checker': { 'off_when_debug': SettingsValue(bool, True, None), 'scheduling': SettingsValue((None, dict), None, None), diff --git a/searx/webapp.py b/searx/webapp.py index 9d51b5e8c..f8f3d5c93 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -1292,7 +1292,7 @@ def config(): _plugins = [] for _ in searx.plugins.STORAGE: - _plugins.append({'name': _.id, 'enabled': _.default_on}) + _plugins.append({'name': _.id}) _limiter_cfg = limiter.get_cfg() diff --git a/tests/unit/test_plugins.py b/tests/unit/test_plugins.py index 33df0f320..c1d86e2c5 100644 --- a/tests/unit/test_plugins.py +++ b/tests/unit/test_plugins.py @@ -47,10 +47,16 @@ def do_post_search(query, storage, **kwargs) -> Mock: class PluginMock(searx.plugins.Plugin): - def __init__(self, _id: str, name: str, default_on: bool): + def __init__(self, _id: str, name: str, default_enabled: bool = False): self.id = _id - self.default_on = default_on self._name = name + self.default_enabled = default_enabled + self.info = searx.plugins.PluginInfo( + id=id, + name=name, + description=f"Dummy plugin: {id}", + preference_section="general", + ) super().__init__() # pylint: disable= unused-argument @@ -63,14 +69,6 @@ class PluginMock(searx.plugins.Plugin): def on_result(self, request, search, result) -> bool: return False - def info(self): - return searx.plugins.PluginInfo( - id=self.id, - name=self._name, - description=f"Dummy plugin: {self.id}", - preference_section="general", - ) - class PluginStorage(SearxTestCase): @@ -78,9 +76,13 @@ class PluginStorage(SearxTestCase): super().setUp() engines = {} + searx.settings['plugins'] = [ + {'id': 'plg001', 'default_on': True}, + {'id': 'plg002', 'default_on': False}, + ] self.storage = searx.plugins.PluginStorage() - self.storage.register(PluginMock("plg001", "first plugin", True)) - self.storage.register(PluginMock("plg002", "second plugin", True)) + self.storage.register(PluginMock("plg001", "first plugin")) + self.storage.register(PluginMock("plg002", "second plugin")) self.storage.init(self.app) self.pref = searx.preferences.Preferences(["simple"], ["general"], engines, self.storage) self.pref.parse_dict({"locale": "en"}) diff --git a/tests/unit/test_preferences.py b/tests/unit/test_preferences.py index d7009cbfd..2c77f5a8b 100644 --- a/tests/unit/test_preferences.py +++ b/tests/unit/test_preferences.py @@ -115,19 +115,26 @@ class TestSettings(SearxTestCase): # plugins settings - def test_plugins_setting_all_default_enabled(self): - storage = searx.plugins.PluginStorage() - storage.register(PluginMock("plg001", "first plugin", True)) - storage.register(PluginMock("plg002", "second plugin", True)) - plgs_settings = PluginsSetting(False, storage) - self.assertEqual(set(plgs_settings.get_enabled()), {"plg001", "plg002"}) - def test_plugins_setting_few_default_enabled(self): + searx.settings['plugins'] = [ + { + 'id': 'plg001', + }, + { + 'id': 'plg002', + 'default_on': False, + }, + { + 'id': 'plg003', + 'default_on': True, + }, + ] storage = searx.plugins.PluginStorage() - storage.register(PluginMock("plg001", "first plugin", True)) - storage.register(PluginMock("plg002", "second plugin", False)) - storage.register(PluginMock("plg003", "third plugin", True)) + storage.register(PluginMock("plg001", "first plugin")) + storage.register(PluginMock("plg002", "second plugin")) + storage.register(PluginMock("plg003", "third plugin")) plgs_settings = PluginsSetting(False, storage) + self.assertEqual(set(plgs_settings.get_disabled()), set(['plg002'])) self.assertEqual(set(plgs_settings.get_enabled()), set(['plg001', 'plg003'])) diff --git a/utils/templates/etc/searxng/settings.yml b/utils/templates/etc/searxng/settings.yml index be79810b1..999233e8f 100644 --- a/utils/templates/etc/searxng/settings.yml +++ b/utils/templates/etc/searxng/settings.yml @@ -31,16 +31,20 @@ ui: # - autocomplete # - method -enabled_plugins: - - 'Hash plugin' - - 'Self Information' - - 'Tracker URL remover' - - 'Ahmia blacklist' - # - 'Hostnames plugin' # see 'hostnames' configuration below - # - 'Open Access DOI rewrite' - -# plugins: -# - only_show_green_results +plugins: + - id: 'calculator' + default_on: true + - id: 'hash_plugin' + default_on: true + - id: 'self_info' + default_on: true + - id: 'tracker_url_remover' + default_on: true + - id: 'unit_converter' + default_on: true +# - id: 'ahmia_filter' # activation depends on outgoing.using_tor_proxy +# - fqn: only_show_green_results +# default_on: true # hostnames: # replace: