Case Study
Open Source · 2026
Independent Product · Design, Build & Release · Jul 2026
Moodle
Downloader
A desktop app that keeps every lecture slide, tutorial sheet and assignment brief published on a university Moodle filed into weekly folders — on its own, all semester, for students who have never opened a terminal.
0
Passwords handled
3h
Auto-sync cadence
13
Folders per unit
Course material arrives late, and never in one place.
Every unit publishes files across a dozen nested sections, and rarely all at once — slides land before class, worksheets during the week, solutions days later. Keeping a clean local copy means revisiting every unit, every week, and remembering what was already downloaded.
Existing browser extensions only shortened the clicking: the student still had to open each course page and decide what to grab. The real problem was not download speed — it was that the task depended on a person remembering to do it at all.
Root causes identified
Staggered publishing
Solutions and extra reading appear days after the lecture — one pass over a unit is never enough.
Deeply nested structure
Weeks are sub-sections of a parent section, each holding further sub-sections; nothing sits on the course home page.
Authentication friction
Any automation has to survive single sign-on and multi-factor authentication without ever holding a password.
Non-technical audience
The people with this problem are not going to install Python, edit a config file, or run a command.
As-Is → To-Be
As-Is · Manual
Remember to check Moodle
Log in, open each unit
Expand every week section
Compare against local folder
Download the new files
File them by hand
⏱ Weekly, per unit · Easy to forget · Late files missed
To-Be · Automated
App wakes on its own
Session restored silently
Every section crawled
Manifest diffed
Only new files fetched
Filed into the right week
⚡ Every 3 hours · Nothing to remember · Late files self-collect
Two screens, then never again
Distribution was treated as part of the design, not an afterthought. The whole product is a single 70 MB file: no runtime to install, no admin rights, no command line. Setup asks two questions and shows a tick-list of courses it discovered by itself; after that the window exists only to answer “is it working?”
Reading the system before automating it
The first crawler returned nothing. The course home page carries no file links at all: this Moodle runs a custom course format where weeks are sub-sections of a Learning section, and each week holds further sub-sections (Own-time, Real-time, Wrap-up) that actually contain the material.
Mapping it meant fetching real pages and reading the markup: section numbers are not sequential (week 1 is section 7, week 2 is section 11), so the crawler walks section links breadth-first, builds a parent tree, and lets a child inherit its parent's week.
Course discovery uses the same AJAX endpoint the Moodle dashboard calls, which also exposes which courses the student has starred — so the tool can offer a list instead of asking anyone to copy an ID.
Assumed — flat
↓ expected file links here ↓
0 files found
Actual — nested
↓ week sub-sections (7, 11, 15 …) ↓
↓ inherits week from parent ↓
↓
How one sync runs
Wake
A logon trigger and a three-hour repeat start the sync silently; a lock file guarantees only one run at a time.
Session restore
Saved cookies are re-injected into a real browser context. If the session lapsed, renewal happens off-screen — invisible unless a human is genuinely required.
Crawl
Every section page is fetched breadth-first, following sub-section links until the whole tree is mapped.
Classify
Section titles are matched to weeks by configurable patterns; children inherit their parent's week, and assessment activities are routed separately.
Diff
Each candidate file is checked against a manifest of everything downloaded so far — and against the disk, so deleted files come back.
File
New files only are written into the matching week folder; replaced versions land beside the originals instead of overwriting them.
Never hold what you don't need to hold
Asking students for their university password would have made the tool trivial to build and impossible to trust. Instead the login runs where it belongs: a real browser window on the institution's own sign-on page, complete with multi-factor authentication. The application never sees a credential.
What it does keep is the resulting session, stored locally outside the project directory so it can never be committed or synced to the cloud. Because session cookies die with the browser process, they are exported and re-injected on the next run — which is what makes unattended syncing possible at all.
Nothing leaves the machine: there is no server, no telemetry and no account. Console output prints paths relative to the download folder, so a shared screenshot never leaks a username.
University SSO + MFA
Runs in a real browser — user types, app watches
↓ issues
Session cookies
Stored locally, outside the repo and cloud folders
↓ re-injected each run
Silent headless sync
No window, no password, no server
Shipped outcomes
0
Passwords stored or transmitted
Login stays inside university SSO
0
Duplicate downloads across runs
Manifest-tracked incremental sync
3h
Unattended sync interval
Plus one run at every logon
13
Folders maintained per unit
Week 00–12, Assignments, _Other
1
File to download, nothing to install
No Python, no admin rights
2
Documentation languages
English and Simplified Chinese
Deliverables
Course-structure reverse engineering
Breadth-first section crawler
SSO session persistence
Incremental manifest sync
Assignment & assessment capture
Desktop GUI (setup + dashboard)
Background auto-sync service
Bilingual docs + signed release
Key takeaways
01
Remove the task, not the clicks
Faster downloading still leaves a chore on the student. Only running unattended actually solves the problem the chore represents — that was a product decision, not a technical one.
02
Read the system first
The initial crawler failed because I automated an assumed structure. Fetching the real markup and mapping how weeks nest was what made everything downstream possible.
03
Trust is a design constraint
Keeping credentials out of the tool made it harder to build and is the reason it can be handed to a stranger. Security decided the architecture, not the other way round.
04
Distribution is part of the product
For a non-technical audience, a single double-clickable file with a window is the product. A perfect script nobody can install helps nobody.