Initial commit — OFApp client + server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
95
CLAUDE.md
Normal file
95
CLAUDE.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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]`.
|
||||
Reference in New Issue
Block a user