1 # Copyright (C) 2023 Free Software Foundation
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License as published
5 # by the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU Affero General Public License for more details.
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <https://www.gnu.org/licenses/>.
18 from urllib.parse import urlparse
26 from fba.helpers import config
27 from fba.helpers import cookies
28 from fba.helpers import domain as domain_helper
29 from fba.helpers import software as software_helper
30 from fba.helpers import tidyup
31 from fba.helpers import version
33 from fba.http import network
35 from fba.models import instances
37 from fba.networks import lemmy
38 from fba.networks import misskey
39 from fba.networks import peertube
41 logging.basicConfig(level=logging.INFO)
42 logger = logging.getLogger(__name__)
44 def fetch_instances(domain: str, origin: str, software: str, command: str, path: str = None):
45 logger.debug("domain='%s',origin='%s',software='%s',command='%s',path='%s' - CALLED!", domain, origin, software, command, path)
46 domain_helper.raise_on(domain)
48 if not isinstance(origin, str) and origin is not None:
49 raise ValueError(f"Parameter origin[]='{type(origin)}' is not of type 'str'")
50 elif not isinstance(command, str):
51 raise ValueError(f"Parameter command[]='{type(command)}' is not of type 'str'")
53 raise ValueError("Parameter 'command' is empty")
54 elif software is None:
56 logger.debug("Software for domain='%s' is not set, determining ...", domain)
57 software = determine_software(domain, path)
58 except network.exceptions as exception:
59 logger.warning("Exception '%s' during determining software type", type(exception))
60 instances.set_last_error(domain, exception)
62 logger.debug("Determined software='%s' for domain='%s'", software, domain)
63 elif not isinstance(software, str):
64 raise ValueError(f"Parameter software[]='{type(software)}' is not of type 'str'")
66 logger.debug("Checking if domain='%s' is registered ...", domain)
67 if not instances.is_registered(domain):
68 logger.debug("Adding new domain='%s',origin='%s',command='%s',path='%s',software='%s'", domain, origin, command, path, software)
69 instances.add(domain, origin, command, path, software)
71 logger.debug("Updating last_instance_fetch for domain='%s' ...", domain)
72 instances.set_last_instance_fetch(domain)
76 logger.debug("Fetching instances for domain='%s',software='%s',origin='%s'", domain, software, origin)
77 peerlist = fetch_peers(domain, software, origin)
78 except network.exceptions as exception:
79 logger.warning("Cannot fetch peers from domain='%s': '%s'", domain, type(exception))
81 logger.debug("peerlist[]='%s'", type(peerlist))
82 if isinstance(peerlist, list):
83 logger.debug("Invoking instances.set_total_peerlist(%s,%d) ...", domain, len(peerlist))
84 instances.set_total_peers(domain, peerlist)
86 logger.debug("peerlist[]='%s'", type(peerlist))
87 if peerlist is None or len(peerlist) == 0:
88 logger.warning("Cannot fetch peers: domain='%s'", domain)
90 if instances.has_pending(domain):
91 logger.debug("Flushing updates for domain='%s' ...", domain)
92 instances.update_data(domain)
94 logger.debug("Invoking cookies.clear(%s) ...", domain)
100 logger.info("Checking %d instance(s) from domain='%s',software='%s' ...", len(peerlist), domain, software)
101 for instance in peerlist:
102 logger.debug("instance='%s'", instance)
104 # Skip "None" types as tidup.domain() cannot parse them
107 logger.debug("instance='%s' - BEFORE!", instance)
108 instance = tidyup.domain(instance)
109 logger.debug("instance='%s' - AFTER!", instance)
112 logger.warning("Empty instance after tidyup.domain(), domain='%s'", domain)
114 elif not utils.is_domain_wanted(instance):
115 logger.debug("instance='%s' is not wanted - SKIPPED!", instance)
117 elif instance.find("/profile/") > 0 or instance.find("/users/") > 0 or (instances.is_registered(instance.split("/")[0]) and instance.find("/c/") > 0):
118 logger.debug("instance='%s' is a link to a single user profile - SKIPPED!", instance)
120 elif instance.find("/tag/") > 0:
121 logger.debug("instance='%s' is a link to a tag - SKIPPED!", instance)
123 elif not instances.is_registered(instance):
124 logger.debug("Adding new instance='%s',domain='%s',command='%s'", instance, domain, command)
125 instances.add(instance, domain, command)
127 logger.debug("Invoking cookies.clear(%s) ...", domain)
128 cookies.clear(domain)
130 logger.debug("Checking if domain='%s' has pending updates ...", domain)
131 if instances.has_pending(domain):
132 logger.debug("Flushing updates for domain='%s' ...", domain)
133 instances.update_data(domain)
135 logger.debug("EXIT!")
137 def fetch_peers(domain: str, software: str, origin: str) -> list:
138 logger.debug("domain='%s',software='%s',origin='%s' - CALLED!", domain, software, origin)
139 domain_helper.raise_on(domain)
141 if not isinstance(software, str) and software is not None:
142 raise ValueError(f"software[]='{type(software)}' is not of type 'str'")
144 if software == "misskey":
145 logger.debug("Invoking misskey.fetch_peers(%s) ...", domain)
146 return misskey.fetch_peers(domain)
147 elif software == "lemmy":
148 logger.debug("Invoking lemmy.fetch_peers(%s,%s) ...", domain, origin)
149 return lemmy.fetch_peers(domain, origin)
150 elif software == "peertube":
151 logger.debug("Invoking peertube.fetch_peers(%s) ...", domain)
152 return peertube.fetch_peers(domain)
154 # No CSRF by default, you don't have to add network.api_headers by yourself here
158 logger.debug("Checking CSRF for domain='%s'", domain)
159 headers = csrf.determine(domain, dict())
160 except network.exceptions as exception:
161 logger.warning("Exception '%s' during checking CSRF (fetch_peers,%s) - EXIT!", type(exception), __name__)
162 instances.set_last_error(domain, exception)
166 "/api/v1/instance/peers",
170 # Init peers variable
173 logger.debug("Checking %d paths ...", len(paths))
175 logger.debug("Fetching path='%s' from domain='%s',software='%s' ...", path, domain, software)
176 data = network.get_json_api(
180 (config.get("connection_timeout"), config.get("read_timeout"))
183 logger.debug("data[]='%s'", type(data))
184 if "error_message" in data:
185 logger.debug("Was not able to fetch peers from path='%s',domain='%s' ...", path, domain)
186 instances.set_last_error(domain, data)
187 elif "json" in data and len(data["json"]) > 0:
188 logger.debug("Querying API path='%s' was successful: domain='%s',data[json][%s]()=%d", path, domain, type(data['json']), len(data['json']))
191 logger.debug("Marking domain='%s' as successfully handled ...", domain)
192 instances.set_success(domain)
195 if not isinstance(peers, list):
196 logger.warning("peers[]='%s' is not of type 'list', maybe bad API response?", type(peers))
199 logger.debug("Invoking instances.set_total_peers(%s,%d) ...", domain, len(peers))
200 instances.set_total_peers(domain, peers)
202 logger.debug("peers()=%d - EXIT!", len(peers))
205 def fetch_nodeinfo(domain: str, path: str = None) -> dict:
206 logger.debug("domain='%s',path='%s' - CALLED!", domain, path)
207 domain_helper.raise_on(domain)
209 if not isinstance(path, str) and path is not None:
210 raise ValueError(f"Parameter path[]='{type(path)}' is not of type 'str'")
212 logger.debug("Fetching nodeinfo from domain='%s' ...", domain)
213 nodeinfo = fetch_wellknown_nodeinfo(domain)
215 logger.debug("nodeinfo[%s](%d='%s'", type(nodeinfo), len(nodeinfo), nodeinfo)
216 if "error_message" not in nodeinfo and "json" in nodeinfo and len(nodeinfo["json"]) > 0:
217 logger.debug("Found nodeinfo[json]()=%d - EXIT!", len(nodeinfo['json']))
220 # No CSRF by default, you don't have to add network.api_headers by yourself here
225 logger.debug("Checking CSRF for domain='%s'", domain)
226 headers = csrf.determine(domain, dict())
227 except network.exceptions as exception:
228 logger.warning("Exception '%s' during checking CSRF (nodeinfo,%s) - EXIT!", type(exception), __name__)
229 instances.set_last_error(domain, exception)
230 instances.set_software(domain, None)
231 instances.set_detection_mode(domain, None)
232 instances.set_nodeinfo_url(domain, None)
235 "error_message": f"exception[{type(exception)}]='{str(exception)}'",
236 "exception" : exception,
240 "/nodeinfo/2.1.json",
242 "/nodeinfo/2.0.json",
244 "/nodeinfo/1.0.json",
249 for request in request_paths:
250 logger.debug("request='%s'", request)
251 http_url = f"http://{domain}{path}"
252 https_url = f"https://{domain}{path}"
254 logger.debug("path[%s]='%s',request='%s',http_url='%s',https_url='%s'", type(path), path, request, http_url, https_url)
255 if path is None or path in [request, http_url, https_url]:
256 logger.debug("Fetching request='%s' from domain='%s' ...", request, domain)
257 if path in [http_url, https_url]:
258 logger.debug("domain='%s',path='%s' has protocol in path, splitting ...", domain, path)
259 components = urlparse(path)
260 path = components.path
262 data = network.get_json_api(
266 (config.get("nodeinfo_connection_timeout"), config.get("nodeinfo_read_timeout"))
269 logger.debug("data[]='%s'", type(data))
270 if "error_message" not in data and "json" in data:
271 logger.debug("Success: request='%s' - Setting detection_mode=STATIC_CHECK ...", request)
272 instances.set_detection_mode(domain, "STATIC_CHECK")
273 instances.set_nodeinfo_url(domain, request)
276 logger.warning("Failed fetching nodeinfo from domain='%s',status_code='%s',error_message='%s'", domain, data['status_code'], data['error_message'])
278 logger.debug("data()=%d - EXIT!", len(data))
281 def fetch_wellknown_nodeinfo(domain: str) -> dict:
282 logger.debug("domain='%s' - CALLED!", domain)
283 domain_helper.raise_on(domain)
285 # "rel" identifiers (no real URLs)
286 nodeinfo_identifier = [
287 "https://nodeinfo.diaspora.software/ns/schema/2.1",
288 "http://nodeinfo.diaspora.software/ns/schema/2.1",
289 "https://nodeinfo.diaspora.software/ns/schema/2.0",
290 "http://nodeinfo.diaspora.software/ns/schema/2.0",
291 "https://nodeinfo.diaspora.software/ns/schema/1.1",
292 "http://nodeinfo.diaspora.software/ns/schema/1.1",
293 "https://nodeinfo.diaspora.software/ns/schema/1.0",
294 "http://nodeinfo.diaspora.software/ns/schema/1.0",
297 # No CSRF by default, you don't have to add network.api_headers by yourself here
301 logger.debug("Checking CSRF for domain='%s'", domain)
302 headers = csrf.determine(domain, dict())
303 except network.exceptions as exception:
304 logger.warning("Exception '%s' during checking CSRF (fetch_wellknown_nodeinfo,%s) - EXIT!", type(exception), __name__)
305 instances.set_last_error(domain, exception)
308 "error_message": type(exception),
309 "exception" : exception,
312 logger.debug("Fetching .well-known info for domain='%s'", domain)
313 data = network.get_json_api(
315 "/.well-known/nodeinfo",
317 (config.get("nodeinfo_connection_timeout"), config.get("nodeinfo_read_timeout"))
320 logger.debug("data[]='%s'", type(data))
321 if "error_message" not in data:
322 nodeinfo = data["json"]
324 logger.debug("Marking domain='%s' as successfully handled ...", domain)
325 instances.set_success(domain)
327 logger.debug("Found entries: nodeinfo()=%d,domain='%s'", len(nodeinfo), domain)
328 if "links" in nodeinfo:
329 logger.debug("Found nodeinfo[links]()=%d record(s),", len(nodeinfo["links"]))
330 for niid in nodeinfo_identifier:
333 logger.debug("Checking niid='%s' ...", niid)
334 for link in nodeinfo["links"]:
335 logger.debug("link[%s]='%s'", type(link), link)
336 if not isinstance(link, dict) or not "rel" in link:
337 logger.debug("link[]='%s' is not of type 'dict' or no element 'rel' found - SKIPPED!", type(link))
339 elif link["rel"] != niid:
340 logger.debug("link[re]='%s' does not matched niid='%s' - SKIPPED!", link["rel"], niid)
342 elif "href" not in link:
343 logger.warning("link[rel]='%s' has no element 'href' - SKIPPED!", link["rel"])
346 # Default is that 'href' has a complete URL, but some hosts don't send that
347 logger.debug("link[rel]='%s' matches niid='%s'", link["rel"], niid)
349 components = urlparse(link["href"])
351 logger.debug("components[%s]='%s'", type(components), components)
352 if components.scheme == "" and components.netloc == "":
353 logger.warning("link[href]='%s' has no scheme and host name in it, prepending from domain='%s'", link['href'], domain)
354 url = f"https://{domain}{url}"
355 components = urlparse(url)
356 elif components.netloc == "":
357 logger.warning("link[href]='%s' has no netloc set, setting domain='%s'", link["href"], domain)
358 url = f"{components.scheme}://{domain}{components.path}"
359 components = urlparse(url)
361 if not utils.is_domain_wanted(components.netloc):
362 logger.debug("components.netloc='%s' is not wanted - SKIPPED!", components.netloc)
365 logger.debug("Fetching nodeinfo from url='%s' ...", url)
366 data = network.fetch_api_url(
368 (config.get("connection_timeout"), config.get("read_timeout"))
371 logger.debug("link[href]='%s',data[]='%s'", link["href"], type(data))
372 if "error_message" not in data and "json" in data:
373 logger.debug("Found JSON data()=%d,link[href]='%s' - Setting detection_mode=AUTO_DISCOVERY ...", len(data), link["href"])
374 instances.set_detection_mode(domain, "AUTO_DISCOVERY")
375 instances.set_nodeinfo_url(domain, link["href"])
377 logger.debug("Marking domain='%s' as successfully handled ...", domain)
378 instances.set_success(domain)
381 logger.debug("Setting last error for domain='%s',data[]='%s'", domain, type(data))
382 instances.set_last_error(domain, data)
384 logger.debug("data()=%d", len(data))
385 if "error_message" not in data and "json" in data:
386 logger.debug("Auto-discovery successful: domain='%s'", domain)
389 logger.warning("nodeinfo does not contain 'links': domain='%s'", domain)
391 logger.debug("Returning data[]='%s' - EXIT!", type(data))
394 def fetch_generator_from_path(domain: str, path: str = "/") -> str:
395 logger.debug("domain(%d)='%s',path='%s' - CALLED!", len(domain), domain, path)
396 domain_helper.raise_on(domain)
398 if not isinstance(path, str):
399 raise ValueError(f"path[]='{type(path)}' is not of type 'str'")
401 raise ValueError("Parameter 'path' is empty")
403 logger.debug("domain='%s',path='%s' - CALLED!", domain, path)
406 logger.debug("Fetching path='%s' from domain='%s' ...", path, domain)
407 response = network.fetch_response(
410 (config.get("connection_timeout"), config.get("read_timeout")),
414 logger.debug("response.ok='%s',response.status_code=%d,response.text()=%d", response.ok, response.status_code, len(response.text))
415 if response.ok and response.status_code < 300 and response.text.find("<html") > 0 and domain_helper.is_in_url(domain, response.url):
416 logger.debug("Parsing response.text()=%d Bytes ...", len(response.text))
417 doc = bs4.BeautifulSoup(response.text, "html.parser")
419 logger.debug("doc[]='%s'", type(doc))
420 generator = doc.find("meta", {"name" : "generator"})
421 site_name = doc.find("meta", {"property": "og:site_name"})
422 platform = doc.find("meta", {"property": "og:platform"})
424 logger.debug("generator[]='%s',site_name[]='%s',platform[]='%s'", type(generator), type(site_name), type(platform))
425 if isinstance(generator, bs4.element.Tag) and isinstance(generator.get("content"), str):
426 logger.debug("Found generator meta tag: domain='%s'", domain)
427 software = tidyup.domain(generator.get("content"))
429 logger.debug("software[%s]='%s'", type(software), software)
430 if software is not None and software != "":
431 logger.info("domain='%s' is generated by software='%s' - Setting detection_mode=GENERATOR ...", domain, software)
432 instances.set_detection_mode(domain, "GENERATOR")
433 elif isinstance(site_name, bs4.element.Tag) and isinstance(site_name.get("content"), str):
434 logger.debug("Found property=og:site_name, domain='%s'", domain)
435 software = tidyup.domain(site_name.get("content"))
437 logger.debug("software[%s]='%s'", type(software), software)
438 if software is not None and software != "":
439 logger.debug("domain='%s' has og:site_name='%s' - Setting detection_mode=SITE_NAME ...", domain, software)
440 instances.set_detection_mode(domain, "SITE_NAME")
441 elif isinstance(platform, bs4.element.Tag) and isinstance(platform.get("content"), str):
442 logger.debug("Found property=og:platform, domain='%s'", domain)
443 software = tidyup.domain(platform.get("content"))
445 logger.debug("software[%s]='%s'", type(software), software)
446 if software is not None and software != "":
447 logger.debug("domain='%s' has og:platform='%s' - Setting detection_mode=PLATFORM ...", domain, software)
448 instances.set_detection_mode(domain, "PLATFORM")
449 elif not domain_helper.is_in_url(domain, response.url):
450 logger.warning("domain='%s' doesn't match response.url='%s', maybe redirect to other domain?", domain, response.url)
451 message = f"Redirect from domain='{domain}' to response.url='{response.url}'"
452 instances.set_last_error(domain, message)
453 instances.set_software(domain, None)
454 instances.set_detection_mode(domain, None)
455 instances.set_nodeinfo_url(domain, None)
456 raise requests.exceptions.TooManyRedirects(message)
458 logger.debug("software[]='%s'", type(software))
459 if isinstance(software, str) and software == "":
460 logger.debug("Corrected empty string to None for software of domain='%s'", domain)
462 elif isinstance(software, str) and ("." in software or " " in software):
463 logger.debug("software='%s' may contain a version number, domain='%s', removing it ...", software, domain)
464 software = version.remove(software)
466 logger.debug("software[]='%s'", type(software))
467 if isinstance(software, str) and "powered by " in software:
468 logger.debug("software='%s' has 'powered by' in it", software)
469 software = version.remove(version.strip_powered_by(software))
470 elif isinstance(software, str) and " hosted on " in software:
471 logger.debug("software='%s' has 'hosted on' in it", software)
472 software = version.remove(version.strip_hosted_on(software))
473 elif isinstance(software, str) and " by " in software:
474 logger.debug("software='%s' has ' by ' in it", software)
475 software = version.strip_until(software, " by ")
476 elif isinstance(software, str) and " see " in software:
477 logger.debug("software='%s' has ' see ' in it", software)
478 software = version.strip_until(software, " see ")
480 logger.debug("software='%s' - EXIT!", software)
483 def determine_software(domain: str, path: str = None) -> str:
484 logger.debug("domain(%d)='%s',path='%s' - CALLED!", len(domain), domain, path)
485 domain_helper.raise_on(domain)
487 if not isinstance(path, str) and path is not None:
488 raise ValueError(f"Parameter path[]='{type(path)}' is not of type 'str'")
490 logger.debug("Determining software for domain='%s',path='%s'", domain, path)
493 logger.debug("Fetching nodeinfo from domain='%s' ...", domain)
494 data = fetch_nodeinfo(domain, path)
496 logger.debug("data[%s]='%s'", type(data), data)
497 if "exception" in data:
498 # Continue raising it
499 logger.debug("data()=%d contains exception='%s' - raising ...", len(data), type(data["exception"]))
500 raise data["exception"]
501 elif "error_message" in data:
502 logger.debug("Returned error_message during fetching nodeinfo: '%s',status_code=%d", data['error_message'], data['status_code'])
503 software = fetch_generator_from_path(domain)
504 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
506 logger.debug("domain='%s',path='%s',data[json] found ...", domain, path)
509 logger.debug("JSON response from domain='%s' does not include [software][name], fetching / ...", domain)
510 software = fetch_generator_from_path(domain)
511 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
513 if "status" in data and data["status"] == "error" and "message" in data:
514 logger.warning("JSON response is an error: '%s' - Resetting detection_mode,nodeinfo_url ...", data["message"])
515 instances.set_last_error(domain, data["message"])
516 instances.set_detection_mode(domain, None)
517 instances.set_nodeinfo_url(domain, None)
518 software = fetch_generator_from_path(domain)
519 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
520 elif "software" in data and "name" in data["software"]:
521 logger.debug("Found data[json][software][name] in JSON response")
522 software = data["software"]["name"]
523 logger.debug("software[%s]='%s' - FOUND!", type(software), software)
524 elif "message" in data:
525 logger.warning("JSON response contains only a message: '%s' - Resetting detection_mode,nodeinfo_url ...", data["message"])
526 instances.set_last_error(domain, data["message"])
527 instances.set_detection_mode(domain, None)
528 instances.set_nodeinfo_url(domain, None)
530 logger.debug("Invoking fetch_generator_from_path(%s) ...", domain)
531 software = fetch_generator_from_path(domain)
532 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
533 elif "software" not in data or "name" not in data["software"]:
534 logger.debug("JSON response from domain='%s' does not include [software][name] - Resetting detection_mode,nodeinfo_url ...", domain)
535 instances.set_detection_mode(domain, None)
536 instances.set_nodeinfo_url(domain, None)
538 logger.debug("Invoking fetch_generator_from_path(%s) ...", domain)
539 software = fetch_generator_from_path(domain)
540 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
542 logger.debug("software[%s]='%s'", type(software), software)
544 logger.debug("Returning None - EXIT!")
547 logger.debug("software='%s'- BEFORE!", software)
548 software = software_helper.alias(software)
549 logger.debug("software['%s']='%s' - AFTER!", type(software), software)
551 if str(software) == "":
552 logger.debug("software for domain='%s' was not detected, trying generator ...", domain)
553 software = fetch_generator_from_path(domain)
554 elif len(str(software)) > 0 and ("." in software or " " in software):
555 logger.debug("software='%s' may contain a version number, domain='%s', removing it ...", software, domain)
556 software = version.remove(software)
558 logger.debug("software[]='%s'", type(software))
559 if isinstance(software, str) and "powered by" in software:
560 logger.debug("software='%s' has 'powered by' in it", software)
561 software = version.remove(version.strip_powered_by(software))
563 logger.debug("software='%s' - EXIT!", software)
566 def find_domains(tag: bs4.element.Tag) -> list:
567 logger.debug("tag[]='%s' - CALLED!", type(tag))
568 if not isinstance(tag, bs4.element.Tag):
569 raise ValueError(f"Parameter tag[]='{type(tag)}' is not type of bs4.element.Tag")
570 elif len(tag.select("tr")) == 0:
571 raise KeyError("No table rows found in table!")
574 for element in tag.select("tr"):
575 logger.debug("element[]='%s'", type(element))
576 if not element.find("td"):
577 logger.debug("Skipping element, no <td> found")
580 domain = tidyup.domain(element.find("td").text)
581 reason = tidyup.reason(element.findAll("td")[1].text)
583 logger.debug("domain='%s',reason='%s'", domain, reason)
585 if not utils.is_domain_wanted(domain):
586 logger.debug("domain='%s' is blacklisted - SKIPPED!", domain)
588 elif domain == "gab.com/.ai, develop.gab.com":
589 logger.debug("Multiple domains detected in one row")
599 "domain": "develop.gab.com",
603 elif not validators.domain(domain.split("/")[0]):
604 logger.warning("domain='%s' is not a valid domain - SKIPPED!", domain)
607 logger.debug("Adding domain='%s',reason='%s' ...", domain, reason)
613 logger.debug("domains()=%d - EXIT!", len(domains))
616 def add_peers(rows: dict) -> list:
617 logger.debug("rows[]='%s' - CALLED!", type(rows))
618 if not isinstance(rows, dict):
619 raise ValueError(f"Parameter rows[]='{type(rows)}' is not of type 'dict'")
622 for key in ["linked", "allowed", "blocked"]:
623 logger.debug("Checking key='%s'", key)
624 if key not in rows or rows[key] is None:
625 logger.debug("Cannot find key='%s' or it is NoneType - SKIPPED!", key)
628 logger.debug("Adding %d peer(s) to peers list ...", len(rows[key]))
629 for peer in rows[key]:
630 logger.debug("peer[%s]='%s' - BEFORE!", type(peer), peer)
631 if peer is None or peer == "":
632 logger.debug("peer is empty - SKIPPED")
634 elif isinstance(peer, dict) and "domain" in peer:
635 logger.debug("peer[domain]='%s'", peer["domain"])
636 peer = tidyup.domain(peer["domain"])
637 elif isinstance(peer, str):
638 logger.debug("peer='%s'", peer)
639 peer = tidyup.domain(peer)
641 raise ValueError(f"peer[]='{type(peer)}' is not supported,key='{key}'")
643 logger.debug("peer[%s]='%s' - AFTER!", type(peer), peer)
644 if not utils.is_domain_wanted(peer):
645 logger.debug("peer='%s' is not wanted - SKIPPED!", peer)
648 logger.debug("Appending peer='%s' ...", peer)
651 logger.debug("peers()=%d - EXIT!", len(peers))