Sign inSign up

oixa/ankipaper

By oixa

•Updated 1 day ago

Anki client for Kindle built on top of the official AnkiWeb sync protocol

Image
0

356

oixa/ankipaper repository overview

⁠AnkiPaper

AI Level 3

AnkiWeb client for Amazon Kindle and other e-ink browsers. AnkiPaper lets you sign in to AnkiWeb, browse decks, and review cards in a server-rendered interface designed for slow monochrome devices.

It uses Anki's native collection format, the official AnkiWeb sync protocol, and the same Rust scheduler backend used by Anki Desktop. Card editing, analytics, and heavy client-side UI are intentionally out of scope.

⁠Features

  • AnkiWeb login with persistent, signed cookie sessions.
  • Multiple AnkiWeb accounts per instance, isolated in separate data directories.
  • Incremental collection sync through Anki's Rust backend.
  • Explicit full-sync flow:
    • detects download, upload, and conflict states;
    • asks the user to choose and confirm a destructive direction when required;
    • runs media sync in the background after the collection sync.
  • Sync status with a progress indicator and a pending-changes marker.
  • Per-deck New / Learn / Review counts from the FSRS scheduler.
  • Card review with Again / Hard / Good / Easy ratings and interval previews.
  • Mark and flag controls in the study toolbar.
  • Filtered (cram) deck rebuilding.
  • E-ink rendering:
    • card scripts are stripped;
    • audio, video, and unsupported embeds are removed;
    • only images and fonts are synchronized;
    • cloze styling and visited links are adjusted for monochrome screens.

⁠Limitations

  • Cards and notes cannot be edited.
  • There is no card browser or analytics dashboard.
  • JavaScript-dependent notes may lose functionality because script tags are removed.
  • Only .jpg, .jpeg, .png, .webp, .otf, .ttf, .woff, and .woff2 files are synchronized. Audio, video, JavaScript, and other media are intentionally skipped.
  • Media files above the configured per-file size limit are not downloaded.
  • Kindle-specific CSS is deliberately simple: no color-dependent UI, animations, shadows, or complex responsive behavior.
  • AnkiWeb credentials and deck data pass through the server. Self-host the application if that is not acceptable for your threat model.
  • There is currently no in-app account deletion flow; remove an account's data directory only with the application stopped and an appropriate backup in place.
  • The scheduler day boundary (Anki Desktop's global "next day starts at" preference) is applied in the server's timezone, which is UTC by default: with a 4 AM rollover, new-day reviews unlock at 4:00 UTC. Anki treats devices in different timezones as a normal travel scenario, so scheduling data stays consistent - users far from UTC simply see the day roll over at a different local time. A single-timezone self-hosted instance can add TZ=Europe/Moscow (any IANA name) to .env and restart to shift the boundary to local time.

AnkiPaper is an unofficial third-party client. It is not affiliated with Ankitects Pty Ltd and is not on the list of approved clients in the AnkiWeb Terms of Service⁠.

The AnkiWeb ToS restricts direct access to the sync service to approved clients (Anki Desktop, AnkiMobile, AnkiDroid, AnkiUniversal). AnkiPaper uses that same sync protocol to talk to AnkiWeb directly, which is outside the approved-clients list. Ankitects have historically tolerated small personal-use clients that only sync their own data and do not add noticeable load, but they may change their policy or suspend accounts at their sole discretion.

Practical implications:

  • Your AnkiWeb account may be suspended or terminated by Ankitects for any reason, including the use of AnkiPaper. This is at Ankitects' sole discretion.
  • If Ankitects block AnkiPaper on their servers, the application will stop working. The author will not try to circumvent such a block.
  • Use is at your own risk. Keep a local backup of your collection.

⁠Stack

  • Python 3.11+ (FastAPI, Jinja2, Uvicorn)
  • Redis
  • Docker Compose

The application image serves HTTP on port 8000. TLS is expected to be terminated by an external reverse proxy.

⁠Quick start with Docker Compose

Clone repository. Then from the repository root:

cp deploy/.env.example .env && cp deploy/docker-compose.yml docker-compose.yml
docker compose up --build

The Compose stack starts:

  • ankipaper — FastAPI application on 127.0.0.1:8000;
  • redis — Redis rate-limit backend.

The application is available at http://localhost:8000. Persistent data is stored in ./.data/; Redis persistence is stored in ./.redis/.

⁠Configuration

Settings are read from environment variables or .env using the ANKIPAPER_ prefix. deploy/.env.example contains an example.

VariableDefaultPurpose
ANKIPAPER_COOKIE_MAX_AGE_DAYS30Signed session lifetime in days.
ANKIPAPER_TRUSTED_UPSTREAM_HTTPS_PROXYfalseSet to true behind a TLS-terminating reverse proxy (nginx, cloudflared, etc.). Enables Secure cookies, X-Forwarded-For IP trust, and proxy-aware canonical URLs.
ANKIPAPER_COOKIE_INSECURE_HOSTS""Comma-separated list of Host header values (exact match or *.suffix wildcard) for which the session cookie is set without Secure. Use this when the same process serves both Cloudflare-fronted HTTPS and a direct LAN access over plain HTTP.
ANKIPAPER_SHOW_PRIVACY_POLICYfalseAdds a link to /static/privacy_policy.html in login and deck-list footers.
ANKIPAPER_CONTACTS_EMAIL""Email address shown as a "Contact Us" link in the page footer. Empty hides the link.
ANKIPAPER_REDIS_URLredis://localhost:6379/0Redis URL for login and sync rate limiting. In Docker Compose use redis://redis:6379/0.
ANKIPAPER_LOGIN_IP_MAX_ATTEMPTS5Maximum login attempts per IP in the IP window.
ANKIPAPER_LOGIN_IP_WINDOW_SECONDS60Login rate-limit window per IP.
ANKIPAPER_LOGIN_USER_MAX_ATTEMPTS10Maximum login attempts per username in the username window.
ANKIPAPER_LOGIN_USER_WINDOW_SECONDS3600Login rate-limit window per username.
ANKIPAPER_DATA_MAX_BYTES0Maximum total /data size for creating new accounts. 0 disables the limit; existing accounts remain accessible.
ANKIPAPER_MEDIA_MAX_FILE_BYTES1048576 (1 MiB)Maximum size of one media file. Larger files are skipped during media sync.
ANKIPAPER_METRICS_USERNAME""HTTP Basic username required by the Prometheus scraper. Empty disables /metrics with HTTP 503.
ANKIPAPER_METRICS_PASSWORD""HTTP Basic password required by the Prometheus scraper. Empty disables /metrics with HTTP 503.
ANKIPAPER_PUBLIC_URL""Public origin (for example, https://ankipaper.study) used in canonical, Open Graph, and sitemap URLs. When empty, the origin is derived from the incoming request (requires ANKIPAPER_TRUSTED_UPSTREAM_HTTPS_PROXY=true).

Redis is required for protected login and sync operations. If Redis is unavailable, rate limiting fails closed: login returns an error and sync is blocked rather than running without protection.

⁠Reverse proxy and production notes

The Compose service binds port 8000 to loopback only. Put nginx, Cloudflare Tunnel, or another TLS reverse proxy in front of it for external access.

For a reverse-proxy deployment:

  1. Set ANKIPAPER_TRUSTED_UPSTREAM_HTTPS_PROXY=true.
  2. Make sure the X-Forwarded-For header carries the real client IP. The application also understands Cloudflare's CF-Connecting-IP header when proxy mode is enabled.

If the same instance is also reachable over plain HTTP from the LAN, list the LAN hostnames in ANKIPAPER_COOKIE_INSECURE_HOSTS so the session cookie is set without the Secure flag for those hosts:

ANKIPAPER_TRUSTED_UPSTREAM_HTTPS_PROXY=true
ANKIPAPER_COOKIE_INSECURE_HOSTS=ankipaper.lan,192.168.1.10

⁠Monitoring

Set both ANKIPAPER_METRICS_USERNAME and ANKIPAPER_METRICS_PASSWORD to expose GET /metrics for Prometheus scrapers. With either credential left empty, the endpoint returns 503 instead of exposing data unauthenticated.

Tag summary

Content type

Image

Digest

sha256:e4c0411b9…

Size

93 MB

Last updated

1 day ago

docker pull oixa/ankipaper