diff --git a/public/iframe.html b/public/iframe.html
deleted file mode 100644
index 93638c6..0000000
--- a/public/iframe.html
+++ /dev/null
@@ -1,135 +0,0 @@
-
-
-
-
-
-Авторизация и регистрация
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/server.js b/server.js
index a258dd9..cf2f329 100644
--- a/server.js
+++ b/server.js
@@ -7,17 +7,13 @@ const { ProxyAgent, Agent, setGlobalDispatcher } = require('undici');
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
const directAgent = new Agent();
const anthropicAgent = proxyUrl ? new ProxyAgent(proxyUrl) : directAgent;
-// Force global default = DIRECT. Anthropic calls override with their own dispatcher.
setGlobalDispatcher(directAgent);
-if (proxyUrl) console.log(`✓ Anthropic proxy: ${proxyUrl} (sechenov: direct)`);
+if (proxyUrl) console.log(`✓ Anthropic proxy: ${proxyUrl}`);
const app = express();
const PUBLIC_DIR = path.join(__dirname, 'public');
-const UPSTREAM = 'https://student.sechenov.ru';
-// ══════════════════════════════════════════════════════════════
-// CLAUDE CHAT (/api/chat)
-// ══════════════════════════════════════════════════════════════
+app.use(express.static(PUBLIC_DIR));
const MODELS = {
sonnet: 'claude-sonnet-4-6',
@@ -137,299 +133,4 @@ app.post('/api/chat', express.json({ limit: '10mb' }), async (req, res) => {
}
});
-// ══════════════════════════════════════════════════════════════
-// LEGACY / LOCAL ROUTES
-// ══════════════════════════════════════════════════════════════
-
-// Main page: local clone (reverse-proxy disabled — Sechenov banned our IPs)
-app.use(express.static(PUBLIC_DIR));
-app.get(['/old', '/old.html'], (_req, res) => res.sendFile(path.join(PUBLIC_DIR, 'index.html')));
-app.get(['/iframe', '/iframe.html'], (_req, res) => res.sendFile(path.join(PUBLIC_DIR, 'iframe.html')));
-
-// Legacy HTML-only proxy used by /iframe.html
-app.get('/proxy/*', async (req, res) => {
- const upstreamPath = req.url.replace(/^\/proxy/, '') || '/';
- try {
- const r = await fetch(UPSTREAM + upstreamPath, {
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
- 'Accept': req.headers.accept || '*/*',
- 'Accept-Language': req.headers['accept-language'] || 'ru,en;q=0.9',
- },
- });
- const ct = r.headers.get('content-type') || 'application/octet-stream';
- res.status(r.status);
- res.setHeader('Content-Type', ct);
- res.removeHeader('X-Frame-Options');
- res.removeHeader('Content-Security-Policy');
- if (ct.includes('text/html')) {
- let html = await r.text();
- html = html
- .replace(/\b(href|action)="\/(?!\/)/g, '$1="/proxy/')
- .replace(/\bsrc="\/(?!\/)/g, 'src="' + UPSTREAM + '/')
- .replace(/url\(["']?\/(?!\/)/g, 'url(' + UPSTREAM + '/')
- .replace(/\btarget=["'](_top|_parent|_blank)["']/gi, '')
- .replace(/]+http-equiv=["']?(Content-Security-Policy|X-Frame-Options)["']?[^>]*>/gi, '')
- .replace(/\b(document|window|top|self)\.location(\.href)?\s*=\s*(['"])\/(?!\/)/g, "document.location.href=$3/proxy/")
- .replace(/\blocation\.replace\(\s*(['"])\/(?!\/)/g, "location.replace($1/proxy/");
- html = html.replace(/]*>/i, m => m + '\n');
- res.send(html);
- } else if (ct.includes('text/css')) {
- let css = await r.text();
- css = css.replace(/url\(\s*(["']?)\/(?!\/)/g, 'url($1' + UPSTREAM + '/');
- res.send(css);
- } else {
- res.send(Buffer.from(await r.arrayBuffer()));
- }
- } catch (e) {
- res.status(502).send('proxy error: ' + e.message);
- }
-});
-
-// ══════════════════════════════════════════════════════════════
-// FULL REVERSE PROXY (everything else → student.sechenov.ru)
-// ══════════════════════════════════════════════════════════════
-
-function readRawBody(req) {
- return new Promise((resolve, reject) => {
- const chunks = [];
- req.on('data', c => chunks.push(c));
- req.on('end', () => resolve(Buffer.concat(chunks)));
- req.on('error', reject);
- });
-}
-
-const CHAT_OVERLAY = `
-
-
-
-
-`;
-
-// ══════════════════════════════════════════════════════════════
-// MIRROR — one-time asset mirror + in-memory HTML cache
-// Goal: cache everything locally, refresh HTML rarely, never
-// hammer sechenov. Steady-state: ~1 upstream request / 30 min.
-// ══════════════════════════════════════════════════════════════
-
-const MIRROR_DIR = path.join(PUBLIC_DIR, 'assets', 'mirror');
-const MIRROR_TTL_MS = 30 * 60 * 1000;
-const MIRROR_MIN_GAP = 60 * 1000;
-const UPSTREAM_DELAY = 500;
-const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
-
-let cachedMirrorHtml = null;
-let mirrorInProgress = false;
-let lastMirrorAt = 0;
-let mirrorSessionCookies = '';
-
-const sleep = ms => new Promise(r => setTimeout(r, ms));
-
-async function safeFetch(url, extraHeaders = {}) {
- const h = {
- 'User-Agent': BROWSER_UA,
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
- 'Accept-Language': 'ru,en;q=0.9',
- 'Referer': UPSTREAM + '/',
- ...extraHeaders,
- };
- if (mirrorSessionCookies) h['Cookie'] = mirrorSessionCookies;
- return fetch(url, { dispatcher: directAgent, headers: h });
-}
-
-function extractAssetUrls(html) {
- const urls = new Set();
- const patterns = [
- /(?:href|src)="(\/[^"\s]+)"/g,
- /url\(["']?(\/[^"'\s\)]+)/g,
- ];
- for (const p of patterns) {
- let m;
- while ((m = p.exec(html)) !== null) {
- const u = m[1];
- if (u.startsWith('//')) continue;
- if (u.startsWith('/api/') || u.startsWith('/assets/') || u.startsWith('/proxy/')) continue;
- urls.add(u);
- }
- }
- return [...urls];
-}
-
-async function mirrorAsset(urlPath) {
- const pure = urlPath.split(/[?#]/)[0];
- const local = path.join(MIRROR_DIR, pure);
- try { await fs.promises.stat(local); return false; } catch {}
- const r = await safeFetch(UPSTREAM + urlPath);
- if (!r.ok) { console.warn(`[mirror] ${r.status} ${urlPath}`); return false; }
- const ct = r.headers.get('content-type') || '';
- let buf;
- if (ct.includes('text/css')) {
- let css = await r.text();
- css = css.replace(/url\(\s*(["']?)\/(?!\/)/g, 'url($1/assets/mirror/');
- buf = Buffer.from(css, 'utf8');
- } else {
- buf = Buffer.from(await r.arrayBuffer());
- }
- await fs.promises.mkdir(path.dirname(local), { recursive: true });
- await fs.promises.writeFile(local, buf);
- console.log(`[mirror] + ${pure} (${buf.length}b)`);
- await sleep(UPSTREAM_DELAY);
- return true;
-}
-
-async function refreshMirror() {
- if (mirrorInProgress) return;
- if (Date.now() - lastMirrorAt < MIRROR_MIN_GAP && cachedMirrorHtml) return;
- mirrorInProgress = true;
- lastMirrorAt = Date.now();
- try {
- console.log('[mirror] fetching HTML…');
- const r = await safeFetch(UPSTREAM + '/auth.php');
- if (!r.ok) throw new Error('upstream ' + r.status);
-
- const setCookies = typeof r.headers.getSetCookie === 'function' ? r.headers.getSetCookie() : [];
- if (setCookies.length) {
- mirrorSessionCookies = setCookies.map(c => c.split(';')[0]).join('; ');
- }
-
- let html = await r.text();
- const assetUrls = extractAssetUrls(html);
- console.log(`[mirror] HTML ${html.length}b, ${assetUrls.length} assets referenced`);
-
- let downloaded = 0;
- for (const u of assetUrls) {
- try { if (await mirrorAsset(u)) downloaded++; } catch (e) { console.warn('[mirror]', u, e.message); }
- }
- console.log(`[mirror] downloaded ${downloaded} new assets (cached: ${assetUrls.length - downloaded})`);
-
- html = html
- .replace(/(href|src)="\/(?!\/)/g, '$1="/assets/mirror/')
- .replace(/url\(["']?\/(?!\/)/g, 'url(/assets/mirror/')
- .replace(/https?:\/\/student\.sechenov\.ru/gi, '')
- .replace(/]+http-equiv=["']?(Content-Security-Policy|X-Frame-Options)["']?[^>]*>/gi, '')
- .replace(/\btarget=["'](_top|_parent|_blank)["']/gi, '');
- html = html.replace(/<\/body>/i, CHAT_OVERLAY + '');
-
- cachedMirrorHtml = html;
- console.log('[mirror] ready');
- } catch (e) {
- console.error('[mirror fail]', e.message);
- } finally {
- mirrorInProgress = false;
- }
-}
-
-setTimeout(() => refreshMirror(), 3000);
-setInterval(() => refreshMirror(), MIRROR_TTL_MS);
-
-const serveMirror = (req, res) => {
- if (!cachedMirrorHtml) return res.status(503).send('mirror initializing — try again in 60s');
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
- res.send(cachedMirrorHtml);
-};
-
-// Entry points that render the cached mirror HTML (any HTTP method)
-app.all(['/m', '/m.html', '/mirror'], serveMirror);
-app.all(['/auth.php', '/index.php'], serveMirror);
-app.all(['/assets/mirror', '/assets/mirror/', '/assets/mirror/auth.php', '/assets/mirror/index.php'], serveMirror);
-
-app.use('/assets/mirror', express.static(MIRROR_DIR));
-
-// Reverse-proxy disabled: Sechenov banned us by IP. Left here for reference.
-app.use('/_disabled_reverse_proxy', async (req, res) => {
- const upstreamUrl = UPSTREAM + req.url;
-
- // Build forward headers
- const headers = {};
- for (const [k, v] of Object.entries(req.headers)) {
- const lk = k.toLowerCase();
- if (['host', 'content-length', 'x-forwarded-for', 'x-forwarded-proto',
- 'x-forwarded-host', 'x-real-ip', 'connection'].includes(lk)) continue;
- headers[k] = v;
- }
- if (!headers['user-agent']) {
- headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
- }
-
- let body;
- if (!['GET', 'HEAD'].includes(req.method)) {
- try { body = await readRawBody(req); } catch {}
- }
-
- try {
- const upstream = await fetch(upstreamUrl, {
- method: req.method, headers, body, redirect: 'manual',
- });
-
- res.status(upstream.status);
-
- // Set-Cookie: strip Domain= so they bind to our origin
- const setCookies = typeof upstream.headers.getSetCookie === 'function'
- ? upstream.headers.getSetCookie()
- : (upstream.headers.raw && upstream.headers.raw()['set-cookie']) || [];
- if (setCookies.length) {
- const rewritten = setCookies.map(c =>
- c.replace(/;\s*Domain=[^;]+/gi, '')
- .replace(/;\s*domain=[^;]+/gi, '')
- );
- res.setHeader('set-cookie', rewritten);
- }
-
- for (const [k, v] of upstream.headers) {
- const lk = k.toLowerCase();
- if (['set-cookie', 'content-encoding', 'content-length',
- 'transfer-encoding', 'x-frame-options',
- 'content-security-policy', 'strict-transport-security',
- 'connection'].includes(lk)) continue;
- if (lk === 'location') {
- res.setHeader('location', v.replace(new RegExp('^https?://student\\.sechenov\\.ru', 'i'), ''));
- continue;
- }
- res.setHeader(k, v);
- }
-
- const ct = upstream.headers.get('content-type') || '';
-
- if (ct.includes('text/html')) {
- let html = await upstream.text();
- html = html
- .replace(/https?:\/\/student\.sechenov\.ru/gi, '')
- .replace(/]+http-equiv=["']?(Content-Security-Policy|X-Frame-Options)["']?[^>]*>/gi, '');
- // inject chat overlay
- if (html.match(/<\/body>/i)) {
- html = html.replace(/<\/body>/i, CHAT_OVERLAY + '