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 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
@@ -0,0 +1,8 @@
1
+ """Entry point: python -m bdl ..."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
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)