Browse Source

[mod] implement plugin class: only_show_green_results.SXNGPlugin

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
Markus Heiser 1 month ago
parent
commit
5ab676bfd8
11 changed files with 918 additions and 590 deletions
  1. 34 0
      .editorconfig
  2. 2 1
      .gitignore
  3. 0 444
      .pylintrc
  4. 52 0
      README.rst
  5. 0 107
      only_show_green_results.py
  6. 590 0
      pylintrc.toml
  7. 86 0
      pyproject.toml
  8. 6 0
      pyrightconfig.json
  9. 0 38
      setup.py
  10. 57 0
      src/only_show_green_results/__init__.py
  11. 91 0
      src/only_show_green_results/greencheck.py

+ 34 - 0
.editorconfig

@@ -0,0 +1,34 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+max_line_length = 80
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+end_of_line = lf
+charset = utf-8
+insert_final_newline = ignore
+
+[*.py]
+indent_size = 4
+
+[*.html]
+indent_size = 2
+
+[*.js]
+indent_size = 2
+
+[*.json]
+indent_size = 2
+
+[*.bat]
+indent_size = 2
+
+[*.rst]
+max_line_length = 79
+
+[*.toml]
+indent_size = 2

+ 2 - 1
.gitignore

@@ -1,4 +1,5 @@
 *~
 *.pyc
 */*.pyc
-tgwf_searx_plugins.egg-info
+tgwf_searx_plugins.egg-info
+build/

+ 0 - 444
.pylintrc

@@ -1,444 +0,0 @@
-# -*- coding: utf-8; mode: conf -*-
-# lint Python modules using external checkers.
-#
-# This is the main checker controlling the other ones and the reports
-# generation. It is itself both a raw checker and an astng checker in order
-# to:
-# * handle message activation / deactivation at the module level
-# * handle some basic but necessary stats'data (number of classes, methods...)
-#
-[MASTER]
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code
-extension-pkg-whitelist=
-
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=CVS, .git, .svn
-
-# Add files or directories matching the regex patterns to the blacklist. The
-# regex matches against base names, not paths.
-ignore-patterns=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Use multiple processes to speed up Pylint.
-jobs=1
-
-# List of plugins (as comma separated values of python modules names) to load,
-# usually to register additional checkers.
-load-plugins=
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# Specify a configuration file.
-#rcfile=
-
-# Allow loading of arbitrary C extensions. Extensions are imported into the
-# active Python interpreter and may run arbitrary code.
-unsafe-load-any-extension=no
-
-
-[MESSAGES CONTROL]
-
-# Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
-confidence=
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once).You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use"--disable=all --enable=classes
-# --disable=W"
-disable=bad-whitespace, duplicate-code
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once). See also the "--disable" option for examples.
-enable=
-
-
-[REPORTS]
-
-# Python expression which should return a note less than 10 (10 is the highest
-# note). You have access to the variables errors warning, statement which
-# respectively contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details
-
-# HINT: do not set this here, use argument --msg-template=...
-#msg-template={path}:{line}: [{msg_id}({symbol}),{obj}] {msg}
-
-# Set the output format. Available formats are text, parseable, colorized, json
-# and msvs (visual studio).You can also give a reporter class, eg
-# mypackage.mymodule.MyReporterClass.
-
-# HINT: do not set this here, use argument --output-format=...
-#output-format=text
-
-# Tells whether to display a full report or only the messages
-reports=no
-
-# Activate the evaluation score.
-score=yes
-
-
-[REFACTORING]
-
-# Maximum number of nested blocks for function / method body
-max-nested-blocks=5
-
-
-[BASIC]
-
-# List of builtins function names that should not be used, separated by a comma
-bad-functions=map,filter,apply,input
-
-# Naming hint for argument names
-argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Regular expression matching correct argument names
-argument-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Naming hint for attribute names
-attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Regular expression matching correct attribute names
-attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*)|([A-Z0-9_]*))$
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# Naming hint for class attribute names
-class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Regular expression matching correct class attribute names
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Naming hint for class names
-class-name-hint=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression matching correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Naming hint for constant names
-const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression matching correct constant names
-const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$
-#const-rgx=[f]?[A-Z_][a-zA-Z0-9_]{2,30}$
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-# Naming hint for function names
-function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Regular expression matching correct function names
-function-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,k,ex,Run,_,log,logger,cfg,id
-
-# Include a hint for the correct naming format with invalid-name
-include-naming-hint=no
-
-# Naming hint for inline iteration names
-inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
-
-# Regular expression matching correct inline iteration names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Naming hint for method names
-method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Regular expression matching correct method names
-method-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Naming hint for module names
-module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression matching correct module names
-#module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-module-rgx=([a-z_][a-z0-9_]*)$
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
-
-# List of decorators that produce properties, such as abc.abstractproperty. Add
-# to this list to register other decorators that produce valid properties.
-property-classes=abc.abstractproperty
-
-# Naming hint for variable names
-variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
-
-# Regular expression matching correct variable names
-variable-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*)|([a-z]))$
-
-
-[FORMAT]
-
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
-
-# Number of spaces of indent required inside a hanging  or continued line.
-indent-after-paren=4
-
-# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
-
-# Maximum number of characters on a single line.
-max-line-length=120
-
-# Maximum number of lines in a module
-max-module-lines=2000
-
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,dict-separator
-
-# Allow the body of a class to be on the same line as the declaration if body
-# contains single statement.No config file found, using default configuration
-
-single-line-class-stmt=no
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-
-[LOGGING]
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format
-logging-modules=logging
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
-
-
-[SIMILARITIES]
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
-
-# Ignore imports when computing similarities.
-ignore-imports=no
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-
-[SPELLING]
-
-# Spelling dictionary name. Available dictionaries: none. To make it working
-# install python-enchant package.
-spelling-dict=
-
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
-
-# A path to a file that contains private dictionary; one word per line.
-spelling-private-dict-file=
-
-# Tells whether to store unknown words to indicated private dictionary in
-# --spelling-private-dict-file option instead of raising a message.
-spelling-store-unknown-words=no
-
-
-[TYPECHECK]
-
-# List of decorators that produce context managers, such as
-# contextlib.contextmanager. Add to this list to register other decorators that
-# produce valid context managers.
-contextmanager-decorators=contextlib.contextmanager
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# This flag controls whether pylint should warn about no-member and similar
-# checks whenever an opaque object is returned when inferring. The inference
-# can return multiple potential results while evaluating a Python object, but
-# some branches might not be evaluated, which results in partial inference. In
-# that case, it might be useful to still emit no-member and other checks for
-# the rest of the inferred objects.
-ignore-on-opaque-inference=yes
-
-# List of class names for which member attributes should not be checked (useful
-# for classes with dynamically set attributes). This supports the use of
-# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis. It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
-
-# Show a hint with possible names when a member name was not found. The aspect
-# of finding the hint is based on edit distance.
-missing-member-hint=yes
-
-# The minimum edit distance a name should have in order to be considered a
-# similar match for a missing member name.
-missing-member-hint-distance=1
-
-# The total number of similar names that should be taken in consideration when
-# showing a hint for a missing member.
-missing-member-max-choices=1
-
-
-[VARIABLES]
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-# Tells whether unused global variables should be treated as a violation.
-allow-global-unused-variables=yes
-
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,_cb
-
-# A regular expression matching the name of dummy variables (i.e. expectedly
-# not used).
-dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*|^ignored_|^unused_
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# List of qualified module names which can have objects that can redefine
-# builtins.
-redefining-builtins-modules=six.moves,future.builtins
-
-
-[CLASSES]
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-
-[DESIGN]
-
-# Maximum number of arguments for function / method
-max-args=8
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=20
-
-# Maximum number of boolean expressions in a if statement
-max-bool-expr=5
-
-# Maximum number of branch for function / method body
-max-branches=12
-
-# Maximum number of locals for function / method body
-max-locals=20
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of statements in function / method body
-max-statements=50
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
-
-
-[IMPORTS]
-
-# Allow wildcard imports from modules that define __all__.
-allow-wildcard-with-all=no
-
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=optparse,tkinter.tix
-
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
-
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
-
-# Force import order to recognize a module as part of the standard
-# compatibility libraries.
-known-standard-library=
-
-# Force import order to recognize a module as part of a third party library.
-known-third-party=enchant
-
-
-[EXCEPTIONS]
-
-# Exceptions that will emit a warning when being caught. Defaults to
-# "Exception"
-overgeneral-exceptions=Exception

+ 52 - 0
README.rst

@@ -7,8 +7,60 @@ Only show green hosted results
 A `SearXNG plugin <https://docs.searxng.org/dev/plugins.html>`__ to check if a
 domain is part of the `Green WEB <https://www.thegreenwebfoundation.org/>`__.
 
+Installation
+============
+
+**USE WITH CARE**
+
 Its recommended to install (and regularly update) `The Green Domains dataset
 <https://github.com/thegreenwebfoundation/admin-portal/blob/master/docs/working-with-greenweb-datasets.md>`__
 otherwise the plugin uses the API what sends a lot of requests from the SearXNG
 instance to the Green WEB foundation and slows down the SearXNG instance
 massively.
+
+Change to the environment in which the plugin is to be installed::
+
+     $ sudo utils/searxng.sh instance cmd python -m \
+         pip install git+https://github.com/return42/tgwf-searx-plugins
+
+
+Development
+===========
+
+This project is managed by `hatch <https://hatch.pypa.io>`_, for development
+tasks you should install ``hatch``:
+
+.. code:: sh
+
+    $ pipx install hatch
+
+Format and *lint* your code before commit:
+
+.. code:: sh
+
+    $ hatch run fix
+    $ hatch run check
+
+To enter the development environment use ``shell``:
+
+.. code:: sh
+
+   $ hatch shell
+
+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

+ 0 - 107
only_show_green_results.py

@@ -1,107 +0,0 @@
-# SPDX-License-Identifier: AGPL-3.0-or-later
-"""Only show green hosted results"""
-
-import os
-import logging
-import sqlite3
-from urllib.parse import urlparse
-
-from flask_babel import gettext
-from searx import network
-
-logger = logging.getLogger(__name__)
-
-name = gettext('Only show green hosted results')
-description = gettext('Any results not being hosted on green infrastructure will be filtered')
-default_on = False
-preference_section = 'general'
-allow_api_connections = True
-database_name = "url2green.db"
-api_server = "https://api.thegreenwebfoundation.org"
-
-
-class GreenCheck:
-    """Implement methods to check if a domain is part of the Green WEB"""
-
-    def __init__(self):
-        self.db = True  # pylint: disable=invalid-name
-
-        try:
-            self.db = bool(os.stat(database_name))
-        except Exception:  # pylint: disable=broad-except
-            self.db = False
-
-        if self.db:
-            logger.debug(
-                "Database found at %s. Using it for lookups instead of the Greencheck API",
-                database_name)
-            return
-
-        logger.warning("No database found at %s.", database_name)
-        if allow_api_connections:
-            logger.warning(
-                "Falling back to the (much slower) Greencheck API, as 'allow_api_connections' is set to %s.",
-                allow_api_connections)
-        else:
-            logger.debug(
-                "filtering inactive: no database found at %s and 'allow_api_connections=%s'",
-                database_name, allow_api_connections)
-
-    def check_url(self, url=None):
-        """Check a url passed in, and return a true or false result, based on whether
-        the domain is marked as a one running on green energy."""
-        logger.debug(url)
-
-        parsed_domain = urlparse(url).hostname
-        ret_val = False
-
-        if parsed_domain:
-            logger.debug("Checking %s, parsed from %s", parsed_domain, url)
-            if self.db:
-                ret_val = self.check_in_db(parsed_domain)
-            elif allow_api_connections:
-                ret_val = self.check_against_api(parsed_domain)
-
-        return ret_val
-
-    def check_in_db(self, domain=None):  # pylint: disable=no-self-use
-        """Checks wether ``domain`` is in the green database
-
-        We basically treat the the sqlite database like an immutable, read-only
-        datastructure.  This allows multiple concurrent connections as no state
-        is ever being changed - only read with SELECT
-
-        - https://docs.python.org/3.8/library/sqlite3.html#//apple_ref/Function/sqlite3.connect
-        - https://sqlite.org/lockingv3.html
-
-        """
-        with sqlite3.connect(
-                "file:{}?mode=ro".format(database_name),
-                uri=True,
-                check_same_thread=False
-        ) as con:
-            cur = con.cursor()
-            cur.execute("SELECT green FROM green_presenting WHERE url=? LIMIT 1",
-                        [domain])
-            res = cur.fetchone()
-            logger.debug(res)
-            return bool(res)
-
-    def check_against_api(self, domain=None):  # pylint: disable=no-self-use
-        """Checks ``domain`` against https://api.thegreenwebfoundation.org API"""
-        api_url = "{}/greencheck/{}".format(api_server, domain)
-        logger.debug(api_url)
-        response = network.get(api_url).json()
-        return bool(response.get("green"))
-
-GC = GreenCheck()
-
-def post_search(request, search):  # pylint: disable=unused-argument
-    """Filter searx results."""
-
-    # pylint: disable=protected-access
-    green_results = [
-        result for result in list(search.result_container._merged_results)
-        if GC.check_url(result.get('url'))
-    ]
-    search.result_container._merged_results = green_results

+ 590 - 0
pylintrc.toml

@@ -0,0 +1,590 @@
+# -*- coding: utf-8; mode: conf-toml -*-
+
+[tool.pylint.main]
+# Analyse import fallback blocks. This can be used to support both Python 2 and 3
+# compatible code, which means that the block might have code that exists only in
+# one or another interpreter, leading to false positives when analysed.
+# analyse-fallback-blocks =
+
+# Clear in-memory caches upon conclusion of linting. Useful if running pylint in
+# a server-like mode.
+# clear-cache-post-run =
+
+# Always return a 0 (non-error) status code, even if lint errors are found. This
+# is primarily useful in continuous integration scripts.
+# exit-zero =
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+# extension-pkg-allow-list =
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
+# for backward compatibility.)
+# extension-pkg-whitelist =
+
+# Return non-zero exit code if any of these messages/categories are detected,
+# even if score is above --fail-under value. Syntax same as enable. Messages
+# specified are enabled, while categories only check already-enabled messages.
+# fail-on =
+
+# Specify a score threshold under which the program will exit with error.
+fail-under = 10
+
+# Interpret the stdin as a python script, whose filename needs to be passed as
+# the module_or_package argument.
+# from-stdin =
+
+# Files or directories to be skipped. They should be base names, not paths.
+ignore = ["CVS"]
+
+# Add files or directories matching the regular expressions patterns to the
+# ignore-list. The regex matches against paths and can be in Posix or Windows
+# format. Because '\\' represents the directory delimiter on Windows systems, it
+# can't be used as an escape character.
+# ignore-paths =
+
+# Files or directories matching the regular expression patterns are skipped. The
+# regex matches against base names, not paths. The default value ignores Emacs
+# file locks
+ignore-patterns = ["^\\.#"]
+
+# List of module names for which member attributes should not be checked and will
+# not be imported (useful for modules/projects where namespaces are manipulated
+# during runtime and thus existing member attributes cannot be deduced by static
+# analysis). It supports qualified module names, as well as Unix pattern
+# matching.
+# ignored-modules =
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+# init-hook =
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use, and will cap the count on Windows to
+# avoid hangs.
+jobs = 1
+
+# Control the amount of potential inferred values when inferring a single object.
+# This can help the performance when dealing with large functions or complex,
+# nested conditions.
+limit-inference-results = 100
+
+# List of plugins (as comma separated values of python module names) to load,
+# usually to register additional checkers.
+# load-plugins =
+
+# Pickle collected data for later comparisons.
+persistent = true
+
+# Resolve imports to .pyi stubs if available. May reduce no-member messages and
+# increase not-an-iterable messages.
+# prefer-stubs =
+
+# Minimum Python version to use for version dependent checks. Will default to the
+# version used to run pylint.
+py-version = "3.10"
+
+# Discover python modules and packages in the file system subtree.
+# recursive =
+
+# Add paths to the list of the source roots. Supports globbing patterns. The
+# source root is an absolute path or a path relative to the current working
+# directory used to determine a package namespace for modules located under the
+# source root.
+# source-roots =
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode = true
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+# unsafe-load-any-extension =
+
+[tool.pylint.basic]
+# Naming style matching correct argument names.
+argument-naming-style = "snake_case"
+
+# Regular expression matching correct argument names. Overrides argument-naming-
+# style. If left empty, argument names will be checked with the set naming style.
+# argument-rgx =
+
+# Naming style matching correct attribute names.
+attr-naming-style = "snake_case"
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style. If left empty, attribute names will be checked with the set naming
+# style.
+# attr-rgx =
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"]
+
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+# bad-names-rgxs =
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style = "any"
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style. If left empty, class attribute names will be checked
+# with the set naming style.
+# class-attribute-rgx =
+
+# Naming style matching correct class constant names.
+class-const-naming-style = "UPPER_CASE"
+
+# Regular expression matching correct class constant names. Overrides class-
+# const-naming-style. If left empty, class constant names will be checked with
+# the set naming style.
+# class-const-rgx =
+
+# Naming style matching correct class names.
+class-naming-style = "PascalCase"
+
+# Regular expression matching correct class names. Overrides class-naming-style.
+# If left empty, class names will be checked with the set naming style.
+# class-rgx =
+
+# Naming style matching correct constant names.
+const-naming-style = "UPPER_CASE"
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style. If left empty, constant names will be checked with the set naming style.
+# const-rgx =
+
+# Minimum line length for functions/classes that require docstrings, shorter ones
+# are exempt.
+docstring-min-length = -1
+
+# Naming style matching correct function names.
+function-naming-style = "snake_case"
+
+# Regular expression matching correct function names. Overrides function-naming-
+# style. If left empty, function names will be checked with the set naming style.
+# function-rgx =
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names = ["i", "j", "k", "ex", "Run", "_"]
+
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+# good-names-rgxs =
+
+# Include a hint for the correct naming format with invalid-name.
+# include-naming-hint =
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style = "any"
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style. If left empty, inline iteration names will be checked
+# with the set naming style.
+# inlinevar-rgx =
+
+# Naming style matching correct method names.
+method-naming-style = "snake_case"
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style. If left empty, method names will be checked with the set naming style.
+# method-rgx =
+
+# Naming style matching correct module names.
+module-naming-style = "snake_case"
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style. If left empty, module names will be checked with the set naming style.
+# module-rgx =
+
+# Colon-delimited sets of names that determine each other's naming style when the
+# name regexes allow several styles.
+# name-group =
+
+# Regular expression which should only match function or class names that do not
+# require a docstring.
+no-docstring-rgx = "^_"
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties. These
+# decorators are taken in consideration only for invalid-name.
+property-classes = ["abc.abstractproperty"]
+
+# Regular expression matching correct type alias names. If left empty, type alias
+# names will be checked with the set naming style.
+# typealias-rgx =
+
+# Regular expression matching correct type variable names. If left empty, type
+# variable names will be checked with the set naming style.
+# typevar-rgx =
+
+# Naming style matching correct variable names.
+variable-naming-style = "snake_case"
+
+# Regular expression matching correct variable names. Overrides variable-naming-
+# style. If left empty, variable names will be checked with the set naming style.
+# variable-rgx =
+
+[tool.pylint.classes]
+# Warn about protected attribute access inside special methods
+# check-protected-access-in-special-methods =
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods = ["__init__", "__new__", "setUp", "asyncSetUp", "__post_init__"]
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make", "os._exit"]
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg = ["cls"]
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg = ["mcs"]
+
+[tool.pylint.design]
+# List of regular expressions of class ancestor names to ignore when counting
+# public methods (see R0903)
+# exclude-too-few-public-methods =
+
+# List of qualified class names to ignore when counting class parents (see R0901)
+# ignored-parents =
+
+# Maximum number of arguments for function / method.
+max-args = 5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes = 7
+
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr = 5
+
+# Maximum number of branch for function / method body.
+max-branches = 12
+
+# Maximum number of locals for function / method body.
+max-locals = 15
+
+# Maximum number of parents for a class (see R0901).
+max-parents = 7
+
+# Maximum number of positional arguments for function / method.
+max-positional-arguments = 5
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods = 20
+
+# Maximum number of return / yield for function / method body.
+max-returns = 6
+
+# Maximum number of statements in function / method body.
+max-statements = 50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods = 2
+
+[tool.pylint.exceptions]
+# Exceptions that will emit a warning when caught.
+overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
+
+[tool.pylint.format]
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+# expected-line-ending-format =
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines = "^\\s*(# )?<?https?://\\S+>?$"
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren = 4
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string = "    "
+
+# Maximum number of characters on a single line.
+max-line-length = 120
+
+# Maximum number of lines in a module.
+max-module-lines = 2000
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+# single-line-class-stmt =
+
+# Allow the body of an if to be on the same line as the test if there is no else.
+# single-line-if-stmt =
+
+[tool.pylint.imports]
+# List of modules that can be imported at any level, not just the top level one.
+# allow-any-import-level =
+
+# Allow explicit reexports by alias from a package __init__.
+# allow-reexport-from-package =
+
+# Allow wildcard imports from modules that define __all__.
+# allow-wildcard-with-all =
+
+# Deprecated modules which should not be used, separated by a comma.
+# deprecated-modules =
+
+# Output a graph (.gv or any supported image format) of external dependencies to
+# the given file (report RP0402 must not be disabled).
+# ext-import-graph =
+
+# Output a graph (.gv or any supported image format) of all (i.e. internal and
+# external) dependencies to the given file (report RP0402 must not be disabled).
+# import-graph =
+
+# Output a graph (.gv or any supported image format) of internal dependencies to
+# the given file (report RP0402 must not be disabled).
+# int-import-graph =
+
+# Force import order to recognize a module as part of the standard compatibility
+# libraries.
+# known-standard-library =
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party = ["enchant"]
+
+# Couples of modules and preferred modules, separated by a comma.
+# preferred-modules =
+
+[tool.pylint.logging]
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style = "new"
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules = ["logging"]
+
+[tool.pylint."messages control"]
+# Only show warnings with the listed confidence levels. Leave empty to show all.
+# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"]
+
+# Disable the message, report, category or checker with the given id(s). You can
+# either give multiple identifiers separated by comma (,) or put this option
+# multiple times (only on the command line, not in the configuration file where
+# it should appear only once). You can also use "--disable=all" to disable
+# everything first and then re-enable specific checks. For example, if you want
+# to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+
+disable = [
+  "raw-checker-failed",
+  "bad-inline-option",
+  "locally-disabled",
+  "file-ignored",
+  "suppressed-message",
+  "useless-suppression",
+  "deprecated-pragma",
+  "use-implicit-booleaness-not-comparison-to-string",
+  "use-implicit-booleaness-not-comparison-to-zero",
+  "use-symbolic-message-instead",
+  "too-few-public-methods",
+  "logging-fstring-interpolation",
+  "duplicate-code",
+]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where it
+# should appear only once). See also the "--disable" option for examples.
+# enable =
+
+[tool.pylint.method_args]
+# List of qualified names (i.e., library.method) which require a timeout
+# parameter e.g. 'requests.api.get,requests.api.post'
+timeout-methods = [
+  "requests.api.delete",
+  "requests.api.get",
+  "requests.api.head",
+  "requests.api.options",
+  "requests.api.patch",
+  "requests.api.post",
+  "requests.api.put",
+  "requests.api.request"
+]
+
+[tool.pylint.miscellaneous]
+# List of note tags to take in consideration, separated by a comma.
+notes = ["FIXME", "XXX", "TODO"]
+
+# Regular expression of note tags to take in consideration.
+# notes-rgx =
+
+[tool.pylint.refactoring]
+# Maximum number of nested blocks for function / method body
+max-nested-blocks = 5
+
+# Complete name of functions that never returns. When checking for inconsistent-
+# return-statements if a never returning function is called then it will be
+# considered as an explicit return statement and no message will be printed.
+never-returning-functions = ["sys.exit", "argparse.parse_error"]
+
+# Let 'consider-using-join' be raised when the separator to join on would be non-
+# empty (resulting in expected fixes of the type: ``"- " + " - ".join(items)``)
+suggest-join-with-non-empty-separator = true
+
+[tool.pylint.reports]
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'fatal', 'error', 'warning', 'refactor',
+# 'convention', and 'info' which contain the number of messages in each category,
+# as well as 'statement' which is the total number of statements analyzed. This
+# score is used by the global evaluation report (RP0004).
+evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))"
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+# msg-template =
+
+# Set the output format. Available formats are: text, parseable, colorized, json2
+# (improved json format), json (old json format) and msvs (visual studio). You
+# can also give a reporter class, e.g. mypackage.mymodule.MyReporterClass.
+# output-format =
+
+# Tells whether to display a full report or only the messages.
+# reports =
+
+# Activate the evaluation score.
+score = true
+
+[tool.pylint.similarities]
+# Comments are removed from the similarity computation
+ignore-comments = true
+
+# Docstrings are removed from the similarity computation
+ignore-docstrings = true
+
+# Imports are removed from the similarity computation
+ignore-imports = true
+
+# Signatures are removed from the similarity computation
+ignore-signatures = true
+
+# Minimum lines number of a similarity.
+min-similarity-lines = 4
+
+[tool.pylint.spelling]
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions = 4
+
+# Spelling dictionary name. No available dictionaries : You need to install both
+# the python package and the system dependency for enchant to work.
+# spelling-dict =
+
+# List of comma separated words that should be considered directives if they
+# appear at the beginning of a comment and should not be checked.
+spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:"
+
+# List of comma separated words that should not be checked.
+# spelling-ignore-words =
+
+# A path to a file that contains the private dictionary; one word per line.
+# spelling-private-dict-file =
+
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+# spelling-store-unknown-words =
+
+[tool.pylint.typecheck]
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators = ["contextlib.contextmanager"]
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+# generated-members =
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# class is considered mixin if its name matches the mixin-class-rgx option.
+# Tells whether to warn about missing members when the owner of the attribute is
+# inferred to be None.
+ignore-none = true
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference can
+# return multiple potential results while evaluating a Python object, but some
+# branches might not be evaluated, which results in partial inference. In that
+# case, it might be useful to still emit no-member and other checks for the rest
+# of the inferred objects.
+ignore-on-opaque-inference = true
+
+# List of symbolic message names to ignore for Mixin members.
+ignored-checks-for-mixins = [
+  "no-member",
+  "not-async-context-manager",
+  "not-context-manager",
+  "attribute-defined-outside-init",
+]
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes = [
+  "optparse.Values",
+  "thread._local",
+  "_thread._local",
+  "argparse.Namespace",
+]
+
+# Show a hint with possible names when a member name was not found. The aspect of
+# finding the hint is based on edit distance.
+missing-member-hint = true
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance = 1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices = 1
+
+# Regex pattern to define which classes are considered mixins.
+mixin-class-rgx = ".*[Mm]ixin"
+
+# List of decorators that change the signature of a decorated function.
+# signature-mutators =
+
+[tool.pylint.variables]
+# List of additional names supposed to be defined in builtins. Remember that you
+# should avoid defining new builtins when possible.
+# additional-builtins =
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables = true
+
+# List of names allowed to shadow builtins
+# allowed-redefined-builtins =
+
+# List of strings which can identify a callback function by name. A callback name
+# must start or end with one of those strings.
+callbacks = ["cb_", "_cb"]
+
+# A regular expression matching the name of dummy variables (i.e. expected to not
+# be used).
+dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_"
+
+# Argument names that match this expression will be ignored.
+ignored-argument-names = "_.*|^ignored_|^unused_"
+
+# Tells whether we should check for unused import in __init__ files.
+# init-import =
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules = [
+  "six.moves",
+  "past.builtins",
+  "future.builtins",
+  "builtins",
+  "io",
+]

+ 86 - 0
pyproject.toml

@@ -0,0 +1,86 @@
+# ==============================================================================
+[build-system]
+# ==============================================================================
+
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+# ==============================================================================
+[project]
+# ==============================================================================
+
+name = "only_show_green_results"
+dynamic = ["version"]
+
+readme = "README.rst"
+requires-python = ">=3.11"
+license = "AGPL-3.0-or-later"
+description = "The Green Web Foundation searx plugins"
+authors = [
+    { name = "The Green Web Foundation", email = "chris@productscience.co.uk" },
+    { name = "SearXNG Community", email = "markus.heiser@darmarit.de" },
+]
+
+dependencies = [
+  "flask_babel",
+]
+
+[project.urls]
+Code = "https://github.com/return42/tgwf-searx-plugins"
+Homepage = "https://github.com/return42/tgwf-searx-plugins"
+"Issue tracker" = "https://github.com/return42/tgwf-searx-plugins/issues"
+
+# ==============================================================================
+[tool.hatch]
+# ==============================================================================
+
+[tool.hatch.version]
+path = "src/only_show_green_results/__init__.py"
+
+[tool.hatch.metadata]
+allow-direct-references = true
+
+# [tool.hatch.build.targets.sdist]
+# include = []
+
+# hatch env: default
+# ------------------
+
+[tool.hatch.envs.default]
+python = "3.13"
+dependencies = [
+  "basedpyright==1.27.*",
+  "black==25.1.*",
+  "pylint==3.3.*",
+  "isort==6.0.*",
+]
+
+# FIXME: ..
+post-install-commands = [
+  "python -m pip install --upgrade pip",
+  "python -m pip install setuptools pyyaml",
+  # "python -m pip install git+https://github.com/searxng/searxng.git@master",
+]
+
+[tool.hatch.envs.default.scripts]
+
+fix = [
+  "isort {args:./src}",
+  "black {args:./src}",
+]
+
+check = [
+  "isort --diff {args:./src}",
+  "black --check --diff {args:./src}",
+  "pylint --output-format=parseable {args:./src}",
+  "basedpyright {args:./src}",
+]
+
+# ==============================================================================
+[tool.isort]
+# ==============================================================================
+
+profile = "black"
+atomic = true
+use_parentheses = true
+include_trailing_comma = true

+ 6 - 0
pyrightconfig.json

@@ -0,0 +1,6 @@
+{
+  "enableTypeIgnoreComments": true,
+  "reportAny": false,
+  "reportExplicitAny": false,
+  "reportIgnoreCommentWithoutRule": false
+}

+ 0 - 38
setup.py

@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-# SPDX-License-Identifier: AGPL-3.0-or-later
-"""
-Searx plugins from The Green Web Foundation
-
-tgwf-searx-plugins
-  All results of searx queries that not hosted on a green infrastructure are
-  filtered
-
-"""
-
-from setuptools import setup
-
-GIT_URL='https://github.com/return42/tgwf-searx-plugins'
-
-setup(
-    name                = 'tgwf-searx-plugins'
-    , version           = '0.4'
-    , description       = 'The Green Web Foundation searx plugins'
-    , long_description  = __doc__
-    , url               =  GIT_URL
-    , author            = 'The Green Web Foundation'
-    , author_email      = 'chris@productscience.co.uk'
-    , project_urls      = {
-        "Code"              : GIT_URL
-        , "Issue tracker"   : GIT_URL + "/issues"
-    }
-    , license           = 'GNU Affero General Public License'
-    , zip_safe          = False
-    , py_modules        = [
-        'only_show_green_results'
-    ]
-    , entry_points      = {
-        'searxng.plugins' : [
-            'tgwf.green-results = only_show_green_results'
-        ]
-    }
-)

+ 57 - 0
src/only_show_green_results/__init__.py

@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""Only show green hosted results.  Example of a *custom*
+SearXNG plugin."""
+
+from __future__ import annotations
+
+import typing
+
+from flask_babel import gettext
+from searx.extended_types import SXNG_Request
+from searx.plugins import Plugin, PluginInfo
+from searx.result_types import Result
+
+from .greencheck import GreenCheck
+
+if typing.TYPE_CHECKING:
+    from searx.plugins import PluginCfg
+    from searx.search import SearchWithPlugins
+
+
+__version__ = "0.5.0"
+
+GC = GreenCheck()
+
+
+class SXNGPlugin(Plugin):
+    """Plugin converts strings to different hash digests.  The results are
+    displayed in area for the "answers".
+    """
+
+    id = "only_show_green_results"
+
+    def __init__(self, plg_cfg: "PluginCfg") -> None:
+        super().__init__(plg_cfg)
+
+        self.info = PluginInfo(
+            id=self.id,
+            name=gettext("Only show green hosted results"),
+            description=gettext(
+                "Any results not being hosted on green infrastructure will be filtered"
+            ),
+            preference_section="general",
+        )
+
+    def on_result(
+        # pylint: disable=unused-argument
+        self,
+        request: SXNG_Request,
+        search: "SearchWithPlugins",
+        result: Result,
+    ) -> bool:
+        """Return a true or false value, based on whether the domain is marked
+        as a one running on green energy.
+        """
+        if not result.url:
+            return True
+        return GC.check_url(result.url)

+ 91 - 0
src/only_show_green_results/greencheck.py

@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""Example of a *custom* SearXNG plugin."""
+
+import logging
+import os
+import sqlite3
+from urllib.parse import urlparse
+
+from searx import network
+
+log = logging.getLogger(__name__)
+
+ALLOW_API_CONNECTIONS = True
+DB_NAME = "url2green.db"
+API_SERVER = "https://api.thegreenwebfoundation.org"
+
+
+class GreenCheck:
+    """Implement methods to check if a domain is part of the Green WEB"""
+
+    def __init__(self):
+        self.db = True  # pylint: disable=invalid-name
+
+        try:
+            self.db = bool(os.stat(DB_NAME))
+        except Exception:  # pylint: disable=broad-except
+            self.db = False
+
+        if self.db:
+            log.debug(
+                f"Database found at {DB_NAME}. Using it for lookups instead of the Greencheck API"
+            )
+            return
+
+        log.warning(f"No database found at {DB_NAME}.")
+        if ALLOW_API_CONNECTIONS:
+            log.warning(
+                f"Falling back to the (much slower) Greencheck API, "
+                f"as 'ALLOW_API_CONNECTIONS' is set to {ALLOW_API_CONNECTIONS}."
+            )
+        else:
+            log.debug(
+                f"filtering inactive: no database found at {DB_NAME}"
+                f" and 'ALLOW_API_CONNECTIONS={ALLOW_API_CONNECTIONS}'"
+            )
+
+    def check_url(self, url=None):
+        """Check a url passed in, and return a true or false result, based on whether
+        the domain is marked as a one running on green energy."""
+        log.debug(url)
+
+        parsed_domain = urlparse(url).hostname
+        ret_val = False
+
+        if parsed_domain:
+            log.debug(f"Checking {parsed_domain}, parsed from {url}")
+            if self.db:
+                ret_val = self.check_in_db(parsed_domain)
+            elif ALLOW_API_CONNECTIONS:
+                ret_val = self.check_against_api(parsed_domain)
+
+        return ret_val
+
+    def check_in_db(self, domain=None):
+        """Checks wether ``domain`` is in the green database
+
+        We basically treat the the sqlite database like an immutable, read-only
+        datastructure.  This allows multiple concurrent connections as no state
+        is ever being changed - only read with SELECT
+
+        - https://docs.python.org/3.8/library/sqlite3.html#//apple_ref/Function/sqlite3.connect
+        - https://sqlite.org/lockingv3.html
+
+        """
+        with sqlite3.connect(
+            f"file:{DB_NAME}?mode=ro", uri=True, check_same_thread=False
+        ) as con:
+            cur = con.cursor()
+            cur.execute(
+                "SELECT green FROM green_presenting WHERE url=? LIMIT 1", [domain]
+            )
+            res = cur.fetchone()
+            log.debug(res)
+            return bool(res)
+
+    def check_against_api(self, domain=None):
+        """Checks ``domain`` against https://api.thegreenwebfoundation.org API"""
+        api_url = f"{API_SERVER}/greencheck/{domain}"
+        log.debug(api_url)
+        response = network.get(api_url).json()
+        return bool(response.get("green"))