Skip to content

Latest commit

Β 

History

61 Commits

Folders and files

Canvas Assignment Tracker: your Canvas To Do list, ranked every morning, with reminders every 2 hours until you submit

CI Tests: 426 Coverage: 94% mypy: strict License: Apache-2.0 Status: private beta, 6 users

Python 3.13 | 3.14 asyncio python-telegram-bot 22.8 Telegram Bot API Canvas LMS REST API httpx 0.28 SQLite 3 Pydantic 2 cryptography Fernet
Docker Google Cloud GitHub Actions uv pytest 9 Hypothesis Ruff Bash

How it works Β· Get started Β· Architecture Β· Documentation Β· Changelog


Canvas tells you about a deadline once, in an email you probably didn't read. This bot keeps telling you until the work is in.

It reads your Canvas To Do list through the official API, sends you a ranked list every morning, and while anything is due within 24 hours it sends one combined reminder every 2 hours, until Canvas shows it submitted. It runs as a Telegram bot: there's nothing to install, and the buttons work from your lock screen.

Private beta: 6 students at several universities use it every day. A public release is planned. See the roadmap.

The morning list with URGENT and HIGH sections and Done buttons, a 2-hourly reminder with Done, Snooze and Open buttons, and an 11:30 PM warning about work due overnight

Features

β˜€οΈ Ranked morning list Everything due in the next 14 days, ranked by urgency, share of your final grade and estimated effort, with a one-line explanation for each item. It arrives at the time you choose, in your own time zone.
⏰ Reminders until submitted One combined message every 2 hours while anything is due within 24 hours. Submitting in Canvas stops it automatically, usually within 10 minutes.
βœ… One-tap buttons Done for paper or in-class work Canvas can't see, Snooze 2h, Open in Canvas, and Undo within 10 minutes.
πŸŒ™ Respects your sleep Quiet hours you choose, an 11:30 PM warning about anything due overnight, and one overdue notice instead of endless nagging.
πŸ”’ Private by design Read-only Canvas access, tokens encrypted at rest, your token message deleted from the chat, invite-only access, and /disconnect erases everything.
🧭 Honest about stale data If Canvas can't be reached, every message says how old its data is. It never claims "nothing due" when it doesn't know.

How it works

flowchart LR
    C[("Canvas LMS<br/>Planner API")] -- "every 10 min (30 min at night)<br/>ETag-cached, read-only" --> S[Sync]
    S --> DB[("Your own<br/>SQLite file")]
    DB --> P{{"Scheduler<br/>(every minute)"}}
    P -- "8:00 AM" --> D["β˜€οΈ Ranked morning list"]
    P -- "every 2 h while<br/>due < 24 h" --> R["⏰ Reminder"]
    P -- "23:30" --> O["πŸŒ™ Overnight warning"]
    P -- "once, when late" --> X["❗ Overdue notice"]
    D & R & O & X --> T(["Telegram"])
    T -- "βœ… Done Β· πŸ’€ Snooze Β· ↩️ Undo" --> DB
Loading

An item counts as done when Canvas shows it submitted, graded (for paper or in-class work), excused or marked complete, or when you tap βœ… Done. If its due date moves, an extension re-opens it automatically. The ranking is a transparent 100-point score:

pie showData
    title The 100-point priority score
    "Urgency: time left (0–50)" : 50
    "Importance: share of grade (0–35)" : 35
    "Effort: estimated hours vs. time left (0–15)" : 15
Loading

The full rules, with a timeline of a real reminder day, are in How it works.

Get started

πŸŽ“ Join a bot (2 minutes)

You need an invite link from whoever runs the bot.

  1. Tap the link and press Start.
  2. Send your Canvas address (e.g. school.instructure.com).
  3. Create a Canvas access token (the bot shows you how) and paste it. The bot deletes the message.
  4. Pick your time zone, morning time and quiet hours.

Full guide for students β†’

πŸ–₯️ Host a bot for your friends (20 minutes)

You need a Telegram account and an always-on Linux server (Google Cloud's free tier works).

  1. Create a bot with @BotFather.
  2. On the server, run:
    curl -fsSL https://raw.githubusercontent.com/rmaytr/CanvasAssignmentTrackingBot/HEAD/deploy/install.sh | bash
  3. Send the bot the /start CODE the installer prints.
  4. Send /invite for each friend.

Full hosting guide β†’

Commands

Command What it does
/today Β· /week What's due today, or in the next 7 days by priority
/digest Your morning list, right now
/sync Β· /status Check Canvas now Β· last sync, token health, next list and reminder
/pause 3h Pause reminders (30m, until 18:00, off)
/settings Time zone, morning time, quiet hours, Canvas token or address
/email you@school.edu Also email the morning list and a heads-up 2 h before each deadline (/email off stops it)
/disconnect Delete everything the bot knows about you
/invite Β· /users Β· /remove N Owner only: invite links, user health (never anyone's assignments), removal

Architecture

flowchart TB
    TG(["Telegram"]) <--> H

    subgraph Bot["Bot container"]
        H["Handlers<br/>invite-only gate Β· per-user ordering"] --> HUB["Hub<br/>sign-up Β· settings Β· owner commands"]
        HUB <--> REG[("registry.db<br/>users Β· invites<br/>πŸ” encrypted tokens")]
        HUB --> T3 & T2 & T1
        subgraph T3["Tenant: …"]
            N1["Sync"] --> N2[("….db")] --> N3["Scheduler"]
        end
        subgraph T2["Tenant: Ben"]
            B1["Sync ⇄ Ben's Canvas"] --> B2[("ben.db")] --> B3["Scheduler"]
        end
        subgraph T1["Tenant: Ana"]
            A1["Sync ⇄ Ana's Canvas"] --> A2[("ana.db")] --> A3["Scheduler"]
        end
    end
Loading
  • Isolation by construction. Every user gets their own database file, Canvas client and scheduler. Each Telegram update is routed by the sender's ID, so no code path can reach another user's data.
  • Functional core, imperative shell. All scheduling and ranking is a pure function, plan(now, snapshot, settings) β†’ Plan, with no I/O. That makes it simple to test exhaustively.
  • No duplicate messages. Every message has a deterministic key, claimed atomically in SQLite before it's sent, so restarts, crashes and clock changes never send something twice.
  • Fault isolation. Each user's tick runs on its own. One school's Canvas hanging never delays anyone else's reminders.

Architecture deep dive β†’ (components, data model, sign-up state machine, delivery guarantees, failure modes)

Tech stack

Area Choice Why
Language Python 3.13 (asyncio), strict typing One process handles polling, scheduling and chat concurrently
Telegram python-telegram-bot 22 Mature async library: inline keyboards, deep links, concurrent updates
HTTP httpx Async, timeouts, mockable transport for tests
Storage SQLite (WAL), versioned SQL migrations Zero-ops, a file per user, atomic claims with UNIQUE
Config Pydantic Settings Validated environment, secrets wrapped so they're never echoed
Crypto cryptography (Fernet) Authenticated encryption for stored Canvas tokens
Time zoneinfo + tzdata IANA zones, correct across daylight-saving changes
Packaging uv (locked), Docker multi-stage build Reproducible installs, small hardened image
Quality pytest, Hypothesis, respx, time-machine, mypy --strict, Ruff (incl. Bandit rules), pip-audit See Testing
CI/CD GitHub Actions (SHA-pinned), Dependabot Lint, types, tests on 3.13 and 3.14, coverage gates, audit, Docker build
Hosting Docker Compose on a Debian/Ubuntu VM Runs at $0 on Google Cloud's free tier

Quality at a glance

426 automated tests Unit, property-based, integration, end-to-end and a 20-day minute-by-minute simulation
94% line + branch coverage 98% on the scheduling and ranking core (CI fails below 95%)
0 mypy --strict errors Across 106 source and test files
18 review findings fixed From two independent whole-codebase reviews, each with a regression test
28,800 simulated scheduler ticks Across a daylight-saving change, against a hand-reviewed golden file

Testing strategy β†’

Security

Canvas tokens are as powerful as a password, so the bot treats them that way:

  • Encrypted at rest: Fernet (AES-128-CBC + HMAC-SHA256), with the key stored outside the data volume.
  • Never left in chat: a message containing a token is deleted at whatever step it was pasted.
  • Never in logs: known secrets and token-shaped strings are redacted from every log line, including exceptions.
  • Hardened Canvas client: read-only (GET only), HTTPS on port 443, host-pinned with no redirects or cross-host pagination, and private or internal addresses refused.
  • Least-privilege container: non-root, read-only filesystem, no Linux capabilities, file-based secrets.

Security policy and threat model β†’

Canvas API policy

Important

Instructure's API policy says apps used by multiple people should use OAuth, and that asking other users to create and paste a personal token is not allowed. The shared bot does exactly that, so it's run as a small, invite-only private beta among friends who understand the trade-off. Before a public launch the plan is to use OAuth through participating schools, or a calendar-feed mode that needs no token (see the roadmap). Using your own token for your own data is the most defensible setup; check your school's IT policy if you're unsure.

Project layout

β”œβ”€β”€ src/canvas_tracker/
β”‚   β”œβ”€β”€ domain/      # models, completion rule, 100-point priority score (pure)
β”‚   β”œβ”€β”€ planning/    # scheduler: digest, reminder cycles, quiet hours, send keys (pure)
β”‚   β”œβ”€β”€ render/      # message text, keyboards, 4096-char splitting, all user-facing copy
β”‚   β”œβ”€β”€ sources/     # read-only Canvas REST client, Planner and enrichment mappers
β”‚   β”œβ”€β”€ store/       # SQLite repository and migrations (one file per user)
β”‚   β”œβ”€β”€ runtime/     # tick loop, sync scheduling, secret-redacting logs
β”‚   β”œβ”€β”€ notify/      # Telegram and email (SMTP) delivery
β”‚   β”œβ”€β”€ bot/         # per-user commands and buttons
β”‚   └── hub/         # multi-user layer: registry, encryption, sign-up, routing
β”œβ”€β”€ tests/           # unit Β· property Β· integration Β· simulation Β· contract
β”œβ”€β”€ deploy/          # one-command server installer
β”œβ”€β”€ spike/           # owner-run probes and a PII scrubber for real Canvas data
β”œβ”€β”€ docs/            # guides, architecture, design records, research
β”œβ”€β”€ Dockerfile Β· compose.yaml
└── pyproject.toml Β· uv.lock

Roadmap

  • Single-user MVP: digest, reminders, buttons, quiet hours, Docker (v0.1)
  • Friends mode: shared invite-only bot, sign-up in chat, encrypted tokens, one-command install (v0.2)
  • Beta feedback: tune ranking thresholds on real use; contract tests on scrubbed real Canvas data
  • Calendar-feed mode for schools that block personal tokens (no token needed)
  • OAuth with participating schools and stronger protection for a public release (per-user key wrapping, rate limiting, monitoring)
  • Optional email: the morning list and a heads-up 2 hours before each deadline (any SMTP provider)
  • Per-user reminder rhythm, localization (all copy already lives in one module)

Documentation

Joining guide For students using a bot someone invited them to
Hosting guide Set up, run, update and back up your own bot
How it works Ranking, completion rule, reminder timeline, quiet hours
Architecture Components, data model, state machine, guarantees
Testing Test layers, simulation, coverage, how to run
Security Threat model, controls, reporting a vulnerability
Design records Product requirements and the friends-mode design
Background research Canvas data access, channels, hosting and policy research

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for setup (uv sync), the checks CI runs, and conventions. Please report security problems privately as described in SECURITY.md.

License

Apache-2.0. Not affiliated with Instructure or Telegram. Canvas is a trademark of Instructure, Inc.

Why Telegram and not SMS? A bot has to rent a phone number and register with US carriers (about $20 up front plus $7–8 a month), and the free email-to-text gateways have shut down. Telegram is free, works on every phone and has buttons.