Files
OFApp/CLAUDE.md
Trey t c60de19348 Initial commit — OFApp client + server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 20:07:06 -06:00

96 lines
5.2 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Run
```bash
npm run install:all # Install server + client deps
npm run dev # Concurrent: client (5173) + server (3001)
npm run dev:server # Server only with --watch
npm run dev:client # Vite dev server only
npm run build # Build client to client/dist/
npm start # Production server (serves client/dist/)
```
## Deployment
Remote server: `root@10.3.3.11`, password `Intel22`, path `/mnt/user/appdata/OFApp/`
```bash
# Deploy
sshpass -p 'Intel22' rsync -avz --exclude='node_modules' --exclude='.git' --exclude='dist' --exclude='data' /Users/treyt/Desktop/code/OFApp/ root@10.3.3.11:/mnt/user/appdata/OFApp/
# Rebuild container
sshpass -p 'Intel22' ssh root@10.3.3.11 'cd /mnt/user/appdata/OFApp && docker-compose down && docker-compose up -d --build'
# Check logs
sshpass -p 'Intel22' ssh root@10.3.3.11 'docker logs ofapp 2>&1 | tail -20'
```
Port mapping: HTTP 3002->3001, HTTPS 3003->3443. Data volumes at `/mnt/user/downloads/OFApp/{db,media}`.
## Architecture
**Client**: React 18 + Vite + Tailwind. Pages in `client/src/pages/`, API wrappers in `client/src/api.js`. Vite proxies `/api` to `localhost:3001` in dev.
**Server**: Express (ESM modules). All routes are in separate router files mounted in `server/index.js`.
### Server Modules
| File | Purpose |
|------|---------|
| `proxy.js` | OF API proxy with auth headers + media/DRM proxy endpoints |
| `signing.js` | Dynamic request signing (fetches rules from GitHub, auto-refreshes hourly) |
| `download.js` | Background media download orchestration with resume support |
| `db.js` | SQLite (better-sqlite3, WAL mode) — auth, download history, cursors, settings |
| `gallery.js` | Serves downloaded media as browsable gallery |
| `hls.js` | On-demand HLS transcoding via FFmpeg |
| `settings.js` | Key-value settings API |
### Auth & Signing Flow
All OF API requests go through `proxy.js` which adds auth headers (Cookie, user-id, x-bc, x-of-rev, app-token) plus a dynamic `sign` header generated by `signing.js`. The signing uses SHA-1 with parameters from community-maintained rules fetched from GitHub repos (rafa-9, datawhores, DATAHOARDERS — tried in order).
### DRM Video Playback
OF videos use PallyCon DRM (Widevine for Chrome/Firefox, FairPlay for Safari). The flow:
1. `MediaGrid.jsx` detects `files.drm` on video items, extracts DASH manifest URL + CloudFront cookies (and keeps the parent entity context: `entityType` + `entityId`, e.g. `post/{postId}`)
2. `DrmVideo.jsx` uses Shaka Player to load the DASH manifest via `/api/drm-hls` proxy
3. Shaka's request filter rewrites CDN segment URLs through `/api/drm-hls` (attaches CloudFront cookies server-side)
4. Widevine license challenges go to `/api/drm-license` which forwards to OF's DRM resolver:
- Own media: `POST /api2/v2/users/media/{mediaId}/drm/?type=widevine`
- Posts/messages/etc: `POST /api2/v2/users/media/{mediaId}/drm/{entityType}/{entityId}?type=widevine` (e.g. `post/{postId}`)
5. The `/api/drm-hls` proxy handles DASH manifests (injects `<BaseURL>`), HLS playlists (rewrites URLs), and binary segments (pipes through)
**EME requires HTTPS** — the server auto-generates a self-signed cert at `/data/certs/` on first boot. Access DRM content via `https://10.3.3.11:3003`.
### Media Proxy
CDN media from `*.onlyfans.com` is proxied through `/api/media-proxy` to avoid CORS issues. It passes Range headers and caches responses. URLs from `public.` and `thumbs.` subdomains are NOT proxied (client-side check in `MediaGrid.jsx`).
### Download System
`download.js` runs background downloads in-memory (not persisted across restarts). It paginates through `/api2/v2/users/:id/posts/medias`, downloads each file to `MEDIA_PATH/:username/`, records in SQLite, and supports cursor-based resume for interrupted downloads.
## Environment Variables
| Variable | Default | Notes |
|----------|---------|-------|
| `PORT` | 3001 | HTTP |
| `HTTPS_PORT` | 3443 | HTTPS for DRM/EME |
| `DB_PATH` | /data/db/ofapp.db | SQLite |
| `MEDIA_PATH` | /data/media | Downloaded files |
| `DOWNLOAD_DELAY` | 1000 | ms between downloads |
| `HLS_ENABLED` | false | FFmpeg HLS transcoding |
## Key Gotchas
- Widevine license challenges must be treated as raw binary. `server/index.js` mounts `express.raw()` for `/api/drm-license` before the global `express.json()`, and `server/proxy.js` prefers `req.body` (Buffer) with a fallback stream read.
- For subscribed content, `/api/drm-license` must include `entityType` + `entityId` (e.g. `entityType=post&entityId=<postId>`). Missing entity context typically yields OF `403 User have no permissions`, which Shaka surfaces as `6007` (`LICENSE_REQUEST_FAILED`).
- Request signing rules normalize differently across GitHub sources — `signing.js` handles both old format (static_param) and new format (static-param with dashes).
- CloudFront cookies for DRM content are IP-locked to the server's public IP (47.185.183.191). They won't work from a different IP.
- The `/api/drm-hls` proxy skips `skd://` URIs in HLS manifests (FairPlay key identifiers, Safari only).
- Server log prefixes: `[signing]`, `[drm-license]`, `[drm-hls]`, `[download]`, `[media-proxy]`, `[hls]`.