bunkr-api 0.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.
@@ -0,0 +1,697 @@
1
+ Metadata-Version: 2.4
2
+ Name: bunkr-api
3
+ Version: 0.1.0
4
+ Summary: Interactive Python API wrapper for Bunkr
5
+ Author-email: mrflattire <mrflattirex@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: bunkr,api,scraper,downloader,media-downloader,yt-dlp,cli
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Internet :: WWW/HTTP
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: rich>=13.0.0
20
+ Requires-Dist: curl_cffi>=0.7.0
21
+ Requires-Dist: beautifulsoup4>=4.12.0
22
+ Requires-Dist: yt-dlp>=2024.3.10
23
+ Requires-Dist: urllib3>=2.0.0
24
+ Dynamic: license-file
25
+
26
+ <div align="center">
27
+
28
+ # bunkr-api
29
+
30
+ **Modular Python toolkit and API wrapper for Bunkr**
31
+ Search, catalog, download, and stream media with a high-performance interactive terminal UI.
32
+
33
+ [![CI](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/mrflattire/bunkr-api/main/ci-badge.json&cacheSeconds=300&&logo=github)](https://github.com/mrflattire/bunkr-api/actions)
34
+ [![PyPI version](https://badge.fury.io/py/bunkr-api.svg?icon=si%3Apython&maxAge=300)](https://pypi.org/project/bunkr-api)
35
+ [![Python](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
36
+ ![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/mrflattire/bunkr-api/main/coverage-badge.json&cacheSeconds=300&logo=pytest)
37
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
38
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
39
+
40
+ [Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Usage](#-detailed-usage--advanced-workflows)
41
+
42
+ </div>
43
+
44
+ ## 🚀 Version Status
45
+
46
+ | Component | Status | Notes |
47
+ | :--- | :--- | :--- |
48
+ | **Scraper** | ✅ Working | TLS Impersonation (Chrome) for deep metadata resolution. |
49
+ | **Downloader** | ✅ Working | Multi-threaded segmented retrieval via `yt-dlp`. |
50
+ | **Streamer** | ✅ Working | Real-time IPC syncing with `mpv`. |
51
+ | **Database** | ✅ Working | Persistent SQLite tracking in `~/.bunkr_api/`. |
52
+
53
+ ## ✨ Features
54
+
55
+ * **Interactive Dashboard**: A full-featured terminal hub to manage your catalog, staged files, and downloads.
56
+ * **Deep Resolution**: Automatically extracts raw asset IDs from `window.albumFiles` JavaScript arrays.
57
+ * **High-Speed Downloads**: Parallel multi-worker queue powered by the industry-standard `yt-dlp` engine.
58
+ * **Direct Streaming**: Play content instantly via `mpv` or `vlc` with temporary playlist (M3U) generation.
59
+ * **Token Maintenance**: Automated "Escape Hatch" logic to renew expiring signed CDN signatures.
60
+ * **Developer Friendly**: Clean Python API (`BunkrAPI`) for integration into third-party scripts.
61
+
62
+ ## 📦 Installation
63
+
64
+ ### CLI (Recommended)
65
+
66
+ ```sh
67
+ # Using uv (fastest)
68
+ uv tool install bunkr-api
69
+
70
+ # Using pip
71
+ pip install bunkr-api
72
+ ```
73
+
74
+ > **Windows users:** `uv tool install` places executables in `%USERPROFILE%\.local\bin`, which is **not** on `PATH` by default on Windows (unlike most Linux/macOS setups). If `bunkr-api`, `bunkr-download`, etc. aren't recognized after install, run:
75
+ > ```powershell
76
+ > uv tool update-shell
77
+ > ```
78
+ > then restart your terminal. Alternatively, for just the current session: `$env:PATH = "$env:USERPROFILE\.local\bin;$env:PATH"`.
79
+
80
+ ### From Source (Editable)
81
+
82
+ ```sh
83
+ git clone https://github.com/mrflattire/bunkr-api.git
84
+ cd bunkr-api
85
+ pip install -e .
86
+ ```
87
+
88
+ ### Media Players (Required for Streaming)
89
+
90
+ To stream content directly, ensure one of the following is installed on your system:
91
+
92
+ * **[MPV](https://mpv.io/)** (Recommended for status syncing)
93
+ * **[VLC](https://www.videolan.org/vlc/)**
94
+
95
+ <details>
96
+ <summary>Linux</summary>
97
+
98
+ ```sh
99
+ # Ubuntu/Debian
100
+ sudo apt install mpv
101
+
102
+ # Fedora/RHEL
103
+ sudo dnf install mpv
104
+
105
+ # Arch Linux
106
+ sudo pacman -S mpv
107
+ ```
108
+ </details>
109
+
110
+ <details>
111
+ <summary>macOS</summary>
112
+
113
+ ```sh
114
+ brew install mpv
115
+ ```
116
+ </details>
117
+
118
+ <details>
119
+ <summary>Windows</summary>
120
+
121
+ Download from [mpv.io/installation](https://mpv.io/installation/).
122
+ </details>
123
+
124
+ ---
125
+
126
+ ## 🛠 Prerequisites
127
+
128
+ Unlike standard video tools, **FFmpeg is not a prerequisite** for this package. The core requirements are:
129
+ 1. **Python 3.12 or higher**.
130
+ 2. **yt-dlp** (installed automatically via dependencies).
131
+ 3. **mpv** or **vlc** (if you intend to use the streaming features).
132
+
133
+ ---
134
+
135
+ ## 🚦 Quick Start
136
+
137
+ ### Command Line Interface
138
+
139
+ ```sh
140
+ # Launch the main interactive catalog menu
141
+ bunkr-api
142
+
143
+ # Search for a creator and catalog their albums
144
+ bunkr-scrape "creator_name"
145
+
146
+ # Download a specific album by its database ID
147
+ bunkr-download --db-id 5 --workers 5
148
+
149
+ # Stream a specific album directly to MPV
150
+ bunkr-stream --db-id 5
151
+ ```
152
+
153
+ ### Python API
154
+
155
+ ```python
156
+ import asyncio
157
+ from bunkr_api import BunkrAPI
158
+
159
+ async def main():
160
+ api = BunkrAPI()
161
+
162
+ # Programmatic search and resolution
163
+ results = await api.search("Natalie Roush")
164
+ if results:
165
+ album_id = await api.resolve_album(results[0]['url'])
166
+ print(f"Album registered with ID: {album_id}")
167
+
168
+ # Trigger parallel download
169
+ await api.download_album(album_id, workers=3)
170
+
171
+ asyncio.run(main())
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 📖 Detailed Usage & Advanced Workflows
177
+
178
+ <details open>
179
+ <summary><h3>Main Dashboard Interface (`bunkr-api`)</h3></summary>
180
+
181
+ The primary entry point of the toolkit. It serves as an interactive hub for managing your entire media catalog. It is designed to intelligently handle inputs, whether you want to browse your collection, jump to a specific album, or import json metadata files.
182
+
183
+ ```sh
184
+ bunkr-api [OPTIONS] [PATH/ID]
185
+ ```
186
+
187
+ **Common Examples:**
188
+
189
+ ```sh
190
+ # Launch the main interactive catalog menu
191
+ bunkr-api
192
+
193
+ # Jump directly to a specific album detail view by its Database ID
194
+ bunkr-api --db-id 17
195
+ # OR simply (legacy support)
196
+ bunkr-api 17
197
+
198
+ # Import a local JSON metadata file and view it immediately
199
+ bunkr-api -i ./album_metadata.json
200
+ # OR simply drop the file onto the terminal
201
+ bunkr-api "./path/to/metadata.json"
202
+
203
+ # Perform a quick search and resolution directly from the launch command
204
+ bunkr-api "Natalie Roush" --mode broad --per 40 --sort latest
205
+ ```
206
+
207
+ **Main Catalog Commands:**
208
+
209
+ Inside the main menu, use these single-character commands:
210
+
211
+ | Key | Action |
212
+ | :--- | :--- |
213
+ | `s` | **Search / Scrape**: Enter a creator name to find and catalog new albums. |
214
+ | `t` | **Trending**: Browse the most popular videos, images, or files from the last 24h/7d. |
215
+ | `d` | **Delete**: Remove albums from your database (supports single IDs, lists, or ranges). |
216
+ | `q` | **Quit**: Safely exit the application. |
217
+
218
+ **The [交互 Engine] Detail View:**
219
+
220
+ Once inside an album, the "Action Context" menu allows for granular media control:
221
+
222
+ 1. **Stream target(s)**: Launch your media player (MPV/VLC). Supports range selection.
223
+ 2. **Download target(s)**: Queue specific files for multi-threaded download.
224
+ 3. **Download ALL**: Immediately begin retrieving every file in the album.
225
+ 4. **Copy link**: Output the specific signed CDN URL for the chosen track to the terminal.
226
+ 5. **Mint new tokens**: Triggers a manual batch refresh of CDN signatures with a real-time progress bar.
227
+ 6. **Stage/Unstage**: Flag specific items (or the whole album) for later batch retrieval.
228
+ 7. **Navigation (`n`/`p`)**: Flip through pages of large asset inventories.
229
+
230
+ > **Intelligent Routing**: `bunkr-api` prioritizes its arguments. If it detects a path ending in `.json`, it imports it. If it detects a number, it jumps to that ID. Otherwise, it treats the input as a search term to launch the scraper.
231
+
232
+ ### Why this command is the best choice for users:
233
+ 1. **Centralized Control**: You never need to leave this interface to perform 99% of your tasks.
234
+ 2. **State Management**: It automatically saves your progress; if you "Stage" items, they stay staged until you decide to download them.
235
+ 3. **Visual Feedback**: Uses the full power of the `Rich` library to provide a clean, color-coded view of your library's health and token status.
236
+
237
+ </details>
238
+
239
+ ---
240
+
241
+ <details>
242
+ <summary><h3>Standalone Downloader (`bunkr-download`)</h3></summary>
243
+
244
+ A high-performance retrieval engine that utilizes a multi-threaded worker pool and the `yt-dlp` backend to maximize your bandwidth. It handles token resolution and directory organization automatically.
245
+
246
+ ```sh
247
+ bunkr-download [OPTIONS]
248
+ ```
249
+
250
+ **Common Examples:**
251
+
252
+ ```sh
253
+ # Download an entire album using 5 concurrent workers
254
+ bunkr-download --db-id 17 --workers 5
255
+
256
+ # Download a specific range of files (e.g., first five files)
257
+ bunkr-download --db-id 17 --number 1-5
258
+
259
+ # The Triage Workflow: Retry all files currently marked as 'FAILED' in the DB
260
+ bunkr-download --triage --workers 3
261
+
262
+ # The Staged Workflow: Download everything globally marked as 'STAGED'
263
+ bunkr-download --staged
264
+
265
+ # Point a download to a specific external hard drive path
266
+ bunkr-download --db-id 17 -o "D:/Media/Archive"
267
+ ```
268
+
269
+ **Available Options:**
270
+
271
+ | Option | Description |
272
+ | :--- | :--- |
273
+ | `--db-id` | The unique ID of the album in your SQLite database. |
274
+ | `-w, --workers` | Number of concurrent download threads (Default: 1, Max: 5). |
275
+ | `-n, --number` | Restrict download to specific indices (supports `1,2`, `1-5`, or `all`). |
276
+ | `-o, --output` | Override the default download directory for this specific run. |
277
+ | `--staged` | Process all items globally flagged as staged across the entire database. |
278
+ | `--triage` | Automatically aggregate and retry all items with a `FAILED` download status. |
279
+ | `-i, --input` | Path to a local legacy JSON file to download without using the database. |
280
+
281
+ > **Interactive Mode**: If you run `bunkr-download` without any arguments, it launches a catalog browser identical to the main dashboard, allowing you to pick an album and specify workers visually.
282
+
283
+ ### Why this standalone command is useful:
284
+ 1. **Error Recovery**: The `--triage` flag is the fastest way to clean up a catalog that had network interruptions during a large batch.
285
+ 2. **Automation**: It is perfect for headless environments or cron jobs (e.g., `bunkr-download --staged` running every night).
286
+ 3. **Resource Management**: You can precisely control system load by adjusting the `--workers` flag based on your current internet speed and CPU availability.
287
+
288
+ </details>
289
+
290
+ ---
291
+
292
+ <details>
293
+ <summary><h3>Standalone Streamer (`bunkr-stream`)</h3></summary>
294
+
295
+ Stream content directly to your media player without downloading. This command automatically triggers the "Escape Hatch" to refresh any expired tokens before playback starts.
296
+
297
+ ```sh
298
+ bunkr-stream --db-id ID [OPTIONS]
299
+ ```
300
+
301
+ **Common Examples:**
302
+
303
+ ```sh
304
+ # Stream an entire album by its Database ID
305
+ bunkr-stream --db-id 17
306
+
307
+ # Stream a specific selection (e.g., tracks 1, 5, and 10 through 15)
308
+ bunkr-stream --db-id 17 --number 1,5,10-15
309
+
310
+ # Force playback in VLC instead of the default MPV
311
+ bunkr-stream --db-id 17 --player vlc
312
+
313
+ # Stream everything currently marked as 'STAGED' in your database
314
+ bunkr-stream --staged
315
+ ```
316
+
317
+ **Available Options:**
318
+
319
+ | Option | Description |
320
+ | :--- | :--- |
321
+ | `--db-id` | The unique ID of the album in your SQLite database. |
322
+ | `-n, --number` | Restrict playback to specific indices (supports `1,2`, `1-5`, or `all`). |
323
+ | `--player` | Choose your backend engine: `mpv` (default) or `vlc`. |
324
+ | `--staged` | Bypass album IDs and stream all items globally flagged as staged. |
325
+
326
+ > **Note on MPV**: When using MPV, `bunkr-api` opens a JSON IPC server. This allows the terminal to show you exactly which track is playing, the current timestamp, and the buffer/cache duration in real-time.
327
+
328
+ ### Why this standalone command is useful:
329
+ 1. **Low Latency**: It bypasses the Main Menu logic, allowing you to start video playback in one command.
330
+ 2. **Headless Maintenance**: If you know an album ID, you can start a stream without ever seeing the catalog list.
331
+ 3. **Staging Workflow**: You can spend time "Staging" items via `bunkr-api` or `bunkr-inspect`, and then later just run `bunkr-stream --staged` to play your curated queue.
332
+
333
+ </details>
334
+
335
+ ---
336
+
337
+ <details>
338
+ <summary><h3>Standalone Scraper (`bunkr-scrape`)</h3></summary>
339
+
340
+ A deep-resolution engine designed to discover albums, extract asset metadata, and synchronize findings with your local database. Bypasses detection and provides real-time feedback during the resolution process.
341
+
342
+ ```sh
343
+ bunkr-scrape [QUERY] [OPTIONS]
344
+ ```
345
+
346
+ **Common Examples:**
347
+
348
+ ```sh
349
+ # Search for a creator and enter the interactive selection menu
350
+ bunkr-scrape "InpossibleOreo"
351
+
352
+ # Perform a strict search with high result volume and custom sorting
353
+ bunkr-scrape "Natalie" --mode strict --per 60 --sort mostfiles
354
+
355
+ # Browse trending videos from the last 24 hours
356
+ bunkr-scrape --top videos
357
+
358
+ # Archive metadata to a specific folder without using the default export path
359
+ bunkr-scrape "CreatorName" --save-json --output "C:/Backups/Bunkr_Metadata"
360
+ ```
361
+
362
+ **Available Options:**
363
+
364
+ | Option | Description |
365
+ | :--- | :--- |
366
+ | `search` | The positional query string (e.g., creator name or album title). |
367
+ | `-m, --mode` | Filtering execution mode: `broad`, `strict`, `fuzzy`, `substring`, `whole`. |
368
+ | `-p, --per` | Total results requested per page: `20`, `40`, `60`, `100`. |
369
+ | `-s, --sort` | Result array sorting metric: `latest`, `oldest`, `mostfiles`. |
370
+ | `-t, --top` | Crawl trending layout categories: `albums`, `videos`, `files`, `images`. |
371
+ | `--save-json` | Export the resolved album metadata to a `.json` file. |
372
+ | `-o, --output` | Override the default directory for JSON exports. |
373
+
374
+ > **Deep Resolution Intelligence**: Unlike basic scrapers, `bunkr-scrape` performs a deep resolution. It navigates into your selected album, parses the internal JavaScript state, and extracts the **True IDs** required for streaming and downloading.
375
+
376
+ ### Why this standalone command is useful:
377
+ 1. **Direct Cataloging**: It is the primary way to "feed" your database. Any album resolved here becomes immediately available in `bunkr-api`, `bunkr-download`, and `bunkr-stream`.
378
+ 2. **Metadata Archival**: By using `--save-json`, you can create a portable snapshot of an album's contents (including original filenames and sizes) before a link potentially goes dead.
379
+ 3. **Bypass Navigation**: If you know exactly what you are looking for, flags like `--mode strict` and `--sort mostfiles` get you to the correct result much faster than manual browsing.
380
+
381
+ </details>
382
+
383
+ ---
384
+
385
+ <details>
386
+ <summary><h3>System Management (`bunkr-inspect`)</h3></summary>
387
+
388
+ The "Swiss Army Knife" for your local database. `bunkr-inspect` allows you to generate reports, manage staging queues, and perform database maintenance or schema migrations without writing raw SQL.
389
+
390
+ ```sh
391
+ bunkr-inspect [COMMAND] [TARGET] [OPTIONS]
392
+ ```
393
+
394
+ **Common Examples:**
395
+
396
+ ```sh
397
+ # View a global summary of your database health and pipeline metrics
398
+ bunkr-inspect view dashboard
399
+
400
+ # Get a detailed per-album breakdown of completion % and total sizes
401
+ bunkr-inspect view albums
402
+
403
+ # Dump every internal field for a specific album (useful for debugging IDs)
404
+ bunkr-inspect view album --id 11
405
+
406
+ # Stage a range of assets globally for the next download run
407
+ bunkr-inspect stage asset 50-100
408
+
409
+ # Repair metadata drift: Recompute file counts and aggregate sizes from actual records
410
+ bunkr-inspect db --recount
411
+
412
+ # Permanently delete specific albums and their associated file records
413
+ bunkr-inspect db --wipe 12,15-20
414
+ ```
415
+
416
+ **Available Sub-Commands:**
417
+
418
+ #### 1. `view` (Reporting)
419
+ | Target | Description |
420
+ | :--- | :--- |
421
+ | `dashboard` | The default view. Shows row counts and success/fail metrics for all tables. |
422
+ | `albums` | Summarizes every album: completeness, failed counts, and formatted size. |
423
+ | `expiring` | Identifies assets with missing or near-expired security tokens. |
424
+ | `staged` | Groups all currently staged items by their parent album. |
425
+ | `album` | Requires `--id`. A deep, untruncated dump of an album and its files. |
426
+ | `[table_name]` | Performs a raw dump of any table (e.g., `assets` or `system_config`). |
427
+
428
+ #### 2. `stage` (Queue Management)
429
+ Used to flag items for batch actions in `bunkr-download` or `bunkr-stream`.
430
+ * **Targets**: `album` (cascades to all files) or `asset` (individual files).
431
+ * **Selection**: Supports IDs, comma-separated lists, ranges, or `all`.
432
+ * **Options**: Use `--off` to remove the staging flag.
433
+
434
+ #### 3. `db` (Maintenance & Write Ops)
435
+ | Option | Description |
436
+ | :--- | :--- |
437
+ | `--wipe [IDS]` | Deletes albums. Use `--wipe all` to clear everything except settings. |
438
+ | `--recount` | Fixes UI drift by recalculating `file_count` from the actual assets table. |
439
+ | `--exec "SQL"` | Runs a raw write-statement (UPDATE/DELETE/VACUUM) with confirmation. |
440
+ | `--nuke` | Factory reset. Drops all tables and resets config to defaults. |
441
+ | `-y, --yes` | Skips confirmation prompts for destructive operations. |
442
+
443
+ > **Raw Table Dumps**: You can filter any raw table view using `--search "text"` or change the output volume with `--limit 50` or `--all`.
444
+
445
+ ### Why this standalone command is useful:
446
+ 1. **Data Integrity**: If the scraper crashes mid-way, `--recount` ensures your dashboard doesn't show "33 files" when only 10 were successfully cataloged.
447
+ 2. **Audit Logs**: `view expiring` helps you predict which downloads might fail before you even start them.
448
+ 3. **Schema Evolution**: As the project grows, `add-column` allows you to update your local database structure without losing your existing history.
449
+
450
+ </details>
451
+
452
+ ---
453
+
454
+ <details>
455
+ <summary><h3>Token Maintenance (`bunkr-mint`)</h3></summary>
456
+
457
+ A dedicated maintenance utility designed to keep your media library "hot." Because Bunkr CDN links use 2-hour time-limited expiration (`ex`) timestamps, they eventually stop working. `bunkr-mint` automates the process of fetching fresh signatures using the stored **True IDs** in your database.
458
+
459
+ ```sh
460
+ bunkr-mint [OPTIONS]
461
+ ```
462
+
463
+ **Common Examples:**
464
+
465
+ ```sh
466
+ # Launch the background daemon
467
+ # This will poll your entire database every 60s and refresh tokens near expiry
468
+ bunkr-mint
469
+
470
+ # Perform a one-shot targeted refresh for a specific album and then exit
471
+ bunkr-mint --album-id 17
472
+ ```
473
+
474
+ **Available Options:**
475
+
476
+ | Option | Description |
477
+ | :--- | :--- |
478
+ | `-a, --album-id` | Target a specific album for a single maintenance pass. |
479
+ | `-h, --help` | Show the help message and exit. |
480
+
481
+ **The "Escape Hatch" Workflow:**
482
+
483
+ While the main `bunkr-api` dashboard triggers an automated "Escape Hatch" refresh when you start a stream or download, running `bunkr-mint` in a separate terminal window is the recommended workflow for heavy users.
484
+
485
+ * **Concurrency**: Uses a semaphore-controlled worker pool to refresh dozens of tokens simultaneously without hitting rate limits.
486
+ * **Progress Tracking**: Features a real-time `Rich` progress bar and spinner so you can see exactly which assets are being updated.
487
+ * **Database Sync**: Automatically updates the `signed_cdn_url` and `token_expiry_timestamp` columns in your SQLite database.
488
+
489
+ > **Pro Tip**: If you are planning a massive download session (100+ files), run `bunkr-mint` in the background first to ensure every link is valid before the download engine starts.
490
+
491
+ ### Why this standalone command is useful:
492
+ 1. **Continuous Uptime**: Keeps your catalog ready for instant streaming at any time, even weeks after the initial scrape.
493
+ 2. **Headless Operations**: Can be run on a server or a separate monitor to act as a "watchdog" for your media library.
494
+ 3. **Low Overhead**: The daemon stays idle most of the time, only consuming network resources when tokens are actually nearing their expiration window (default 10 minutes).
495
+
496
+ </details>
497
+
498
+ ---
499
+
500
+ <details>
501
+ <summary><h3>Python API (`BunkrAPI`)</h3></summary>
502
+
503
+ Everything the CLI tools do is also available as a plain Python library, so you can script your own workflows, like cron jobs, batch imports, custom dashboards, all without touching the terminal UI at all. `BunkrAPI` is a single facade class wrapping the scraper, downloader, player, and database.
504
+
505
+ ```python
506
+ from bunkr_api import BunkrAPI
507
+
508
+ api = BunkrAPI() # uses the same ~/.bunkr_api/media_tracker.db as the CLI tools
509
+ ```
510
+
511
+ > **Async by design**: nearly every method that touches the network, disk, or a subprocess is `async def` including `download_album`, which internally spawns a real worker pool via `loop.run_in_executor()` rather than blocking. This means you can safely `await` these from inside your own event loop (e.g. a web server request handler) without deadlocking. The only fully synchronous methods are the pure catalog lookups (`get_albums`, `get_album`, `get_assets`, `get_valid_url`) and `delete_album`. If you're writing a simple standalone script (not already inside an event loop), wrap your entry point in `asyncio.run(...)` as shown in every example below.
512
+
513
+ **Full Method Reference:**
514
+
515
+ #### Catalog & Retrieval *(sync)*
516
+ | Method | Description |
517
+ | :--- | :--- |
518
+ | `get_albums()` | Returns every cataloged album as a list of dicts. |
519
+ | `get_album(album_id)` | Returns a single album's metadata, or `None` if it doesn't exist. |
520
+ | `get_assets(album_id)` | Returns every asset belonging to an album. |
521
+ | `get_valid_url(asset_id)` | Returns a fresh, valid signed CDN URL for one asset, re-minting automatically if the cached token is missing or expiring soon. |
522
+
523
+ #### Scraping & Resolution *(async)*
524
+ | Method | Description |
525
+ | :--- | :--- |
526
+ | `search(term, mode="broad", per=20, sort="latest")` | Programmatic search. Returns a list of album dicts (`title`, `url`, `file_count`). |
527
+ | `resolve_album(url, search_context="API_User", save_json=False)` | Scrapes and registers a bunkr album URL. Returns the new database ID. |
528
+ | `resolve_and_download(url, workers=3, output_dir=..., save_json=False)` | One-shot convenience: `resolve_album()` immediately followed by `download_album()`. Returns the database ID. |
529
+
530
+ #### Media Execution *(async)*
531
+ | Method | Description |
532
+ | :--- | :--- |
533
+ | `download_album(album_id, workers=3, output_dir=...)` | Multi-threaded download for a whole album. Raises `ValueError` if the album doesn't exist. |
534
+ | `download_staged(workers=3, output_dir=...)` | Downloads everything currently flagged as staged (see `bunkr-inspect stage` or the dashboard's `6` action). A no-op if nothing is staged. |
535
+ | `retry_failed(workers=3, output_dir=...)` | Retries every asset currently marked `FAILED`. A no-op if nothing has failed. |
536
+ | `stream_album(album_id, indices_spec="all", player="mpv")` | Resolves tokens and launches `mpv`/`vlc` for the given selection. Raises `ValueError` if the album has no assets. |
537
+
538
+ #### Maintenance
539
+ | Method | Description |
540
+ | :--- | :--- |
541
+ | `refresh_tokens(album_id=None)` *(async)* | Refreshes any expiring CDN tokens; scoped to one album, or the whole database if omitted. Always a single pass; never blocks forever. |
542
+ | `delete_album(album_id)` *(sync)* | Deletes an album and all its assets (cascades at the database level). Returns `True` if something was actually deleted. |
543
+
544
+ ---
545
+
546
+ **Example ; the classic three-step flow:**
547
+
548
+ ```python
549
+ import asyncio
550
+ from bunkr_api import BunkrAPI
551
+
552
+ async def main():
553
+ api = BunkrAPI()
554
+
555
+ results = await api.search("Natalie Roush")
556
+ if results:
557
+ album_id = await api.resolve_album(results[0]["url"])
558
+ print(f"Album registered with ID: {album_id}")
559
+ await api.download_album(album_id, workers=3)
560
+
561
+ asyncio.run(main())
562
+ ```
563
+
564
+ **Example ; the same flow, collapsed into one call:**
565
+
566
+ ```python
567
+ import asyncio
568
+ from bunkr_api import BunkrAPI
569
+
570
+ async def main():
571
+ api = BunkrAPI()
572
+ results = await api.search("Natalie Roush")
573
+ if results:
574
+ album_id = await api.resolve_and_download(results[0]["url"], workers=5)
575
+ print(f"Resolved and downloaded album {album_id}")
576
+
577
+ asyncio.run(main())
578
+ ```
579
+
580
+ **Example ; browsing your existing catalog without touching the network:**
581
+
582
+ ```python
583
+ from bunkr_api import BunkrAPI
584
+
585
+ api = BunkrAPI()
586
+
587
+ for album in api.get_albums():
588
+ print(f"#{album['id']} — {album['title']} ({album['file_count']} files)")
589
+
590
+ # Drill into one album
591
+ album = api.get_album(5)
592
+ if album:
593
+ for asset in api.get_assets(5):
594
+ print(f" {asset['title']} — {asset['download_status']}")
595
+ # Get a guaranteed-fresh link, even if the cached token expired
596
+ print(f" -> {api.get_valid_url(asset['id'])}")
597
+ ```
598
+
599
+ **Example ; streaming programmatically:**
600
+
601
+ ```python
602
+ import asyncio
603
+ from bunkr_api import BunkrAPI
604
+
605
+ async def main():
606
+ api = BunkrAPI()
607
+ # Stream tracks 1, 3, and 5-8 from album 17 in VLC instead of the MPV default
608
+ await api.stream_album(17, indices_spec="1,3,5-8", player="vlc")
609
+
610
+ asyncio.run(main())
611
+ ```
612
+
613
+ **Example ; maintenance batch jobs (great for a nightly cron script):**
614
+
615
+ ```python
616
+ import asyncio
617
+ from bunkr_api import BunkrAPI
618
+
619
+ async def nightly_maintenance():
620
+ api = BunkrAPI()
621
+
622
+ # Keep every token in the database fresh
623
+ await api.refresh_tokens()
624
+
625
+ # Pick up anything you staged earlier via `bunkr-inspect stage` or the dashboard
626
+ await api.download_staged(workers=4)
627
+
628
+ # Give yesterday's network blips another chance
629
+ await api.retry_failed(workers=4)
630
+
631
+ asyncio.run(nightly_maintenance())
632
+ ```
633
+
634
+ **Example ; resolving several albums concurrently with `asyncio.gather`:**
635
+
636
+ ```python
637
+ import asyncio
638
+ from bunkr_api import BunkrAPI
639
+
640
+ async def main():
641
+ api = BunkrAPI()
642
+ urls = [
643
+ "https://bunkr.cr/a/first-album",
644
+ "https://bunkr.cr/a/second-album",
645
+ "https://bunkr.cr/a/third-album",
646
+ ]
647
+
648
+ # resolve_album() itself is I/O-bound (network), so gathering several
649
+ # at once is a genuine speedup, not just cosmetic concurrency.
650
+ album_ids = await asyncio.gather(*(api.resolve_album(u) for u in urls))
651
+ print(f"Registered {len(album_ids)} albums: {album_ids}")
652
+
653
+ # Then download them all, one at a time (download_album already
654
+ # parallelizes its own worker pool internally via `workers=`)
655
+ for album_id in album_ids:
656
+ await api.download_album(album_id, workers=5)
657
+
658
+ asyncio.run(main())
659
+ ```
660
+
661
+ **Example ; cleaning up an album you no longer want:**
662
+
663
+ ```python
664
+ from bunkr_api import BunkrAPI
665
+
666
+ api = BunkrAPI()
667
+ was_deleted = api.delete_album(5)
668
+ print(f"Album 5 removed: {was_deleted}") # False if it never existed
669
+ ```
670
+
671
+ ### Why the Python API is useful:
672
+ 1. **No CLI required for automation**: cron jobs, scheduled maintenance, or a custom Discord bot can all drive the same database the interactive tools use, with zero terminal interaction.
673
+ 2. **Composable**: every method returns plain dicts/lists/ints/bools. No custom objects to learn, so it's trivial to slot into an existing script, web framework, or notebook.
674
+ 3. **Safe to embed**: because the network/disk/subprocess methods are genuinely async (not just decorated), you can call them from inside your own already-running event loop; a FastAPI route handler, a Discord.py command, a Jupyter cell, all without the classic "asyncio.run() cannot be called from a running event loop" crash.
675
+
676
+ </details>
677
+
678
+ ---
679
+
680
+ ## 📂 Configuration & Data
681
+
682
+ The application stores all data in a hidden directory in your user profile to ensure persistence:
683
+
684
+ * **SQLite Database**: `~/.bunkr_api/media_tracker.db`
685
+ * **Logs**: `~/.bunkr_api/logs/`
686
+ * **Default Downloads**: `~/Downloads/bunkr_downloads/`
687
+ * **Default JSON Exports**: `~/Downloads/bunkr_downloads/jsons/` Used by both `bunkr-api --save-json` and `bunkr-scrape --save-json` when `-o/--output` isn't given.
688
+
689
+ > **Pro Tip**: Every command that writes files (`bunkr-scrape --save-json`, `bunkr-download`, etc.) accepts an `-o/--output` flag to override its destination for that run.
690
+
691
+ ---
692
+
693
+ ## ⚖️ Disclaimer
694
+
695
+ **bunkr-api** is an independent project and is not affiliated with, authorized, maintained, or endorsed by Bunkr. This tool is provided for educational and personal archival purposes only. Users are responsible for complying with the Terms of Service of the platforms they interact with and the copyrights of the content creators.
696
+
697
+ <div align="center">Made with ❤️ for the terminal natives.</div>