122 lines
7.6 KiB
Markdown
122 lines
7.6 KiB
Markdown
# sechenov — Claude Chat + Stealth Portal + OpenAI-compat Endpoint
|
||
|
||
Express-сервер, совмещающий три роли:
|
||
1. **Stealth чат** для студента на клоне портала Сеченовки (`https://s.akyra.ru/`, `https://student.sechen0v.ru/`, `https://dl.sechen0v.ru/`)
|
||
2. **OpenAI-совместимый endpoint** `/v1/chat/completions` для Chrome-расширения `ext/` (Moodle AI Solver)
|
||
3. **Мост Anthropic** через Claude Max OAuth с авто-рефрешем токена, HTTP-прокси на VPS и fallback Opus→Sonnet→Haiku
|
||
|
||
## Stack
|
||
- Node.js + Express
|
||
- Anthropic API напрямую (`https://api.anthropic.com/v1/messages`) через Claude Max OAuth (`.credentials.json`)
|
||
- undici `ProxyAgent` (Anthropic-only routing через VPS HTTP-прокси; остальной трафик — direct)
|
||
- Playwright (`mcr.microsoft.com/playwright:v1.59.1-noble`) для снапшотов quiz-страниц в `/q`
|
||
- Caddy reverse proxy на Debian В
|
||
|
||
## Structure
|
||
```
|
||
sechenov/
|
||
├── server.js — всё: /api/chat, /api/notify, /v1/chat/completions, /q, /auth.php, OAuth refresh
|
||
├── public/
|
||
│ ├── index.html — stealth portal + скрытый чат
|
||
│ ├── q/index.html — Playwright-снятый клон quiz-страницы со слотами чата
|
||
│ └── assets/... — зеркало статики с оригинала
|
||
├── scripts/
|
||
│ └── mirror.js — Playwright fullPage screenshot + инжект чат-слотов
|
||
└── package.json
|
||
```
|
||
|
||
## Models
|
||
```js
|
||
MODELS = {
|
||
opus: 'claude-opus-4-7',
|
||
sonnet: 'claude-sonnet-4-6',
|
||
haiku: 'claude-haiku-4-5-20251001'
|
||
}
|
||
THINKING_MODELS = new Set(['claude-opus-4-7'])
|
||
THINKING_BUDGET_TOKENS = 16000
|
||
```
|
||
- `/api/chat` — default `opus`, fallback на Haiku при 429
|
||
- `/v1/chat/completions` — принимает OpenAI-совместимый payload, mapping модели по substring, конвертирует `image_url` → Anthropic `image/base64`
|
||
|
||
## Endpoints
|
||
| Путь | Назначение |
|
||
|---|---|
|
||
| `/` | `public/index.html` (stealth portal) |
|
||
| `/q`, `/q.html`, `/mod/quiz/attempt.php` | `public/q/index.html` (клон quiz) |
|
||
| `/auth.php` | `public/index.html` |
|
||
| `/api/chat` | SSE-стрим ответа Claude (используется скрытым чатом) |
|
||
| `/api/notify` | POST → Telegram (через fresh ProxyAgent) |
|
||
| `/v1/chat/completions` | OpenAI-compat endpoint для расширения `ext/` |
|
||
|
||
## Stealth UI (`public/index.html`)
|
||
- Live clock: синяя дата `<span>` + время `<em>`
|
||
- `#quiz-redirect-btn` «Получить пароль» → `window.location.href = 'https://dl.sechen0v.ru/mod/quiz/attempt.php?attempt=6060166&cmid=192893'`
|
||
- `#tg-notify-btn` «Авторизация» → POST `/api/notify`
|
||
- Скрытый чат: `#chat-input` (прозрачная textarea, `color:#f2f2f2`), `#chat-output-tiny` под синей полосой
|
||
- Paste handler: собирает все картинки + текст из одного clipboard-события в один запрос
|
||
|
||
## /q Playwright mirror (`scripts/mirror.js`)
|
||
- Запуск в docker: `mcr.microsoft.com/playwright:v1.59.1-noble` + `npm install playwright@1.59.1 --no-save`
|
||
- Viewport 1600×900, deviceScaleFactor 2, `fullPage: true`
|
||
- Инжект `.que + .que { margin-top: 180px !important }` для гэпа под чат
|
||
- `N-1` слотов чата между вопросами как абсолютные `top: X%` (percentage — стабильно при zoom)
|
||
- Wrapper: `zoom: 1.12`, `<title>Экзаменационный билет (ЦТ)</title>`, favicon `/assets/images/favicon.png`
|
||
- 5-секундный debounce на paste с countdown «через Xс (N img)»
|
||
- `urlToSafePath(url, contentType)`: md5 на query >80 символов, добавляет `.css/.js/.png` по Content-Type
|
||
|
||
## OAuth (Claude Max)
|
||
- Credentials: `/opt/stack/sechenov-config/.credentials.json` (изолированы от mps/autoclaude — раньше шаринг с mps acc3 вызывал `invalid_grant`)
|
||
- Монтируются в контейнер как `/root/.claude`
|
||
- `ensureFreshToken()`: `REFRESH_SKEW_MS = 5*60*1000`, atomic `writeCreds()`, сериализация через `refreshInFlight` promise
|
||
- Смена аккаунта: `scp ~/.claude/.credentials.json aedes@192.168.1.20:/opt/stack/sechenov-config/`
|
||
|
||
## VPS Proxy (IPv4-only HTTP)
|
||
- VPS: **194.87.245.138:8889** (актуальный), скрипт `/opt/claude-proxy-8889.py`, systemd `claude-proxy.service`
|
||
- Патч: `ipv4_connect()` через `getaddrinfo(..., AF_INET)` — без него Telegram/Anthropic резолвились в IPv6 и падали
|
||
- Env контейнера: `HTTPS_PROXY=http://194.87.245.138:8889` (в `/opt/stack/docker-compose.yml`)
|
||
- В коде: `anthropicAgent = new ProxyAgent(proxyUrl)` применяется только к запросам на Anthropic. Глобально — `directAgent`.
|
||
- **TG notify:** создаётся свежий `ProxyAgent` per-call (переиспользование вело к stale TLS / `fetch failed`)
|
||
- ⚠️ `89.125.243.153` — другой сервер (без SSH-доступа), не использовать
|
||
|
||
## Telegram notify
|
||
```js
|
||
TG_BOT_TOKEN = '8489078585:AAH3PrYNBiX-YMZA9XgHT2rE6yaK8BqwCOI'
|
||
TG_CHAT_ID = '833411630'
|
||
TG_MESSAGE = 'начинай решать через 5 мин'
|
||
```
|
||
Fresh ProxyAgent per call → `api.telegram.org/bot<token>/sendMessage`.
|
||
|
||
## Chrome Extension integration (`../ext/`)
|
||
- `OR_URL = 'https://s.akyra.ru/v1/chat/completions'`
|
||
- `DEFAULT_MODEL = 'opus'`, `FALLBACK_MODELS = ['sonnet', 'haiku']`
|
||
- `DEFAULT_KEY = 'sechenov-local'` (сервер принимает любой bearer)
|
||
- Расширение маскируется под uBlock Origin, активация Alt+w на quiz-странице
|
||
|
||
## Local (machine B)
|
||
- Path: `C:\Users\aedes\REUNION\sync\scriptsAedes\sechenov`
|
||
|
||
## Remote (Debian В)
|
||
- Path: `/opt/stack/sechenov/`
|
||
- URL: `https://s.akyra.ru` + алиасы `student.sechen0v.ru`, `dl.sechen0v.ru`
|
||
- Port: 8108
|
||
- Caddy: блоки для всех трёх доменов → `127.0.0.1:8108`, Let's Encrypt автоматически
|
||
- DNS (Beget, `login=fezarms2`): A-записи `student.sechen0v.ru`, `dl.sechen0v.ru` → `94.141.253.12`
|
||
- Deploy:
|
||
```bash
|
||
git push
|
||
ssh aedes@192.168.1.20 "cd /opt/stack/sechenov && git pull && docker compose restart sechenov"
|
||
```
|
||
|
||
## Известные грабли
|
||
- **invalid_grant** если шарить `.credentials.json` с mps/autoclaude — держать изолированно в `sechenov-config/`. Авторефреш через `scripts/sync-creds.ps1` (Windows Task Scheduler, смотрит `~/.claude/.credentials.json`)
|
||
- **TG 502** → IPv6 в proxy; фикс — `AF_INET` в `/opt/claude-proxy.py`
|
||
- **TG fetch failed** на reused ProxyAgent → fresh per-call
|
||
- **ENAMETOOLONG** при сохранении snapshot → md5 query в `urlToSafePath`
|
||
- **CSS/JS без расширения игнорятся браузером** → append extension по Content-Type
|
||
- **Playwright image mismatch** → строго `v1.59.1-noble` + `playwright@1.59.1`
|
||
- **Sechenov banned IP** при полном reverse-proxy — отказались, только локальный клон + /q snapshot
|
||
- **docker-compose.yml sed** легко задевает mps/autoclaude mounts — править вручную
|
||
|
||
## Update this file
|
||
После изменений в структуре, стеке, конфигах или инфре — обновить этот CLAUDE.md.
|