A Python toolkit for Master's students to wire Canvas LMS + Zotero → Obsidian — automatically.
Built to survive the daily grind of graduate coursework: fetch lecture pages and readings from Canvas, generate Obsidian notes with the right frontmatter, and sync your Zotero library into a browsable literature index. Nothing to type twice.
Originally built for the Advanced Master's in Privacy, Cybersecurity & Data Management at Maastricht University (ECPC), but generic enough for any Canvas institution and any Zotero library.
Four standalone Python scripts you can run individually:
| Script | What it does |
|---|---|
canvas_explore.py |
Lists your active Canvas courses and their upcoming assignments |
canvas_modules.py |
Walks a course's module tree — pages, files, assignments, quizzes |
canvas_fetch_course.py |
Generates lecture / literature / tutorial stubs in your vault, with readings extracted from Canvas pages |
sync_zotero.py |
Creates one markdown file per Zotero item in 02-literature/, with hierarchical tags derived from collections |
Plus Obsidian-ready templates and a Dataview-powered literature index.
- Python 3.10+
- Canvas LMS account at your institution + an API token
- Zotero 7 or 8, running locally, with the local HTTP API enabled
- Better BibTeX (recommended, see
docs/setup-zotero.md) - Obsidian with the Dataview plugin (for the literature index)
git clone https://github.com/grcengineeringforeal/masterblasterbrain.git
cd masterblasterbrain
pip install -r requirements.txt
cp .env.example .env
# open .env and fill in CANVAS_API_TOKEN, CANVAS_BASE_URL, VAULT_ROOTLoad the env vars into your shell:
set -a; source .env; set +a# 1. Find your Canvas course IDs
python3 scripts/canvas_explore.py
# 2. Inspect a specific course before fetching
python3 scripts/canvas_modules.py 25119
# 3. Generate lecture/literature/tutorial notes for it
python3 scripts/canvas_fetch_course.py --course-id 25119
# 4. Sync your Zotero library into 02-literature/
python3 scripts/sync_zotero.pyAll scripts are idempotent. Re-running only creates new files; existing notes are left alone (unless you pass --force).
The scripts expect (and create) this layout inside $VAULT_ROOT:
vault/
├── 01-lectures/
│ └── <course-slug>/
│ ├── session-01-literature-<topic>.md ← generated
│ ├── lecture-01-<topic>.md ← generated
│ └── tutorial-01-<topic>.md ← generated
├── 02-literature/
│ ├── _index.md ← Dataview index (you create this once)
│ └── <citekey>.md ← one per Zotero item, generated
└── (anything else you already have)
Nothing outside these folders is ever touched.
canvas_fetch_course.py detects sessions by matching Canvas page titles against these patterns:
Session N - LiteratureLecture N - <topic>Tutorial N - <topic>(orTutorial N : <topic>)
If your course uses different titles, edit the regex patterns (SESSION_RE, LECTURE_RE, TUTORIAL_RE) at the top of the script.
A reference config for Maastricht ECPC courses lives at examples/maastricht.yaml. It includes:
- Course name → LAW code mappings (so Zotero tags become
law5076/session-3/decisionsinstead ofadvanced-privacy-and-data-protection-law/session-3/decisions) - Sample course IDs for the 2025–2026 academic year
- A
drop_collection_namesentry for the "Master Courses 2025" top-level wrapper
To use it, copy to masterblasterbrain.yaml in your vault root:
cp examples/maastricht.yaml "$VAULT_ROOT/masterblasterbrain.yaml"sync_zotero.py will auto-detect it. Edit the values to match your own collection names.
- Canvas setup — generating an API token, storing it safely
- Zotero setup — enabling the local API and installing Better BibTeX
- Workflow — recommended day-to-day use
- Idempotent by default: re-running never destroys your notes.
--forceis an explicit opt-in. - Standalone scripts: grab one file, edit it, use it. No package install needed.
- Local first: Zotero via the local HTTP API, no cloud key. Canvas via your personal token, nothing leaves your machine except direct requests to Canvas.
- Plain markdown + YAML frontmatter: everything stays readable without Obsidian or any specific tool.
- Canvas: authenticated HTTPS requests to your institution's
CANVAS_BASE_URL. The token goes in theAuthorization: Bearer ...header, never in the URL or query string. The scripts refuse to run ifCANVAS_BASE_URLis nothttps://.... - Zotero: unauthenticated HTTP to
http://localhost:23119(Zotero's local API). Nothing leaves your machine. - No telemetry, no third-party analytics, no pings home.
A Canvas API token has the full scope of your account — read AND write to assignments, messages, grades, files. Canvas does not offer read-only scoping for personal tokens last we checked. Treat it like a password:
- Store it in
.env(which is gitignored), or in a shell file like~/.canvas_envwithchmod 600. Never paste it into the repo, a commit message, a Slack/WhatsApp chat, or a screenshot. - If you ever leak it, revoke immediately: Canvas → Profile → Settings → Approved Integrations → Delete. Generate a fresh one.
- Set an expiry when generating the token if your institution's Canvas allows it. A dead token is a safe token.
sync_zotero.pyderives filenames from Zotero citation keys. Thesafe_filename()helper strips/,., and other special chars, so a crafted citekey cannot escape02-literature/.canvas_fetch_course.pyaccepts--slugfrom the command line. The value is slugified (lowercase, word-chars only) and then the resulting path is re-checked againstVAULT_ROOT.resolve()before any file write. A--slug "../../evil"is rejected.
requirements.txt pins minimum versions (requests, pyzotero, PyYAML). Supply-chain risk is the usual PyPI baseline — prefer installing in a virtualenv (python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt) rather than system-wide. YAML loading uses yaml.safe_load (never yaml.load) so a malicious config file cannot execute arbitrary code.
Canvas / Instructure personal access tokens are not on GitHub's list of partner secret patterns, so GitHub's own secret scanning is blind to them. The repo ships with a pre-commit config that catches Canvas tokens, generic Authorization: Bearer ... snippets, and common api_key= / token= patterns before they ever leave your machine.
Install once per clone:
pip install pre-commit
pre-commit installFrom then on, every git commit runs the hooks. If you accidentally stage a file containing something that looks like a Canvas token, the commit is aborted with a pointer to the offending file and line:
Block Canvas/Instructure API tokens......................................Failed
- hook id: block-canvas-tokens
- exit code: 1
scripts/my_test.py:2:CANVAS_API_TOKEN = "15183~<redacted>"
You can always --no-verify a commit to bypass the hooks, but treat that as a deliberate override, not a default. The rules themselves live in .pre-commit-config.yaml — edit them if you have legitimate long strings that trip false positives.
- No
eval,exec,pickle,shell=True, or SQL anywhere. - No writing outside
$VAULT_ROOT. - No uploading of your data to any service.
- No modification of items on Canvas or Zotero — all reads.
Please open a GitHub issue or contact the maintainer directly. Responsible disclosure appreciated.
Pull requests welcome, especially:
- Regex patterns for other institutions' page-title conventions
- Support for other LMS backends
- Better citekey fallback strategies
- Template variations
MIT — do what you want, no warranty.