booru-dl 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bdl/__init__.py +27 -0
- bdl/__main__.py +8 -0
- bdl/blacklist.py +74 -0
- bdl/cli.py +849 -0
- bdl/config.py +187 -0
- bdl/db.py +355 -0
- bdl/downloader.py +913 -0
- bdl/filters.py +184 -0
- bdl/i18n/__init__.py +126 -0
- bdl/i18n/en.json +60 -0
- bdl/i18n/ru.json +60 -0
- bdl/media.py +77 -0
- bdl/meta.py +83 -0
- bdl/models.py +27 -0
- bdl/net/__init__.py +0 -0
- bdl/net/doh.py +103 -0
- bdl/notifier.py +31 -0
- bdl/perceptual.py +50 -0
- bdl/preview.py +232 -0
- bdl/providers/__init__.py +246 -0
- bdl/providers/base.py +232 -0
- bdl/providers/client.py +244 -0
- bdl/tags/__init__.py +0 -0
- bdl/tags/catalog.py +205 -0
- bdl/tags/commands.py +83 -0
- bdl/tags/families.py +126 -0
- bdl/tags/library.py +879 -0
- bdl/tags/store.py +309 -0
- bdl/tags/suggest.py +202 -0
- bdl/tui/__init__.py +12 -0
- bdl/tui/style.py +70 -0
- bdl/tui/wizard.py +453 -0
- booru_dl-2.1.0.dist-info/METADATA +216 -0
- booru_dl-2.1.0.dist-info/RECORD +38 -0
- booru_dl-2.1.0.dist-info/WHEEL +5 -0
- booru_dl-2.1.0.dist-info/entry_points.txt +2 -0
- booru_dl-2.1.0.dist-info/licenses/LICENSE +21 -0
- booru_dl-2.1.0.dist-info/top_level.txt +1 -0
bdl/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Standalone CLI for batch downloading and syncing content from Booru boards.
|
|
2
|
+
|
|
3
|
+
Self-contained: own providers, DoH resolver, models, dedup database and tag
|
|
4
|
+
dictionaries. Runs as ``python -m bdl`` or via the ``bdl`` console script.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "2.1.0"
|
|
8
|
+
|
|
9
|
+
#: Repository the project is published from.
|
|
10
|
+
REPO_SLUG = "github.com/wexqk-boo/Booru-Downloader-py"
|
|
11
|
+
|
|
12
|
+
#: User-Agent for normal outgoing API and media requests.
|
|
13
|
+
USER_AGENT = f"bdl/{__version__} ({REPO_SLUG})"
|
|
14
|
+
|
|
15
|
+
#: Xbooru has an open DAPI with no keys, but its Cloudflare edge challenges a
|
|
16
|
+
#: client that announces itself as a script, so both the API call and the media
|
|
17
|
+
#: fetch present a full set of browser headers instead.
|
|
18
|
+
XBOORU_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0"
|
|
19
|
+
|
|
20
|
+
XBOORU_HEADERS: dict[str, str] = {
|
|
21
|
+
"User-Agent": XBOORU_USER_AGENT,
|
|
22
|
+
"Referer": "https://xbooru.com/index.php?page=post&s=list",
|
|
23
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
24
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
__all__ = ["REPO_SLUG", "USER_AGENT", "XBOORU_HEADERS", "XBOORU_USER_AGENT", "__version__"]
|
bdl/__main__.py
ADDED
bdl/blacklist.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tag blacklist: persistent file and management helpers.
|
|
2
|
+
|
|
3
|
+
Default file: ``~/.config/bdl/blacklist.txt`` (one tag per line, ``#`` starts a
|
|
4
|
+
comment). Posts matching any of these tags are skipped during download, and a
|
|
5
|
+
base tag such as ``ai`` also pulls in its whole family.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .config import xdg_config_home
|
|
13
|
+
from .tags.families import expand_blacklist_tags
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def default_blacklist_path() -> Path:
|
|
17
|
+
return xdg_config_home() / "bdl" / "blacklist.txt"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_blacklist(path: Path | str | None = None) -> set[str]:
|
|
21
|
+
"""Read the tag list in lowercase; a missing file yields an empty set."""
|
|
22
|
+
file_path = Path(path).expanduser() if path else default_blacklist_path()
|
|
23
|
+
if not file_path.is_file():
|
|
24
|
+
return set()
|
|
25
|
+
tags: set[str] = set()
|
|
26
|
+
for line in file_path.read_text(encoding="utf-8").splitlines():
|
|
27
|
+
tag = line.strip().lower()
|
|
28
|
+
if tag and not tag.startswith("#"):
|
|
29
|
+
tags.add(tag)
|
|
30
|
+
return tags
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def save_blacklist(tags: set[str], path: Path | str | None = None) -> Path:
|
|
34
|
+
"""Write the sorted tag list and return the file path."""
|
|
35
|
+
file_path = Path(path).expanduser() if path else default_blacklist_path()
|
|
36
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
lines = [f"{tag}\n" for tag in sorted(tags)]
|
|
38
|
+
file_path.write_text("".join(lines), encoding="utf-8")
|
|
39
|
+
return file_path
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def expand_tag(tag: str) -> set[str]:
|
|
43
|
+
"""Tag family: the tag itself plus related sub-tags (ai/furry/guro/...)."""
|
|
44
|
+
clean = tag.strip().lower()
|
|
45
|
+
if not clean:
|
|
46
|
+
return set()
|
|
47
|
+
expanded = set(expand_blacklist_tags(clean))
|
|
48
|
+
expanded.add(clean)
|
|
49
|
+
return expanded
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def add_tag(tag: str, path: Path | str | None = None) -> int:
|
|
53
|
+
"""Add a tag and its family; return how many entries were added."""
|
|
54
|
+
expanded = expand_tag(tag)
|
|
55
|
+
if not expanded:
|
|
56
|
+
return 0
|
|
57
|
+
tags = load_blacklist(path)
|
|
58
|
+
before = len(tags)
|
|
59
|
+
tags |= expanded
|
|
60
|
+
if len(tags) != before:
|
|
61
|
+
save_blacklist(tags, path)
|
|
62
|
+
return len(tags) - before
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def remove_tag(tag: str, path: Path | str | None = None) -> int:
|
|
66
|
+
"""Remove a tag and its family if present; return how many entries were removed."""
|
|
67
|
+
clean = tag.strip().lower()
|
|
68
|
+
tags = load_blacklist(path)
|
|
69
|
+
to_remove = expand_tag(clean) & tags
|
|
70
|
+
if not to_remove:
|
|
71
|
+
return 0
|
|
72
|
+
tags -= to_remove
|
|
73
|
+
save_blacklist(tags, path)
|
|
74
|
+
return len(to_remove)
|