CLI tool that crawls a Blackboard Ultra course via the REST API and exports the structure as:
- HTML – collapsible, searchable tree (with file badges + live highlights)
- TXT – plain-text tree (diff-friendly)
- CSV – flat map for audits/spreadsheets
For admins, IDs, and anyone who’s muttered “where did that PDF go?” 🕵️♂️
- One call → full recursive course map
- Ultra Page + Document merge (each Ultra Doc reads as one node)
- Parses embedded files (name, MIME, render mode) from Ultra Doc body
- Extracts embedded content links (contentId + link type)
- HTML viewer:
- Instant, case-insensitive search across titles + file badges
- Per-occurrence match count and
<mark>highlights - Auto-expands only paths that match; collapses stale panels as you edit
- “Expand all / Collapse all” for quick skims
- Output selection flags:
--txt,--csv,--html(if none, HTML by default) - Stable CSV header for automation
- Python 3.9+
requests(pip install requests)- Blackboard REST API app credentials
Precedence (each later source fills in missing values):
CLI (--host --key --secret) → Env (BB_HOST, BB_KEY, BB_SECRET) → TOML (--config)
Example secrets.toml:
[blackboard]
host = "https://blackboard.example.edu"
key = "YOUR_APP_KEY"
secret = "YOUR_APP_SECRET"Environment variables (optional):
export BB_HOST="https://blackboard.example.edu"
export BB_KEY="YOUR_APP_KEY"
export BB_SECRET="YOUR_APP_SECRET"Use whichever combo you like—CLI args override env/TOML, and env overrides TOML. Fewer flags, fewer frowns. 😌
.gitignore tip (keep secrets out of git):
# secrets
secrets.toml
.env
*.env
# generated outputs
*_tree_*.html
*_tree_*.txt
*_map_*.csv
# or put outputs in a folder and ignore it:
# /artifacts/# 1) Install deps
pip install requests
# 2) Run (HTML is default if no outputs are specified)
# Option A: everything from TOML
python course_map.py \
--course-id <COURSE_ID_OR_PK1> \
--config secrets.toml
# Option B: override host (or use env)
python course_map.py \
--host https://<your-bb-host> \
--course-id <COURSE_ID_OR_PK1> \
--config secrets.tomlBatch mode (file of IDs, one per line; # comments allowed):
# From TOML (no --host flag needed):
python course_map.py \
--courses-file courses.txt \
--config secrets.tomlRequired
- One of:
--course-id <id>or--courses-file <path>
Host
--hostBase URL, e.g.https://blackboard.example.edu
Optional if provided by env (BB_HOST) or TOML ([blackboard].host).
Auth
--key <app key>/--secret <app secret>(or envBB_KEY/BB_SECRET/ TOML)--config <secrets.toml>
General
--out-dir <dir>output directory (default: current)--hide-bodieshide “UltraBody” nodes--tree-file-limit <n>max file badges shown per node (default: 10)--no-tree-truncateshow all file badges (overrides limit)
Outputs (multi-select; default = HTML if none specified)
--txtwrite text tree--csvwrite CSV map--htmlwrite HTML viewer
HTML only (default):
python course_map.py --course-id 10501107-1-2025fall --config secrets.tomlTXT + CSV (no HTML):
python course_map.py --course-id 10501107-1-2025fall --txt --csv --config secrets.tomlAll three:
python course_map.py --course-id 10501107-1-2025fall --txt --csv --html --config secrets.tomlOutput filenames
<course>_tree_YYYYMMDD-HHMMSS.html
<course>_tree_YYYYMMDD-HHMMSS.txt
<course>_map_YYYYMMDD-HHMMSS.csv
course_id, id (merged for Ultra Page + Doc), parentId, title, handler_id, type,
availability, position, depth, path, web_url,
embedded_file_count, embedded_files (name|mime|render; …),
embedded_content_links (contentId|linkType; …)
- Searches summary titles and file badges (even inside nested
<details>) - Each keystroke:
- closes only panels opened by the last search
- re-highlights matches with
<mark class="hit"> - opens ancestors so hits are visible
- counts every occurrence, not just rows
- Scrolls first hit into view (nice little UX bow 🎁)
course_map.py # CLI & API orchestration
cm_shared.py # shared helpers (parsers, typing, formatting)
export_txt.py # TXT renderer (+ returns rows for CSV)
export_csv.py # CSV writer
export_html.py # HTML renderer (search + highlight scripts embedded)
- 401/403 → verify REST app permissions and base URL/host
- No results → check
courseIdvspk1(script resolves pk1 for you) - HTML search misses files → badges live inside
<details>; the script accounts for this - Match count seems low → it counts occurrences; one Ultra Doc with three PDFs = 3 matches
- 2025-10-08 — Host can be provided via
secrets.toml([blackboard].host) orBB_HOST;--hostnow optional. CLI/env/TOML precedence clarified. Error message updated if any of host/key/secret are missing.
Built with ❤️ for Blackboard admins and course wranglers.
If this helps you find that one elusive .pptx, treat yourself to a donut. 🍩