Author SHA1 Message Date
klemek 0a8775dfc9 chore: 1.3.0
Python Lint CI / ty (push) Successful in 4m5s
Python Lint CI / ruff-format-check (push) Successful in 4m6s
Python Lint CI / ruff (push) Successful in 55s
2026-06-16 00:24:47 +02:00
klemek 015eebd6ed feat: blacklist 2026-06-16 00:24:40 +02:00
klemek 3df8cfffc7 chore: 1.2.0
Python Lint CI / ruff-format-check (push) Successful in 2m32s
Python Lint CI / ty (push) Successful in 3m7s
Python Lint CI / ruff (push) Successful in 1m5s
2026-06-15 15:09:33 +02:00
klemek 85a1c47c8c feat: custom word list 2026-06-15 15:09:27 +02:00
klemek f91384173b chore: 1.1.2
Python Lint CI / ruff (push) Successful in 1m39s
Python Lint CI / ruff-format-check (push) Successful in 4m16s
Python Lint CI / ty (push) Successful in 2m59s
2026-06-15 14:43:55 +02:00
klemek f253115e62 chore: lower base python
Python Lint CI / ruff (push) Has been cancelled
Python Lint CI / ruff-format-check (push) Has been cancelled
Python Lint CI / ty (push) Has been cancelled
2026-06-15 14:43:24 +02:00
klemek 75c96efc76 tools(make): release target 2026-06-15 14:40:07 +02:00
klemek 15a937e39c chore: version 1.1.1
Python Lint CI / ruff (push) Successful in 2m30s
Python Lint CI / ruff-format-check (push) Successful in 3m3s
Python Lint CI / ty (push) Successful in 3m6s
2026-06-15 14:31:51 +02:00
klemek d1deaad03f fix: space in words
Python Lint CI / ruff (push) Has been cancelled
Python Lint CI / ruff-format-check (push) Has been cancelled
Python Lint CI / ty (push) Has been cancelled
2026-06-15 14:31:21 +02:00
klemek 4df2bab348 chore: version 1.1.0
Python Lint CI / ruff (push) Failing after 1m35s
Python Lint CI / ruff-format-check (push) Successful in 3m3s
Python Lint CI / ty (push) Successful in 2m51s
2026-06-15 14:22:52 +02:00
klemek d5b9018ef2 feat: multiple words 2026-06-15 14:22:40 +02:00
klemek b387fec2b3 fix: check dig NS
Python Lint CI / ruff-format-check (push) Successful in 44s
Python Lint CI / ty (push) Successful in 44s
Python Lint CI / ruff (push) Successful in 44s
2026-06-15 14:18:58 +02:00
12 changed files with 1432 additions and 74 deletions
+1
View File
@@ -9,3 +9,4 @@ coverage.xml
build build
dist dist
.pytest_cache .pytest_cache
words.txt
+32 -24
View File
@@ -18,22 +18,22 @@ OPEN ?= xdg-open
.PHONY: help .PHONY: help
help: ## show this message help: ## show this message
@echo "Usage: $(MAKE) [target1] [target2] ..." echo "Usage: $(MAKE) [target1] [target2] ..."
@echo "" echo ""
@echo "Commands/Targets:" echo "Commands/Targets:"
@cat $(MAKEFILE_LIST) | grep -E '(^[a-zA-Z0-9_%-]+:.*?##.*$$)|(^##)' | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-20s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' cat $(MAKEFILE_LIST) | grep -E '(^[a-zA-Z0-9_%-]+:.*?##.*$$)|(^##)' | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-20s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'
@echo "" echo ""
@echo "Environment:" echo "Environment:"
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z0-9_-]+\s*\??=.*$$' | grep -Eo '^[a-zA-Z0-9_-]+' | xargs -I {} $(MAKE) -s print-{} 2> /dev/null cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z0-9_-]+\s*\??=.*$$' | grep -Eo '^[a-zA-Z0-9_-]+' | xargs -I {} $(MAKE) -s print-{} 2> /dev/null
.PHONY: print-% .PHONY: print-%
print-%: print-%:
@echo -e '\033[32m$*\033[0m = $($*)' echo -e '\033[32m$*\033[0m = $($*)'
# FILES # FILES
.venv: uv.lock .venv: uv.lock
@$(MAKE) -s uv-sync $(MAKE) -s uv-sync
# ACTIONS # ACTIONS
@@ -57,7 +57,7 @@ test: unittest ## test project
.PHONY: test-% .PHONY: test-%
test-%: ## test project with specific test test-%: ## test project with specific test
@$(MAKE) -s unittest-$* $(MAKE) -s unittest-$*
.PHONY: coverage .PHONY: coverage
coverage: coverage-unittest coverage-xml coverage-report ## test project with coverage coverage: coverage-unittest coverage-xml coverage-report ## test project with coverage
@@ -66,53 +66,61 @@ coverage: coverage-unittest coverage-xml coverage-report ## test project with co
.PHONY: uv-sync .PHONY: uv-sync
uv-sync: ## uv sync uv-sync: ## uv sync
@$(UV) sync --active $(UV) sync --active
.PHONY: uv-update .PHONY: uv-update
uv-upgrade: ## uv sync upgrade uv-upgrade: ## uv sync upgrade
@$(UV) sync --active --upgrade $(UV) sync --active --upgrade
.PHONY: ruff .PHONY: ruff
ruff: .venv ## ruff check ruff: .venv ## ruff check
@$(RUFF) check $(RUFF) check
.PHONY: ruff-fix .PHONY: ruff-fix
ruff-fix: .venv ## ruff check (and fix) ruff-fix: .venv ## ruff check (and fix)
@$(RUFF) check --fix --unsafe-fixes $(RUFF) check --fix --unsafe-fixes
.PHONY: ruff-format .PHONY: ruff-format
ruff-format: .venv ## ruff format ruff-format: .venv ## ruff format
@$(RUFF) format $(RUFF) format
.PHONY: ruff-format-check .PHONY: ruff-format-check
ruff-format-check: .venv ## ruff format (check only) ruff-format-check: .venv ## ruff format (check only)
@$(RUFF) format --check $(RUFF) format --check
.PHONY: ty .PHONY: ty
ty: .venv ## ty check ty: .venv ## ty check
@$(TY) check $(TY) check
.PHONY: unittest .PHONY: unittest
unittest: .venv ## unittest unittest: .venv ## unittest
@$(UNITTEST) -v $(UNITTEST) -v
.PHONY: unittest-% .PHONY: unittest-%
unittest-%: .venv ## unittest -k [filter] unittest-%: .venv ## unittest -k [filter]
@$(UNITTEST) -v -k $* $(UNITTEST) -v -k $*
.PHONY: coverage-unittest .PHONY: coverage-unittest
coverage-unittest: .venv ## coverage run -m unittest coverage-unittest: .venv ## coverage run -m unittest
@$(COVERAGE) run -m unittest -v $(COVERAGE) run -m unittest -v
.PHONY: coverage-report .PHONY: coverage-report
coverage-report: .venv ## coverage report coverage-report: .venv ## coverage report
@$(COVERAGE) report $(COVERAGE) report
.PHONY: coverage-html .PHONY: coverage-html
coverage-html: .venv ## coverage html coverage-html: .venv ## coverage html
@$(COVERAGE) html $(COVERAGE) html
@$(OPEN) htmlcov/index.html || true $(OPEN) htmlcov/index.html || true
.PHONY: coverage-xml .PHONY: coverage-xml
coverage-xml: .venv ## coverage xml coverage-xml: .venv ## coverage xml
@$(COVERAGE) xml $(COVERAGE) xml
.PHONY: release-%
release-%: .venv ## release major/minor/patch
$(UV) version --bump=$*
git add pyproject.toml uv.lock
git commit -m "chore: $$(uv version --short)"
git tag "release/$$(uv version --short)" -m "$$(uv version)"
$(UV) build
+7 -3
View File
@@ -23,14 +23,18 @@ uvx --from git+https://git.klemek.fr/klemek/cool-domain cool-domain
## Usage ## Usage
```txt ```txt
usage: cool-domain [-h] [--count DOMAIN_COUNT] [--lang {english,french}] [--dig | --no-dig] [--debug | --no-debug] [--quiet | --no-quiet] usage: cool-domain [-h] [--count DOMAIN_COUNT] [--words WORD_COUNT] [--lang {english,french,custom}] [--custom-words CUSTOM_WORDS]
[--cache | --no-cache] [--cache-file CACHE_FILE] [--tld-list-uri TLD_LIST_URI] [--dig | --no-dig] [--debug | --no-debug] [--quiet | --no-quiet] [--cache | --no-cache] [--cache-file CACHE_FILE]
[--tld-list-uri TLD_LIST_URI]
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
--count DOMAIN_COUNT number of domains to generate (default: 1) --count DOMAIN_COUNT number of domains to generate (default: 1)
--lang {english,french} --words WORD_COUNT number of words per domain (default: 1)
--lang {english,french,custom}
word list lang (default: english) word list lang (default: english)
--custom-words CUSTOM_WORDS
custom word list location (default: words.txt)
--dig, --no-dig check availability with dig (default: true) --dig, --no-dig check availability with dig (default: true)
--debug, --no-debug --debug, --no-debug
--quiet, --no-quiet --quiet, --no-quiet
+16 -9
View File
@@ -1,25 +1,28 @@
import logging import logging
import sys import sys
from . import PKG_NAME, PKG_VERSION from cool_domain import PKG_NAME, PKG_VERSION
from .cache import Cache from cool_domain.cache import Cache
from .constants import LANGS from cool_domain.constants import LANGS
from .factory import Factory from cool_domain.factory import Factory
from .logs import setup_logs from cool_domain.logs import setup_logs
from .params import parse_parameters from cool_domain.params import parse_parameters
from .tld_list import TLDList from cool_domain.tld_list import TLDList
from cool_domain.word_list import WordList
def main() -> int: def main() -> int:
params = parse_parameters(sys.argv[1:]) params = parse_parameters(sys.argv[1:])
setup_logs(params) setup_logs(params)
logging.getLogger().info("%s %s", PKG_NAME, PKG_VERSION) logging.getLogger().info("%s %s", PKG_NAME, PKG_VERSION)
langs = LANGS
langs["custom"] = WordList("custom", params.custom_words)
with ( with (
Cache(filepath=params.cache_file, no_load=params.cache) as cache, Cache(filepath=params.cache_file, no_load=params.cache) as cache,
TLDList(cache=cache, remote_uri=params.tld_list_uri) as tld_list, TLDList(cache=cache, remote_uri=params.tld_list_uri) as tld_list,
LANGS[params.lang] as word_list, langs[params.lang] as word_list,
): ):
factory = Factory(tld_list=tld_list, word_list=word_list) factory = Factory(tld_list=tld_list, word_list=word_list, words=params.words)
logging.getLogger().info( logging.getLogger().info(
"Searching %s domain%s", params.count, "s" if params.count > 1 else "" "Searching %s domain%s", params.count, "s" if params.count > 1 else ""
) )
@@ -29,3 +32,7 @@ def main() -> int:
else: else:
logging.getLogger().info(factory.random_domain()) logging.getLogger().info(factory.random_domain())
return 0 return 0
if __name__ == "__main__":
main()
+1292 -3
View File
File diff suppressed because it is too large Load Diff
+12 -9
View File
@@ -4,9 +4,8 @@ import shutil
import subprocess import subprocess
import typing import typing
if typing.TYPE_CHECKING: from cool_domain.tld_list import TLDList
from .tld_list import TLDList from cool_domain.word_list import WordList
from .word_list import WordList
class FactoryError(Exception): class FactoryError(Exception):
@@ -14,12 +13,13 @@ class FactoryError(Exception):
class Factory: class Factory:
__slots__ = ["__found", "__tld_list", "__word_list", "logger", "logger"] __slots__ = ["__found", "__tld_list", "__word_list", "__words", "logger"]
def __init__(self, tld_list: TLDList, word_list: WordList) -> None: def __init__(self, tld_list: TLDList, word_list: WordList, words: int) -> None:
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.__tld_list = tld_list self.__tld_list = tld_list
self.__word_list = word_list self.__word_list = word_list
self.__words = words
self.__found: set[str] = set() self.__found: set[str] = set()
def __iter__(self) -> typing.Self: def __iter__(self) -> typing.Self:
@@ -41,10 +41,10 @@ class Factory:
msg = "dig is not installed" msg = "dig is not installed"
raise FactoryError(msg) raise FactoryError(msg)
try: try:
output = subprocess.check_output([dig_bin, domain, "+short"]) # noqa: S603 output = subprocess.check_output([dig_bin, domain, "NS", "+short"]) # noqa: S603
return len(output) == 0 return len(output) == 0
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return True return False
def random_domain(self) -> str: def random_domain(self) -> str:
k = 10000 k = 10000
@@ -65,12 +65,15 @@ class Factory:
return domain return domain
def __random_domain(self) -> str | None: def __random_domain(self) -> str | None:
word = random.choice(self.__word_list.words) # noqa: S311 word = self.__random_word()
for tld in self.__tld_list_shuffled: for tld in self.__tld_list_shuffled:
tld_escaped = tld.replace(".", "") tld_escaped = tld.replace(".", "")
if word.endswith(tld_escaped) and len(word) > len(tld_escaped): if word.endswith(tld_escaped) and len(word) > len(tld_escaped):
domain = word[: -len(tld_escaped)] + "." + tld domain = word[: -len(tld_escaped)] + "." + tld
if domain not in self.__found: if "-." not in domain and domain not in self.__found:
self.__found.add(domain) self.__found.add(domain)
return domain return domain
return None return None
def __random_word(self) -> str:
return "-".join(random.sample(self.__word_list.words, self.__words))
+1 -2
View File
@@ -2,8 +2,7 @@ import enum
import logging import logging
import typing import typing
if typing.TYPE_CHECKING: from cool_domain.params import Parameters
from .params import Parameters
class TermColor(enum.StrEnum): class TermColor(enum.StrEnum):
+21 -6
View File
@@ -5,8 +5,8 @@ import pathlib
import tempfile import tempfile
import typing import typing
from . import PKG_NAME from cool_domain import PKG_NAME
from .constants import LANGS from cool_domain.constants import LANGS
@dataclasses.dataclass(frozen=True, slots=True) @dataclasses.dataclass(frozen=True, slots=True)
@@ -15,13 +15,15 @@ class Parameters:
quiet: bool = False quiet: bool = False
cache: bool = True cache: bool = True
count: int = 1 count: int = 1
words: int = 1
dig: bool = True dig: bool = True
lang: str = "english" lang: str = "english"
cache_file: str = str(pathlib.Path(tempfile.gettempdir()) / ".cool-domain-cache") cache_file: str = str(pathlib.Path(tempfile.gettempdir()) / ".cool-domain-cache")
tld_list_uri: str = "https://publicsuffix.org/list/public_suffix_list.dat" tld_list_uri: str = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt"
custom_words: str = "words.txt"
@classmethod @classmethod
def from_namespace(cls, args: argparse.Namespace) -> Parameters: def from_namespace(cls, args: argparse.Namespace) -> "Parameters":
return Parameters(**vars(args)) return Parameters(**vars(args))
@@ -89,7 +91,6 @@ def parse_parameters(args: typing.Sequence[str]) -> Parameters:
default_values = Parameters() default_values = Parameters()
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
PKG_NAME, PKG_NAME,
suggest_on_error=True,
) )
__add_arg_int( __add_arg_int(
parser, parser,
@@ -98,11 +99,25 @@ def parse_parameters(args: typing.Sequence[str]) -> Parameters:
default=default_values.count, default=default_values.count,
help_txt="number of domains to generate", help_txt="number of domains to generate",
) )
__add_arg_int(
parser,
"--words",
env_var="WORD_COUNT",
default=default_values.words,
help_txt="number of words per domain",
)
parser.add_argument( parser.add_argument(
"--lang", "--lang",
default=default_values.lang, default=default_values.lang,
help="word list lang (default: english)", help="word list lang (default: english)",
choices=LANGS.keys(), choices=[*list(LANGS.keys()), "custom"],
)
__add_arg_str(
parser,
"--custom-words",
env_var="CUSTOM_WORDS",
default=default_values.custom_words,
help_txt="custom word list location",
) )
__add_arg_bool( __add_arg_bool(
parser, parser,
+8 -11
View File
@@ -5,8 +5,8 @@ import typing
import requests import requests
import unidecode import unidecode
if typing.TYPE_CHECKING: from cool_domain.cache import Cache
from .cache import Cache from cool_domain.constants import BLACKLIST
class TLDError(Exception): class TLDError(Exception):
@@ -30,21 +30,18 @@ class TLDList:
raw_lines = response.content.decode().split("\n") raw_lines = response.content.decode().split("\n")
out_lines = [] out_lines = []
for line in raw_lines: for line in raw_lines:
stripped_line = line.strip() stripped_line = line.strip().lower()
if ( if (
len(stripped_line) len(stripped_line)
and not stripped_line.startswith("//") and not stripped_line.startswith("//")
and not stripped_line.startswith("*") and not stripped_line.startswith("*")
and not stripped_line.startswith("#")
and not stripped_line.startswith("xn--")
and "." not in stripped_line
and unidecode.unidecode(stripped_line) == stripped_line and unidecode.unidecode(stripped_line) == stripped_line
and stripped_line not in BLACKLIST
): ):
if "." in stripped_line: out_lines += [stripped_line]
for other in out_lines:
if stripped_line.endswith(other):
break
else:
out_lines += [stripped_line]
else:
out_lines += [stripped_line]
return out_lines return out_lines
def load(self) -> None: def load(self) -> None:
+6 -3
View File
@@ -8,10 +8,10 @@ import unidecode
class WordList: class WordList:
__slots__ = ["__filepath", "__name", "logger", "words"] __slots__ = ["__filepath", "__name", "logger", "words"]
def __init__(self, name: str, path: str, filename: str) -> None: def __init__(self, name: str, path: str) -> None:
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.__name = name self.__name = name
self.__filepath = pathlib.Path(__file__).parent / ".." / path / filename self.__filepath = pathlib.Path(__file__).parent / ".." / path
self.words: list[str] = [] self.words: list[str] = []
def open(self) -> None: def open(self) -> None:
@@ -22,7 +22,10 @@ class WordList:
self.logger.info("[%s] Loaded %d words", self.__name, len(self.words)) self.logger.info("[%s] Loaded %d words", self.__name, len(self.words))
def __escape_word(self, word: str) -> str: def __escape_word(self, word: str) -> str:
return unidecode.unidecode(word.strip()).lower() word = unidecode.unidecode(word.strip()).lower()
if " " in word:
return ""
return word
def close(self) -> None: def close(self) -> None:
pass pass
+2 -2
View File
@@ -1,9 +1,9 @@
[project] [project]
name = "cool-domain" name = "cool-domain"
version = "1.0.0" version = "1.3.0"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.14" requires-python = ">=3.12"
dependencies = [ dependencies = [
"requests>=2.34.2,<3.0.0", "requests>=2.34.2,<3.0.0",
"unidecode>=1.4.0,<2.0.0", "unidecode>=1.4.0,<2.0.0",
Generated
+34 -2
View File
@@ -1,6 +1,6 @@
version = 1 version = 1
revision = 3 revision = 3
requires-python = ">=3.14" requires-python = ">=3.12"
[[package]] [[package]]
name = "certifi" name = "certifi"
@@ -17,6 +17,38 @@ version = "3.4.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
{ url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
{ url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
{ url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
{ url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
{ url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
{ url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
{ url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
{ url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
{ url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
{ url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
{ url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
{ url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
{ url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
{ url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
{ url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
{ url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
{ url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
{ url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
{ url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
{ url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
{ url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
{ url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
{ url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
{ url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
{ url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
{ url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
{ url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
{ url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
{ url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
{ url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
@@ -54,7 +86,7 @@ wheels = [
[[package]] [[package]]
name = "cool-domain" name = "cool-domain"
version = "1.0.0" version = "1.3.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "requests" }, { name = "requests" },