import logging import random import shutil import subprocess import typing if typing.TYPE_CHECKING: from .tld_list import TLDList from .word_list import WordList class FactoryError(Exception): pass class Factory: __slots__ = ["__found", "__tld_list", "__word_list", "logger", "logger"] def __init__(self, tld_list: TLDList, word_list: WordList) -> None: self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.__tld_list = tld_list self.__word_list = word_list self.__found: set[str] = set() def __iter__(self) -> typing.Self: return self def __next__(self) -> str: return self.random_domain() @property def __tld_list_shuffled(self) -> typing.Iterable[str]: if self.__tld_list.tlds is None: msg = "TLD list is not loaded" raise FactoryError(msg) return random.sample(self.__tld_list.tlds, len(self.__tld_list.tlds)) def __is_domain_available(self, domain: str) -> bool: dig_bin = shutil.which("dig") if dig_bin is None: msg = "dig is not installed" raise FactoryError(msg) try: output = subprocess.check_output([dig_bin, domain, "NS", "+short"]) # noqa: S603 return len(output) == 0 except subprocess.CalledProcessError: return False def random_domain(self) -> str: k = 10000 while (k := k - 1) > 0 and (domain := self.__random_domain()) is None: pass if domain is None: raise StopIteration return domain def random_available_domain(self) -> str: k = 10000 while (k := k - 1) > 0 and not self.__is_domain_available( domain := self.random_domain() ): pass if k <= 0: raise StopIteration return domain def __random_domain(self) -> str | None: word = random.choice(self.__word_list.words) # noqa: S311 for tld in self.__tld_list_shuffled: tld_escaped = tld.replace(".", "") if word.endswith(tld_escaped) and len(word) > len(tld_escaped): domain = word[: -len(tld_escaped)] + "." + tld if domain not in self.__found: self.__found.add(domain) return domain return None