]> git.mxchange.org Git - fba.git/blobdiff - fba/models/instances.py
Continued:
[fba.git] / fba / models / instances.py
index cb08eb5bd0f15c20a40bdc6f1df2143fec0806d7..320a24f69d90d639dc7fb29c5a99b85329aad928 100644 (file)
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+import logging
 import json
-import sys
 import time
 
 import requests
 import validators
 
-from fba import blacklist
-from fba import config
-from fba import fba
-from fba import federation
-from fba import network
+from fba import database
+from fba import utils
 
+from fba.helpers import blacklist
 from fba.helpers import cache
+from fba.helpers import config
+from fba.helpers import domain as domain_helper
+
+from fba.http import federation
+from fba.http import network
 
 from fba.models import error_log
 
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
 # Found info from node, such as nodeinfo URL, detection mode that needs to be
 # written to database. Both arrays must be filled at the same time or else
 # update_data() will fail
 _pending = {
-    # Detection mode: 'AUTO_DISCOVERY', 'STATIC_CHECKS' or 'GENERATOR'
+    # Detection mode
     # NULL means all detection methods have failed (maybe still reachable instance)
     "detection_mode"     : {},
     # Found nodeinfo URL
@@ -54,67 +60,60 @@ _pending = {
     "last_status_code"   : {},
     # Last error details
     "last_error_details" : {},
+    # Wether obfuscation has been used
+    "has_obfuscation"    : {},
 }
 
 def _set_data(key: str, domain: str, value: any):
-    # DEBUG: print(f"DEBUG: key='{key}',domain='{domain}',value[]='{type(value)}' - CALLED!")
+    logger.debug("key='%s',domain='%s',value[]='%s' - CALLED!", key, domain, type(value))
+    domain_helper.raise_on(domain)
     if not isinstance(key, str):
         raise ValueError("Parameter key[]='{type(key)}' is not 'str'")
     elif key == "":
         raise ValueError("Parameter 'key' is empty")
-    elif not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
     elif not key in _pending:
         raise ValueError(f"key='{key}' not found in _pending")
-    elif not fba.is_primitive(value):
+    elif not utils.is_primitive(value):
         raise ValueError(f"value[]='{type(value)}' is not a primitive type")
 
     # Set it
     _pending[key][domain] = value
 
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
 def has_pending(domain: str) -> bool:
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
 
     has = False
     for key in _pending:
-        # DEBUG: print(f"DEBUG: key='{key}',domain='{domain}',_pending[key]()='{len(_pending[key])}'")
+        logger.debug("key='%s',domain='%s',_pending[key]()=%d", key, domain, len(_pending[key]))
         if domain in _pending[key]:
             has = True
             break
 
-    # DEBUG: print(f"DEBUG: has='{has}' - EXIT!")
+    logger.debug("has='%s' - EXIT!", has)
     return has
 
 def update_data(domain: str):
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not has_pending(domain):
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
+    if not has_pending(domain):
         raise Exception(f"domain='{domain}' has no pending instance data, but function invoked")
     elif not is_registered(domain):
         raise Exception(f"domain='{domain}' cannot be updated while not being registered")
 
-    # DEBUG: print(f"DEBUG: Updating instance data for domain='{domain}' ...")
+    logger.debug("Updating instance data for domain='%s' ...", domain)
     sql_string = ""
     fields = list()
     for key in _pending:
-        # DEBUG: print("DEBUG: key:", key)
+        logger.debug("Checking key='%s',domain='%s'", key, domain)
         if domain in _pending[key]:
-            # DEBUG: print(f"DEBUG: Adding '{_pending[key][domain]}' for key='{key}' ...")
+            logger.debug("Adding '%s' for key='%s' ...", _pending[key][domain], key)
             fields.append(_pending[key][domain])
             sql_string += f" {key} = ?,"
 
-    # DEBUG: print(f"DEBUG: sql_string()={len(sql_string)}")
+    logger.debug("sql_string()=%d", len(sql_string))
     if sql_string == "":
         raise ValueError(f"No fields have been set, but method invoked, domain='{domain}'")
 
@@ -124,35 +123,33 @@ def update_data(domain: str):
     # For WHERE statement
     fields.append(domain)
 
-    # DEBUG: print(f"DEBUG: sql_string='{sql_string}',fields()={len(fields)}")
+    logger.debug("sql_string='%s',fields()=%d", sql_string, len(fields))
     sql_string = "UPDATE instances SET" + sql_string + " last_updated = ? WHERE domain = ? LIMIT 1"
-    # DEBUG: print("DEBUG: sql_string:", sql_string)
 
-    # DEBUG: print("DEBUG: Executing SQL:", sql_string)
-    fba.cursor.execute(sql_string, fields)
+    logger.debug("Executing SQL: '%s'", sql_string)
+    database.cursor.execute(sql_string, fields)
 
-    # DEBUG: print(f"DEBUG: Success! (rowcount={fba.cursor.rowcount })")
-    if fba.cursor.rowcount == 0:
+    logger.debug("rowcount=%d", database.cursor.rowcount)
+    if database.cursor.rowcount == 0:
         raise Exception(f"Did not update any rows: domain='{domain}',fields()={len(fields)}")
 
-    # DEBUG: print("DEBUG: Committing changes ...")
-    fba.connection.commit()
+    logger.debug("Invoking commit() ...")
+    database.connection.commit()
 
-    # DEBUG: print(f"DEBUG: Deleting _pending for domain='{domain}'")
+    logger.debug("Deleting _pending for domain='%s'", domain)
     for key in _pending:
-        # DEBUG: print(f"DEBUG: domain='{domain}',key='{key}'")
+        logger.debug("domain='%s',key='%s'", domain, key)
         if domain in _pending[key]:
+            logger.debug("Deleting key='%s',domain='%s' ...", key, domain)
             del _pending[key][domain]
 
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
 def add(domain: str, origin: str, command: str, path: str = None, software: str = None):
-    # DEBUG: print(f"DEBUG: domain='{domain}',origin='{origin}',command='{command}',path='{path}',software='{software}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not isinstance(origin, str) and origin is not None:
+    logger.debug("domain='%s',origin='%s',command='%s',path='%s',software='%s' - CALLED!", domain, origin, command, path, software)
+    domain_helper.raise_on(domain)
+
+    if not isinstance(origin, str) and origin is not None:
         raise ValueError(f"origin[]='{type(origin)}' is not 'str'")
     elif origin == "":
         raise ValueError("Parameter 'origin' is empty")
@@ -160,8 +157,6 @@ def add(domain: str, origin: str, command: str, path: str = None, software: str
         raise ValueError(f"command[]='{type(command)}' is not 'str'")
     elif command == "":
         raise ValueError("Parameter 'command' is empty")
-    elif not validators.domain(domain.split("/")[0]):
-        raise ValueError(f"Bad domain name='{domain}'")
     elif not isinstance(path, str) and path is not None:
         raise ValueError(f"path[]='{type(path)}' is not 'str'")
     elif path == "":
@@ -170,274 +165,254 @@ def add(domain: str, origin: str, command: str, path: str = None, software: str
         raise ValueError(f"software[]='{type(software)}' is not 'str'")
     elif software == "":
         raise ValueError("Parameter 'software' is empty")
-    elif domain.endswith(".arpa"):
-        raise ValueError(f"Please don't crawl .arpa domains: domain='{domain}'")
     elif origin is not None and not validators.domain(origin.split("/")[0]):
         raise ValueError(f"Bad origin name='{origin}'")
     elif blacklist.is_blacklisted(domain):
         raise Exception(f"domain='{domain}' is blacklisted, but method invoked")
-    elif domain.find("/profile/") > 0 or domain.find("/users/") > 0 or (software == "lemmy" and domain.find("/c/") > 0):
+    elif domain.find("/profile/") > 0 or domain.find("/users/") > 0 or (is_registered(domain.split("/")[0]) and domain.find("/c/") > 0):
         raise Exception(f"domain='{domain}' is a single user")
+    elif domain.find("/tag/") > 0:
+        raise Exception(f"domain='{domain}' is a tag")
 
     if software is None:
         try:
-            # DEBUG: print("DEBUG: domain,origin,command,path:", domain, origin, command, path)
+            logger.debug("domain='%s',origin='%s',command='%s',path='%s'", domain, origin, command, path)
             software = federation.determine_software(domain, path)
         except network.exceptions as exception:
-            print(f"WARNING: Exception '{type(exception)}' during determining software type")
+            logger.warning("Exception '%s' during determining software type, domain='%s'", type(exception), domain)
             set_last_error(domain, exception)
 
-    # DEBUG: print("DEBUG: Determined software:", software)
+    logger.debug("Determined software='%s'", software)
     if software == "lemmy" and domain.find("/c/") > 0:
         domain = domain.split("/c/")[0]
         if is_registered(domain):
-            print(f"WARNING: domain='{domain}' already registered after cutting off user part. - EXIT!")
+            logger.warning("domain='%s' already registered after cutting off user part. - EXIT!", domain)
             return
 
-    print(f"INFO: Adding instance domain='{domain}' (origin='{origin}',software='{software}')")
-    fba.cursor.execute(
+    logger.info("Adding instance domain='%s',origin='%s',software='%s',command='%s'", domain, origin, software, command)
+    database.cursor.execute(
         "INSERT INTO instances (domain, origin, command, hash, software, first_seen) VALUES (?, ?, ?, ?, ?, ?)",
         (
            domain,
            origin,
            command,
-           fba.get_hash(domain),
+           utils.get_hash(domain),
            software,
            time.time()
         ),
     )
 
-    # DEBUG: print(f"DEBUG: Marking domain='{domain}' as registered.")
+    logger.debug("Marking domain='%s' as registered.", domain)
     cache.set_sub_key("is_registered", domain, True)
 
+    logger.debug("Checking if domain='%s' has pending updates ...", domain)
     if has_pending(domain):
-        # DEBUG: print(f"DEBUG: domain='{domain}' has pending nodeinfo being updated ...")
+        logger.debug("Flushing updates for domain='%s' ...", domain)
         update_data(domain)
 
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
 def set_last_nodeinfo(domain: str):
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
 
-    # DEBUG: print("DEBUG: Updating last_nodeinfo for domain:", domain)
+    logger.debug("Updating last_nodeinfo for domain='%s'", domain)
     _set_data("last_nodeinfo", domain, time.time())
 
-    # Running pending updated
-    # DEBUG: print(f"DEBUG: Invoking update_data({domain}) ...")
-    update_data(domain)
-
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
 def set_last_error(domain: str, error: dict):
-    # DEBUG: print("DEBUG: domain,error[]:", domain, type(error))
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+    logger.debug("domain='%s',error[]='%s' - CALLED!", domain, type(error))
+    domain_helper.raise_on(domain)
 
-    # DEBUG: print("DEBUG: BEFORE error[]:", type(error))
+    logger.debug("error[]='%s' - BEFORE!", type(error))
     if isinstance(error, (BaseException, json.decoder.JSONDecodeError)):
         error = f"error[{type(error)}]='{str(error)}'"
-    # DEBUG: print("DEBUG: AFTER error[]:", type(error))
+    logger.debug("error[]='%s' - AFTER!", type(error))
 
     if isinstance(error, str):
-        # DEBUG: print(f"DEBUG: Setting last_error_details='{error}'")
+        logger.debug("Setting last_error_details='%s' (str)", error)
         _set_data("last_status_code"  , domain, 999)
         _set_data("last_error_details", domain, error if error != "" else None)
     elif isinstance(error, requests.models.Response):
-        # DEBUG: print(f"DEBUG: Setting last_error_details='{error.reason}'")
+        logger.debug("Setting last_error_details='%s' (Response)", error.reason)
         _set_data("last_status_code"  , domain, error.status_code)
         _set_data("last_error_details", domain, error.reason if error.reason != "" else None)
     elif not isinstance(error, dict):
         raise KeyError(f"Cannot handle keys in error[{type(error)}]='{error}'")
     elif "status_code" in error and "error_message" in error:
-        # DEBUG: print(f"DEBUG: Setting last_error_details='{error['error_message']}'")
+        logger.debug("Setting last_error_details='%s' (error_message)", error['error_message'])
         _set_data("last_status_code"  , domain, error["status_code"])
         _set_data("last_error_details", domain, error["error_message"] if error["error_message"] != "" else None)
     elif "json" in error and "error" in error["json"]:
+        logger.debug("Setting last_error_details='%s' (json,error)", error["json"]["error"])
         _set_data("last_status_code"  , domain, error["status_code"])
         _set_data("last_error_details", domain, error["json"]["error"] if error["json"]["error"] != "" else None)
 
-    # DEBUG: print(f"DEBUG: Invoking error_log.add(domain='{domain}',error[]='{type(error)}'")
+    logger.debug("Invoking error_log.add(domain='%s',error[]='%s'", domain, type(error))
     error_log.add(domain, error)
 
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
+
+def set_success(domain: str):
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
+
+    # Set both to success
+    _set_data("last_status_code"  , domain, 200)
+    _set_data("last_error_details", domain, None)
+
+    logger.debug("EXIT!")
 
 def is_registered(domain: str) -> bool:
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
 
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
+    logger.debug("domain='%s' - CALLED!", domain)
     if not cache.key_exists("is_registered"):
-        # DEBUG: print("DEBUG: Cache for 'is_registered' not initialized, fetching all rows ...")
-        fba.cursor.execute("SELECT domain FROM instances")
+        logger.debug("Cache for 'is_registered' not initialized, fetching all rows ...")
+        database.cursor.execute("SELECT domain FROM instances")
 
         # Check Set all
-        cache.set_all("is_registered", fba.cursor.fetchall(), True)
+        cache.set_all("is_registered", database.cursor.fetchall(), True)
 
     # Is cache found?
     registered = cache.sub_key_exists("is_registered", domain)
 
-    # DEBUG: print(f"DEBUG: registered='{registered}' - EXIT!")
+    logger.debug("registered='%s' - EXIT!", registered)
     return registered
 
-def is_recent(domain: str) -> bool:
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+def is_recent(domain: str, column: str = "last_instance_fetch") -> bool:
+    logger.debug("domain='%s',column='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
+
+    if not isinstance(column, str):
+        raise ValueError(f"Parameter column[]='{type(column)}' is not 'str'")
+    elif column not in ["last_instance_fetch", "last_blocked"]:
+        raise ValueError(f"Parameter column='{column}' is not expected")
     elif not is_registered(domain):
-        # DEBUG: print(f"DEBUG: domain='{domain}' is not registered, returning False - EXIT!")
+        logger.debug("domain='%s' is not registered, returning False - EXIT!", domain)
         return False
 
     # Query database
-    fba.cursor.execute("SELECT last_instance_fetch FROM instances WHERE domain = ? LIMIT 1", [domain])
+    database.cursor.execute(f"SELECT {column} FROM instances WHERE domain = ? LIMIT 1", [domain])
 
     # Fetch row
-    fetched = fba.cursor.fetchone()[0]
+    fetched = database.cursor.fetchone()[0]
 
-    # DEBUG: print(f"DEBUG: fetched[{type(fetched)}]='{fetched}'")
+    logger.debug("fetched[%s]='%s'", type(fetched), fetched)
     recently = isinstance(fetched, float) and time.time() - fetched <= config.get("recheck_instance")
 
-    # DEBUG: print(f"DEBUG: recently='{recently}' - EXIT!")
+    logger.debug("recently='%s' - EXIT!", recently)
     return recently
 
-def deobscure(char: str, domain: str, blocked_hash: str = None) -> tuple:
-    # DEBUG: print(f"DEBUG: char='{char}',domain='{domain}',blocked_hash='{blocked_hash}' - CALLED!")
-    if not isinstance(char, str):
-        raise ValueError(f"Parameter char[]='{type(char)}' is not 'str'")
-    elif char == "":
-        raise ValueError("Parameter 'char' is empty")
-    elif not isinstance(domain, str):
+def deobfuscate(char: str, domain: str, blocked_hash: str = None) -> tuple:
+    logger.debug("char='%s',domain='%s',blocked_hash='%s' - CALLED!", char, domain, blocked_hash)
+
+    if not isinstance(domain, str):
         raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
     elif domain == "":
         raise ValueError("Parameter 'domain' is empty")
+    elif domain.lower() != domain:
+        raise ValueError(f"Parameter domain='{domain}' must be all lower-case")
+    elif domain.endswith(".arpa"):
+        raise ValueError(f"domain='{domain}' is a domain for reversed IP addresses, please don't crawl them!")
+    elif domain.endswith(".tld"):
+        raise ValueError(f"domain='{domain}' is a fake domain, please don't crawl them!")
+    elif not isinstance(char, str):
+        raise ValueError(f"Parameter char[]='{type(char)}' is not 'str'")
+    elif char == "":
+        raise ValueError("Parameter 'char' is empty")
+    elif not char in domain:
+        raise ValueError(f"char='{char}' not found in domain='{domain}' but function invoked")
     elif not isinstance(blocked_hash, str) and blocked_hash is not None:
         raise ValueError(f"Parameter blocked_hash[]='{type(blocked_hash)}' is not 'str'")
 
+    logger.debug("blocked_hash[]='%s'", type(blocked_hash))
     if isinstance(blocked_hash, str):
-        # DEBUG: print(f"DEBUG: Looking up blocked_hash='{blocked_hash}' ...")
-        fba.cursor.execute(
-            "SELECT domain, origin, nodeinfo_url FROM instances WHERE hash = ? LIMIT 1", [blocked_hash]
+        logger.debug("Looking up blocked_hash='%s',domain='%s' ...", blocked_hash, domain)
+        database.cursor.execute(
+            "SELECT domain, origin, nodeinfo_url FROM instances WHERE hash = ? OR domain LIKE ? LIMIT 1", [blocked_hash, domain.replace(char, "_")]
         )
 
-        row = fba.cursor.fetchone()
-        # DEBUG: print(f"DEBUG: row[]='{type(row)}'")
+        row = database.cursor.fetchone()
+        logger.debug("row[]='%s'", type(row))
 
         if row is None:
-            # DEBUG: print(f"DEBUG: blocked_hash='{blocked_hash}' not found, trying domain='{domain}' ...")
-            return deobscure(char, domain)
+            logger.debug("blocked_hash='%s' not found, trying domain='%s' ...", blocked_hash, domain)
+            return deobfuscate(char, domain)
     else:
-        # DEBUG: print(f"DEBUG: Looking up domain='{domain}' ...")
-        fba.cursor.execute(
+        logger.debug("Looking up domain='%s' ...", domain)
+        database.cursor.execute(
             "SELECT domain, origin, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [domain.replace(char, "_")]
         )
 
-        row = fba.cursor.fetchone()
-        # DEBUG: print(f"DEBUG: row[]='{type(row)}'")
+        row = database.cursor.fetchone()
+        logger.debug("row[]='%s'", type(row))
 
-    # DEBUG: print(f"DEBUG: row[]='{type(row)}' - EXIT!")
+    logger.debug("row[]='%s' - EXIT!", type(row))
     return row
 
-def set_last_blocked (domain: str):
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+def set_last_blocked(domain: str):
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
 
     # Set timestamp
     _set_data("last_blocked", domain, time.time())
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
-def set_last_instance_fetch (domain: str):
-    # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
+def set_last_instance_fetch(domain: str):
+    logger.debug("domain='%s' - CALLED!", domain)
+    domain_helper.raise_on(domain)
 
     # Set timestamp
     _set_data("last_instance_fetch", domain, time.time())
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
-def set_total_peers (domain: str, peers: list):
-    # DEBUG: print(f"DEBUG: domain='{domain}',peers()={len(peers)} - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not isinstance(peers, list):
-        raise ValueError("Parameter peers[]='{type(peers)}' is not 'list'")
+def set_total_peers(domain: str, peers: list):
+    logger.debug("domain='%s',peers()=%d - CALLED!", domain, len(peers))
+    domain_helper.raise_on(domain)
+
+    if not isinstance(peers, list):
+        raise ValueError(f"Parameter peers[]='{type(peers)}' is not 'list': '%s'")
 
     # Set timestamp
     _set_data("total_peers", domain, len(peers))
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
 
-def set_nodeinfo_url (domain: str, url: list):
-    # DEBUG: print(f"DEBUG: domain='{domain}',url='{url}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not isinstance(url, str):
-        raise ValueError("Parameter url[]='{type(url)}' is not 'list'")
-    elif url == "":
-        raise ValueError("Parameter 'url' is empty")
-
-    # Set timestamp
-    _set_data("nodeinfo_url", domain, url)
-    # DEBUG: print("DEBUG: EXIT!")
+def set_nodeinfo_url(domain: str, url: str):
+    logger.debug("domain='%s',url='%s' - CALLED!", domain, url)
+    domain_helper.raise_on(domain)
 
-def set_detection_mode (domain: str, url: list):
-    # DEBUG: print(f"DEBUG: domain='{domain}',url='{url}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not isinstance(url, str):
+    if not isinstance(url, str):
         raise ValueError("Parameter url[]='{type(url)}' is not 'list'")
     elif url == "":
         raise ValueError("Parameter 'url' is empty")
 
     # Set timestamp
-    _set_data("detection_mode", domain, url)
-    # DEBUG: print("DEBUG: EXIT!")
-
-def set_detection_mode (domain: str, url: list):
-    # DEBUG: print(f"DEBUG: domain='{domain}',url='{url}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not isinstance(url, str):
-        raise ValueError("Parameter url[]='{type(url)}' is not 'list'")
-    elif url == "":
-        raise ValueError("Parameter 'url' is empty")
+    _set_data("nodeinfo_url", domain, url)
+    logger.debug("EXIT!")
 
-    # Set timestamp
-    _set_data("detection_mode", domain, url)
-    # DEBUG: print("DEBUG: EXIT!")
+def set_detection_mode(domain: str, mode: str):
+    logger.debug("domain='%s',mode='%s' - CALLED!", domain, mode)
+    domain_helper.raise_on(domain)
 
-def set_detection_mode (domain: str, mode: list):
-    # DEBUG: print(f"DEBUG: domain='{domain}',mode='{mode}' - CALLED!")
-    if not isinstance(domain, str):
-        raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
-    elif domain == "":
-        raise ValueError("Parameter 'domain' is empty")
-    elif not isinstance(mode, str):
+    if not isinstance(mode, str):
         raise ValueError("Parameter mode[]='{type(mode)}' is not 'list'")
     elif mode == "":
         raise ValueError("Parameter 'mode' is empty")
 
     # Set timestamp
     _set_data("detection_mode", domain, mode)
-    # DEBUG: print("DEBUG: EXIT!")
+    logger.debug("EXIT!")
+
+def set_has_obfuscation(domain: str, status: bool):
+    logger.debug("domain(%d)='%s',status='%s' - CALLED!", len(domain), domain, status)
+    domain_helper.raise_on(domain)
+
+    if not isinstance(status, bool):
+        raise ValueError(f"Parameter status[]='{type(status)}' is not 'bool'")
+
+    # Set timestamp
+    _set_data("has_obfuscation", domain, status)
+    logger.debug("EXIT!")