This commit is contained in:
Bnyro 2025-02-09 15:06:46 +01:00 committed by GitHub
commit 3244860fc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 129 additions and 68 deletions

View File

@ -30,7 +30,6 @@ Configuration defaults (at built time):
{% for plg in plugins %} {% for plg in plugins %}
* - {{plg.info.name}} * - {{plg.info.name}}
- {{(plg.default_on and "y") or ""}}
- {{plg.info.description}} - {{plg.info.description}}
{% endfor %} {% endfor %}

View File

@ -27,13 +27,21 @@ configuration looks like:
.. code:: yaml .. code:: yaml
enabled_plugins: enabled_plugins:
- 'Basic Calculator' - name: 'Basic Calculator'
- 'Hash plugin' default_on: true
- 'Self Information' - name: 'Hash plugin'
- 'Tracker URL remover' default_on: true
- 'Unit converter plugin' - name: 'Self Information'
- 'Ahmia blacklist' default_on: true
- name: 'Tracker URL remover'
default_on: true
- name: 'Unit converter plugin'
default_on: true
- name: 'Ahmia blacklist' # 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``.
.. _settings external_plugins: .. _settings external_plugins:

View File

@ -18,7 +18,6 @@ area:
class MyPlugin(Plugin): class MyPlugin(Plugin):
id = "self_info" id = "self_info"
default_on = True
def __init__(self): def __init__(self):
super().__init__() super().__init__()

View File

@ -75,9 +75,6 @@ class Plugin(abc.ABC):
id: typing.ClassVar[str] id: typing.ClassVar[str]
"""The ID (suffix) in the HTML form.""" """The ID (suffix) in the HTML form."""
default_on: typing.ClassVar[bool]
"""Plugin is enabled/disabled by default."""
keywords: list[str] = [] keywords: list[str] = []
"""Keywords in the search query that activate the plugin. The *keyword* is """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 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: def __init__(self) -> None:
super().__init__() super().__init__()
for attr in ["id", "default_on"]: if getattr(self, "id", None) is None:
if getattr(self, attr, None) is None: raise NotImplementedError(f"plugin {self} is missing attribute id")
raise NotImplementedError(f"plugin {self} is missing attribute {attr}")
if not self.id: if not self.id:
self.id = f"{self.__class__.__module__}.{self.__class__.__name__}" self.id = f"{self.__class__.__module__}.{self.__class__.__name__}"
@ -117,6 +113,26 @@ class Plugin(abc.ABC):
return hash(self) == hash(other) 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!
"""
enabled_plugins = searx.get_setting('enabled_plugins', [])
if not enabled_plugins:
return False
for enabled_plugin in enabled_plugins:
if isinstance(enabled_plugin, dict):
# for legacy reasons, it's still allowed to reference plugins by their
# name instead of their ID in the settings
if enabled_plugin.get('name') in (self.info.name, self.id):
return enabled_plugin.get('default_on', True)
# legacy way of enabling plugins (list of strings) - TODO: remove in the future
return self.info.name in enabled_plugins
def init(self, app: flask.Flask) -> bool: # pylint: disable=unused-argument def init(self, app: flask.Flask) -> bool: # pylint: disable=unused-argument
"""Initialization of the plugin, the return value decides whether this """Initialization of the plugin, the return value decides whether this
plugin is active or not. Initialization only takes place once, at the plugin is active or not. Initialization only takes place once, at the
@ -176,7 +192,7 @@ class ModulePlugin(Plugin):
- `module.logger` --> :py:obj:`Plugin.log` - `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): def __init__(self, mod: types.ModuleType):
"""In case of missing attributes in the module or wrong types are given, """In case of missing attributes in the module or wrong types are given,
@ -197,7 +213,6 @@ class ModulePlugin(Plugin):
self.log.critical(msg) self.log.critical(msg)
raise TypeError(msg) raise TypeError(msg)
self.default_on = mod.default_on
self.info = PluginInfo( self.info = PluginInfo(
id=self.id, id=self.id,
name=self.module.name, name=self.module.name,
@ -291,6 +306,8 @@ class PluginStorage:
"""Register a :py:obj:`Plugin`. In case of name collision (if two """Register a :py:obj:`Plugin`. In case of name collision (if two
plugins have same ID) a :py:obj:`KeyError` exception is raised. plugins have same ID) a :py:obj:`KeyError` exception is raised.
""" """
if not self.plugin_enabled(plugin):
return
if plugin in self.plugin_list: if plugin in self.plugin_list:
msg = f"name collision '{plugin.id}'" msg = f"name collision '{plugin.id}'"
@ -329,6 +346,28 @@ class PluginStorage:
self.register(code_obj()) self.register(code_obj())
def plugin_enabled(self, plugin: searx.plugins.Plugin) -> bool:
"""
Check whether a plugin is enabled based on the instance's configuration
"""
enabled_plugins = searx.get_setting('enabled_plugins', [])
if not enabled_plugins:
return False
for enabled_plugin in enabled_plugins:
if isinstance(enabled_plugin, dict):
# for legacy reasons, it's still allowed to reference plugins by their
# name instead of their ID in the settings
if enabled_plugin.get('name') in (plugin.info.name, plugin.id):
return True
# legacy way of enabling plugins - TODO: remove in the future
elif isinstance(enabled_plugin, str):
if enabled_plugin == plugin.info.name:
return True
return False
def init(self, app: flask.Flask) -> None: def init(self, app: flask.Flask) -> None:
"""Calls the method :py:obj:`Plugin.init` of each plugin in this """Calls the method :py:obj:`Plugin.init` of each plugin in this
storage. Depending on its return value, the plugin is removed from storage. Depending on its return value, the plugin is removed from

View File

@ -12,7 +12,6 @@ from searx import get_setting
name = "Ahmia blacklist" name = "Ahmia blacklist"
description = "Filter out onion results that appear in Ahmia's blacklist. (See https://ahmia.fi/blacklist)" description = "Filter out onion results that appear in Ahmia's blacklist. (See https://ahmia.fi/blacklist)"
default_on = True
preference_section = 'onions' preference_section = 'onions'
ahmia_blacklist: list = [] ahmia_blacklist: list = []

View File

@ -18,7 +18,6 @@ from searx.result_types import EngineResults
name = "Basic Calculator" name = "Basic Calculator"
description = gettext("Calculate mathematical expressions via the search bar") description = gettext("Calculate mathematical expressions via the search bar")
default_on = True
preference_section = 'general' preference_section = 'general'
plugin_id = 'calculator' plugin_id = 'calculator'

View File

@ -22,7 +22,6 @@ class SXNGPlugin(Plugin):
""" """
id = "hash_plugin" id = "hash_plugin"
default_on = True
keywords = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"] keywords = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]
def __init__(self): def __init__(self):

View File

@ -12,7 +12,8 @@ The **Hostnames plugin** can be enabled by adding it to the
.. code:: yaml .. code:: yaml
enabled_plugins: enabled_plugins:
- 'Hostnames plugin' - name: 'Hostnames plugin'
default_on: true
... ...
- ``hostnames.replace``: A **mapping** of regular expressions to hostnames to be - ``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') name = gettext('Hostnames plugin')
description = gettext('Rewrite hostnames, remove results or prioritize them based on the hostname') description = gettext('Rewrite hostnames, remove results or prioritize them based on the hostname')
default_on = False
preference_section = 'general' preference_section = 'general'
plugin_id = 'hostnames' plugin_id = 'hostnames'

View File

@ -14,7 +14,6 @@ regex = re.compile(r'10\.\d{4,9}/[^\s]+')
name = gettext('Open Access DOI rewrite') name = gettext('Open Access DOI rewrite')
description = gettext('Avoid paywalls by redirecting to open-access versions of publications when available') description = gettext('Avoid paywalls by redirecting to open-access versions of publications when available')
default_on = False
preference_section = 'general/doi_resolver' preference_section = 'general/doi_resolver'

View File

@ -23,7 +23,6 @@ class SXNGPlugin(Plugin):
""" """
id = "self_info" id = "self_info"
default_on = True
keywords = ["ip", "user-agent"] keywords = ["ip", "user-agent"]
def __init__(self): def __init__(self):

View File

@ -10,7 +10,8 @@ Enable in ``settings.yml``:
enabled_plugins: enabled_plugins:
.. ..
- 'Tor check plugin' - name: 'Tor check plugin'
default_on: true
""" """
@ -24,8 +25,6 @@ from searx.network import get
from searx.result_types import Answer from searx.result_types import Answer
default_on = False
name = gettext("Tor check plugin") name = gettext("Tor check plugin")
'''Translated name of the plugin''' '''Translated name of the plugin'''

View File

@ -17,7 +17,6 @@ regexes = {
name = gettext('Tracker URL remover') name = gettext('Tracker URL remover')
description = gettext('Remove trackers arguments from the returned URL') description = gettext('Remove trackers arguments from the returned URL')
default_on = True
preference_section = 'privacy' preference_section = 'privacy'

View File

@ -14,7 +14,8 @@ Enable in ``settings.yml``:
enabled_plugins: 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" name = "Unit converter plugin"
description = gettext("Convert between units") description = gettext("Convert between units")
default_on = True
plugin_id = "unit_converter" plugin_id = "unit_converter"
preference_section = "general" preference_section = "general"

View File

@ -316,7 +316,8 @@ class PluginsSetting(BooleanChoices):
"""Plugin settings""" """Plugin settings"""
def __init__(self, default_value, plugins: Iterable[searx.plugins.Plugin]): 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): def transform_form_items(self, items):
return [item[len('plugin_') :] for item in items] return [item[len('plugin_') :] for item in items]

View File

@ -234,21 +234,30 @@ outgoing:
# - mypackage.mymodule.MyOtherPlugin # - mypackage.mymodule.MyOtherPlugin
# - ... # - ...
# Comment or un-comment plugin to activate / deactivate by default. # Comment plugins out to completely disable them.
# https://docs.searxng.org/admin/settings/settings_plugins.html # Set 'default_on' to false in order to disable them by default,
# # but allow users to manually enable them in the settings.
# enabled_plugins: # see https://docs.searxng.org/admin/settings/settings_plugins.html
# # these plugins are enabled if nothing is configured .. enabled_plugins:
# - 'Basic Calculator' - name: 'Basic Calculator'
# - 'Hash plugin' default_on: true
# - 'Self Information' - name: 'Hash plugin'
# - 'Tracker URL remover' default_on: true
# - 'Unit converter plugin' - name: 'Self Information'
# - 'Ahmia blacklist' # activation depends on outgoing.using_tor_proxy default_on: true
# # these plugins are disabled if nothing is configured .. - name: 'Tracker URL remover'
# - 'Hostnames plugin' # see 'hostnames' configuration below default_on: true
# - 'Open Access DOI rewrite' - name: 'Unit converter plugin'
# - 'Tor check plugin' default_on: true
- name: 'Ahmia blacklist' # activation depends on outgoing.using_tor_proxy
default_on: true
# these plugins are completely disabled if nothing is configured ..
# - name: 'Hostnames plugin' # see 'hostnames' configuration below
# default_on: false
# - name: 'Open Access DOI rewrite'
# default_on: false
# - name: 'Tor check plugin'
# default_on: false
# Configuration of the "Hostnames plugin": # Configuration of the "Hostnames plugin":
# #

View File

@ -1292,7 +1292,7 @@ def config():
_plugins = [] _plugins = []
for _ in searx.plugins.STORAGE: for _ in searx.plugins.STORAGE:
_plugins.append({'name': _.id, 'enabled': _.default_on}) _plugins.append({'name': _.id})
_limiter_cfg = limiter.get_cfg() _limiter_cfg = limiter.get_cfg()

View File

@ -47,10 +47,16 @@ def do_post_search(query, storage, **kwargs) -> Mock:
class PluginMock(searx.plugins.Plugin): 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.id = _id
self.default_on = default_on
self._name = name 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__() super().__init__()
# pylint: disable= unused-argument # pylint: disable= unused-argument
@ -63,14 +69,6 @@ class PluginMock(searx.plugins.Plugin):
def on_result(self, request, search, result) -> bool: def on_result(self, request, search, result) -> bool:
return False 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): class PluginStorage(SearxTestCase):
@ -78,9 +76,17 @@ class PluginStorage(SearxTestCase):
super().setUp() super().setUp()
engines = {} engines = {}
searx.settings['enabled_plugins'] = [
{
'name': 'plg001',
},
{
'name': 'plg002',
},
]
self.storage = searx.plugins.PluginStorage() self.storage = searx.plugins.PluginStorage()
self.storage.register(PluginMock("plg001", "first plugin", True)) self.storage.register(PluginMock("plg001", "first plugin"))
self.storage.register(PluginMock("plg002", "second plugin", True)) self.storage.register(PluginMock("plg002", "second plugin"))
self.storage.init(self.app) self.storage.init(self.app)
self.pref = searx.preferences.Preferences(["simple"], ["general"], engines, self.storage) self.pref = searx.preferences.Preferences(["simple"], ["general"], engines, self.storage)
self.pref.parse_dict({"locale": "en"}) self.pref.parse_dict({"locale": "en"})

View File

@ -115,19 +115,26 @@ class TestSettings(SearxTestCase):
# plugins settings # 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): def test_plugins_setting_few_default_enabled(self):
searx.settings['enabled_plugins'] = [
{
'name': 'plg001',
},
{
'name': 'plg002',
'default_on': False,
},
{
'name': 'plg003',
'default_on': True,
},
]
storage = searx.plugins.PluginStorage() storage = searx.plugins.PluginStorage()
storage.register(PluginMock("plg001", "first plugin", True)) storage.register(PluginMock("plg001", "first plugin"))
storage.register(PluginMock("plg002", "second plugin", False)) storage.register(PluginMock("plg002", "second plugin"))
storage.register(PluginMock("plg003", "third plugin", True)) storage.register(PluginMock("plg003", "third plugin"))
plgs_settings = PluginsSetting(False, storage) plgs_settings = PluginsSetting(False, storage)
self.assertEqual(set(plgs_settings.get_disabled()), set(['plg002']))
self.assertEqual(set(plgs_settings.get_enabled()), set(['plg001', 'plg003'])) self.assertEqual(set(plgs_settings.get_enabled()), set(['plg001', 'plg003']))