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("Invoking instances.set_last_nodeinfo(%s) ...", domain)
218 instances.set_last_nodeinfo(domain)
220 logger.debug("Found nodeinfo[json]()=%d - EXIT!", len(nodeinfo['json']))
223 # No CSRF by default, you don't have to add network.api_headers by yourself here
228 logger.debug("Checking CSRF for domain='%s'", domain)
229 headers = csrf.determine(domain, dict())
230 except network.exceptions as exception:
231 logger.warning("Exception '%s' during checking CSRF (nodeinfo,%s) - EXIT!", type(exception), __name__)
232 instances.set_last_error(domain, exception)
233 instances.set_software(domain, None)
234 instances.set_detection_mode(domain, None)
235 instances.set_nodeinfo_url(domain, None)
238 "error_message": f"exception[{type(exception)}]='{str(exception)}'",
239 "exception" : exception,
243 "/nodeinfo/2.1.json",
245 "/nodeinfo/2.0.json",
247 "/nodeinfo/1.0.json",
252 for request in request_paths:
253 logger.debug("request='%s'", request)
254 http_url = f"http://{domain}{path}"
255 https_url = f"https://{domain}{path}"
257 logger.debug("path[%s]='%s',request='%s',http_url='%s',https_url='%s'", type(path), path, request, http_url, https_url)
258 if path is None or path in [request, http_url, https_url]:
259 logger.debug("path='%s',http_url='%s',https_url='%s'", path, http_url, https_url)
260 if path in [http_url, https_url]:
261 logger.debug("domain='%s',path='%s' has protocol in path, splitting ...", domain, path)
262 components = urlparse(path)
263 path = components.path
265 logger.debug("Fetching request='%s' from domain='%s' ...", request, domain)
266 data = network.get_json_api(
270 (config.get("nodeinfo_connection_timeout"), config.get("nodeinfo_read_timeout"))
273 logger.debug("data[]='%s'", type(data))
274 if "error_message" not in data and "json" in data:
275 logger.debug("Success: request='%s' - Setting detection_mode=STATIC_CHECK ...", request)
276 instances.set_last_nodeinfo(domain)
277 instances.set_detection_mode(domain, "STATIC_CHECK")
278 instances.set_nodeinfo_url(domain, request)
281 logger.warning("Failed fetching nodeinfo from domain='%s',status_code='%s',error_message='%s'", domain, data['status_code'], data['error_message'])
283 logger.debug("data()=%d - EXIT!", len(data))
286 def fetch_wellknown_nodeinfo(domain: str) -> dict:
287 logger.debug("domain='%s' - CALLED!", domain)
288 domain_helper.raise_on(domain)
290 # "rel" identifiers (no real URLs)
291 nodeinfo_identifier = [
292 "https://nodeinfo.diaspora.software/ns/schema/2.1",
293 "http://nodeinfo.diaspora.software/ns/schema/2.1",
294 "https://nodeinfo.diaspora.software/ns/schema/2.0",
295 "http://nodeinfo.diaspora.software/ns/schema/2.0",
296 "https://nodeinfo.diaspora.software/ns/schema/1.1",
297 "http://nodeinfo.diaspora.software/ns/schema/1.1",
298 "https://nodeinfo.diaspora.software/ns/schema/1.0",
299 "http://nodeinfo.diaspora.software/ns/schema/1.0",
302 # No CSRF by default, you don't have to add network.api_headers by yourself here
306 logger.debug("Checking CSRF for domain='%s'", domain)
307 headers = csrf.determine(domain, dict())
308 except network.exceptions as exception:
309 logger.warning("Exception '%s' during checking CSRF (fetch_wellknown_nodeinfo,%s) - EXIT!", type(exception), __name__)
310 instances.set_last_error(domain, exception)
313 "error_message": type(exception),
314 "exception" : exception,
317 logger.debug("Fetching .well-known info for domain='%s'", domain)
318 data = network.get_json_api(
320 "/.well-known/nodeinfo",
322 (config.get("nodeinfo_connection_timeout"), config.get("nodeinfo_read_timeout"))
325 logger.debug("data[]='%s'", type(data))
326 if "error_message" not in data:
327 nodeinfo = data["json"]
329 logger.debug("Marking domain='%s' as successfully handled ...", domain)
330 instances.set_success(domain)
332 logger.debug("Found entries: nodeinfo()=%d,domain='%s'", len(nodeinfo), domain)
333 if "links" in nodeinfo:
334 logger.debug("Found nodeinfo[links]()=%d record(s),", len(nodeinfo["links"]))
335 for niid in nodeinfo_identifier:
338 logger.debug("Checking niid='%s' ...", niid)
339 for link in nodeinfo["links"]:
340 logger.debug("link[%s]='%s'", type(link), link)
341 if not isinstance(link, dict) or not "rel" in link:
342 logger.debug("link[]='%s' is not of type 'dict' or no element 'rel' found - SKIPPED!", type(link))
344 elif link["rel"] != niid:
345 logger.debug("link[re]='%s' does not matched niid='%s' - SKIPPED!", link["rel"], niid)
347 elif "href" not in link:
348 logger.warning("link[rel]='%s' has no element 'href' - SKIPPED!", link["rel"])
350 elif link["href"] is None:
351 logger.debug("link[href] is None, link[rel]='%s' - SKIPPED!", link["rel"])
354 # Default is that 'href' has a complete URL, but some hosts don't send that
355 logger.debug("link[rel]='%s' matches niid='%s'", link["rel"], niid)
357 components = urlparse(url)
359 logger.debug("components[%s]='%s'", type(components), components)
360 if components.scheme == "" and components.netloc == "":
361 logger.warning("link[href]='%s' has no scheme and host name in it, prepending from domain='%s'", link['href'], domain)
362 url = f"https://{domain}{url}"
363 components = urlparse(url)
364 elif components.netloc == "":
365 logger.warning("link[href]='%s' has no netloc set, setting domain='%s'", link["href"], domain)
366 url = f"{components.scheme}://{domain}{components.path}"
367 components = urlparse(url)
369 logger.debug("components.netloc[]='%s'", type(components.netloc))
370 if not utils.is_domain_wanted(components.netloc):
371 logger.debug("components.netloc='%s' is not wanted - SKIPPED!", components.netloc)
374 logger.debug("Fetching nodeinfo from url='%s' ...", url)
375 data = network.fetch_api_url(
377 (config.get("connection_timeout"), config.get("read_timeout"))
380 logger.debug("link[href]='%s',data[]='%s'", link["href"], type(data))
381 if "error_message" not in data and "json" in data:
382 logger.debug("Found JSON data()=%d,link[href]='%s' - Setting detection_mode=AUTO_DISCOVERY ...", len(data), link["href"])
383 instances.set_detection_mode(domain, "AUTO_DISCOVERY")
384 instances.set_nodeinfo_url(domain, link["href"])
386 logger.debug("Marking domain='%s' as successfully handled ...", domain)
387 instances.set_success(domain)
390 logger.debug("Setting last error for domain='%s',data[]='%s'", domain, type(data))
391 instances.set_last_error(domain, data)
393 logger.debug("data()=%d", len(data))
394 if "error_message" not in data and "json" in data:
395 logger.debug("Auto-discovery successful: domain='%s'", domain)
398 logger.warning("nodeinfo does not contain 'links': domain='%s'", domain)
400 logger.debug("Returning data[]='%s' - EXIT!", type(data))
403 def fetch_generator_from_path(domain: str, path: str = "/") -> str:
404 logger.debug("domain='%s',path='%s' - CALLED!", domain, path)
405 domain_helper.raise_on(domain)
407 if not isinstance(path, str):
408 raise ValueError(f"path[]='{type(path)}' is not of type 'str'")
410 raise ValueError("Parameter 'path' is empty")
412 logger.debug("domain='%s',path='%s' - CALLED!", domain, path)
415 logger.debug("Fetching path='%s' from domain='%s' ...", path, domain)
416 response = network.fetch_response(
419 (config.get("connection_timeout"), config.get("read_timeout")),
423 logger.debug("response.ok='%s',response.status_code=%d,response.text()=%d", response.ok, response.status_code, len(response.text))
424 if response.ok and response.status_code < 300 and response.text.find("<html") > 0 and domain_helper.is_in_url(domain, response.url):
425 logger.debug("Parsing response.text()=%d Bytes ...", len(response.text))
426 doc = bs4.BeautifulSoup(response.text, "html.parser")
428 logger.debug("doc[]='%s'", type(doc))
429 generator = doc.find("meta", {"name" : "generator"})
430 site_name = doc.find("meta", {"property": "og:site_name"})
431 platform = doc.find("meta", {"property": "og:platform"})
433 logger.debug("generator[]='%s',site_name[]='%s',platform[]='%s'", type(generator), type(site_name), type(platform))
434 if isinstance(generator, bs4.element.Tag) and isinstance(generator.get("content"), str):
435 logger.debug("Found generator meta tag: domain='%s'", domain)
436 software = tidyup.domain(generator.get("content"))
438 logger.debug("software[%s]='%s'", type(software), software)
439 if software is not None and software != "":
440 logger.info("domain='%s' is generated by software='%s' - Setting detection_mode=GENERATOR ...", domain, software)
441 instances.set_detection_mode(domain, "GENERATOR")
442 elif isinstance(site_name, bs4.element.Tag) and isinstance(site_name.get("content"), str):
443 logger.debug("Found property=og:site_name, domain='%s'", domain)
444 software = tidyup.domain(site_name.get("content"))
446 logger.debug("software[%s]='%s'", type(software), software)
447 if software is not None and software != "":
448 logger.debug("domain='%s' has og:site_name='%s' - Setting detection_mode=SITE_NAME ...", domain, software)
449 instances.set_detection_mode(domain, "SITE_NAME")
450 elif isinstance(platform, bs4.element.Tag) and isinstance(platform.get("content"), str):
451 logger.debug("Found property=og:platform, domain='%s'", domain)
452 software = tidyup.domain(platform.get("content"))
454 logger.debug("software[%s]='%s'", type(software), software)
455 if software is not None and software != "":
456 logger.debug("domain='%s' has og:platform='%s' - Setting detection_mode=PLATFORM ...", domain, software)
457 instances.set_detection_mode(domain, "PLATFORM")
458 elif not domain_helper.is_in_url(domain, response.url):
459 logger.warning("domain='%s' doesn't match response.url='%s', maybe redirect to other domain?", domain, response.url)
461 components = urlparse(response.url)
463 logger.debug("components[]='%s'", type(components))
464 if not instances.is_registered(components.netloc):
465 logger.info("components.netloc='%s' is not registered, adding ...", components.netloc)
466 fetch_instances(components.netloc, domain, None, "fetch_generator")
468 message = f"Redirect from domain='{domain}' to response.url='{response.url}'"
469 instances.set_last_error(domain, message)
470 instances.set_software(domain, None)
471 instances.set_detection_mode(domain, None)
472 instances.set_nodeinfo_url(domain, None)
474 raise requests.exceptions.TooManyRedirects(message)
476 logger.debug("software[]='%s'", type(software))
477 if isinstance(software, str) and software == "":
478 logger.debug("Corrected empty string to None for software of domain='%s'", domain)
480 elif isinstance(software, str) and ("." in software or " " in software):
481 logger.debug("software='%s' may contain a version number, domain='%s', removing it ...", software, domain)
482 software = version.remove(software)
484 logger.debug("software[]='%s'", type(software))
485 if isinstance(software, str) and "powered by " in software:
486 logger.debug("software='%s' has 'powered by' in it", software)
487 software = version.remove(version.strip_powered_by(software))
488 elif isinstance(software, str) and " hosted on " in software:
489 logger.debug("software='%s' has 'hosted on' in it", software)
490 software = version.remove(version.strip_hosted_on(software))
491 elif isinstance(software, str) and " by " in software:
492 logger.debug("software='%s' has ' by ' in it", software)
493 software = version.strip_until(software, " by ")
494 elif isinstance(software, str) and " see " in software:
495 logger.debug("software='%s' has ' see ' in it", software)
496 software = version.strip_until(software, " see ")
498 logger.debug("software='%s' - EXIT!", software)
501 def determine_software(domain: str, path: str = None) -> str:
502 logger.debug("domain='%s',path='%s' - CALLED!", domain, path)
503 domain_helper.raise_on(domain)
505 if not isinstance(path, str) and path is not None:
506 raise ValueError(f"Parameter path[]='{type(path)}' is not of type 'str'")
508 logger.debug("Determining software for domain='%s',path='%s'", domain, path)
511 logger.debug("Fetching nodeinfo from domain='%s' ...", domain)
512 data = fetch_nodeinfo(domain, path)
514 logger.debug("data[%s]='%s'", type(data), data)
515 if "exception" in data:
516 # Continue raising it
517 logger.debug("data()=%d contains exception='%s' - raising ...", len(data), type(data["exception"]))
518 raise data["exception"]
519 elif "error_message" in data:
520 logger.debug("Returned error_message during fetching nodeinfo: '%s',status_code=%d", data['error_message'], data['status_code'])
521 software = fetch_generator_from_path(domain)
522 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
524 logger.debug("domain='%s',path='%s',data[json] found ...", domain, path)
527 logger.debug("JSON response from domain='%s' does not include [software][name], fetching / ...", domain)
528 software = fetch_generator_from_path(domain)
529 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
531 if "status" in data and data["status"] == "error" and "message" in data:
532 logger.warning("JSON response is an error: '%s' - Resetting detection_mode,nodeinfo_url ...", data["message"])
533 instances.set_last_error(domain, data["message"])
534 instances.set_detection_mode(domain, None)
535 instances.set_nodeinfo_url(domain, None)
536 software = fetch_generator_from_path(domain)
537 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
538 elif "software" in data and "name" in data["software"]:
539 logger.debug("Found data[json][software][name] in JSON response")
540 software = data["software"]["name"]
541 logger.debug("software[%s]='%s' - FOUND!", type(software), software)
542 elif "message" in data:
543 logger.warning("JSON response contains only a message: '%s' - Resetting detection_mode,nodeinfo_url ...", data["message"])
544 instances.set_last_error(domain, data["message"])
545 instances.set_detection_mode(domain, None)
546 instances.set_nodeinfo_url(domain, None)
548 logger.debug("Invoking fetch_generator_from_path(%s) ...", domain)
549 software = fetch_generator_from_path(domain)
550 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
551 elif "software" not in data or "name" not in data["software"]:
552 logger.debug("JSON response from domain='%s' does not include [software][name] - Resetting detection_mode,nodeinfo_url ...", domain)
553 instances.set_detection_mode(domain, None)
554 instances.set_nodeinfo_url(domain, None)
556 logger.debug("Invoking fetch_generator_from_path(%s) ...", domain)
557 software = fetch_generator_from_path(domain)
558 logger.debug("Generator for domain='%s' is: '%s'", domain, software)
560 logger.debug("software[%s]='%s'", type(software), software)
562 logger.debug("Returning None - EXIT!")
565 logger.debug("software='%s'- BEFORE!", software)
566 software = software_helper.alias(software)
567 logger.debug("software['%s']='%s' - AFTER!", type(software), software)
569 if str(software) == "":
570 logger.debug("software for domain='%s' was not detected, trying generator ...", domain)
571 software = fetch_generator_from_path(domain)
572 elif len(str(software)) > 0 and ("." in software or " " in software):
573 logger.debug("software='%s' may contain a version number, domain='%s', removing it ...", software, domain)
574 software = version.remove(software)
576 logger.debug("software[]='%s'", type(software))
577 if isinstance(software, str) and "powered by" in software:
578 logger.debug("software='%s' has 'powered by' in it", software)
579 software = version.remove(version.strip_powered_by(software))
581 logger.debug("software='%s' - EXIT!", software)
584 def find_domains(tag: bs4.element.Tag) -> list:
585 logger.debug("tag[]='%s' - CALLED!", type(tag))
586 if not isinstance(tag, bs4.element.Tag):
587 raise ValueError(f"Parameter tag[]='{type(tag)}' is not type of bs4.element.Tag")
588 elif len(tag.select("tr")) == 0:
589 raise KeyError("No table rows found in table!")
592 for element in tag.select("tr"):
593 logger.debug("element[]='%s'", type(element))
594 if not element.find("td"):
595 logger.debug("Skipping element, no <td> found")
598 domain = tidyup.domain(element.find("td").text)
599 reason = tidyup.reason(element.findAll("td")[1].text)
601 logger.debug("domain='%s',reason='%s'", domain, reason)
603 if not utils.is_domain_wanted(domain):
604 logger.debug("domain='%s' is blacklisted - SKIPPED!", domain)
606 elif domain == "gab.com/.ai, develop.gab.com":
607 logger.debug("Multiple domains detected in one row")
617 "domain": "develop.gab.com",
621 elif not validators.domain(domain.split("/")[0]):
622 logger.warning("domain='%s' is not a valid domain - SKIPPED!", domain)
625 logger.debug("Adding domain='%s',reason='%s' ...", domain, reason)
631 logger.debug("domains()=%d - EXIT!", len(domains))
634 def add_peers(rows: dict) -> list:
635 logger.debug("rows[]='%s' - CALLED!", type(rows))
636 if not isinstance(rows, dict):
637 raise ValueError(f"Parameter rows[]='{type(rows)}' is not of type 'dict'")
640 for key in ["linked", "allowed", "blocked"]:
641 logger.debug("Checking key='%s'", key)
642 if key not in rows or rows[key] is None:
643 logger.debug("Cannot find key='%s' or it is NoneType - SKIPPED!", key)
646 logger.debug("Adding %d peer(s) to peers list ...", len(rows[key]))
647 for peer in rows[key]:
648 logger.debug("peer[%s]='%s' - BEFORE!", type(peer), peer)
649 if peer is None or peer == "":
650 logger.debug("peer is empty - SKIPPED")
652 elif isinstance(peer, dict) and "domain" in peer:
653 logger.debug("peer[domain]='%s'", peer["domain"])
654 peer = tidyup.domain(peer["domain"])
655 elif isinstance(peer, str):
656 logger.debug("peer='%s'", peer)
657 peer = tidyup.domain(peer)
659 raise ValueError(f"peer[]='{type(peer)}' is not supported,key='{key}'")
661 logger.debug("peer[%s]='%s' - AFTER!", type(peer), peer)
662 if not utils.is_domain_wanted(peer):
663 logger.debug("peer='%s' is not wanted - SKIPPED!", peer)
666 logger.debug("Appending peer='%s' ...", peer)
669 logger.debug("peers()=%d - EXIT!", len(peers))