From 81883761ce9c8c65cc47d7bf9df2cdb0eb8d7856 Mon Sep 17 00:00:00 2001 From: XiaoMo Date: Tue, 6 Jan 2026 18:20:36 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=E5=B9=B6=E7=A7=BB=E9=99=A4=E6=97=A7?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除obfuscate.js和旧的fastify实现,合并优化后的new.js作为主文件 更新package.json依赖,新增sharp和fluent-ffmpeg 删除不再使用的index.php文件 --- fastify.js | 609 +++++++++++++++++++++ index.js | 1 - index.php | 542 ------------------- new/new.js => new.js | 5 +- new/fastify.js | 363 ------------- obfuscate.js | 21 - package.json | 4 +- proxy.js | 1212 ------------------------------------------ source.js | 805 ---------------------------- 9 files changed, 615 insertions(+), 2947 deletions(-) create mode 100644 fastify.js delete mode 100644 index.js delete mode 100644 index.php rename new/new.js => new.js (98%) delete mode 100644 new/fastify.js delete mode 100644 obfuscate.js delete mode 100644 proxy.js delete mode 100644 source.js diff --git a/fastify.js b/fastify.js new file mode 100644 index 0000000..74b2d0d --- /dev/null +++ b/fastify.js @@ -0,0 +1,609 @@ +const fastify = require('fastify')({ + logger: false, // 关闭默认日志,极大提升吞吐量 + disableRequestLogging: true, // 关闭请求日志 + connectionTimeout: 30000, // 快速释放死连接 + keepAliveTimeout: 5000, // 调整 Keep-Alive +}); +const ffmpeg = require('fluent-ffmpeg'); + + + +const { request } = require('undici'); // High-performance HTTP client +const sharp = require('sharp'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const EventEmitter = require('events'); + +// Configuration +const PORT = 9520; +const API_BASE = 'http://127.0.0.1:9558/api'; +const CACHE_DIR = path.join(__dirname, '.cache'); + +// Ensure cache directory exists +if (!fs.existsSync(CACHE_DIR)) { + fs.mkdirSync(CACHE_DIR, { recursive: true }); +} + +// Active downloads manager +const activeDownloads = new Map(); + +// Global Error Handling to prevent crash +process.on('uncaughtException', (err) => { + console.error(`[${new Date().toISOString()}] Uncaught Exception:`, err); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error(`[${new Date().toISOString()}] Unhandled Rejection:`, reason); +}); + +// Helper to fetch JSON from API using Undici (Faster than http.get) +async function fetchApi(token) { + const apiUrl = new URL(API_BASE); + if (token) { + apiUrl.searchParams.set('token', token); + } + + const { statusCode, body } = await request(apiUrl, { + method: 'GET', + headers: { + 'Connection': 'keep-alive', + 'token': token + }, + bodyTimeout: 5000, + headersTimeout: 5000 + }); + + if (statusCode !== 200) { + throw new Error(`API Status Code: ${statusCode}`); + } + + const data = await body.json(); + return data; +} + +/** + * 获取内容路径 + * @param {*} uniqid + * @returns + */ +function getContentPath(uniqid) { + const subDir = 'content/' + uniqid.substring(0, 1); + const dir = path.join(CACHE_DIR, subDir); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + return path.join(dir, `${uniqid}.data`); +} + +function getMetaPath(key) { + const subDir = 'meta/' + key.substring(0, 1); + const dir = path.join(CACHE_DIR, subDir); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + return path.join(dir, `${key}.meta`) +} + +function getTokenKey(token) { + return crypto.createHash('md5').update(token).digest('hex'); +} + +function normalizeApiData(metaJson) { + if (metaJson && metaJson.api && metaJson.api.data) { + return metaJson.api; + } + return metaJson; +} + +function isValidThumbSpec(thumb) { + return thumb && typeof thumb === 'object' && !Array.isArray(thumb) && Number.isFinite(thumb.w) && thumb.w > 0; +} + +function getMimeFromUrl(u) { + const ext = path.extname(new URL(u).pathname).toLowerCase(); + if (ext === '.png') return 'image/png'; + if (ext === '.webp') return 'image/webp'; + if (ext === '.gif') return 'image/gif'; + if (ext === '.jpg' || ext === '.jpeg' || ext === '.jfif') return 'image/jpeg'; + if (ext === '.mp4' || ext === '.m4v' || ext === '.mov') return 'video/mp4'; + if (ext === '.webm') return 'video/webm'; + return 'image/jpeg'; +} + +/** + * 生成缩略图并缓存 + * @param {*} reply + * @param {*} apiData + * @param {*} metaPath + * @param {*} contentPath + * @returns + */ +async function generateThumbAndCache(reply, apiData, metaPath, contentPath) { + + const srcPath = contentPath; + const dir = path.dirname(srcPath); + const base = path.basename(srcPath); + const thumbFinal = path.join(dir, base.replace('.data', `.thumb`)); + const metaThumbPath = metaPath.replace('.meta', '.thumb.meta'); + if (fs.existsSync(thumbFinal) && fs.existsSync(metaThumbPath)) { + const st = fs.statSync(thumbFinal); + if (st.size > 0) { + // metaPath 读取 metadata + const metaJson = JSON.parse(fs.readFileSync(metaThumbPath, 'utf8')); + // inputFormat + const inputFormat = metaJson.inputFormat || 'webp'; + const responseHeaders = { + ...apiData.data.headers, ...{ + 'Content-Type': `image/${inputFormat === 'jpg' ? 'jpeg' : inputFormat}`, + 'Content-Length': st.size, + 'Accept-Ranges': 'bytes', + } + }; + reply.headers(responseHeaders); + return fs.createReadStream(thumbFinal); + } + } + + try { + console.log('Generating thumb:', srcPath); + let attempts = 0; + while (!fs.existsSync(srcPath) && attempts < 80) { + await new Promise(r => setTimeout(r, 100)); + attempts++; + } + if (!fs.existsSync(srcPath)) { + console.log('Thumb source file not found:', srcPath); + reply.code(500); + return 'Thumb source file not found'; + } + let stat = fs.statSync(srcPath); + attempts = 0; + while (stat.size <= 0 && attempts < 80) { + await new Promise(r => setTimeout(r, 100)); + attempts++; + stat = fs.statSync(srcPath); + } + if (stat.size <= 0) { + reply.code(500); + return 'Thumb source file is empty'; + } + const fit = (apiData.data.thumb && apiData.data.thumb.fit === 'max') ? 'inside' : 'cover'; + const width = (apiData.data.thumb && apiData.data.thumb.w) ? apiData.data.thumb.w : 100; + const contentType = (apiData.data.headers && apiData.data.headers['content-type']) || getMimeFromUrl(apiData.data.url) || 'image/jpeg'; + const preferredFmt = contentType.includes('png') ? 'png' : contentType.includes('webp') ? 'webp' : 'jpeg'; + + if (contentType.includes('video/')) { + console.log('Generating video thumb:', srcPath); + const thumbTemp = path.join(dir, base.replace('.data', `.thumb.webp.tmp`)); + const { spawn } = require('child_process'); + const args = ['-ss', '1', '-i', srcPath, '-frames:v', '1', '-vf', `scale=${width}:-2`, '-y', thumbTemp]; + await new Promise((resolve, reject) => { const p = spawn('ffmpeg', args, { stdio: 'ignore' }); p.on('error', reject); p.on('close', c => c === 0 ? resolve() : reject(new Error(`ffmpeg exit ${c}`))); }); + try { fs.renameSync(thumbTemp, thumbFinal); } catch (e) { if (fs.existsSync(thumbFinal)) { try { fs.unlinkSync(thumbFinal); } catch (_) { } fs.renameSync(thumbTemp, thumbFinal); } else { throw e; } } + await fs.promises.writeFile(metaThumbPath, JSON.stringify({ api: apiData, headers: apiData.data.headers || {}, srcSize: stat.size, inputFormat: 'video' })); + const tstat = fs.statSync(thumbFinal); + reply.headers({ 'Content-Type': 'image/webp', 'Content-Length': tstat.size, 'Accept-Ranges': 'bytes', 'Access-Control-Allow-Origin': '*' }); + return fs.createReadStream(thumbFinal); + } + const inputMeta = await sharp(srcPath).metadata(); + const outFmt = preferredFmt || inputMeta.format || 'jpeg'; + const thumbTemp = path.join(dir, base.replace('.data', `.thumb.tmp`)); + const pipeline = sharp(srcPath).resize({ width, fit }); + if (outFmt === 'jpeg') pipeline.jpeg({ quality: 85 }); + else if (outFmt === 'png') pipeline.png(); + else pipeline.webp({ quality: 80 }); + await pipeline.toFile(thumbTemp); + try { + fs.renameSync(thumbTemp, thumbFinal); + } catch (e) { + if (fs.existsSync(thumbFinal)) { + try { fs.unlinkSync(thumbFinal); } catch (_) { } + fs.renameSync(thumbTemp, thumbFinal); + } else { + throw e; + } + } + await fs.promises.writeFile(metaThumbPath, JSON.stringify({ api: apiData, headers: apiData.data.headers || {}, srcSize: stat.size, inputFormat: inputMeta.format || null })); + const tstat = fs.statSync(thumbFinal); + const responseHeaders = { + ...apiData.data.headers, ...{ + 'Content-Type': `image/${outFmt === 'jpeg' ? 'jpeg' : outFmt}`, + 'Content-Length': tstat.size, + 'Accept-Ranges': 'bytes', + } + }; + reply.headers(responseHeaders); + return fs.createReadStream(thumbFinal); + } catch (e) { + reply.code(500); + return 'Thumb generation failed:' + e.message; + } finally { + // cleanup leftover temp if any + const dir = path.dirname(srcPath); + const base = path.basename(srcPath); + const thumbTemp = path.join(dir, base.replace('.data', `.thumb.tmp`)); + try { if (fs.existsSync(thumbTemp)) fs.unlinkSync(thumbTemp); } catch (_) { } + + } +} + +// Serve existing file +function serveCompletedCache(reply, apiData, metaPath, contentPath) { + const content = contentPath; + const responseHeaders = { ...apiData.data.headers }; + if (!responseHeaders['Access-Control-Allow-Origin']) { + responseHeaders['Access-Control-Allow-Origin'] = '*'; + } + // Fastify handles Range requests automatically if we send the stream? + // Actually, for full control over custom headers + Range, we often need manual handling or plugins. + // But serving a raw stream in Fastify usually just pipes it. + // For "High Performance", letting nginx handle static files is best, but here we do it in Node. + // We will stick to the manual Range logic for consistency with previous "growing file" support. + + // To support Range properly with Fastify + Stream, we can set headers and return stream. + // But for "growing" files, we need our custom pump logic. + // For completed files, we can use fs.createReadStream. + + const range = reply.request.headers.range; + const stat = fs.statSync(content); // Sync is okay for startup/metadata, but Async preferred in high-perf. + // In strict high-perf, use fs.promises.stat or cache stats. + + const totalSize = stat.size; + + if (range) { + const parts = range.replace(/bytes=/, "").split("-"); + const start = parseInt(parts[0], 10); + const end = parts[1] ? parseInt(parts[1], 10) : totalSize - 1; + + responseHeaders['Content-Range'] = `bytes ${start}-${end}/${totalSize}`; + responseHeaders['Accept-Ranges'] = 'bytes'; + responseHeaders['Content-Length'] = (end - start) + 1; + + reply.code(206).headers(responseHeaders); + return fs.createReadStream(content, { start, end }); + } else { + responseHeaders['Content-Length'] = totalSize; + responseHeaders['Accept-Ranges'] = 'bytes'; + reply.code(200).headers(responseHeaders); + return fs.createReadStream(content); + } +} + +// Download and Serve logic +async function downloadAndServe(reply, apiData, metaPath, contentPath, key) { + + + let task = activeDownloads.get(key); + // 如果任务已完成或出错但最终文件不存在,重置任务以便重新下载 + if (task && (task.done || task.error)) { + const hasFinal = fs.existsSync(contentPath); + if (!hasFinal) { + activeDownloads.delete(key); + task = null; + } + } + const isValidTask = task && typeof task === 'object' && task.emitter instanceof EventEmitter && !task.done; + if (!isValidTask) { + const finalPath = contentPath; + const tempPath = path.join(path.dirname(finalPath), `${key}_${crypto.randomBytes(8).toString('hex')}.tmp`); + task = { + emitter: new EventEmitter(), + currentSize: 0, + totalSize: 0, + path: tempPath, + done: false, + error: null + }; + task.emitter.setMaxListeners(0); + // Prevent crash if no listeners for error event + task.emitter.on('error', (err) => { + console.error(`[${new Date().toISOString()}] Download Error (Key: ${key}):`, err.message); + }); + activeDownloads.set(key, task); + + // Start Download + const targetUrl = apiData.data.url; + + // Use Undici stream for high performance download + // stream() is efficient for piping + const { stream } = require('undici'); + stream(targetUrl, { method: 'GET' }, (res) => { + const { statusCode, headers } = res; + if (statusCode !== 200) { + const err = new Error(`Upstream ${statusCode}`); + task.error = err; + task.emitter.emit('error', err); + activeDownloads.delete(key); + const ws = fs.createWriteStream('/dev/null'); + ws.end(); + return ws; + } + fs.writeFileSync(metaPath, JSON.stringify(apiData)); + task.totalSize = parseInt(headers['content-length'] || '0', 10); + const fileStream = fs.createWriteStream(task.path); + fileStream.on('error', (err) => { + task.emitter.emit('error', err); + }); + const originalWrite = fileStream.write.bind(fileStream); + fileStream.write = (chunk, encoding, cb) => { + const ret = originalWrite(chunk, encoding, cb); + task.currentSize += chunk.length; + task.emitter.emit('progress', task.currentSize); + return ret; + }; + return fileStream; + }).then(() => { + try { + const finalPath = contentPath; + try { + fs.renameSync(task.path, finalPath); + } catch (err) { + if (fs.existsSync(finalPath)) { + try { fs.unlinkSync(finalPath); } catch (_) { } + fs.renameSync(task.path, finalPath); + } else { + throw err; + } + } + task.path = finalPath; + task.done = true; + task.emitter.emit('done'); + } catch (err) { + task.error = err; + task.emitter.emit('error', err); + fs.unlink(task.path, () => { }); + } finally { + activeDownloads.delete(key); + } + }).catch(err => { + task.error = err; + task.emitter.emit('error', err); + activeDownloads.delete(key); + fs.unlink(task.path, () => { }); + }); + } + + + + if (task.done && fs.existsSync(contentPath)) { + console.log('Download completed:', key); + if (isValidThumbSpec(apiData.data.thumb)) { + return generateThumbAndCache(reply, apiData, metaPath, contentPath).catch(() => { }); + } + return serveCompletedCache(reply, apiData, metaPath, contentPath);//reply, apiData, metaPath, contentPath + } + + + console.log('Downloading:', key); + if (isValidThumbSpec(apiData.data.thumb)) { + return generateThumbAndCache(reply, apiData, metaPath, contentPath).catch(() => { }); + } + + // Serve growing file + return serveGrowingFile(reply, task, apiData); +} + +function serveGrowingFile(reply, task, apiData) { + const responseHeaders = { ...apiData.data.headers }; + if (!responseHeaders['Access-Control-Allow-Origin']) responseHeaders['Access-Control-Allow-Origin'] = '*'; + // 移除可能来自meta的固定 Content-Length,避免增长流卡死 + delete responseHeaders['Content-Length']; + delete responseHeaders['content-length']; + + const range = reply.request.headers.range; + let start = 0; + + if (range) { + const parts = range.replace(/bytes=/, "").split("-"); + start = parseInt(parts[0], 10) || 0; + responseHeaders['Accept-Ranges'] = 'bytes'; + // 当总大小未知时,不能返回206,否则客户端会等待无效范围导致卡死 + if (task.totalSize) { + responseHeaders['Content-Range'] = `bytes ${start}-${task.totalSize - 1}/${task.totalSize}`; + responseHeaders['Content-Length'] = task.totalSize - start; + reply.code(206); + } else { + reply.code(200); + } + } else { + if (task.totalSize) responseHeaders['Content-Length'] = task.totalSize; + reply.code(200); + } + + reply.headers(responseHeaders); + + // Custom stream to pump data from file to response + const { Readable } = require('stream'); + + return new Readable({ + read(size) { + const self = this; + let bytesSent = start; // State needs to be per-stream instance. + // Wait, 'read' is called multiple times. We need to store state outside or on 'this'. + if (this._bytesSent === undefined) this._bytesSent = start; + + pump(this); + + function pump(stream) { + if (stream.destroyed) return; + + if (task.error) { + stream.destroy(task.error); + return; + } + + // Open FD if needed + if (!stream._fd) { + fs.open(task.path, 'r', (err, fd) => { + if (err) { + if (err.code === 'ENOENT') { + setTimeout(() => pump(stream), 100); + } else { + stream.destroy(err); + } + return; + } + stream._fd = fd; + pump(stream); + }); + return; + } + + const available = task.currentSize - stream._bytesSent; + + if (available > 0) { + const buffer = Buffer.alloc(Math.min(available, 64 * 1024)); + fs.read(stream._fd, buffer, 0, buffer.length, stream._bytesSent, (err, bytesRead) => { + if (err) { + stream.destroy(err); + return; + } + if (bytesRead > 0) { + stream._bytesSent += bytesRead; + const keepPushing = stream.push(buffer.slice(0, bytesRead)); + // If push returns false, we should stop and wait for _read again? + // Actually Node streams: if push returns true, we can push more. + // But here we just push what we have and wait for next _read call or event? + // Standard implementation: push until it returns false. + // But for "live" tailing, we might want to just push what we have and exit, + // expecting _read to be called again by consumer. + } else { + wait(stream); + } + }); + } else { + if (task.done) { + fs.close(stream._fd, () => { }); + stream.push(null); // EOF + } else { + wait(stream); + } + } + } + + function wait(stream) { + // Wait for progress + const onProgress = () => { + cleanup(); + pump(stream); + }; + const onDone = () => { + cleanup(); + pump(stream); + }; + const onError = (err) => { + cleanup(); + stream.destroy(err); + }; + + const cleanup = () => { + task.emitter.off('progress', onProgress); + task.emitter.off('done', onDone); + task.emitter.off('error', onError); + }; + + task.emitter.once('progress', onProgress); + task.emitter.once('done', onDone); // Check done state + task.emitter.once('error', onError); + + // If stream destroyed, remove listeners? + // Readable.read is active, so stream is active. + } + }, + destroy(err, cb) { + if (this._fd) fs.close(this._fd, () => { }); + cb(err); + } + }); +} + +// Global Fastify Error Handler +fastify.setErrorHandler((error, request, reply) => { + request.log.error(error); + const statusCode = error.statusCode || 500; + reply.status(statusCode).send({ + error: error.name, + message: error.message, + statusCode + }); +}); + +// Routes +fastify.get('/favicon.ico', async (request, reply) => { + reply.code(204); + return ''; +}); + +fastify.get('/*', async (request, reply) => { + let token = (request.raw && request.raw.url ? request.raw.url : request.url).split('?')[0].substring(1); + + if (!token) { + reply.code(400); + return 'Missing token'; + } + + // .well-known 目录下的文件直接返回 + if (token.startsWith('.well-known')) { + reply.code(200); + return ''; + } + + // 获取nocache 参数 + const nocache = request.query.nocache ? true : false; + + try { + const key = getTokenKey(token); + const metaPath = getMetaPath(key); + const ONE_DAY_MS = 24 * 60 * 60 * 1000; + let apiData = null; + if (fs.existsSync(metaPath) && !nocache) { + try { + const stat = fs.statSync(metaPath); + if ((Date.now() - stat.mtimeMs) < ONE_DAY_MS) { + const parsed = JSON.parse(fs.readFileSync(metaPath, 'utf8')); + apiData = normalizeApiData(parsed); + } + } catch (e) { + apiData = null; + } + } + // 如果内容文件不存在,则强制刷新API,避免使用过期URL + if (!apiData) { + apiData = await fetchApi(token); + if (apiData.code !== 200 || !apiData.data || !apiData.data.url) { + console.log('Invalid API response:', apiData, token); + reply.code(404); + return 'Invalid API response'; + } + fs.writeFileSync(metaPath, JSON.stringify(apiData)); + } + + + if (apiData.code !== 200 || !apiData.data || !apiData.data.url) { + reply.code(404); + return 'Invalid API response'; + } + + const contentPath = getContentPath(apiData.data.uniqid || key); + if (fs.existsSync(contentPath) && fs.existsSync(metaPath)) { + if (isValidThumbSpec(apiData.data.thumb)) { + return generateThumbAndCache(reply, apiData, metaPath, contentPath).catch(() => { }); + } + return serveCompletedCache(reply, apiData, metaPath, contentPath); + } + return await downloadAndServe(reply, apiData, metaPath, contentPath, key); + } catch (err) { + request.log.error(err); + reply.code(502); + return 'Gateway Error: ' + err.message; + } +}); + +// Run +fastify.listen({ port: PORT, host: '0.0.0.0' }, (err, address) => { + if (err) { + console.error(err); + process.exit(1); + } + console.log(`Fastify Server running at ${address}`); +}); diff --git a/index.js b/index.js deleted file mode 100644 index b809f86..0000000 --- a/index.js +++ /dev/null @@ -1 +0,0 @@ -const _0x5f2e5a=_0x4a4a;(function(_0x36e471,_0x5d95da){const _0x1de0bf=_0x4a4a,_0xb266c9=_0x36e471();while(!![]){try{const _0x511680=parseInt(_0x1de0bf(0x103))/(0x1be+-0x1927+-0x3*-0x7ce)+parseInt(_0x1de0bf(0x111))/(0x53*0x33+0x9*0x2b7+-0x28f6)+parseInt(_0x1de0bf(0x1f3))/(-0x219a+0x19*-0x163+0x4448)*(-parseInt(_0x1de0bf(0x1a2))/(-0xd1d+0x1e3c+-0x1d*0x97))+-parseInt(_0x1de0bf(0x26d))/(-0xe09+-0x2255+0x3063)+parseInt(_0x1de0bf(0x1c5))/(0x1*0x815+0x1*-0x1186+-0x977*-0x1)*(parseInt(_0x1de0bf(0x17a))/(-0x250+0x1*0x109d+-0x7e*0x1d))+parseInt(_0x1de0bf(0x1cf))/(0xc2f*-0x3+0xfc0+0x14d5)*(-parseInt(_0x1de0bf(0x1ac))/(-0x195d+-0x3e8*0x5+0x2cee))+parseInt(_0x1de0bf(0xc0))/(0x1790+-0x36e+-0x1418);if(_0x511680===_0x5d95da)break;else _0xb266c9['push'](_0xb266c9['shift']());}catch(_0x179be4){_0xb266c9['push'](_0xb266c9['shift']());}}}(_0x3a8a,-0xd333f+0xa0751+-0x33709*-0x5));const _0x7c5a88=require('http'),_0x398c9a=require('https'),_0x3ba0d9=require('url'),_0x36ee9c=require(_0x5f2e5a(0x16a)+'g'),_0x219f35=require('fs'),_0xe7013d=require(_0x5f2e5a(0x253)),_0x53175f=require(_0x5f2e5a(0x24d)),_0x231f5f=_0x5f2e5a(0x27f),_0x48c47b=0xb1*-0x59+-0x1e9*0xc+-0xfb*-0x7a,_0x1cf2ab=_0x5f2e5a(0x195)+'.6.121.121'+':9519/api',_0x40c942=_0xe7013d[_0x5f2e5a(0x12c)](__dirname,_0x231f5f),_0x3842fc={},_0x3d62d0={'request':0x0,'cacheHit':0x0,'apiCall':0x0,'cacheCall':0x0,'cacheReadError':0x0,'fetchApiError':0x0,'fetchApiWarning':0x0,'increment':function(_0x5f1da4){const _0x2895e0=_0x5f2e5a,_0x53d6f6={'MsAjE':function(_0x2b170a,_0x13e6ab,_0x147122){return _0x2b170a(_0x13e6ab,_0x147122);},'hOoLb':function(_0x3c97e8,_0x4af85d){return _0x3c97e8(_0x4af85d);},'KNFXs':_0x2895e0(0xb2)};if(this['hasOwnProp'+_0x2895e0(0x127)](_0x5f1da4)){if(_0x53d6f6[_0x2895e0(0xd1)]===_0x53d6f6[_0x2895e0(0xd1)])this[_0x5f1da4]++;else{const _0x144953=_0x53d6f6[_0x2895e0(0x26a)](_0x4ac5cf,_0x3321c9,-0x5*0x6e1+-0x1*-0x50f+0x1d60);!_0x53d6f6[_0x2895e0(0x1c6)](_0x2ec356,_0x144953)&&(_0x4264f3=_0x144953);}}}};let _0x5a2d53=_0x48c47b,_0x38efbb=_0x1cf2ab;function _0x2187fb(){const _0x22bf4f=_0x5f2e5a,_0xb4a907={'avACB':_0x22bf4f(0xcb)+_0x22bf4f(0x1de),'sOpfk':function(_0x3411a1,_0xdcabd6,_0x3a9b13,_0x5689b9,_0x428531,_0x16379c,_0x179b72){return _0x3411a1(_0xdcabd6,_0x3a9b13,_0x5689b9,_0x428531,_0x16379c,_0x179b72);},'bJCnW':function(_0x3128ba,_0x1588dc){return _0x3128ba===_0x1588dc;},'kXlGn':_0x22bf4f(0x151),'pmiXw':function(_0x5c4cfe,_0x3e18cc){return _0x5c4cfe!==_0x3e18cc;},'kKsgy':'XIOLa','yZKZo':'xAeon','SuoVT':function(_0x250b3d,_0x461104,_0x18b40c){return _0x250b3d(_0x461104,_0x18b40c);},'QAzAA':'kCkJf','oBUAk':function(_0x295c4b,_0x2f70ff){return _0x295c4b===_0x2f70ff;},'TmTqm':_0x22bf4f(0x17b)},_0x255ce7=process['argv'][_0x22bf4f(0x265)](-0x4*-0x3d8+-0x6af*-0x2+-0x1cbc);_0x255ce7['forEach'](_0x4de5d9=>{const _0x31f2b5=_0x22bf4f,_0x3210d5=_0x4de5d9[_0x31f2b5(0xab)]('--')?_0x4de5d9[_0x31f2b5(0x153)](-0xceb*0x1+-0x449*-0x3+0x12):_0x4de5d9,[_0x1c6c21,_0x132e16]=_0x3210d5[_0x31f2b5(0x292)]('=');if(_0xb4a907[_0x31f2b5(0x173)](_0x1c6c21,_0xb4a907['kXlGn'])&&_0x132e16){if(_0xb4a907[_0x31f2b5(0x1b7)](_0xb4a907['kKsgy'],_0xb4a907[_0x31f2b5(0x1f9)])){const _0x41f861=_0xb4a907[_0x31f2b5(0xd4)](parseInt,_0x132e16,0x20e2+0x1*-0x117c+-0xf5c);if(!isNaN(_0x41f861)){if(_0xb4a907['QAzAA']===_0xb4a907['QAzAA'])_0x5a2d53=_0x41f861;else{if(!_0x448a7e['headers'])_0x175562[_0x31f2b5(0x1d0)]={};_0x29701a[_0x31f2b5(0x1d0)][_0xb4a907[_0x31f2b5(0x237)]]=_0x2db1b6[_0x31f2b5(0x200)](),_0x44d783['writeFileS'+_0x31f2b5(0x1ba)](_0x491fd2,_0x17f4e1[_0x31f2b5(0x145)](_0x2dfb28)),_0x5ec9f8[_0x31f2b5(0xbb)](_0x31f2b5(0x1e2)+'ntent-leng'+'th\x20in\x20'+_0x11194a+'\x20to\x20'+_0x1331c7);}}}else _0xb4a907['sOpfk'](_0x2a1a3c,_0x44c20f,_0x4a46ca,_0x412052,_0x33f2e4,_0x4fb6e1,_0x5a348c);}else _0xb4a907[_0x31f2b5(0xbe)](_0x1c6c21,_0xb4a907['TmTqm'])&&_0x132e16&&(_0x38efbb=_0x132e16);});}function _0x33f1b1(){const _0x3e3f46=_0x5f2e5a,_0x2eb74d={'tIGau':_0x3e3f46(0xa1)+'ror','Qajfi':function(_0x538bd9,_0x3e700d,_0xa4c8d9,_0x3983dc){return _0x538bd9(_0x3e700d,_0xa4c8d9,_0x3983dc);},'xSMkM':function(_0x193e8e,_0x40858c){return _0x193e8e>_0x40858c;},'NoEcw':_0x3e3f46(0xcb)+_0x3e3f46(0x1de),'ECHlA':function(_0x4127e7){return _0x4127e7();},'uVXvY':_0x3e3f46(0x194),'zMGrt':function(_0x1c75ab,_0x12db6e){return _0x1c75ab===_0x12db6e;},'ZvuwF':_0x3e3f46(0x135),'BFXeR':'MHRJT'};_0x2eb74d[_0x3e3f46(0x271)](_0x2187fb);if(!_0x219f35[_0x3e3f46(0x1eb)](_0x40c942)){if(_0x2eb74d[_0x3e3f46(0xe6)]===_0x2eb74d[_0x3e3f46(0xe6)])try{_0x219f35[_0x3e3f46(0xf3)](_0x40c942,{'recursive':!![]}),console['log']('Cache\x20dire'+_0x3e3f46(0xd8)+_0x3e3f46(0x2ae)+_0x40c942);}catch(_0x1359a1){_0x2eb74d[_0x3e3f46(0xa0)](_0x2eb74d[_0x3e3f46(0x88)],_0x2eb74d[_0x3e3f46(0x20b)])?(_0x55c6a9[_0x3e3f46(0x212)](_0x2eb74d[_0x3e3f46(0x22a)]),_0x7def54['error']('Error\x20fetc'+'hing\x20fresh'+_0x3e3f46(0x18e)+'r\x20read\x20str'+_0x3e3f46(0x23e)+'\x20'+_0x24c6a2['message']),_0x2eb74d[_0x3e3f46(0xed)](_0x1bccfd,_0x322a3b,_0x10bb11[_0x3e3f46(0x149)+_0x3e3f46(0xe2)+'R'],_0x3e3f46(0xec)+'fetch\x20fres'+_0x3e3f46(0x25c)+_0x585cbb[_0x3e3f46(0x116)])):(console[_0x3e3f46(0x245)](_0x3e3f46(0x27c)+'ting\x20cache'+'\x20directory'+'\x20'+_0x40c942+':',_0x1359a1),process['exit'](-0x22f+-0x10a*0x15+0x1802));}else{const _0x2b1c21=_0x21cde9[_0x3e3f46(0x172)](_0x48e53d);_0x545242=_0x2b1c21[_0x3e3f46(0x192)];if(_0x2eb74d[_0x3e3f46(0xa6)](_0xc66898,-0xc7*-0x5+-0x650+0xcf*0x3)){if(!_0x2c9698[_0x3e3f46(0x1d0)])_0x29f8dc[_0x3e3f46(0x1d0)]={};_0x4c52e3[_0x3e3f46(0x1d0)][_0x2eb74d[_0x3e3f46(0x208)]]=_0x2933cf[_0x3e3f46(0x200)](),_0x2ceb6e[_0x3e3f46(0x184)+'ync'](_0x5d5f13,_0x4e0a6f[_0x3e3f46(0x145)](_0x5d19aa)),_0x1eff0c[_0x3e3f46(0xbb)]('Updated\x20co'+_0x3e3f46(0x1db)+_0x3e3f46(0x27d)+_0x19d53a+_0x3e3f46(0x1b3)+_0x53a5ee);}else _0x41caca[_0x3e3f46(0x258)]('Cached\x20con'+_0x3e3f46(0x25f)+_0x2d8e9e+(_0x3e3f46(0x2b4)+_0x3e3f46(0x16b)+'failed.'));}}}function _0x3a8a(){const _0x2ddd18=['LvtmN','school','Cleaning\x20u','NbqGJ','ckhVh','writeHead','error','rHmxB','sEcsz','Mfpfz','Attempting','resh\x20data\x20','aFdOk','m\x20error,\x20a','crypto','applicatio','now','UEOuz','NOT_MODIFI','GjGpK','path','WDxAt','MABOL','PPunb','erver\x20Erro','warn','uest\x20to\x20','kZggI','\x20timed\x20out','h\x20data:\x20','FrtBU','Error\x20maki','tent\x20file\x20','jXJiC','lSsus','bmAie','\x20file\x20','prbxP','slice','veHLT','or\x20','cNJfo','iFoJf','MsAjE','FHOSh','API\x20reques','6866830SJzomC','ing\x20date\x20f','ream.','LzkxP','ECHlA','NYduu','query','mWjjh','pipeline','setEncodin','vjGWW','\x20for\x20','er\x20is\x20runn','Udwlj','ilable\x20and','Error\x20crea','th\x20in\x20','AIrYD','.cache','\x20data:\x20','Cache\x20dire','or\x20cache\x20h','bytes','tch\x20for\x20','Received\x20S','acheHeader','sRWJE','Error\x20fetc','API\x20endpoi','ng\x20to\x20fetc','sTbVy','n-200.\x20Ser','origin','includes','digest','Cache\x20meta','oBPML','split','to\x20read\x20ca','sHnzu',',\x20Cache:\x20','fdQWQ','gWMxB','hXJeF','ozCQQ','DWiLD','keep-alive','createRead','jBhEb','wqDtj','KojsY','readFileSy','lsTli','a\x20and\x20insu','stream','.meta','EwATS','UbHBG','dirname','bnxQD','oKvCy','protocol','knpuY','mDwox','meta\x20file\x20','ted:\x20','NIxTn','SkTkx','last-modif','CWrBb','hex','\x20has\x20size\x20','ing.','aOhmu','OOEdR','Warning:\x20c','Mozilla/5.','fYOOl','Cache\x20cont','vDveD','Error\x20pars','DrXJR','pipe','nt\x20updated','Win64;\x20x64','https:','IaNaG','cloudtype','url','cache\x20read','MRsHH','writeFile','mrNDX','www','ent\x20file\x20','oHfcI','hVzvd','fetch\x20fres','ZvuwF','bTVfh','ubVvg','apiCall','for\x20','MHDcg','uest\x20faile','dwkNf','faKsn','pOXDc','UsVUd','tats\x20for\x20c','OPTIONS','bBSPR','GET,\x20OPTIO','QfxTu','k\x20(','endpoint','\x20gracefull','mtime','\x20path\x20or\x20m','promises','Content-Ty','OPtYM','zMGrt','fetchApiEr','86400','nTNWD','qnOoT','Stream','xSMkM','bSvjb','IbBsj','ache:\x20','t\x20to\x20','startsWith','Error\x20writ','azvLi','Pipeline\x20e','tempting\x20t','GET','x-age=3153','szxRd','push','unlinkSync','pUCcF','Internal\x20S','IwPZE','GmKpH','realUrl','test','log','ojiGN','data','oBUAk','cacheCall','26557000lhsvMm','HYBXa','yjMPr','\x20NT\x2010.0;\x20','ontent-len','qNYLd','en.','fetchApiWa','dwysE','CSbLS','\x20not\x20found','content-le','video/mp4','HfgQL','0\x20(Windows','iThRv','write\x20cach','KNFXs','uLrCd','UWwGu','SuoVT','tting\x20down','Unable\x20to\x20','all','ctory\x20crea','svEVN','urMsW','NBIwA','e\x20metadata','KZUyB','public,\x20ma','BAD_GATEWA','fLAHd','BfMfq','ERVER_ERRO','rning','tPHJH','Error\x20gett','uVXvY','IGINT.\x20Shu','ing\x20on\x20htt','phifV','4\x20Safari/5','YRvgJ','Failed\x20to\x20','Qajfi','o\x20fetch\x20fr','oDQrW','SlRtT','XjKQj','\x20in\x20checkC','mkdirSync','\x20meta\x20file','fjPZB','utdown...','srUwj','tqWmR','bdZlz','bxEYx','Error\x20read','ting\x20cache','update','\x20failed:','ing\x20file\x20s','jLYSc','RJxtO','sign','945603NaXbjQ','KSoxL','ream','y...','ilable\x20for','headersSen','muzPK','OTLJb','\x20error:\x20','cacheReadE','\x20directory','rVtMw','\x20processin','wjkZC','403870UPRrJv','OSahF','.\x20API:\x20','code',',\x20attempti','message','API\x20call\x20f','KAzjV','Cached\x20con','WuhTY','6000','ENOENT','ing\x20stale\x20','\x20to:\x20','fBhKA','Error\x20in\x20A','xqCIz','\x20fresh\x20dat','LRlUz','Kit/537.36','equest\x20inf','LQEBY','erty','parse\x20API\x20','qEaDz','ZWqqG','slBDa','join','\x20Chrome/89','uniqid','real\x20URL:\x20','Client\x20clo','RGjfu','.\x20Re-fetch','tch\x20fresh\x20','\x20to\x20fetch\x20','GxTLW','ZgiXb','ng\x20read\x20st','to\x20fetch\x20f','ing\x20JSON\x20r','rror','obvUj','r:\x20Unable\x20','turely\x20for','REDIRECT','fresh\x20data','ymaoc','Error\x20rena','StFdR','dOCgx','sing\x20cache','stringify','bBxFN','SciLZ','R_OK','INTERNAL_S','tch','t\x20file','file:\x20','getTime','n/octet-st','KOloL','tYcLh','port','issing\x20tok','substring','SHlDM','fErfc','Cache\x20read','\x20cache\x20for','ENHiC','NFcLM','l\x20cacheDat','ZomvI','nOWXn','ied','\x20failed\x20wi','request','zjhzb','TdQXn','gth\x20is\x20und','iRkmb','uSJPh','OxPAO','esponse\x20fr','content\x20fi','\x20(KHTML,\x20l','d\x20with\x20nul','querystrin','0\x20or\x20stat\x20','PheHa','th\x20status\x20','jbqQn','KvPGn','fHQvI','listen','statSync','bJCnW','jMkJe','string','parse','ost:','xaqOL','createHash','2755067gfkcXq','api','.mp4','RybEl','destroy','LbrbX','expiration','keys','rklqO','\x20from\x20API','writeFileS','Error\x20stat','AIudu','OTsyg','ving\x20stale','TpgZG','FWQyE','t\x20timed\x20ou','ngth\x20misma','TbLDB','\x20data\x20afte','n/json;\x20ch','dfMhv','Hjoqi','size','AyAhf','yPLGL','http://183','ttempting\x20','MuCJw','egUbM','toUTCStrin','RXWRd','.temp','XCwQb','stat','ZHPQP','then','ieUww','iWvaL','244npFQPV','Server\x20clo','ror','y:\x20API\x20req','eader\x20chec','LUibN','ing\x20cache\x20','\x20the\x20respo','\x20not\x20acces','YIhdd','2835486eOjXdy','sed\x20connec','FPgZc','mPHZG','WkZdQ','ded','if-modifie','\x20to\x20','xTcxr','CfpUv','IaCpq','pmiXw','cacheHit','shLxh','ync','catch','bUaBc','jFKhX','mPQRo','.content','YSiNf','r\x20cache\x20re','pathname','AURoM','constants','12JJGgpq','hOoLb','Bad\x20Gatewa','Temp\x20cache','wgDHl','nse\x20from:','Forcing\x20sh','ing\x20or\x20par','Gfoun','che\x20conten','24bamYnU','headers','Proxy\x20serv','statusCode','SIGINT','tFhyD','ache\x20calle','Read\x20strea','fetch\x20data','md5','SBHrI','om\x20','ntent-leng','eam\x20end\x20fo','FOGFO','ngth','hing\x20fresh','access','pFfBJ','Updated\x20co','efined\x20for','writableEn','MAwaS','fgoDy','readFile','unlink',';charset=U','XdgEd','existsSync','\x20content\x20f','rlsXM','eturned\x20no','read\x20cache','serveFromC','VjssW','GjgPR','70143lpxiCq','content-ty','cfEKS','nnot\x20fetch','KCVrU','aonTe','yZKZo','if-none-ma','wtYTR','favicon.ic','YlqlG','unknown','yzrwZ','toString','qrcode','OpYfk','PIaCU','ywgIW','utf8','rom\x20','length','NoEcw','\x20expired\x20c','mkdir','BFXeR','.\x20Destroyi','ZahvV','ing\x20meta\x20f','r\x20read\x20str','tion\x20prema','fficient\x20r','increment','open','ZRsgz','timeout','data\x20unava','close','Bad\x20Reques','end','ailed\x20or\x20r','method','bakEO','DrZlE','timestamp','VfCdb','exit','DFuaN','o\x20for\x20','ation:','d-since','GVXAx','esh\x20data\x20f','ovOAB','createWrit','SsfzH','tIGau','thumb','vYebf','\x20after\x20str','MHbCb','cDHng','arset=utf-','randomByte','eeaqj','app','SfKBj','p://localh','XyYHK','avACB','response:\x20','UWTcx','kSCUv','UClBU','BAD_REQUES','avatar','eam\x20error:'];_0x3a8a=function(){return _0x2ddd18;};return _0x3a8a();}_0x33f1b1();const _0x201348=(0x5a9+-0x9e+-0x4f3)*(-0x7f8+-0xb5a+0x138e)*(0x25c+-0x1*0xbe1+0x1*0x9c1)*(0x1b75+0x17f*-0x11+0x1e2),_0x6fa39d=(-0x20dc+-0x177c+-0x4*-0xe25)*(-0xa37*-0x1+-0x16a6+0x45*0x2f)*(0xbd4+0x11*-0x103+-0x19*-0x5f),_0x4bde7e={'OK':0xc8,'NO_CONTENT':0xcc,'REDIRECT':0x12e,'NOT_MODIFIED':0x130,'BAD_REQUEST':0x190,'NOT_FOUND':0x194,'INTERNAL_SERVER_ERROR':0x1f4,'BAD_GATEWAY':0x1f6};setInterval(async()=>{const _0x1d6b89=_0x5f2e5a,_0x20ad40={'uSJPh':function(_0x5c8f59){return _0x5c8f59();},'mPHZG':function(_0x2643da,_0x46c03b){return _0x2643da!==_0x46c03b;},'RRXKG':_0x1d6b89(0x11c),'fKJxt':function(_0x524742,_0x4f9cd3){return _0x524742===_0x4f9cd3;},'MABOL':'OTsyg','LvtmN':function(_0x5c13a5,_0x134716){return _0x5c13a5>_0x134716;}},_0x2f95f3=Date[_0x1d6b89(0x24f)](),_0x3244b4=[],_0x5210ee=[];for(const _0x5d9009 in _0x3842fc){if(_0x20ad40['fKJxt'](_0x1d6b89(0x187),_0x20ad40[_0x1d6b89(0x255)])){if(_0x20ad40[_0x1d6b89(0x23f)](_0x2f95f3-_0x3842fc[_0x5d9009][_0x1d6b89(0x21e)],_0x201348)){_0x3244b4[_0x1d6b89(0xb3)](_0x5d9009);const _0x5f1ff4=_0xe7013d[_0x1d6b89(0x12c)](_0x40c942,_0x5d9009+'.meta'),_0xf3d3fd=_0xe7013d[_0x1d6b89(0x12c)](_0x40c942,_0x3842fc[_0x5d9009]['uniqid']+_0x1d6b89(0x1bf));_0x5210ee['push'](_0x5f1ff4,_0xf3d3fd);}}else{_0x20ad40[_0x1d6b89(0x164)](_0x237c2a);if(!_0x351223['existsSync'](_0x1c3571))try{_0x1b8df8[_0x1d6b89(0xf3)](_0x2bc7e2,{'recursive':!![]}),_0x26ea91[_0x1d6b89(0xbb)](_0x1d6b89(0x281)+_0x1d6b89(0xd8)+_0x1d6b89(0x2ae)+_0x21bb2b);}catch(_0x3ecb02){_0x28df89[_0x1d6b89(0x245)]('Error\x20crea'+'ting\x20cache'+'\x20directory'+'\x20'+_0x988107+':',_0x3ecb02),_0x241049[_0x1d6b89(0x220)](-0x222b*0x1+-0xacb+-0x9*-0x4ff);}}}_0x3244b4['forEach'](_0x3b2bb4=>delete _0x3842fc[_0x3b2bb4]);if(_0x5210ee[_0x1d6b89(0x207)]>-0x5d5*-0x1+0x372+-0x13*0x7d){console[_0x1d6b89(0xbb)](_0x1d6b89(0x241)+'p\x20'+_0x3244b4[_0x1d6b89(0x207)]+(_0x1d6b89(0x209)+'ache\x20entri'+'es'));const _0x389709=_0x5210ee['map'](_0x3dab02=>_0x219f35[_0x1d6b89(0x9d)][_0x1d6b89(0x1e8)](_0x3dab02)[_0x1d6b89(0x1bb)](_0x54c8b6=>{const _0x2841cb=_0x1d6b89;_0x20ad40[_0x2841cb(0x1af)](_0x54c8b6[_0x2841cb(0x114)],_0x20ad40['RRXKG'])&&console[_0x2841cb(0x258)]('Failed\x20to\x20'+'delete\x20cac'+'he\x20file\x20'+_0x3dab02+':',_0x54c8b6[_0x2841cb(0x116)]);}));await Promise[_0x1d6b89(0xd7)](_0x389709);}},_0x6fa39d);function _0x49245c(_0x10358e,_0x499771,_0x424e7d){const _0x27e4b7=_0x5f2e5a,_0x3c8b1d={'svEVN':function(_0x29f177,_0x5d7e35,_0x2131f6,_0x153c25){return _0x29f177(_0x5d7e35,_0x2131f6,_0x153c25);},'iThRv':'Internal\x20S'+'erver\x20Erro'+_0x27e4b7(0x13c)+_0x27e4b7(0x293)+'che\x20conten'+_0x27e4b7(0x14b),'muzPK':function(_0x19ad00,_0x5740a8){return _0x19ad00===_0x5740a8;},'qeZEU':_0x27e4b7(0x29e),'RybEl':'lOVyD','OPtYM':'text/plain'+_0x27e4b7(0x1e9)+'TF-8'};!_0x10358e[_0x27e4b7(0x108)+'t']&&(_0x3c8b1d[_0x27e4b7(0x109)](_0x3c8b1d['qeZEU'],_0x3c8b1d[_0x27e4b7(0x17d)])?_0x3c8b1d[_0x27e4b7(0xd9)](_0x1a8bd0,_0x151969,_0x522b82[_0x27e4b7(0x149)+'ERVER_ERRO'+'R'],_0x3c8b1d[_0x27e4b7(0xcf)]):(_0x10358e[_0x27e4b7(0x244)](_0x499771,{'Content-Type':_0x3c8b1d[_0x27e4b7(0x9f)]}),_0x10358e['end'](_0x424e7d)));}async function _0x118bfd(_0x4c29b3,_0x4904f3){const _0x5740ba=_0x5f2e5a;_0x4904f3[_0x5740ba(0x244)](_0x4bde7e['NO_CONTENT']),_0x4904f3[_0x5740ba(0x219)]();}async function _0x586f4f(_0x2aa887,_0x30824b,_0x1cd74){const _0x2bc69e=_0x5f2e5a,_0x4c60f9={'ibcfV':function(_0x2e90c0,_0x1192e9){return _0x2e90c0!==_0x1192e9;},'VjssW':'Jtmsg','qEaDz':_0x2bc69e(0x24e)+_0x2bc69e(0x18f)+_0x2bc69e(0x230)+'8'};if(_0x1cd74[_0x2bc69e(0x273)][_0x2bc69e(0x17b)]){const _0x81a59d=/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w.-]*)*\/?$/;_0x81a59d[_0x2bc69e(0xba)](_0x1cd74[_0x2bc69e(0x273)]['api'])&&(_0x4c60f9['ibcfV'](_0x4c60f9['VjssW'],_0x4c60f9[_0x2bc69e(0x1f1)])?_0x17406c[_0x2bc69e(0x258)](_0x2bc69e(0x2b8)+_0x2bc69e(0xc4)+_0x2bc69e(0x162)+_0x2bc69e(0x1e3)+'\x20the\x20respo'+_0x2bc69e(0x1ca),_0x2b6d3b[_0x2bc69e(0xb9)]):(_0x38efbb=_0x1cd74['query']['api'],console['log'](_0x2bc69e(0x289)+_0x2bc69e(0x2c0)+_0x2bc69e(0x11e)+_0x38efbb)));}_0x30824b['writeHead'](_0x4bde7e['OK'],{'Content-Type':_0x4c60f9[_0x2bc69e(0x129)]}),_0x30824b[_0x2bc69e(0x219)](JSON[_0x2bc69e(0x145)]({'code':_0x4bde7e['OK'],'data':{'api':_0x38efbb,'port':_0x5a2d53,'cacheDir':_0x40c942,'pathIndexCount':Object[_0x2bc69e(0x181)](_0x3842fc)[_0x2bc69e(0x207)],'viewsInfo':{'request':_0x3d62d0[_0x2bc69e(0x15f)],'cacheHit':_0x3d62d0['cacheHit'],'apiCall':_0x3d62d0[_0x2bc69e(0x8b)],'cacheCall':_0x3d62d0[_0x2bc69e(0xbf)],'cacheReadError':_0x3d62d0['cacheReadE'+_0x2bc69e(0x13a)],'fetchApiError':_0x3d62d0[_0x2bc69e(0xa1)+_0x2bc69e(0x1a4)],'fetchApiWarning':_0x3d62d0[_0x2bc69e(0xc7)+_0x2bc69e(0xe3)]}}}));}async function _0x146a4a(_0x9a8853,_0x376df9){const _0x3e7005=_0x5f2e5a;_0x9a8853['writeHead'](_0x4bde7e[_0x3e7005(0x13e)],{'Location':_0x376df9[_0x3e7005(0xbd)][_0x3e7005(0x7e)]}),_0x9a8853[_0x3e7005(0x219)]();}async function _0x4105e0(_0x2d847e,_0x26ef36,_0x42a1c9,_0x3ead6e,_0x3da9e6,_0x469662,_0x121933){const _0x27b2cf=_0x5f2e5a,_0xdb0070={'XyYHK':function(_0x406e70,_0x42e185){return _0x406e70*_0x42e185;},'aonTe':_0x27b2cf(0x2b3),'bSvjb':function(_0x177986,_0xb54191,_0x3e6d84,_0x5a466a){return _0x177986(_0xb54191,_0x3e6d84,_0x5a466a);},'LUibN':_0x27b2cf(0xec)+_0x27b2cf(0xd0)+_0x27b2cf(0xdc)+'.','DjHyh':_0x27b2cf(0x1d4),'UPiGJ':function(_0x2941d3,_0x32ada0){return _0x2941d3<_0x32ada0;},'faKsn':_0x27b2cf(0xcb)+_0x27b2cf(0x1de),'cNJfo':function(_0x2249ab,_0x396bf6){return _0x2249ab!==_0x396bf6;},'CfpUv':'HDnvv','tqWmR':function(_0x50a914,_0x2582a3,_0x5b86df,_0x3e3bca,_0x1f7340,_0x30fb91,_0x16d42d){return _0x50a914(_0x2582a3,_0x5b86df,_0x3e3bca,_0x1f7340,_0x30fb91,_0x16d42d);},'oDQrW':'fZvXw'},{url:_0x44c5c6,cloudtype:_0x1ea225,expiration:_0x51d9e8,path:_0x8cd224,headers:_0x3194f9,uniqid:_0x25a031,thumb:_0x5a9d54}=_0x2d847e[_0x27b2cf(0xbd)],_0x4ca53c={'realUrl':_0x44c5c6,'cloudtype':_0x1ea225,'expiration':_0xdb0070[_0x27b2cf(0x236)](_0x51d9e8,-0x1*0x2cf+0x56b*0x4+0x223*-0x7),'path':_0x8cd224,'headers':_0x3194f9,'uniqid':_0x25a031,'thumb':_0x5a9d54};_0x3842fc[_0x26ef36]={'uniqid':_0x4ca53c[_0x27b2cf(0x12e)],'timestamp':Date[_0x27b2cf(0x24f)]()};const _0xfefe48=_0xe7013d[_0x27b2cf(0x12c)](_0x40c942,_0x26ef36+_0x27b2cf(0x2a4)),_0x179fb3=_0xe7013d['join'](_0x40c942,_0x4ca53c[_0x27b2cf(0x12e)]+_0x27b2cf(0x1bf)),_0x5d8ff2=_0xe7013d[_0x27b2cf(0x12c)](_0x40c942,_0x4ca53c[_0x27b2cf(0x12e)]+'_'+_0x53175f['randomByte'+'s'](0x3*0x392+-0x8b7+-0x1ef)[_0x27b2cf(0x200)](_0xdb0070[_0x27b2cf(0x1f8)])+_0x27b2cf(0x19b));try{_0x219f35[_0x27b2cf(0x184)+_0x27b2cf(0x1ba)](_0xfefe48,JSON[_0x27b2cf(0x145)](_0x4ca53c));}catch(_0x3be835){console[_0x27b2cf(0x245)]('Error\x20writ'+_0x27b2cf(0x20e)+'ile\x20'+_0xfefe48+':',_0x3be835),_0xdb0070[_0x27b2cf(0xa7)](_0x49245c,_0x469662,_0x4bde7e[_0x27b2cf(0x149)+_0x27b2cf(0xe2)+'R'],_0xdb0070[_0x27b2cf(0x1a7)]);return;}if(_0x219f35[_0x27b2cf(0x1eb)](_0x179fb3)){if(_0x27b2cf(0x1d4)===_0xdb0070['DjHyh']){const _0x1dabd4=_0x219f35[_0x27b2cf(0x172)](_0x179fb3),_0x3aa00d=_0x1dabd4[_0x27b2cf(0x192)];_0xdb0070['UPiGJ'](_0x3aa00d,-0x3*0x29e+-0x1f*-0xc9+-0x87d*0x1)&&_0x4ca53c[_0x27b2cf(0x1d0)][_0xdb0070[_0x27b2cf(0x90)]]&&_0xdb0070[_0x27b2cf(0x268)](parseInt(_0x4ca53c[_0x27b2cf(0x1d0)][_0xdb0070['faKsn']],-0x1d67+0x1*0x34b+0x1a26),_0x3aa00d)?_0xdb0070[_0x27b2cf(0x268)](_0xdb0070[_0x27b2cf(0x1b5)],_0xdb0070[_0x27b2cf(0x1b5)])?(_0xf5d0e8[_0x27b2cf(0x245)]('Error\x20crea'+_0x27b2cf(0xfc)+_0x27b2cf(0x10d)+'\x20'+_0x31c77e+':',_0x22bf1d),_0x43585b[_0x27b2cf(0x220)](-0x2b*0xbb+0x8*-0x1be+0x2d*0x102)):(console[_0x27b2cf(0x258)]('Content\x20le'+'ngth\x20misma'+'tch\x20for\x20'+_0x179fb3+'.\x20API:\x20'+_0x4ca53c[_0x27b2cf(0x1d0)][_0xdb0070[_0x27b2cf(0x90)]]+_0x27b2cf(0x295)+_0x3aa00d+(_0x27b2cf(0x132)+_0x27b2cf(0x2b5))),_0xdb0070[_0x27b2cf(0xf8)](_0x3442af,_0x4ca53c,_0x5d8ff2,_0x179fb3,_0xfefe48,_0x469662,_0x121933)):_0x32af4a(_0x4ca53c,_0x179fb3,_0xfefe48,_0x469662,_0x42a1c9,_0x3ead6e,_0x3da9e6,_0x26ef36,_0x121933);}else _0xa2226b[_0x27b2cf(0xbb)](_0x27b2cf(0x1d1)+_0x27b2cf(0x279)+'ing\x20on\x20htt'+_0x27b2cf(0x235)+_0x27b2cf(0x177)+_0x32301d);}else _0xdb0070[_0x27b2cf(0x268)](_0xdb0070[_0x27b2cf(0xef)],_0xdb0070[_0x27b2cf(0xef)])?_0x141ec4=_0x212e7d:_0xdb0070[_0x27b2cf(0xf8)](_0x3442af,_0x4ca53c,_0x5d8ff2,_0x179fb3,_0xfefe48,_0x469662,_0x121933);}async function _0x49b4da(_0x585ed9,_0x4e4b51,_0x51b509,_0x29a79f){const _0x10d0bc=_0x5f2e5a,_0x5919db={'YSiNf':function(_0x3795e8,_0x1efc0a,_0x112e98,_0x57e3ce){return _0x3795e8(_0x1efc0a,_0x112e98,_0x57e3ce);},'AYSmM':'Failed\x20to\x20'+_0x10d0bc(0xd0)+_0x10d0bc(0xdc)+'.','FOGFO':function(_0x4b85a7,_0x35b6d0){return _0x4b85a7!==_0x35b6d0;},'ZWqqG':_0x10d0bc(0x16f),'SfKBj':function(_0x4ef6a8,_0x5007f1){return _0x4ef6a8===_0x5007f1;},'bxEYx':_0x10d0bc(0x205),'GmKpH':function(_0x47b805,_0x166ec8,_0xf3ed57,_0x4a27cf,_0x395f6f,_0x591b44,_0x1c9ff1,_0xc2705b,_0x46dfdd,_0x454db1){return _0x47b805(_0x166ec8,_0xf3ed57,_0x4a27cf,_0x395f6f,_0x591b44,_0x1c9ff1,_0xc2705b,_0x46dfdd,_0x454db1);},'AyAhf':'jXJiC','DFuaN':'Bad\x20Gatewa'+'y'};if(_0x3842fc[_0x585ed9]){const _0x5aec2b=_0xe7013d[_0x10d0bc(0x12c)](_0x40c942,_0x585ed9+'.meta'),_0x2f08ff=_0xe7013d[_0x10d0bc(0x12c)](_0x40c942,_0x3842fc[_0x585ed9][_0x10d0bc(0x12e)]+_0x10d0bc(0x1bf));if(_0x219f35[_0x10d0bc(0x1eb)](_0x5aec2b)&&_0x219f35['existsSync'](_0x2f08ff)){if(_0x5919db[_0x10d0bc(0x1dd)](_0x5919db[_0x10d0bc(0x12a)],_0x5919db[_0x10d0bc(0x12a)])){_0x2eb1fe['error']('Error\x20writ'+_0x10d0bc(0x20e)+'ile\x20'+_0x4cf0e1+':',_0x95fe4),_0x5919db[_0x10d0bc(0x1c0)](_0x54dd6f,_0xd380d5,_0x335849[_0x10d0bc(0x149)+_0x10d0bc(0xe2)+'R'],_0x5919db['AYSmM']);return;}else{console[_0x10d0bc(0x258)]('API\x20call\x20f'+_0x10d0bc(0x21a)+'eturned\x20no'+_0x10d0bc(0x28c)+_0x10d0bc(0x188)+_0x10d0bc(0x157)+'\x20'+_0x585ed9);try{if(_0x5919db[_0x10d0bc(0x234)](_0x10d0bc(0x19c),_0x10d0bc(0x296)))_0x4b8616=new _0x3519fc(_0x155cda['mtime'])[_0x10d0bc(0x199)+'g']();else{const _0x471cda=JSON[_0x10d0bc(0x176)](_0x219f35[_0x10d0bc(0x2a0)+'nc'](_0x5aec2b,_0x5919db[_0x10d0bc(0xfa)]));_0x5919db[_0x10d0bc(0xb8)](_0x32af4a,_0x471cda,_0x2f08ff,_0x5aec2b,_0x4e4b51,null,null,null,_0x585ed9,_0x29a79f);return;}}catch(_0x100924){_0x10d0bc(0x260)!==_0x5919db[_0x10d0bc(0x193)]?_0x155aa8[_0x10d0bc(0x184)+_0x10d0bc(0x1ba)](_0x3bd8a9,_0x27582f[_0x10d0bc(0x145)](_0x52e8ef)):console[_0x10d0bc(0x245)]('Error\x20pars'+_0x10d0bc(0x11d)+'meta\x20file\x20'+_0x5aec2b+':',_0x100924);}}}}_0x5919db[_0x10d0bc(0x1c0)](_0x49245c,_0x4e4b51,_0x4bde7e[_0x10d0bc(0xdf)+'Y'],_0x51b509||_0x5919db[_0x10d0bc(0x221)]);}async function _0x321e06(_0xcca834,_0x387c14){const _0x5d95bc=_0x5f2e5a,_0x5c0ce7={'DrZlE':function(_0x53fffa,_0x518296){return _0x53fffa*_0x518296;},'LQEBY':_0x5d95bc(0x2b3),'zkDsz':function(_0x2033c9,_0x42cd66,_0x24bdf5,_0xd1cd79,_0x25e0a6,_0xfa3bad,_0x49b656,_0xc45cbe,_0x220502,_0x127c34){return _0x2033c9(_0x42cd66,_0x24bdf5,_0xd1cd79,_0x25e0a6,_0xfa3bad,_0x49b656,_0xc45cbe,_0x220502,_0x127c34);},'RXWRd':'fetchApiWa'+_0x5d95bc(0xe3),'ymaoc':function(_0x46aa9d,_0x29991f,_0x4e9152,_0x7e68e){return _0x46aa9d(_0x29991f,_0x4e9152,_0x7e68e);},'hVzvd':_0x5d95bc(0xec)+_0x5d95bc(0x1d7)+_0x5d95bc(0x183),'hXJeF':function(_0x519c1b,_0xd0894b){return _0x519c1b===_0xd0894b;},'KAzjV':_0x5d95bc(0x94),'ckhVh':_0x5d95bc(0x96)+'NS','lsTli':_0x5d95bc(0x9e)+'pe','UClBU':_0x5d95bc(0xa2),'TpgZG':_0x5d95bc(0x1fc)+'o','AIrYD':function(_0x1b8de4,_0x37397f,_0xe4ed07){return _0x1b8de4(_0x37397f,_0xe4ed07);},'dwkNf':_0x5d95bc(0x99),'egUbM':function(_0x491948,_0x122dec){return _0x491948&&_0x122dec;},'NYduu':_0x5d95bc(0x233),'JGcbJ':_0x5d95bc(0x23d),'XYacG':'bbs','oHfcI':_0x5d95bc(0x83),'shLxh':_0x5d95bc(0x201),'YIhdd':_0x5d95bc(0x240),'LRlUz':'request','SHlDM':function(_0x54ab25,_0x23895c){return _0x54ab25+_0x23895c;},'vjGWW':function(_0x42e8de,_0x327934){return _0x42e8de+_0x327934;},'FWQyE':function(_0xb9381d,_0x2be495){return _0xb9381d!==_0x2be495;},'FrtBU':_0x5d95bc(0x19e),'veHLT':_0x5d95bc(0x18d),'bUaBc':_0x5d95bc(0x1b8),'fgoDy':_0x5d95bc(0x232),'VfCdb':_0x5d95bc(0x8b),'NFcLM':_0x5d95bc(0x131),'nTNWD':function(_0x1eb136,_0x2f5d02){return _0x1eb136===_0x2f5d02;},'KojsY':'yBzZW','HIKbs':function(_0x3eb20b,_0x35b782,_0x507528,_0x8e118b,_0x5a168b,_0x4dba6e,_0x20c05a,_0x157c14){return _0x3eb20b(_0x35b782,_0x507528,_0x8e118b,_0x5a168b,_0x4dba6e,_0x20c05a,_0x157c14);},'pUCcF':function(_0x2487fd,_0x520135,_0x485e59,_0x25730d,_0x29624f){return _0x2487fd(_0x520135,_0x485e59,_0x25730d,_0x29624f);},'fBhKA':_0x5d95bc(0xa1)+_0x5d95bc(0x1a4),'YRvgJ':_0x5d95bc(0x120)+'PI\x20call\x20or'+_0x5d95bc(0x10f)+'g:'};if(_0x5c0ce7[_0x5d95bc(0x298)](_0xcca834[_0x5d95bc(0x21b)],_0x5c0ce7[_0x5d95bc(0x118)])){_0x387c14[_0x5d95bc(0x244)](-0x5*0x1f7+-0x131*0x7+0x12f2*0x1,{'Access-Control-Allow-Origin':_0xcca834[_0x5d95bc(0x1d0)]['origin']||'*','Access-Control-Allow-Methods':_0x5c0ce7[_0x5d95bc(0x243)],'Access-Control-Allow-Headers':_0x5c0ce7[_0x5d95bc(0x2a1)],'Access-Control-Max-Age':_0x5c0ce7[_0x5d95bc(0x23b)]}),_0x387c14[_0x5d95bc(0x219)]();return;}_0xcca834[_0x5d95bc(0x7e)]=_0xcca834[_0x5d95bc(0x7e)]['replace'](/\/{2,}/g,'/');const _0x2ad60e=_0x3ba0d9[_0x5d95bc(0x176)](_0xcca834['url'],!![]),_0x370825=_0x2ad60e[_0x5d95bc(0x273)][_0x5d95bc(0x102)]||'';let _0x5372b1=_0x2ad60e['pathname']['split']('/')[-0x1558*-0x1+0x6fb+-0x5*0x5aa]||'',_0x2533f0=_0x2ad60e[_0x5d95bc(0x1c2)][_0x5d95bc(0x292)]('/')['slice'](-0x75e+-0x5ba+0xd1a)[_0x5d95bc(0x12c)]('/');if(_0x5c0ce7[_0x5d95bc(0x298)](_0x5372b1,_0x5c0ce7[_0x5d95bc(0x189)]))return _0x5c0ce7[_0x5d95bc(0x27e)](_0x118bfd,_0xcca834,_0x387c14);if(_0x5c0ce7[_0x5d95bc(0x298)](_0x5372b1,_0x5c0ce7[_0x5d95bc(0x8f)]))return _0x5c0ce7[_0x5d95bc(0x140)](_0x586f4f,_0xcca834,_0x387c14,_0x2ad60e);_0x5c0ce7[_0x5d95bc(0x198)](!_0x2533f0,_0x5372b1)&&(_0x2533f0=_0x5372b1,_0x5372b1=_0x5c0ce7[_0x5d95bc(0x272)]);const _0xf6b9c7=[_0x5c0ce7['JGcbJ'],'go',_0x5c0ce7['XYacG'],_0x5c0ce7[_0x5d95bc(0x85)],'url',_0x5d95bc(0x22b),_0x5d95bc(0x233),_0x5c0ce7[_0x5d95bc(0x1b9)],_0x5c0ce7[_0x5d95bc(0x1ab)]];if(!_0xf6b9c7['includes'](_0x5372b1)||!_0x2533f0)return _0x5c0ce7[_0x5d95bc(0x140)](_0x49245c,_0x387c14,_0x4bde7e[_0x5d95bc(0x23c)+'T'],_0x5d95bc(0x218)+'t:\x20Invalid'+_0x5d95bc(0x9c)+_0x5d95bc(0x152)+_0x5d95bc(0xc6));_0x3d62d0[_0x5d95bc(0x212)](_0x5c0ce7[_0x5d95bc(0x123)]);const _0x22ff99=_0x53175f['createHash'](_0x5d95bc(0x1d8))[_0x5d95bc(0xfd)](_0x5c0ce7[_0x5d95bc(0x154)](_0x5c0ce7[_0x5d95bc(0x277)](_0x5372b1,_0x2533f0),_0x370825))[_0x5d95bc(0x28f)](_0x5c0ce7['LQEBY']);let _0x51d3a0='',_0x4f3b5b='';if(_0x3842fc[_0x22ff99]){if(_0x5c0ce7[_0x5d95bc(0x18a)](_0x5c0ce7[_0x5d95bc(0x25d)],'gppvg'))_0x51d3a0=_0xe7013d[_0x5d95bc(0x12c)](_0x40c942,_0x22ff99+_0x5d95bc(0x2a4)),_0x4f3b5b=_0xe7013d[_0x5d95bc(0x12c)](_0x40c942,_0x3842fc[_0x22ff99][_0x5d95bc(0x12e)]+_0x5d95bc(0x1bf));else{const {url:_0x4c5a97,cloudtype:_0x5be830,expiration:_0x474145,path:_0x1189dd,headers:_0x537735,uniqid:_0x5072c0,thumb:_0x2c276e}=_0x539527['data'],_0x45184a={'realUrl':_0x4c5a97,'cloudtype':_0x5be830,'expiration':_0x5c0ce7[_0x5d95bc(0x21d)](_0x474145,-0x61*-0x5f+-0x3*-0x6a+-0x2155),'path':_0x1189dd,'headers':_0x537735,'uniqid':_0x5072c0,'thumb':_0x2c276e};_0x2ead8e[_0x5a5443]={'uniqid':_0x45184a[_0x5d95bc(0x12e)],'timestamp':_0xe981cb['now']()};const _0x17ee1f=_0x4e529d[_0x5d95bc(0x12c)](_0x4ce3cb,_0x45184a['uniqid']+'_'+_0x4bb821[_0x5d95bc(0x231)+'s'](0x1aca+0x8e9+-0x23a3)[_0x5d95bc(0x200)](_0x5c0ce7[_0x5d95bc(0x126)])+_0x5d95bc(0x19b));_0x493b74(_0x45184a,_0x17ee1f,_0x1d32a9,_0x215b47,_0x5212d3,_0x4b4c39);return;}}if(_0x3842fc[_0x22ff99]&&_0x5c0ce7[_0x5d95bc(0x27e)](_0x2f8053,_0x51d3a0,_0x4f3b5b)){const {cacheData:_0x273da2,isNotModified:_0x3349dc}=await _0x5c0ce7[_0x5d95bc(0x27e)](_0x1e350b,_0xcca834,_0x51d3a0);_0x3349dc?_0x5c0ce7['FWQyE'](_0x5d95bc(0x287),_0x5c0ce7[_0x5d95bc(0x266)])?(_0x387c14[_0x5d95bc(0x244)](_0x4bde7e[_0x5d95bc(0x251)+'ED']),_0x387c14['end']()):_0x5c0ce7['zkDsz'](_0x5acb8c,_0x37d4cf,_0x5784be,_0x2133e0,_0x5ba71f,_0x1d66c7,_0x1aa1d9,_0x427a73,_0x3d005e,_0xad59aa):(_0x3d62d0[_0x5d95bc(0x212)](_0x5c0ce7[_0x5d95bc(0x1bc)]),_0x5c0ce7['zkDsz'](_0x32af4a,_0x273da2,_0x4f3b5b,_0x51d3a0,_0x387c14,_0x5372b1,_0x2533f0,_0x370825,_0x22ff99,_0xcca834));}else{if(_0x5c0ce7[_0x5d95bc(0x1e6)]===_0x5d95bc(0x23a)){_0x3ebe5a[_0x5d95bc(0x212)](_0x5c0ce7['RXWRd']),_0x5c0ce7[_0x5d95bc(0x140)](_0x328b49,_0x448d49,_0x17d6c7[_0x5d95bc(0xdf)+'Y'],_0x286df1[_0x5d95bc(0x116)]||_0x5c0ce7[_0x5d95bc(0x86)]);return;}else try{_0x3d62d0[_0x5d95bc(0x212)](_0x5c0ce7[_0x5d95bc(0x21f)]);const _0xccc8a2=await _0x427907(_0x5372b1,_0x2533f0,_0x370825);if(_0xccc8a2[_0x5d95bc(0x114)]===_0x4bde7e['REDIRECT']||_0x5c0ce7[_0x5d95bc(0x298)](_0xccc8a2['code'],-0x16d9+-0x17a4+0x2faa*0x1)){if(_0x5c0ce7['hXJeF'](_0x5c0ce7[_0x5d95bc(0x159)],_0x5c0ce7[_0x5d95bc(0x159)]))return _0x146a4a(_0x387c14,_0xccc8a2);else{_0x5cb1b4[_0x5d95bc(0x244)](_0x5e08ca[_0x5d95bc(0x13e)],{'Location':_0x5f4c10[_0x5d95bc(0xbd)][_0x5d95bc(0x7e)]}),_0x109d5a['end']();return;}}_0x5c0ce7['hXJeF'](_0xccc8a2[_0x5d95bc(0x114)],_0x4bde7e['OK'])&&_0xccc8a2[_0x5d95bc(0xbd)]&&_0xccc8a2['data']['url']?_0x5c0ce7[_0x5d95bc(0xa3)](_0x5c0ce7[_0x5d95bc(0x29f)],_0x5c0ce7[_0x5d95bc(0x29f)])?await _0x5c0ce7['HIKbs'](_0x4105e0,_0xccc8a2,_0x22ff99,_0x5372b1,_0x2533f0,_0x370825,_0x387c14,_0xcca834):(_0x25ed45[_0x5d95bc(0x245)](_0x5d95bc(0x2bd)+_0x5d95bc(0x139)+_0x5d95bc(0x166)+_0x5d95bc(0x1da)+_0x87815+':',_0x3ddac,_0x3e690f),_0x567f7d(new _0x28aa5e(_0x5d95bc(0xec)+_0x5d95bc(0x128)+_0x5d95bc(0x238)+_0x2f3240[_0x5d95bc(0x116)]))):(_0x3d62d0[_0x5d95bc(0x212)](_0x5c0ce7[_0x5d95bc(0x19a)]),await _0x5c0ce7[_0x5d95bc(0xb5)](_0x49b4da,_0x22ff99,_0x387c14,_0xccc8a2[_0x5d95bc(0x116)],_0xcca834));}catch(_0x277aa6){_0x3d62d0[_0x5d95bc(0x212)](_0x5c0ce7[_0x5d95bc(0x11f)]),console['error'](_0x5c0ce7[_0x5d95bc(0xeb)],_0x277aa6),await _0x5c0ce7['pUCcF'](_0x49b4da,_0x22ff99,_0x387c14,_0x5d95bc(0x1c7)+_0x5d95bc(0x1a5)+_0x5d95bc(0x8e)+'d.\x20'+_0x277aa6[_0x5d95bc(0x116)],_0xcca834);}}}const _0x326b5e=_0x7c5a88['createServ'+'er'](_0x321e06);async function _0x1e350b(_0x2bf6af,_0x494b5c){const _0x3bfbbe=_0x5f2e5a,_0x41de27={'ctRbu':function(_0x24ef94,_0x22a845){return _0x24ef94<_0x22a845;},'UbHBG':function(_0x304d69,_0x1061c7){return _0x304d69!==_0x1061c7;},'rlsXM':function(_0x5ced02,_0x3c0ef1,_0x576c4b){return _0x5ced02(_0x3c0ef1,_0x576c4b);},'LbrbX':'content-le'+_0x3bfbbe(0x1de),'MuCJw':function(_0x4e08ae,_0x41a97e,_0x29856e,_0x4c09ec,_0x4e1420,_0x3fcfcb,_0x483157){return _0x4e08ae(_0x41a97e,_0x29856e,_0x4c09ec,_0x4e1420,_0x3fcfcb,_0x483157);},'eyUMt':function(_0x35fd18,_0x19d29c){return _0x35fd18(_0x19d29c);},'aOhmu':function(_0x5a9010,_0x2f0eeb,_0x489fdc,_0x49fd7d){return _0x5a9010(_0x2f0eeb,_0x489fdc,_0x49fd7d);},'FuLNV':_0x3bfbbe(0x205),'DnKHo':_0x3bfbbe(0x1fa)+_0x3bfbbe(0x14a),'KOloL':_0x3bfbbe(0x1b2)+_0x3bfbbe(0x224),'bnxQD':function(_0x53bca4,_0x3262a9){return _0x53bca4===_0x3262a9;},'MRxtw':_0x3bfbbe(0x252),'PIaCU':_0x3bfbbe(0xe9),'mrNDX':_0x3bfbbe(0x2b1)+_0x3bfbbe(0x15d),'sZnhX':function(_0x597c5d,_0x5f00fb){return _0x597c5d<=_0x5f00fb;},'IaCpq':_0x3bfbbe(0xcd)};try{const _0xedf9eb=await _0x219f35[_0x3bfbbe(0x9d)][_0x3bfbbe(0x1e7)](_0x494b5c,_0x41de27['FuLNV']),_0xa13536=JSON[_0x3bfbbe(0x176)](_0xedf9eb),_0x29ac0a=_0x2bf6af['headers'][_0x41de27['DnKHo']],_0x424c44=_0x2bf6af[_0x3bfbbe(0x1d0)][_0x41de27[_0x3bfbbe(0x14f)]];if(_0x29ac0a&&_0xa13536[_0x3bfbbe(0x12e)]&&_0x41de27['bnxQD'](_0x29ac0a,_0xa13536[_0x3bfbbe(0x12e)])){if(_0x41de27[_0x3bfbbe(0x2a8)](_0x41de27['MRxtw'],_0x3bfbbe(0x92))){const _0x78353c=_0x411055[_0x3bfbbe(0x172)](_0x574c27),_0x536bfc=_0x78353c[_0x3bfbbe(0x192)];_0x41de27['ctRbu'](_0x536bfc,-0x1*-0x476+-0x125a+0x15e4)&&_0x91c004[_0x3bfbbe(0x1d0)][_0x3bfbbe(0xcb)+_0x3bfbbe(0x1de)]&&_0x41de27[_0x3bfbbe(0x2a6)](_0x41de27[_0x3bfbbe(0x1ed)](_0x40ea07,_0x110f20[_0x3bfbbe(0x1d0)][_0x41de27[_0x3bfbbe(0x17f)]],-0x1*0x1331+0x1*0x67f+0x2*0x65e),_0x536bfc)?(_0x96f8db[_0x3bfbbe(0x258)]('Content\x20le'+_0x3bfbbe(0x18c)+_0x3bfbbe(0x284)+_0x51a79e+_0x3bfbbe(0x113)+_0x59d543['headers'][_0x41de27[_0x3bfbbe(0x17f)]]+_0x3bfbbe(0x295)+_0x536bfc+(_0x3bfbbe(0x132)+_0x3bfbbe(0x2b5))),_0x41de27[_0x3bfbbe(0x197)](_0x3f1add,_0x541952,_0x184e80,_0x14b890,_0x4d0f5f,_0x565f49,_0x1f193d)):_0x351441(_0xa5784b,_0x3760d5,_0x39aace,_0x3fc12c,_0x2cefb0,_0x3db0c3,_0x2d221d,_0x36d85d,_0x2ed9c5);}else return{'cacheData':_0xa13536,'isNotModified':!![]};}if(_0x424c44&&_0xa13536[_0x3bfbbe(0x1d0)]&&_0xa13536[_0x3bfbbe(0x1d0)][_0x3bfbbe(0x2b1)+_0x3bfbbe(0x15d)])try{if(_0x41de27[_0x3bfbbe(0x203)]!==_0x41de27[_0x3bfbbe(0x203)]){if(_0x2051bd[_0x3bfbbe(0x1d2)]>=0x1b25+0x216d+-0x3b02){_0x7db891[_0x3bfbbe(0x245)](_0x3bfbbe(0x26c)+'t\x20to\x20'+_0x15da6d+(_0x3bfbbe(0x15e)+_0x3bfbbe(0x16d))+_0x170624[_0x3bfbbe(0x1d2)]+':\x20'+_0x3d020c);let _0x576c3b={'code':_0x3f0c4f[_0x3bfbbe(0x1d2)],'message':'API\x20Error:'+'\x20'+_0x4cadb3[_0x3bfbbe(0x1d2)]};try{const _0xc16db=_0x462525[_0x3bfbbe(0x176)](_0x1d1bba);if(_0xc16db&&_0xc16db[_0x3bfbbe(0x116)])_0x576c3b['message']=_0xc16db['message'];}catch(_0x4faaf2){}_0x41de27['eyUMt'](_0x1c3bfb,_0x576c3b);return;}_0x23bfc0(_0x588c8e[_0x3bfbbe(0x176)](_0x77c192));}else{const _0x1ae062=new Date(_0xa13536[_0x3bfbbe(0x1d0)][_0x41de27[_0x3bfbbe(0x82)]]),_0x28c007=new Date(_0x424c44);if(_0x41de27['sZnhX'](_0x1ae062['getTime'](),_0x28c007[_0x3bfbbe(0x14d)]()))return{'cacheData':_0xa13536,'isNotModified':!![]};}}catch(_0x5c99b6){console[_0x3bfbbe(0x258)]('Error\x20pars'+_0x3bfbbe(0x26e)+_0x3bfbbe(0x282)+_0x3bfbbe(0x1a6)+_0x3bfbbe(0x98)+_0x494b5c+'):',_0x5c99b6);}return{'cacheData':_0xa13536,'isNotModified':![]};}catch(_0x1bd867){if(_0x41de27['bnxQD'](_0x41de27['IaCpq'],_0x41de27[_0x3bfbbe(0x1b6)]))return console[_0x3bfbbe(0x245)](_0x3bfbbe(0xfb)+_0x3bfbbe(0x1cc)+_0x3bfbbe(0x144)+_0x3bfbbe(0xf4)+'\x20'+_0x494b5c+(_0x3bfbbe(0xf2)+_0x3bfbbe(0x286)+'s:'),_0x1bd867),{'cacheData':null,'isNotModified':![]};else{_0x41de27[_0x3bfbbe(0x2b6)](_0x37476e,_0x2971ff,_0x52e365[_0x3bfbbe(0xdf)+'Y'],_0x364090['message']||_0x3bfbbe(0xec)+_0x3bfbbe(0x1d7)+_0x3bfbbe(0x183));return;}}}async function _0x2f8053(_0x4c1bfd,_0x354b8f){const _0x1ecd4d=_0x5f2e5a,_0x47bda9={'yjMPr':function(_0x21709b,_0x2c8c4a){return _0x21709b||_0x2c8c4a;},'ubVvg':'utf8','IdNng':'number','PKpeX':function(_0x416627,_0x283739){return _0x416627>_0x283739;},'MHbCb':function(_0x14efda,_0x39f50f){return _0x14efda!==_0x39f50f;},'mDwox':'QfxTu'};try{const [_0x13ab7b,_0x13320e]=await Promise[_0x1ecd4d(0xd7)]([_0x219f35[_0x1ecd4d(0x9d)][_0x1ecd4d(0x1e0)](_0x4c1bfd)[_0x1ecd4d(0x19f)](()=>!![])[_0x1ecd4d(0x1bb)](()=>![]),_0x219f35[_0x1ecd4d(0x9d)][_0x1ecd4d(0x1e0)](_0x354b8f)[_0x1ecd4d(0x19f)](()=>!![])[_0x1ecd4d(0x1bb)](()=>![])]);if(_0x47bda9[_0x1ecd4d(0xc2)](!_0x13ab7b,!_0x13320e))return![];const _0x1beb8e=await _0x219f35['promises'][_0x1ecd4d(0x1e7)](_0x4c1bfd,_0x47bda9[_0x1ecd4d(0x8a)]),_0x511339=JSON[_0x1ecd4d(0x176)](_0x1beb8e);return typeof _0x511339[_0x1ecd4d(0x180)]===_0x47bda9['IdNng']&&_0x47bda9['PKpeX'](_0x511339[_0x1ecd4d(0x180)],Date['now']());}catch(_0xaffafb){if(_0x47bda9[_0x1ecd4d(0x22e)](_0x1ecd4d(0x97),_0x47bda9[_0x1ecd4d(0x2ac)]))_0x1c082e[_0x1ecd4d(0x258)](_0x1ecd4d(0x1c8)+_0x1ecd4d(0x263)+_0xd9a5f3+(_0x1ecd4d(0xca)+_0x1ecd4d(0x22d)+_0x1ecd4d(0x1dc)+'r\x20')+_0x466f2e[_0x1ecd4d(0xb9)]);else return console[_0x1ecd4d(0x258)](_0x1ecd4d(0xfb)+_0x1ecd4d(0x1cc)+'sing\x20cache'+'\x20meta\x20file'+'\x20'+_0x4c1bfd+('\x20for\x20valid'+_0x1ecd4d(0x223)),_0xaffafb),![];}}const _0x4a9040=0x1743+-0x1b35+0x259*0xa,_0x1aef9e=_0x5f2e5a(0x2b9)+_0x5f2e5a(0xce)+_0x5f2e5a(0xc3)+_0x5f2e5a(0x2c1)+')\x20AppleWeb'+_0x5f2e5a(0x124)+_0x5f2e5a(0x168)+'ike\x20Gecko)'+_0x5f2e5a(0x12d)+'.0.4389.11'+_0x5f2e5a(0xea)+'37.36';async function _0x427907(_0x5f1d56,_0xf805cd,_0x496360){const _0x152a7d=_0x5f2e5a,_0x467496={'FfzFd':function(_0x17371f,_0x59746d,_0x24b8bc,_0x11700f){return _0x17371f(_0x59746d,_0x24b8bc,_0x11700f);},'nOWXn':function(_0x438012,_0x27542d){return _0x438012>=_0x27542d;},'OTLJb':function(_0x421fa5,_0x1d19e9){return _0x421fa5(_0x1d19e9);},'Mfpfz':_0x152a7d(0x205),'HCWtK':_0x152a7d(0x219),'dwysE':function(_0x2a2a4b,_0x26d063){return _0x2a2a4b!==_0x26d063;},'ieUww':_0x152a7d(0x1e5),'XdgEd':_0x152a7d(0x215),'BfMfq':function(_0x2e3cc1,_0x2030ce){return _0x2e3cc1===_0x2030ce;},'wgDHl':_0x152a7d(0xb0),'bakEO':'applicatio'+'n/json;\x20ch'+_0x152a7d(0x230)+'8'},_0x3e7c80=_0x36ee9c[_0x152a7d(0x145)]({'type':_0x5f1d56,'sign':_0x496360}),_0x5dae96=_0x38efbb+'?'+_0x3e7c80,_0x155d72=new URL(_0x5dae96),_0x4432cf=_0x467496[_0x152a7d(0xe1)](_0x155d72[_0x152a7d(0x2aa)],'https:')?_0x398c9a:_0x7c5a88,_0x16a35a={'method':_0x467496[_0x152a7d(0x1c9)],'headers':{'Accept':_0x467496[_0x152a7d(0x21c)],'User-Agent':_0x1aef9e,'token':_0xf805cd},'timeout':_0x4a9040,'rejectUnauthorized':![]};return new Promise((_0x4aa9bf,_0x17c661)=>{const _0x481ad4=_0x152a7d,_0x69d14d={'LzkxP':_0x467496[_0x481ad4(0x248)],'rHmxB':function(_0x461c2c,_0x4cd935,_0x315104,_0x1e8c6d,_0x5632ba,_0xdae3b8,_0x10250f,_0x25c913,_0x4f4c00,_0x12af26){return _0x461c2c(_0x4cd935,_0x315104,_0x1e8c6d,_0x5632ba,_0xdae3b8,_0x10250f,_0x25c913,_0x4f4c00,_0x12af26);},'NbqGJ':function(_0x4a0008,_0x3b376c){const _0x44952e=_0x481ad4;return _0x467496[_0x44952e(0xc8)](_0x4a0008,_0x3b376c);},'fLAHd':_0x467496[_0x481ad4(0x1a0)],'KSoxL':function(_0x26130d,_0x136acd){const _0x9ded30=_0x481ad4;return _0x467496[_0x9ded30(0x10a)](_0x26130d,_0x136acd);},'RJxtO':function(_0x4b238b,_0x469cbb){return _0x4b238b(_0x469cbb);}},_0x5cd087=_0x4432cf[_0x481ad4(0x15f)](_0x5dae96,_0x16a35a,_0x26ec3f=>{const _0x56f2af=_0x481ad4,_0x27cce3={'KXlnw':function(_0x40e510,_0x6f1034,_0x2628b8,_0x4d486f){return _0x467496['FfzFd'](_0x40e510,_0x6f1034,_0x2628b8,_0x4d486f);},'sVnwO':function(_0xb569b4,_0x197856){const _0x4ab69b=_0x4a4a;return _0x467496[_0x4ab69b(0x15c)](_0xb569b4,_0x197856);},'knpuY':function(_0x114df3,_0x184d43){return _0x114df3===_0x184d43;},'lBJxm':_0x56f2af(0x1f7),'ZgiXb':function(_0x471e04,_0x586692){return _0x467496['OTLJb'](_0x471e04,_0x586692);}};let _0x329e88='';_0x26ec3f[_0x56f2af(0x276)+'g'](_0x467496[_0x56f2af(0x248)]),_0x26ec3f['on'](_0x56f2af(0xbd),_0x283795=>_0x329e88+=_0x283795),_0x26ec3f['on'](_0x467496['HCWtK'],()=>{const _0x10a788=_0x56f2af,_0x4e9638={'bdZlz':function(_0x3d8a21,_0x373dc2,_0x581111,_0x1263ac){return _0x27cce3['KXlnw'](_0x3d8a21,_0x373dc2,_0x581111,_0x1263ac);}};try{if(_0x27cce3['sVnwO'](_0x26ec3f[_0x10a788(0x1d2)],0x8b*0x1a+-0x211+-0xa7d)){if(_0x27cce3[_0x10a788(0x2ab)]('KCVrU',_0x27cce3['lBJxm'])){console[_0x10a788(0x245)]('API\x20reques'+_0x10a788(0xaa)+_0x5dae96+(_0x10a788(0x15e)+_0x10a788(0x16d))+_0x26ec3f[_0x10a788(0x1d2)]+':\x20'+_0x329e88);let _0x1c0a04={'code':_0x26ec3f[_0x10a788(0x1d2)],'message':'API\x20Error:'+'\x20'+_0x26ec3f[_0x10a788(0x1d2)]};try{if(_0x27cce3[_0x10a788(0x2ab)](_0x10a788(0x191),_0x10a788(0x191))){const _0x126a88=JSON['parse'](_0x329e88);if(_0x126a88&&_0x126a88[_0x10a788(0x116)])_0x1c0a04[_0x10a788(0x116)]=_0x126a88[_0x10a788(0x116)];}else _0x2e085c=_0x50b3db[_0x10a788(0x12c)](_0x32e1dc,_0x3a8f4c+_0x10a788(0x2a4)),_0x13fbd7=_0x3a4f93[_0x10a788(0x12c)](_0x1b0497,_0x414571[_0x2c690b][_0x10a788(0x12e)]+_0x10a788(0x1bf));}catch(_0x233a56){}_0x27cce3[_0x10a788(0x136)](_0x4aa9bf,_0x1c0a04);return;}else{_0x13137a[_0x10a788(0x245)](_0x10a788(0x288)+'hing\x20fresh'+_0x10a788(0x280)+_0x231351['message']),_0x4e9638[_0x10a788(0xf9)](_0x13b1a5,_0xa5efe6,_0x1a94b5[_0x10a788(0x149)+'ERVER_ERRO'+'R'],_0x10a788(0xec)+'fetch\x20fres'+_0x10a788(0x25c)+_0x147e83[_0x10a788(0x116)]);return;}}_0x27cce3[_0x10a788(0x136)](_0x4aa9bf,JSON[_0x10a788(0x176)](_0x329e88));}catch(_0x3eaa53){console[_0x10a788(0x245)]('Error\x20pars'+_0x10a788(0x139)+_0x10a788(0x166)+_0x10a788(0x1da)+_0x5dae96+':',_0x3eaa53,_0x329e88),_0x17c661(new Error(_0x10a788(0xec)+_0x10a788(0x128)+_0x10a788(0x238)+_0x3eaa53['message']));}});});_0x5cd087['on'](_0x467496[_0x481ad4(0x1ea)],()=>{const _0x47a4dc=_0x481ad4;if(_0x69d14d[_0x47a4dc(0x242)](_0x69d14d[_0x47a4dc(0xe0)],_0x69d14d[_0x47a4dc(0xe0)])){_0x4e1355['warn']('API\x20call\x20f'+_0x47a4dc(0x21a)+_0x47a4dc(0x1ee)+_0x47a4dc(0x28c)+'ving\x20stale'+_0x47a4dc(0x157)+'\x20'+_0x2b297a);try{const _0x1a8cd1=_0x159d45[_0x47a4dc(0x176)](_0x1a2547[_0x47a4dc(0x2a0)+'nc'](_0xdb36fb,_0x69d14d[_0x47a4dc(0x270)]));_0x69d14d[_0x47a4dc(0x246)](_0x1c520d,_0x1a8cd1,_0xf25cd,_0xbd4cf3,_0x10dbea,null,null,null,_0x47c5b5,_0x475d76);return;}catch(_0x1dcb39){_0x349fdd['error'](_0x47a4dc(0x2bd)+'ing\x20stale\x20'+_0x47a4dc(0x2ad)+_0x26215e+':',_0x1dcb39);}}else _0x5cd087['destroy'](),console[_0x47a4dc(0x245)]('API\x20reques'+'t\x20to\x20'+_0x5dae96+(_0x47a4dc(0x25b)+'\x20after\x20')+_0x4a9040+'ms'),_0x69d14d[_0x47a4dc(0x104)](_0x17c661,new Error('API\x20reques'+_0x47a4dc(0x18b)+'t'));}),_0x5cd087['on'](_0x481ad4(0x245),_0x4c194e=>{const _0x2e4885=_0x481ad4;console[_0x2e4885(0x245)](_0x2e4885(0x26c)+_0x2e4885(0xaa)+_0x5dae96+_0x2e4885(0xfe),_0x4c194e),_0x69d14d[_0x2e4885(0x101)](_0x17c661,_0x4c194e);}),_0x5cd087[_0x481ad4(0x219)]();});}const _0x2cb70d=-0x2*-0x12bf+0x33*0x14+-0x297a,_0x3442af=async(_0x59cc3b,_0x2539e7,_0x4ca9d1,_0x227ed3,_0x539c7a,_0x230ced)=>{const _0x408f5e=_0x5f2e5a,_0x23d514={'Udwlj':function(_0x1e676d,_0xdbf769){return _0x1e676d===_0xdbf769;},'RAeZo':_0x408f5e(0x175),'sEcsz':_0x408f5e(0x17c),'xaqOL':'content-le'+_0x408f5e(0x1de),'oKvCy':_0x408f5e(0x2b8)+_0x408f5e(0xc4)+_0x408f5e(0x162)+_0x408f5e(0x1e3)+_0x408f5e(0x1a9)+_0x408f5e(0x1ca),'EEAVE':function(_0x54b670,_0x168577){return _0x54b670+_0x168577;},'AURoM':_0x408f5e(0x283),'FHOSh':'keep-alive','WDxAt':_0x408f5e(0x2b1)+_0x408f5e(0x15d),'kZggI':_0x408f5e(0x96)+'NS','GVXAx':_0x408f5e(0x9e)+'pe','zjhzb':'content-ty'+'pe','dhxin':_0x408f5e(0xcc),'urMsW':function(_0x39ab26,_0x5aa1c4){return _0x39ab26(_0x5aa1c4);},'iFoJf':_0x408f5e(0x2a3),'Quspx':_0x408f5e(0x2b7),'fErfc':function(_0x56e381,_0x2854a0,_0x255fb0,_0x15cfef){return _0x56e381(_0x2854a0,_0x255fb0,_0x15cfef);},'pGqOe':_0x408f5e(0x2c2),'HYBXa':'error'},_0x2ee497=_0x59cc3b[_0x408f5e(0xb9)][_0x408f5e(0xab)](_0x23d514['pGqOe'])?_0x398c9a:_0x7c5a88;_0x2ee497['get'](_0x59cc3b[_0x408f5e(0xb9)],{'timeout':_0x2cb70d,'rejectUnauthorized':![]},_0x4678aa=>{const _0x2b5451=_0x408f5e,_0x28c213={'XjKQj':function(_0x318e26,_0x489488){return _0x318e26!==_0x489488;},'tPHJH':_0x2b5451(0x299)},_0x39d166=_0x219f35[_0x2b5451(0x228)+'eStream'](_0x2539e7,{'flags':'w','highWaterMark':(-0x1cc8+0x1656+0x2*0x359)*(0x1f03+0x607*-0x1+-0x14fc)});let _0xd5a151=_0x59cc3b[_0x2b5451(0x253)]&&_0x23d514['Udwlj'](typeof _0x59cc3b[_0x2b5451(0x253)],_0x23d514['RAeZo'])&&_0x59cc3b[_0x2b5451(0x253)][_0x2b5451(0x28e)](_0x23d514[_0x2b5451(0x247)]);const _0x3d5301=_0x4678aa[_0x2b5451(0x1d0)][_0x23d514[_0x2b5451(0x178)]];_0x3d5301?(_0x59cc3b[_0x2b5451(0x1d0)][_0x23d514[_0x2b5451(0x178)]]=_0x3d5301,_0x219f35[_0x2b5451(0x9d)]['writeFile'](_0x227ed3,JSON['stringify'](_0x59cc3b))['catch'](_0x352fc1=>console[_0x2b5451(0x245)]('Error\x20writ'+_0x2b5451(0x20e)+'ile\x20'+_0x227ed3+':',_0x352fc1))):console[_0x2b5451(0x258)](_0x23d514[_0x2b5451(0x2a9)],_0x59cc3b[_0x2b5451(0xb9)]);const _0x60b7ee={'Cloud-Type':_0x59cc3b[_0x2b5451(0x7d)],'ETag':_0x59cc3b[_0x2b5451(0x12e)]||'','Cache-Control':'public,\x20ma'+_0x2b5451(0xb1)+_0x2b5451(0x11b),'Expires':new Date(_0x23d514['EEAVE'](Date[_0x2b5451(0x24f)](),-0x71ba*-0x1c4ecc+0x8393e0e10+-0x24318*0x5f2e3))[_0x2b5451(0x199)+'g'](),'Accept-Ranges':_0x23d514[_0x2b5451(0x1c3)],'Connection':_0x23d514[_0x2b5451(0x26b)],'Date':new Date()['toUTCStrin'+'g'](),'Last-Modified':_0x59cc3b[_0x2b5451(0x1d0)][_0x23d514[_0x2b5451(0x254)]]||new Date()[_0x2b5451(0x199)+'g'](),'Access-Control-Allow-Origin':_0x230ced[_0x2b5451(0x1d0)][_0x2b5451(0x28d)]||'*','Access-Control-Allow-Methods':_0x23d514[_0x2b5451(0x25a)],'Access-Control-Allow-Headers':_0x23d514[_0x2b5451(0x225)]},_0xd705c8={..._0x60b7ee,'Content-Type':_0x4678aa[_0x2b5451(0x1d0)][_0x23d514[_0x2b5451(0x160)]]||(_0xd5a151?_0x23d514['dhxin']:_0x2b5451(0x24e)+_0x2b5451(0x14e)+_0x2b5451(0x105)),..._0x59cc3b[_0x2b5451(0x1d0)]};_0x539c7a[_0x2b5451(0x244)](_0x4678aa['statusCode'],_0xd705c8);const _0x78fca1=_0x23d514['urMsW'](require,_0x23d514[_0x2b5451(0x269)])[_0x2b5451(0x275)],{PassThrough:_0x2e702d}=_0x23d514[_0x2b5451(0xda)](require,_0x23d514['iFoJf']),_0x338d9e=new _0x2e702d();_0x338d9e[_0x2b5451(0x2bf)](_0x39d166),_0x338d9e[_0x2b5451(0x2bf)](_0x539c7a),_0x78fca1(_0x4678aa,_0x338d9e,_0x2aa653=>{const _0x296dd5=_0x2b5451;if(_0x2aa653){console['error'](_0x296dd5(0xae)+'rror\x20for\x20'+_0x59cc3b[_0x296dd5(0xb9)]+':',_0x2aa653),_0x3b30d7(_0x539c7a,_0x2539e7,_0x59cc3b['realUrl']);return;}_0x219f35['promises'][_0x296dd5(0x1e0)](_0x2539e7)[_0x296dd5(0x19f)](()=>{const _0x4232b7=_0x296dd5;return _0x219f35[_0x4232b7(0x9d)][_0x4232b7(0x20a)](_0xe7013d[_0x4232b7(0x2a7)](_0x4ca9d1),{'recursive':!![]})[_0x4232b7(0x19f)](()=>_0x219f35['promises']['rename'](_0x2539e7,_0x4ca9d1))[_0x4232b7(0x19f)](()=>console[_0x4232b7(0xbb)]('Successful'+'ly\x20cached:'+'\x20'+_0x4ca9d1))[_0x4232b7(0x1bb)](_0x181e48=>{const _0xa9c20e=_0x4232b7;return console['error'](_0xa9c20e(0x141)+'ming\x20temp\x20'+'cache\x20file'+'\x20'+_0x2539e7+_0xa9c20e(0x1b3)+_0x4ca9d1+':',_0x181e48),_0x219f35['promises']['unlink'](_0x2539e7)[_0xa9c20e(0x1bb)](()=>{});});})[_0x296dd5(0x1bb)](()=>{const _0x286533=_0x296dd5;_0x28c213[_0x286533(0xf1)](_0x28c213['tPHJH'],_0x28c213[_0x286533(0xe4)])?(_0xba9a09=_0x401596[_0x286533(0x273)][_0x286533(0x17b)],_0x2e8805[_0x286533(0xbb)](_0x286533(0x289)+'nt\x20updated'+_0x286533(0x11e)+_0x23a244)):console[_0x286533(0x258)](_0x286533(0x1c8)+_0x286533(0x263)+_0x2539e7+(_0x286533(0xca)+_0x286533(0x22d)+_0x286533(0x1dc)+'r\x20')+_0x59cc3b['realUrl']);});});})['on'](_0x23d514[_0x408f5e(0xc1)],_0x25ae32=>{const _0x45b4b3=_0x408f5e;if(_0x23d514[_0x45b4b3(0x27a)](_0x45b4b3(0x214),_0x23d514['Quspx']))return _0x2e9792[_0x45b4b3(0x245)](_0x45b4b3(0x141)+'ming\x20temp\x20'+'cache\x20file'+'\x20'+_0x1f9781+_0x45b4b3(0x1b3)+_0x57fbbc+':',_0x54eb0f),_0xe48622['promises'][_0x45b4b3(0x1e8)](_0x156417)[_0x45b4b3(0x1bb)](()=>{});else console['error'](_0x45b4b3(0x25e)+'ng\x20GET\x20req'+_0x45b4b3(0x259)+_0x59cc3b[_0x45b4b3(0xb9)]+':',_0x25ae32),_0x23d514[_0x45b4b3(0x155)](_0x3b30d7,_0x539c7a,_0x2539e7,_0x59cc3b['realUrl']);});};async function _0x32af4a(_0x1243d1,_0x115cfd,_0x5b08c0,_0x549969,_0x5c3e35,_0x8d575f,_0x3b1c08,_0x591912,_0x1a74ac){const _0xcb06ad=_0x5f2e5a,_0xb4d618={'cfEKS':function(_0x148113,_0x36e810,_0x5e6df4,_0x1bc68d,_0xfc79d2,_0x5808d4,_0x4bad66){return _0x148113(_0x36e810,_0x5e6df4,_0x1bc68d,_0xfc79d2,_0x5808d4,_0x4bad66);},'gWMxB':'Cache\x20meta'+'data\x20unava'+_0xcb06ad(0x27b)+'\x20cannot\x20fe'+_0xcb06ad(0x133)+'data.','jBhEb':'hex','IaNaG':_0xcb06ad(0xcb)+_0xcb06ad(0x1de),'DWiLD':'utf8','fjPZB':function(_0x2bddea,_0x1158d3,_0x5eee2e,_0x4be5dd,_0x40be4b,_0x2105e6,_0x2baf52,_0x5e2f0e,_0x521d00,_0x146b21){return _0x2bddea(_0x1158d3,_0x5eee2e,_0x4be5dd,_0x40be4b,_0x2105e6,_0x2baf52,_0x5e2f0e,_0x521d00,_0x146b21);},'fYOOl':_0xcb06ad(0x24e)+'n/json;\x20ch'+_0xcb06ad(0x230)+'8','NAOnQ':_0xcb06ad(0x1f4)+'pe','zzIla':_0xcb06ad(0x24e)+_0xcb06ad(0x14e)+'ream','iWvaL':function(_0x2a3743,_0x5304ee){return _0x2a3743!==_0x5304ee;},'VyeCi':_0xcb06ad(0x1e1),'WuhTY':'QownU','tYcLh':function(_0x1e3a2b,_0x2d2d6d){return _0x1e3a2b===_0x2d2d6d;},'prbxP':function(_0x334cc5,_0x4893d3){return _0x334cc5*_0x4893d3;},'bBSPR':_0xcb06ad(0xc7)+_0xcb06ad(0xe3),'QfxKR':function(_0x1cbb61,_0x36c893,_0x2b64bc,_0x3fd6f5){return _0x1cbb61(_0x36c893,_0x2b64bc,_0x3fd6f5);},'ZomvI':_0xcb06ad(0xec)+_0xcb06ad(0x1d7)+_0xcb06ad(0x183),'ZahvV':function(_0x51d8ff,_0x5bf862){return _0x51d8ff===_0x5bf862;},'yzrwZ':_0xcb06ad(0xa1)+'ror','wtYTR':function(_0x1c5b49,_0x5559e7,_0x295c6e,_0xa3d21d){return _0x1c5b49(_0x5559e7,_0x295c6e,_0xa3d21d);},'jMkJe':function(_0x5e9a97,_0x32fe55){return _0x5e9a97&&_0x32fe55;},'azvLi':'apiCall','sTbVy':function(_0x38893e,_0x204681,_0x471c4b,_0x5de75c,_0xf80910,_0x5ee203,_0x5b3c09){return _0x38893e(_0x204681,_0x471c4b,_0x5de75c,_0xf80910,_0x5ee203,_0x5b3c09);},'SBHrI':'FUZkH','uqiRZ':function(_0x12738b,_0x4f31ac){return _0x12738b!==_0x4f31ac;},'OxPAO':_0xcb06ad(0x142),'AIudu':_0xcb06ad(0xf0),'sLwkL':function(_0x7741b,_0x42d88c,_0x1904f8,_0x53d3b6){return _0x7741b(_0x42d88c,_0x1904f8,_0x53d3b6);},'SciLZ':function(_0x1c56f7,_0x28acd4){return _0x1c56f7===_0x28acd4;},'Ibied':function(_0x160178,_0x71ab82){return _0x160178*_0x71ab82;},'NIxTn':_0xcb06ad(0x182),'jLYSc':_0xcb06ad(0x288)+_0xcb06ad(0x1df)+'\x20data:','SkTkx':function(_0x16c3d4,_0x20f014){return _0x16c3d4!==_0x20f014;},'PGUZN':'RDrPT','GjgPR':_0xcb06ad(0x2b1)+'ied','xqCIz':function(_0x646059,_0x26038f){return _0x646059||_0x26038f;},'YlqlG':'GtbRN','mPQRo':_0xcb06ad(0x1d8),'uLrCd':_0xcb06ad(0x1fe),'qnOoT':function(_0x1b23c6,_0x31e0c4){return _0x1b23c6||_0x31e0c4;},'srUwj':_0xcb06ad(0xde)+_0xcb06ad(0xb1)+_0xcb06ad(0x11b),'pOXDc':function(_0xa93567,_0x3eb332){return _0xa93567+_0x3eb332;},'bFoUt':_0xcb06ad(0x283),'OSahF':_0xcb06ad(0x29b),'OMWiT':_0xcb06ad(0xbf),'rVtMw':function(_0x125d92,_0x2edeb7){return _0x125d92&&_0x2edeb7;},'bTVfh':_0xcb06ad(0x2bc),'CWrBb':function(_0x2b5794,_0x12ec40){return _0x2b5794===_0x12ec40;},'MHDcg':function(_0x43519d,_0x2ac6cc,_0x37d9ec,_0xa886fa){return _0x43519d(_0x2ac6cc,_0x37d9ec,_0xa886fa);},'bmAie':_0xcb06ad(0x1ae),'CSbLS':function(_0x43014c,_0x499296,_0xe9b602,_0x559014){return _0x43014c(_0x499296,_0xe9b602,_0x559014);},'JrcIw':function(_0x597d1d,_0x11fceb,_0x2b7243,_0x17b685){return _0x597d1d(_0x11fceb,_0x2b7243,_0x17b685);},'TdQXn':function(_0x20b953,_0x4a1c55){return _0x20b953===_0x4a1c55;},'yeUGy':_0xcb06ad(0x175),'uWpZa':_0xcb06ad(0x17c),'ZqEuR':function(_0x1c8c82,_0x5bf8e3,_0x5413f5){return _0x1c8c82(_0x5bf8e3,_0x5413f5);},'DrXJR':function(_0x138d72,_0x21cc7f){return _0x138d72===_0x21cc7f;},'zRbXd':function(_0x5c65f0,_0x11df55){return _0x5c65f0>_0x11df55;},'NBIwA':function(_0x27339e,_0x13634f){return _0x27339e===_0x13634f;},'dOCgx':'ctIlE','lSsus':_0xcb06ad(0x256),'ovOAB':_0xcb06ad(0x274),'xJlux':_0xcb06ad(0x213),'MRsHH':_0xcb06ad(0x245),'ojiGN':_0xcb06ad(0x217)};if(!_0x1243d1){if(_0xb4d618['uqiRZ'](_0xb4d618[_0xcb06ad(0x165)],_0xb4d618[_0xcb06ad(0x186)])){console[_0xcb06ad(0x258)](_0xcb06ad(0x290)+_0xcb06ad(0x216)+_0xcb06ad(0x107)+'\x20'+_0x115cfd+(_0xcb06ad(0x115)+_0xcb06ad(0x28a)+'h\x20fresh\x20da'+'ta'));if(_0x5c3e35&&_0x8d575f)try{_0x3d62d0[_0xcb06ad(0x212)](_0xcb06ad(0x8b));const _0x422ab1=await _0xb4d618['sLwkL'](_0x427907,_0x5c3e35,_0x8d575f,_0x3b1c08);if(_0xb4d618[_0xcb06ad(0x147)](_0x422ab1[_0xcb06ad(0x114)],_0x4bde7e[_0xcb06ad(0x13e)])||_0x422ab1[_0xcb06ad(0x114)]===0x6*0xe9+-0x218*-0x4+-0xca9){_0x549969[_0xcb06ad(0x244)](_0x4bde7e[_0xcb06ad(0x13e)],{'Location':_0x422ab1[_0xcb06ad(0xbd)]['url']}),_0x549969['end']();return;}if(_0xb4d618[_0xcb06ad(0x20d)](_0x422ab1[_0xcb06ad(0x114)],_0x4bde7e['OK'])&&_0x422ab1[_0xcb06ad(0xbd)]&&_0x422ab1[_0xcb06ad(0xbd)][_0xcb06ad(0x7e)]){const {url:_0x8f80f5,cloudtype:_0x5d32a7,expiration:_0x4a3a13,path:_0x50f7f8,headers:_0x7460c4,uniqid:_0x3589b7,thumb:_0x2c40a9}=_0x422ab1[_0xcb06ad(0xbd)],_0x30635e={'realUrl':_0x8f80f5,'cloudtype':_0x5d32a7,'expiration':_0xb4d618['Ibied'](_0x4a3a13,0xabc+0x1dd*-0x7+-0x1*-0x637),'path':_0x50f7f8,'headers':_0x7460c4,'uniqid':_0x3589b7,'thumb':_0x2c40a9};_0x3842fc[_0x591912]={'uniqid':_0x30635e[_0xcb06ad(0x12e)],'timestamp':Date[_0xcb06ad(0x24f)]()},await _0x219f35['promises'][_0xcb06ad(0x20a)](_0xe7013d[_0xcb06ad(0x2a7)](_0x5b08c0),{'recursive':!![]}),await _0x219f35[_0xcb06ad(0x9d)][_0xcb06ad(0x81)](_0x5b08c0,JSON[_0xcb06ad(0x145)](_0x30635e));const _0x27afb3=_0xe7013d[_0xcb06ad(0x12c)](_0x40c942,_0x30635e[_0xcb06ad(0x12e)]+'_'+_0x53175f[_0xcb06ad(0x231)+'s'](0x1ddb+-0x5*0x5c3+-0xfc)[_0xcb06ad(0x200)]('hex')+_0xcb06ad(0x19b));_0xb4d618[_0xcb06ad(0x1f5)](_0x3442af,_0x30635e,_0x27afb3,_0x115cfd,_0x5b08c0,_0x549969,_0x1a74ac);return;}else{if(_0xb4d618[_0xcb06ad(0x2af)]!==_0xb4d618[_0xcb06ad(0x2af)])_0xb4d618[_0xcb06ad(0x1f5)](_0x1a275e,_0x2ebe6e,_0x4bcd34,_0x31a5a6,_0x2c27f3,_0x36ff8b,_0x34a007);else{_0x3d62d0['increment'](_0xb4d618[_0xcb06ad(0x95)]),_0x49245c(_0x549969,_0x4bde7e[_0xcb06ad(0xdf)+'Y'],_0x422ab1['message']||_0xb4d618[_0xcb06ad(0x15b)]);return;}}}catch(_0x372f6d){_0x3d62d0['increment'](_0xb4d618[_0xcb06ad(0x1ff)]),console['error'](_0xb4d618[_0xcb06ad(0x100)],_0x372f6d),_0x49245c(_0x549969,_0x4bde7e[_0xcb06ad(0x149)+'ERVER_ERRO'+'R'],_0xcb06ad(0xec)+_0xcb06ad(0x87)+_0xcb06ad(0x25c)+_0x372f6d[_0xcb06ad(0x116)]);return;}else{if(_0xb4d618[_0xcb06ad(0x2b0)](_0xb4d618['PGUZN'],'BvIAV')){console[_0xcb06ad(0x245)](_0xcb06ad(0x1f0)+'ache\x20calle'+_0xcb06ad(0x169)+_0xcb06ad(0x15a)+_0xcb06ad(0x2a2)+_0xcb06ad(0x211)+'equest\x20inf'+_0xcb06ad(0x222)+_0x115cfd),_0xb4d618[_0xcb06ad(0x1fb)](_0x49245c,_0x549969,_0x4bde7e['INTERNAL_S'+_0xcb06ad(0xe2)+'R'],_0xb4d618[_0xcb06ad(0x297)]);return;}else{_0x19b758['error'](_0xcb06ad(0x1f0)+_0xcb06ad(0x1d5)+_0xcb06ad(0x169)+_0xcb06ad(0x15a)+_0xcb06ad(0x2a2)+'fficient\x20r'+_0xcb06ad(0x125)+_0xcb06ad(0x222)+_0x10b923),_0x44359e(_0x308f2b,_0x1589ef['INTERNAL_S'+_0xcb06ad(0xe2)+'R'],_0xb4d618[_0xcb06ad(0x297)]);return;}}}else return{'cacheData':_0x5e9659,'isNotModified':!![]};}let _0x1f1ed8=_0x1243d1[_0xcb06ad(0x12e)],_0x2723ec=_0x1243d1['headers']&&_0x1243d1[_0xcb06ad(0x1d0)][_0xb4d618[_0xcb06ad(0x1f2)]];if(_0xb4d618[_0xcb06ad(0x121)](!_0x1f1ed8,!_0x2723ec)){if(_0xb4d618[_0xcb06ad(0x1fd)]!==_0xb4d618['YlqlG']){const {url:_0x4d65a5,cloudtype:_0x3f7d8b,expiration:_0x571a2d,path:_0x12e69f,headers:_0x13212e,uniqid:_0x39890c,thumb:_0x421d68}=_0x466bb2[_0xcb06ad(0xbd)],_0x145766={'realUrl':_0x4d65a5,'cloudtype':_0x3f7d8b,'expiration':_0x571a2d*(-0x1c00+-0x2511*0x1+-0x1*-0x44f9),'path':_0x12e69f,'headers':_0x13212e,'uniqid':_0x39890c,'thumb':_0x421d68};_0x47bce9[_0x414aa8]={'uniqid':_0x145766['uniqid'],'timestamp':_0x31a3ff[_0xcb06ad(0x24f)]()};const _0x4bd258=_0x5ec1cf['join'](_0x30207d,_0x145766['uniqid']+'_'+_0x20df97[_0xcb06ad(0x231)+'s'](-0x2*0x13a+0x175*-0x9+0xfa1)[_0xcb06ad(0x200)](_0xb4d618[_0xcb06ad(0x29d)])+_0xcb06ad(0x19b));_0x13b2e8(_0x145766,_0x4bd258,_0x200c5e,_0x49e4bc,_0x1e1a45,_0x3afdd6);}else try{const [_0xb1181c,_0x217fa8]=await Promise[_0xcb06ad(0xd7)]([_0x219f35[_0xcb06ad(0x9d)]['stat'](_0x115cfd)['catch'](()=>null),_0x219f35['promises'][_0xcb06ad(0x19d)](_0x5b08c0)['catch'](()=>null)]);_0xb4d618[_0xcb06ad(0x174)](!_0x1f1ed8,_0xb1181c)&&(_0x1f1ed8=_0x53175f[_0xcb06ad(0x179)](_0xb4d618[_0xcb06ad(0x1be)])[_0xcb06ad(0xfd)](_0xb1181c[_0xcb06ad(0x192)]+'-'+_0xb1181c[_0xcb06ad(0x9b)][_0xcb06ad(0x14d)]())[_0xcb06ad(0x28f)](_0xb4d618[_0xcb06ad(0x29d)])),_0xb4d618[_0xcb06ad(0x174)](!_0x2723ec,_0x217fa8)&&(_0x2723ec=new Date(_0x217fa8[_0xcb06ad(0x9b)])[_0xcb06ad(0x199)+'g']());}catch(_0x22b1af){console[_0xcb06ad(0x258)](_0xcb06ad(0xe5)+_0xcb06ad(0xff)+_0xcb06ad(0x93)+_0xcb06ad(0xa9)+_0x22b1af[_0xcb06ad(0x116)]);}}const _0xf4ae47={'Cloud-Type':_0x1243d1[_0xcb06ad(0x7d)]||_0xb4d618[_0xcb06ad(0xd2)],'ETag':_0xb4d618[_0xcb06ad(0xa4)](_0x1f1ed8,''),'Cache-Control':_0xb4d618[_0xcb06ad(0xf7)],'Expires':new Date(_0xb4d618[_0xcb06ad(0x91)](Date['now'](),-0xf62267b*0x6d+-0xe99ccf07e+0x1c7e487edd))[_0xcb06ad(0x199)+'g'](),'Accept-Ranges':_0xb4d618['bFoUt'],'Connection':_0xb4d618[_0xcb06ad(0x112)],'Date':new Date()[_0xcb06ad(0x199)+'g'](),'Last-Modified':_0x2723ec||new Date()[_0xcb06ad(0x199)+'g'](),'Access-Control-Allow-Origin':_0x1a74ac[_0xcb06ad(0x1d0)][_0xcb06ad(0x28d)]||'*','Access-Control-Allow-Methods':'GET,\x20OPTIO'+'NS','Access-Control-Allow-Headers':_0xcb06ad(0x9e)+'pe'};_0x3d62d0[_0xcb06ad(0x212)](_0xb4d618['OMWiT']);try{await _0x219f35[_0xcb06ad(0x9d)][_0xcb06ad(0x1e0)](_0x115cfd,_0x219f35[_0xcb06ad(0x1c4)][_0xcb06ad(0x148)]);}catch(_0x5c0111){console['warn'](_0xcb06ad(0x2bb)+_0xcb06ad(0x84)+_0x115cfd+(_0xcb06ad(0x1aa)+'sible:\x20')+_0x5c0111[_0xcb06ad(0x116)]);if(_0xb4d618[_0xcb06ad(0x10e)](_0x5c3e35,_0x8d575f)){console[_0xcb06ad(0xbb)](_0xcb06ad(0x249)+_0xcb06ad(0x134)+_0xcb06ad(0x13f)+_0xcb06ad(0x278)+_0x115cfd);try{if(_0xb4d618[_0xcb06ad(0x20d)](_0xb4d618['bTVfh'],_0xb4d618[_0xcb06ad(0x89)])){_0x3d62d0[_0xcb06ad(0x212)](_0xb4d618[_0xcb06ad(0xad)]);const _0x125981=await _0xb4d618['QfxKR'](_0x427907,_0x5c3e35,_0x8d575f,_0x3b1c08);if(_0xb4d618[_0xcb06ad(0x2b2)](_0x125981[_0xcb06ad(0x114)],_0x4bde7e['OK'])&&_0x125981['data']&&_0x125981[_0xcb06ad(0xbd)]['url']){const {url:_0xf1279b,cloudtype:_0x54e50c,expiration:_0x153740,path:_0x38c3d1,headers:_0x107608,uniqid:_0x1cf692,thumb:_0x48f475}=_0x125981[_0xcb06ad(0xbd)],_0x181b2d={'realUrl':_0xf1279b,'cloudtype':_0x54e50c,'expiration':_0xb4d618[_0xcb06ad(0x264)](_0x153740,-0xfe6+0x1df4+0x1*-0xa26),'path':_0x38c3d1,'headers':_0x107608,'uniqid':_0x1cf692,'thumb':_0x48f475};_0x3842fc[_0x591912]={'uniqid':_0x181b2d[_0xcb06ad(0x12e)],'timestamp':Date[_0xcb06ad(0x24f)]()};const _0x1ace9f=_0xe7013d[_0xcb06ad(0x12c)](_0x40c942,_0x181b2d[_0xcb06ad(0x12e)]+'_'+_0x53175f['randomByte'+'s'](-0x2355+0x1b28+0x83d)[_0xcb06ad(0x200)](_0xb4d618[_0xcb06ad(0x29d)])+_0xcb06ad(0x19b));_0xb4d618['cfEKS'](_0x3442af,_0x181b2d,_0x1ace9f,_0x115cfd,_0x5b08c0,_0x549969,_0x1a74ac);return;}else{_0xb4d618[_0xcb06ad(0x8d)](_0x49245c,_0x549969,_0x4bde7e[_0xcb06ad(0xdf)+'Y'],_0x125981[_0xcb06ad(0x116)]||_0xb4d618[_0xcb06ad(0x15b)]);return;}}else _0x44442b['warn']('Content\x20le'+'ngth\x20misma'+_0xcb06ad(0x284)+_0x2fc1df+'.\x20API:\x20'+_0x209107['headers'][_0xb4d618[_0xcb06ad(0x7c)]]+',\x20Cache:\x20'+_0x4e72a1+(_0xcb06ad(0x132)+'ing.')),_0x211985(_0x2194a0,_0x54e6d1,_0x15b7a7,_0x33789c,_0x48027b,_0x3dfcb7);}catch(_0x4cb8c0){if(_0xb4d618[_0xcb06ad(0x262)]===_0xb4d618[_0xcb06ad(0x262)]){console[_0xcb06ad(0x245)](_0xcb06ad(0x288)+_0xcb06ad(0x1df)+_0xcb06ad(0x280)+_0x4cb8c0['message']),_0xb4d618[_0xcb06ad(0xc9)](_0x49245c,_0x549969,_0x4bde7e[_0xcb06ad(0x149)+_0xcb06ad(0xe2)+'R'],_0xcb06ad(0xec)+_0xcb06ad(0x87)+_0xcb06ad(0x25c)+_0x4cb8c0[_0xcb06ad(0x116)]);return;}else{const _0x48d3f0=_0xf91603[_0xcb06ad(0x12c)](_0x1f78c9,_0x3a36e8+'.meta'),_0x1b1939=_0x40b4b9['join'](_0x23f383,_0x3cee00[_0x35075a]['uniqid']+_0xcb06ad(0x1bf));if(_0x22994f['existsSync'](_0x48d3f0)&&_0x108915[_0xcb06ad(0x1eb)](_0x1b1939)){_0x9b4bca[_0xcb06ad(0x258)](_0xcb06ad(0x117)+'ailed\x20or\x20r'+_0xcb06ad(0x1ee)+'n-200.\x20Ser'+_0xcb06ad(0x188)+_0xcb06ad(0x157)+'\x20'+_0x5248cd);try{const _0x983abf=_0x33bc44[_0xcb06ad(0x176)](_0x2e3f8f['readFileSy'+'nc'](_0x48d3f0,_0xb4d618[_0xcb06ad(0x29a)]));_0xb4d618[_0xcb06ad(0xf5)](_0x1069ae,_0x983abf,_0x1b1939,_0x48d3f0,_0x28e326,null,null,null,_0x4dca15,_0x3cdc3f);return;}catch(_0x11ab61){_0x13299b[_0xcb06ad(0x245)](_0xcb06ad(0x2bd)+'ing\x20stale\x20'+_0xcb06ad(0x2ad)+_0x48d3f0+':',_0x11ab61);}}}}}else{_0xb4d618['JrcIw'](_0x49245c,_0x549969,_0x4bde7e[_0xcb06ad(0x149)+_0xcb06ad(0xe2)+'R'],_0xcb06ad(0xd6)+_0xcb06ad(0x1ef)+_0xcb06ad(0x1ec)+'ile\x20and\x20ca'+_0xcb06ad(0x1f6)+_0xcb06ad(0x122)+'a.');return;}}const _0x28f7a7=_0x219f35[_0xcb06ad(0x29c)+_0xcb06ad(0xa5)](_0x115cfd,{'highWaterMark':(0x1*-0x11b1+0x226e+0x25b*-0x7)*(0x144f*0x1+0x493*0x7+-0x3054)}),_0x304757=_0x1243d1['path']&&_0xb4d618[_0xcb06ad(0x161)](typeof _0x1243d1[_0xcb06ad(0x253)],_0xb4d618['yeUGy'])&&_0x1243d1['path'][_0xcb06ad(0x28e)](_0xb4d618['uWpZa']);let _0x1ed58c=_0x1243d1[_0xcb06ad(0x1d0)]&&_0x1243d1['headers'][_0xb4d618['IaNaG']]?_0xb4d618['ZqEuR'](parseInt,_0x1243d1[_0xcb06ad(0x1d0)][_0xcb06ad(0xcb)+_0xcb06ad(0x1de)],0x1859+-0x7a+-0x17d5):-0x16e1+0x6*-0x5fb+0x31*0x133;if(!_0x1ed58c||_0xb4d618[_0xcb06ad(0x2be)](_0x1ed58c,0x4f*0x17+0xf*0x23f+0x17*-0x1c6))try{const _0x19278b=_0x219f35[_0xcb06ad(0x172)](_0x115cfd);_0x1ed58c=_0x19278b[_0xcb06ad(0x192)];if(_0xb4d618['zRbXd'](_0x1ed58c,0xda9+-0xf5+-0xcb4)){if(_0xb4d618[_0xcb06ad(0xdb)](_0xb4d618[_0xcb06ad(0x143)],_0xb4d618[_0xcb06ad(0x143)])){if(!_0x1243d1[_0xcb06ad(0x1d0)])_0x1243d1[_0xcb06ad(0x1d0)]={};_0x1243d1[_0xcb06ad(0x1d0)][_0xb4d618[_0xcb06ad(0x7c)]]=_0x1ed58c['toString'](),_0x219f35['writeFileS'+'ync'](_0x5b08c0,JSON['stringify'](_0x1243d1)),console[_0xcb06ad(0xbb)](_0xcb06ad(0x1e2)+_0xcb06ad(0x1db)+_0xcb06ad(0x27d)+_0x5b08c0+_0xcb06ad(0x1b3)+_0x1ed58c);}else!_0x380b1f[_0xcb06ad(0x1e4)+_0xcb06ad(0x1b1)]&&(_0x53b529[_0xcb06ad(0xbb)](_0xcb06ad(0x130)+_0xcb06ad(0x1ad)+'tion\x20prema'+_0xcb06ad(0x13d)+'\x20'+_0x33f911+(_0xcb06ad(0x20c)+_0xcb06ad(0x137)+_0xcb06ad(0x26f))),_0x408342[_0xcb06ad(0x17e)]());}else console['warn'](_0xcb06ad(0x119)+'tent\x20file\x20'+_0x115cfd+(_0xcb06ad(0x2b4)+_0xcb06ad(0x16b)+'failed.'));}catch(_0x3d5b0a){if(_0xb4d618[_0xcb06ad(0x2b0)](_0xb4d618[_0xcb06ad(0x261)],_0xb4d618[_0xcb06ad(0x227)])){console[_0xcb06ad(0x245)](_0xcb06ad(0x185)+_0xcb06ad(0x1a8)+_0xcb06ad(0x167)+'le\x20'+_0x115cfd+':',_0x3d5b0a),_0xb4d618[_0xcb06ad(0x1f5)](_0x48a095,_0x549969,_0x115cfd,_0x5c3e35,_0x8d575f,_0x3b1c08,_0x591912);return;}else{if(_0x105ab1['query']['api']){const _0x1824af=/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w.-]*)*\/?$/;_0x1824af['test'](_0x585e98['query'][_0xcb06ad(0x17b)])&&(_0x3e55c2=_0x2898b5[_0xcb06ad(0x273)][_0xcb06ad(0x17b)],_0x2d294e[_0xcb06ad(0xbb)](_0xcb06ad(0x289)+_0xcb06ad(0x2c0)+_0xcb06ad(0x11e)+_0x264cf8));}_0x538bd5[_0xcb06ad(0x244)](_0x3f707b['OK'],{'Content-Type':_0xb4d618[_0xcb06ad(0x2ba)]}),_0x142418[_0xcb06ad(0x219)](_0x26041a[_0xcb06ad(0x145)]({'code':_0x3e931b['OK'],'data':{'api':_0x28db1c,'port':_0x22efc1,'cacheDir':_0x22bc96,'pathIndexCount':_0x890fc3[_0xcb06ad(0x181)](_0x18c79a)[_0xcb06ad(0x207)],'viewsInfo':{'request':_0xf353db['request'],'cacheHit':_0x4a5dc6[_0xcb06ad(0x1b8)],'apiCall':_0x5c6cfa[_0xcb06ad(0x8b)],'cacheCall':_0x2719ad[_0xcb06ad(0xbf)],'cacheReadError':_0x50323b[_0xcb06ad(0x10c)+'rror'],'fetchApiError':_0x186e29['fetchApiEr'+_0xcb06ad(0x1a4)],'fetchApiWarning':_0x2dc43e[_0xcb06ad(0xc7)+_0xcb06ad(0xe3)]}}}));}}_0x28f7a7['on'](_0xb4d618['xJlux'],()=>{const _0x325af9=_0xcb06ad,_0x45de77={..._0xf4ae47,'Content-Type':_0x1243d1[_0x325af9(0x1d0)]&&_0x1243d1[_0x325af9(0x1d0)][_0xb4d618['NAOnQ']]||(_0x304757?_0x325af9(0xcc):_0xb4d618['zzIla']),..._0x1243d1[_0x325af9(0x1d0)]||{}};_0x549969[_0x325af9(0x244)](_0x4bde7e['OK'],_0x45de77),_0x28f7a7[_0x325af9(0x2bf)](_0x549969);}),_0x28f7a7['on'](_0xb4d618[_0xcb06ad(0x80)],_0x51c7b3=>{const _0x34118d=_0xcb06ad,_0x23486b={'lnxLW':function(_0x5c3fd4,_0x64221b){return _0xb4d618['ZahvV'](_0x5c3fd4,_0x64221b);},'bIlxa':_0xb4d618[_0x34118d(0x1ff)],'vmRUN':function(_0x37119a,_0x3fdbdb,_0x34c5d0,_0x53e48f){const _0x31ce27=_0x34118d;return _0xb4d618[_0x31ce27(0x1fb)](_0x37119a,_0x3fdbdb,_0x34c5d0,_0x53e48f);}};console[_0x34118d(0x245)](_0x34118d(0x1d6)+'m\x20error\x20fo'+'r\x20'+_0x115cfd+':',_0x51c7b3),_0xb4d618[_0x34118d(0x174)](_0x5c3e35,_0x8d575f)?(console[_0x34118d(0xbb)]('Read\x20strea'+_0x34118d(0x24c)+_0x34118d(0x196)+_0x34118d(0x138)+_0x34118d(0x24a)+_0x34118d(0x8c)+_0x115cfd),_0x3d62d0['increment'](_0xb4d618['azvLi']),_0x427907(_0x5c3e35,_0x8d575f,_0x3b1c08)[_0x34118d(0x19f)](_0x17ae22=>{const _0x134a7e=_0x34118d;if(_0xb4d618[_0x134a7e(0x1a1)](_0xb4d618['VyeCi'],_0xb4d618[_0x134a7e(0x11a)])){if(_0xb4d618[_0x134a7e(0x150)](_0x17ae22['code'],_0x4bde7e['OK'])&&_0x17ae22['data']&&_0x17ae22[_0x134a7e(0xbd)]['url']){const {url:_0x107256,cloudtype:_0x5c1a5c,expiration:_0x1fd6c8,path:_0x1efe88,headers:_0x4b4242,uniqid:_0x3f29ff,thumb:_0x256fb8}=_0x17ae22[_0x134a7e(0xbd)],_0x15db48={'realUrl':_0x107256,'cloudtype':_0x5c1a5c,'expiration':_0xb4d618[_0x134a7e(0x264)](_0x1fd6c8,-0x5*0x4fd+0x19d0+0x309),'path':_0x1efe88,'headers':_0x4b4242,'uniqid':_0x3f29ff,'thumb':_0x256fb8};_0x3842fc[_0x591912]={'uniqid':_0x15db48[_0x134a7e(0x12e)],'timestamp':Date[_0x134a7e(0x24f)]()};const _0x3ee906=_0xe7013d[_0x134a7e(0x12c)](_0x40c942,_0x15db48[_0x134a7e(0x12e)]+'_'+_0x53175f['randomByte'+'s'](0x1a08+0x266+-0x1c5e)['toString'](_0xb4d618['jBhEb'])+_0x134a7e(0x19b));_0x3442af(_0x15db48,_0x3ee906,_0x115cfd,_0x5b08c0,_0x549969,_0x1a74ac);}else _0x3d62d0[_0x134a7e(0x212)](_0xb4d618['bBSPR']),_0xb4d618['QfxKR'](_0x49245c,_0x549969,_0x4bde7e['BAD_GATEWA'+'Y'],_0x17ae22['message']||_0xb4d618['ZomvI']);}else _0x469bfc['writeHead'](_0xa7b169[_0x134a7e(0x13e)],{'Location':_0x5bbea5[_0x134a7e(0xbd)]['url']}),_0x130da5[_0x134a7e(0x219)]();})[_0x34118d(0x1bb)](_0x1a6c42=>{const _0x2781ca=_0x34118d;_0x23486b['lnxLW'](_0x2781ca(0xa8),'IbBsj')?(_0x3d62d0['increment'](_0x23486b['bIlxa']),console['error'](_0x2781ca(0x288)+_0x2781ca(0x1df)+'\x20data\x20afte'+_0x2781ca(0x20f)+_0x2781ca(0x23e)+'\x20'+_0x1a6c42[_0x2781ca(0x116)]),_0x23486b['vmRUN'](_0x49245c,_0x549969,_0x4bde7e[_0x2781ca(0x149)+_0x2781ca(0xe2)+'R'],'Failed\x20to\x20'+_0x2781ca(0x87)+_0x2781ca(0x25c)+_0x1a6c42[_0x2781ca(0x116)])):_0x320b0f[_0x5bd280]={'uniqid':_0x3c9bf9[_0x2781ca(0x12e)],'timestamp':_0x44eabe['now']()};})):_0xb4d618[_0x34118d(0x28b)](_0x48a095,_0x549969,_0x115cfd,_0x5c3e35,_0x8d575f,_0x3b1c08,_0x591912);}),_0x549969['on'](_0xb4d618[_0xcb06ad(0xbc)],()=>{const _0x285434=_0xcb06ad;if(!_0x549969[_0x285434(0x1e4)+_0x285434(0x1b1)]){if(_0xb4d618[_0x285434(0x1d9)]!==_0xb4d618[_0x285434(0x1d9)])try{_0xdc7893[_0x285434(0xf3)](_0x28b08d,{'recursive':!![]}),_0x5da90c[_0x285434(0xbb)](_0x285434(0x281)+_0x285434(0xd8)+'ted:\x20'+_0x4e84c3);}catch(_0x59c697){_0x3f7513['error'](_0x285434(0x27c)+_0x285434(0xfc)+'\x20directory'+'\x20'+_0x1a6892+':',_0x59c697),_0x12e31d[_0x285434(0x220)](0x13*-0x6c+-0x228d+0x2a92);}else console[_0x285434(0xbb)](_0x285434(0x130)+_0x285434(0x1ad)+_0x285434(0x210)+_0x285434(0x13d)+'\x20'+_0x115cfd+(_0x285434(0x20c)+_0x285434(0x137)+_0x285434(0x26f))),_0x28f7a7['destroy']();}});}function _0x4a4a(_0x4f218e,_0x4f5992){const _0x5171ee=_0x3a8a();return _0x4a4a=function(_0x7f01a8,_0x2a2b37){_0x7f01a8=_0x7f01a8-(0x105*-0x1d+-0xca*-0x5+0x1a1b);let _0x4d1105=_0x5171ee[_0x7f01a8];return _0x4d1105;},_0x4a4a(_0x4f218e,_0x4f5992);}const _0x3b30d7=(_0x1b9518,_0x21f2dd,_0x383281)=>{const _0x5837f8=_0x5f2e5a,_0x447ec2={'jFKhX':function(_0x3eace6,_0x836fd1,_0x31e7fb,_0x254269,_0x216ff0,_0x1198e8,_0x4919f6){return _0x3eace6(_0x836fd1,_0x31e7fb,_0x254269,_0x216ff0,_0x1198e8,_0x4919f6);},'GuIvp':function(_0x37de4a,_0x16fc50){return _0x37de4a>_0x16fc50;},'PheHa':_0x5837f8(0xa1)+_0x5837f8(0x1a4),'jMipU':function(_0x38013e,_0xa2c764,_0x511364,_0x194b45){return _0x38013e(_0xa2c764,_0x511364,_0x194b45);},'CAwQN':_0x5837f8(0x204),'iRkmb':function(_0x2388a1,_0x414d74){return _0x2388a1===_0x414d74;},'fHQvI':_0x5837f8(0x1b4)};_0x3d62d0['increment'](_0x447ec2[_0x5837f8(0x16c)]),console['error'](_0x5837f8(0x288)+'hing\x20from\x20'+_0x5837f8(0x12f)+_0x383281),_0x447ec2['jMipU'](_0x49245c,_0x1b9518,_0x4bde7e['BAD_GATEWA'+'Y'],_0x5837f8(0x1c7)+'y:\x20Failed\x20'+_0x5837f8(0x138)+_0x5837f8(0x206)+_0x383281);if(_0x219f35[_0x5837f8(0x1eb)](_0x21f2dd))try{_0x447ec2['CAwQN']!=='myimk'?_0x219f35['unlinkSync'](_0x21f2dd):_0x447ec2[_0x5837f8(0x1bd)](_0x3f223d,_0x13c5c7,_0x52b295,_0x558f8e,_0x55ea6e,_0x43498d,_0xd33f8e);}catch(_0x40d962){if(_0x447ec2[_0x5837f8(0x163)](_0x447ec2[_0x5837f8(0x170)],_0x447ec2[_0x5837f8(0x170)]))console[_0x5837f8(0x245)]('Error\x20unli'+'nking\x20temp'+'\x20file\x20'+_0x21f2dd+':',_0x40d962);else{if(_0x447ec2['GuIvp'](_0x47bff2-_0x164d73[_0x5f18b9][_0x5837f8(0x21e)],_0x1585ba)){_0x3b47cd[_0x5837f8(0xb3)](_0x4d4c71);const _0x201a43=_0x2a317e['join'](_0x58160c,_0x4cb50b+_0x5837f8(0x2a4)),_0x163fb2=_0x287029[_0x5837f8(0x12c)](_0x3d8e9d,_0x3f741c[_0x2559b8][_0x5837f8(0x12e)]+_0x5837f8(0x1bf));_0x4416ef[_0x5837f8(0xb3)](_0x201a43,_0x163fb2);}}}},_0x48a095=(_0x5b2431,_0x291ea0,_0x9ea73e,_0x1f7d41,_0x47a8f0,_0x231537)=>{const _0x1499a6=_0x5f2e5a,_0x40e0bd={'obvUj':function(_0xa009cb,_0x4582d1,_0x3f59dd,_0x4f2c3b,_0x497fc4,_0x3de096,_0xce4c8f){return _0xa009cb(_0x4582d1,_0x3f59dd,_0x4f2c3b,_0x497fc4,_0x3de096,_0xce4c8f);},'slBDa':function(_0x215f68,_0x541115){return _0x215f68!==_0x541115;},'vYebf':_0x1499a6(0x110),'avfMW':_0x1499a6(0x250),'Gfoun':function(_0x17578c,_0xb24a93,_0xba4b70,_0x479d61){return _0x17578c(_0xb24a93,_0xba4b70,_0x479d61);},'ZuAbO':_0x1499a6(0xec)+_0x1499a6(0xd0)+_0x1499a6(0xdc),'xxfQl':function(_0x421fd0,_0x379be8){return _0x421fd0===_0x379be8;},'EwATS':_0x1499a6(0x294),'bBxFN':'jtfQR','lRDXn':function(_0x1aa6d7,_0xa755f8){return _0x1aa6d7!==_0xa755f8;},'IwPZE':_0x1499a6(0x239),'cDHng':_0x1499a6(0xec)+_0x1499a6(0x1d7)+_0x1499a6(0x183),'dfMhv':_0x1499a6(0xa1)+'ror','WkZdQ':_0x1499a6(0x10c)+_0x1499a6(0x13a),'UWwGu':'apiCall','SsfzH':_0x1499a6(0xb6)+_0x1499a6(0x257)+'r:\x20Unable\x20'+_0x1499a6(0x293)+_0x1499a6(0x1ce)+_0x1499a6(0x14b)};_0x3d62d0[_0x1499a6(0x212)](_0x40e0bd[_0x1499a6(0x1b0)]),console[_0x1499a6(0x245)](_0x1499a6(0xfb)+_0x1499a6(0x1a8)+_0x1499a6(0x14c)+_0x291ea0),_0x9ea73e&&_0x1f7d41?(console['log'](_0x1499a6(0x156)+'\x20error,\x20at'+_0x1499a6(0xaf)+_0x1499a6(0xee)+_0x1499a6(0x226)+_0x1499a6(0x267)+_0x291ea0),_0x3d62d0['increment'](_0x40e0bd[_0x1499a6(0xd3)]),_0x40e0bd[_0x1499a6(0x1cd)](_0x427907,_0x9ea73e,_0x1f7d41,_0x47a8f0)['then'](_0x10f3db=>{const _0x42c488=_0x1499a6,_0x4da0d6={'aFdOk':_0x42c488(0x1f4)+'pe'};if(_0x40e0bd['xxfQl'](_0x10f3db[_0x42c488(0x114)],_0x4bde7e['OK'])&&_0x10f3db[_0x42c488(0xbd)]&&_0x10f3db[_0x42c488(0xbd)][_0x42c488(0x7e)]){if(_0x40e0bd[_0x42c488(0x2a5)]!==_0x40e0bd[_0x42c488(0x146)]){const {url:_0x21bf8e,cloudtype:_0x37269c,expiration:_0x3ed923,path:_0x932f7b,headers:_0x370ea5,uniqid:_0x377153,thumb:_0x4a05f3}=_0x10f3db[_0x42c488(0xbd)],_0x38d4ad={'realUrl':_0x21bf8e,'cloudtype':_0x37269c,'expiration':_0x3ed923*(-0x9*-0x3c7+-0x1*0x1d85+-0x2*0x49),'path':_0x932f7b,'headers':_0x370ea5,'uniqid':_0x377153,'thumb':_0x4a05f3};_0x231537&&(_0x3842fc[_0x231537]={'uniqid':_0x38d4ad[_0x42c488(0x12e)],'timestamp':Date['now']()});const _0x2b363e=_0xe7013d[_0x42c488(0x12c)](_0x40c942,_0x231537+_0x42c488(0x2a4)),_0x260606=_0xe7013d[_0x42c488(0x12c)](_0x40c942,_0x38d4ad[_0x42c488(0x12e)]+_0x42c488(0x1bf)),_0xfe1388=_0xe7013d['join'](_0x40c942,_0x38d4ad[_0x42c488(0x12e)]+'_'+_0x53175f[_0x42c488(0x231)+'s'](-0x1522*-0x1+-0x10*0x48+-0x1092)[_0x42c488(0x200)]('hex')+_0x42c488(0x19b));_0x219f35[_0x42c488(0x9d)]['mkdir'](_0xe7013d[_0x42c488(0x2a7)](_0x2b363e),{'recursive':!![]})[_0x42c488(0x19f)](()=>_0x219f35[_0x42c488(0x9d)]['writeFile'](_0x2b363e,JSON[_0x42c488(0x145)](_0x38d4ad)))[_0x42c488(0x19f)](()=>{const _0x175752=_0x42c488;_0x40e0bd[_0x175752(0x13b)](_0x3442af,_0x38d4ad,_0xfe1388,_0x260606,_0x2b363e,_0x5b2431,req);})[_0x42c488(0x1bb)](_0x226baa=>{const _0x175a22=_0x42c488;if(_0x40e0bd[_0x175a22(0x12b)](_0x40e0bd[_0x175a22(0x22c)],_0x40e0bd['avfMW']))console[_0x175a22(0x245)](_0x175a22(0xac)+_0x175a22(0x20e)+'ile\x20after\x20'+_0x175a22(0x7f)+_0x175a22(0x10b)+_0x226baa[_0x175a22(0x116)]),_0x40e0bd[_0x175a22(0x1cd)](_0x49245c,_0x5b2431,_0x4bde7e[_0x175a22(0x149)+'ERVER_ERRO'+'R'],_0x40e0bd['ZuAbO']);else{const _0x2e09b1={..._0x2d6dca,'Content-Type':_0x1ceab5[_0x175a22(0x1d0)]&&_0x514d3a['headers'][_0x4da0d6[_0x175a22(0x24b)]]||(_0x5547c3?_0x175a22(0xcc):_0x175a22(0x24e)+'n/octet-st'+_0x175a22(0x105)),..._0x10cb26[_0x175a22(0x1d0)]||{}};_0xd3723d[_0x175a22(0x244)](_0x1b112a['OK'],_0x2e09b1),_0x3248a5[_0x175a22(0x2bf)](_0x42803b);}});}else _0x4a43ca=_0x11f149;}else{if(_0x40e0bd['lRDXn'](_0x42c488(0x239),_0x40e0bd[_0x42c488(0xb7)]))return _0x5e9dae[_0x42c488(0x258)](_0x42c488(0xfb)+'ing\x20or\x20par'+_0x42c488(0x144)+_0x42c488(0xf4)+'\x20'+_0x2858ed+('\x20for\x20valid'+_0x42c488(0x223)),_0x5115a8),![];else _0x3d62d0[_0x42c488(0x212)]('fetchApiWa'+_0x42c488(0xe3)),_0x49245c(_0x5b2431,_0x4bde7e[_0x42c488(0xdf)+'Y'],_0x10f3db['message']||_0x40e0bd[_0x42c488(0x22f)]);}})[_0x1499a6(0x1bb)](_0x3836de=>{const _0xe8460c=_0x1499a6;_0x3d62d0[_0xe8460c(0x212)](_0x40e0bd[_0xe8460c(0x190)]),console['error']('Error\x20fetc'+'hing\x20fresh'+'\x20data\x20afte'+_0xe8460c(0x1c1)+'ad\x20error:\x20'+_0x3836de['message']),_0x40e0bd[_0xe8460c(0x1cd)](_0x49245c,_0x5b2431,_0x4bde7e[_0xe8460c(0x149)+_0xe8460c(0xe2)+'R'],_0xe8460c(0xec)+'fetch\x20fres'+_0xe8460c(0x25c)+_0x3836de[_0xe8460c(0x116)]);})):_0x49245c(_0x5b2431,_0x4bde7e[_0x1499a6(0x149)+_0x1499a6(0xe2)+'R'],_0x40e0bd[_0x1499a6(0x229)]);};_0x326b5e[_0x5f2e5a(0x171)](_0x5a2d53,()=>{const _0x4235c2=_0x5f2e5a;console[_0x4235c2(0xbb)](_0x4235c2(0x1d1)+_0x4235c2(0x279)+_0x4235c2(0xe8)+_0x4235c2(0x235)+'ost:'+_0x5a2d53);}),process['on'](_0x5f2e5a(0x1d3),()=>{const _0x52a127=_0x5f2e5a,_0x34b99c={'jbqQn':function(_0x585fd0,_0x3ca5e0){return _0x585fd0===_0x3ca5e0;},'KZUyB':_0x52a127(0x158),'VspEU':_0x52a127(0x1a3)+'sed.','oBPML':_0x52a127(0x1cb)+_0x52a127(0xf6),'qNYLd':_0x52a127(0x285)+_0x52a127(0xe7)+_0x52a127(0xd5)+_0x52a127(0x9a)+_0x52a127(0x106),'OpYfk':function(_0xff3e6c,_0x1d145f,_0x111f34){return _0xff3e6c(_0x1d145f,_0x111f34);}};console[_0x52a127(0xbb)](_0x34b99c[_0x52a127(0xc5)]),_0x326b5e[_0x52a127(0x217)](()=>{const _0x3bdb42=_0x52a127;_0x34b99c[_0x3bdb42(0x16e)](_0x34b99c[_0x3bdb42(0xdd)],_0x34b99c[_0x3bdb42(0xdd)])?(console[_0x3bdb42(0xbb)](_0x34b99c['VspEU']),process[_0x3bdb42(0x220)](-0x43a*-0x5+-0x1*0x59+-0x14c9)):_0x23e8ef[_0x3bdb42(0xb4)](_0xbd901a);}),_0x34b99c[_0x52a127(0x202)](setTimeout,()=>{const _0x52d56b=_0x52a127;console[_0x52d56b(0x245)](_0x34b99c[_0x52d56b(0x291)]),process['exit'](-0x23b*0xd+0x3*-0x112+0x2036);},0x39*-0xd3+0x48d1+-0x2*-0x69d);}); \ No newline at end of file diff --git a/index.php b/index.php deleted file mode 100644 index b5de43f..0000000 --- a/index.php +++ /dev/null @@ -1,542 +0,0 @@ - 200, - 'NO_CONTENT' => 204, - 'REDIRECT' => 302, - 'NOT_MODIFIED' => 304, - 'BAD_REQUEST' => 400, - 'NOT_FOUND' => 404, - 'INTERNAL_SERVER_ERROR' => 500, - 'BAD_GATEWAY' => 502, -]; - -// 初始化变量 -$cacheDir = __DIR__ . '/' . CACHE_DIR_NAME; -$pathIndex = []; -$port = DEFAULT_PORT; -$apiEndpoint = DEFAULT_API_ENDPOINT; - -// 访问计数器 -$viewsInfo = [ - 'request' => 0, - 'cacheHit' => 0, - 'apiCall' => 0, - 'cacheCall' => 0, - 'cacheReadError' => 0, - 'fetchApiError' => 0, - 'fetchApiWarning' => 0, -]; - -// 解析命令行参数 -function parseArguments() { - global $port, $apiEndpoint; - - $options = getopt('', ['port:', 'api:']); - - if (isset($options['port'])) { - $parsedPort = intval($options['port']); - if ($parsedPort > 0) { - $port = $parsedPort; - } - } - - if (isset($options['api'])) { - $apiEndpoint = $options['api']; - } -} - -// 初始化应用 -function initializeApp() { - global $cacheDir; - - parseArguments(); - - if (!file_exists($cacheDir)) { - try { - mkdir($cacheDir, 0777, true); - echo "Cache directory created: {$cacheDir}\n"; - } catch (Exception $e) { - echo "Error creating cache directory {$cacheDir}: " . $e->getMessage() . "\n"; - exit(1); - } - } -} - -// 发送错误响应 -function sendErrorResponse($res, int $statusCode, string $message) { - if (!$res->isWritable()) { - return; - } - $res->status($statusCode); - $res->header('Content-Type', 'text/plain;charset=UTF-8'); - $res->end($message); -} - -// 处理favicon请求 -function handleFavicon($req, $res) { - $res->status(HTTP_STATUS['NO_CONTENT']); - $res->end(); -} - -// 处理endpoint请求 -function handleEndpoint($req, $res, array $queryParams) { - global $apiEndpoint, $port, $cacheDir, $pathIndex, $viewsInfo; - - if (isset($queryParams['api'])) { - $urlRegex = '/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w.-]*)*\/?$/'; - if (preg_match($urlRegex, $queryParams['api'])) { - $apiEndpoint = $queryParams['api']; - echo "API endpoint updated to: {$apiEndpoint}\n"; - } - } - - $res->status(HTTP_STATUS['OK']); - $res->header('Content-Type', 'application/json; charset=utf-8'); - $res->end(json_encode([ - 'code' => HTTP_STATUS['OK'], - 'data' => [ - 'api' => $apiEndpoint, - 'port' => $port, - 'cacheDir' => $cacheDir, - 'pathIndexCount' => count($pathIndex), - 'viewsInfo' => $viewsInfo - ] - ])); -} - -// 处理API重定向 -function handleApiRedirect($res, array $apiData) { - $res->status(HTTP_STATUS['REDIRECT']); - $res->header('Location', $apiData['data']['url']); - $res->end(); -} - -// 检查缓存头并返回是否为304 -function checkCacheHeaders($req, string $cacheMetaFile) { - try { - $metaContent = file_get_contents($cacheMetaFile); - $cacheData = json_decode($metaContent, true); - $ifNoneMatch = isset($req->header['if-none-match']) ? $req->header['if-none-match'] : null; - $ifModifiedSince = isset($req->header['if-modified-since']) ? $req->header['if-modified-since'] : null; - - // 检查ETag - if ($ifNoneMatch && isset($cacheData['uniqid']) && $ifNoneMatch === $cacheData['uniqid']) { - return ['cacheData' => $cacheData, 'isNotModified' => true]; - } - - // 检查If-Modified-Since - if ($ifModifiedSince && isset($cacheData['headers']['last-modified'])) { - try { - $lastModifiedDate = strtotime($cacheData['headers']['last-modified']); - $ifModifiedSinceDate = strtotime($ifModifiedSince); - - if ($lastModifiedDate <= $ifModifiedSinceDate) { - return ['cacheData' => $cacheData, 'isNotModified' => true]; - } - } catch (Exception $e) { - echo "Error parsing date for cache header check ({$cacheMetaFile}): " . $e->getMessage() . "\n"; - } - } - - return ['cacheData' => $cacheData, 'isNotModified' => false]; - } catch (Exception $e) { - echo "Error reading or parsing cache meta file {$cacheMetaFile} in checkCacheHeaders: " . $e->getMessage() . "\n"; - return ['cacheData' => null, 'isNotModified' => false]; - } -} - -// 检查缓存是否有效 -function isCacheValid(string $cacheMetaFile, string $cacheContentFile) { - if (!file_exists($cacheMetaFile) || !file_exists($cacheContentFile)) { - return false; - } - - try { - $metaContent = file_get_contents($cacheMetaFile); - $cacheData = json_decode($metaContent, true); - - return isset($cacheData['expiration']) && is_numeric($cacheData['expiration']) && $cacheData['expiration'] > time() * 1000; - } catch (Exception $e) { - echo "Error reading or parsing cache meta file {$cacheMetaFile} for validation: " . $e->getMessage() . "\n"; - return false; - } -} - -// 从API获取数据 -function fetchApiData(string $reqPath, string $token, string $sign) { - global $apiEndpoint; - - $queryParams = http_build_query([ - 'type' => $reqPath, - 'sign' => $sign - ]); - - $apiUrl = "{$apiEndpoint}?{$queryParams}"; - $parsedApiUrl = parse_url($apiUrl); - - $client = new Client($parsedApiUrl['host'], $parsedApiUrl['port'] ?? ($parsedApiUrl['scheme'] === 'https' ? 443 : 80), $parsedApiUrl['scheme'] === 'https'); - $client->setHeaders([ - 'Accept' => 'application/json; charset=utf-8', - 'User-Agent' => USER_AGENT, - 'token' => $token - ]); - $client->set(['timeout' => API_TIMEOUT_MS / 1000]); - - $path = isset($parsedApiUrl['path']) ? $parsedApiUrl['path'] : '/'; - if (isset($parsedApiUrl['query'])) { - $path .= '?' . $parsedApiUrl['query']; - } - - $client->get($path); - - if ($client->statusCode >= 400) { - echo "API request to {$apiUrl} failed with status {$client->statusCode}: {$client->body}\n"; - $errorPayload = ['code' => $client->statusCode, 'message' => "API Error: {$client->statusCode}"]; - - try { - $parsedError = json_decode($client->body, true); - if ($parsedError && isset($parsedError['message'])) { - $errorPayload['message'] = $parsedError['message']; - } - } catch (Exception $e) { - // Ignore if response is not JSON - } - - return $errorPayload; - } - - try { - return json_decode($client->body, true); - } catch (Exception $e) { - echo "Error parsing JSON response from {$apiUrl}: " . $e->getMessage() . ", {$client->body}\n"; - throw new Exception("Failed to parse API response: " . $e->getMessage()); - } -} - -// 从缓存中读取数据并返回 -function serveFromCache(array $cacheData, string $cacheContentFile, string $cacheMetaFile, $res) { - global $viewsInfo; - - if (!$cacheData) { - echo "serveFromCache called with null cacheData for {$cacheContentFile}\n"; - sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], 'Cache metadata unavailable.'); - return; - } - - $viewsInfo['cacheCall']++; - - try { - $fileContent = file_get_contents($cacheContentFile); - - if ($fileContent === false) { - throw new Exception("Failed to read cache file"); - } - - $baseHeaders = [ - 'Cloud-Type' => $cacheData['cloudtype'] ?? 'unknown', - 'Cloud-Expiration' => $cacheData['expiration'] ?? 0, - 'ETag' => $cacheData['uniqid'] ?? '', - 'Cache-Control' => 'public, max-age=31536000', - 'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT', - 'Accept-Ranges' => 'bytes', - 'Connection' => 'keep-alive', - 'Date' => gmdate('D, d M Y H:i:s') . ' GMT', - 'Last-Modified' => isset($cacheData['headers']['last-modified']) ? $cacheData['headers']['last-modified'] : gmdate('D, d M Y H:i:s', filemtime($cacheMetaFile)) . ' GMT', - ]; - - $isVideo = isset($cacheData['path']) && is_string($cacheData['path']) && strpos($cacheData['path'], '.mp4') !== false; - $contentType = isset($cacheData['headers']['Content-Type']) ? $cacheData['headers']['Content-Type'] : ($isVideo ? 'video/mp4' : 'application/octet-stream'); - - $responseHeaders = array_merge($baseHeaders, [ - 'Content-Type' => $contentType, - ]); - - foreach ($responseHeaders as $key => $value) { - $res->header($key, $value); - } - - $res->status(HTTP_STATUS['OK']); - $res->end($fileContent); - } catch (Exception $e) { - $viewsInfo['cacheReadError']++; - echo "Error reading from cache {$cacheContentFile}: " . $e->getMessage() . "\n"; - sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], "Failed to read from cache: " . $e->getMessage()); - } -} - -// 从真实URL获取数据并写入缓存 -function fetchAndServe(array $data, string $tempCacheContentFile, string $cacheContentFile, string $cacheMetaFile, $res) { - global $viewsInfo; - - $parsedUrl = parse_url($data['realUrl']); - $client = new Client($parsedUrl['host'], $parsedUrl['port'] ?? ($parsedUrl['scheme'] === 'https' ? 443 : 80), $parsedUrl['scheme'] === 'https'); - $client->setHeaders([ - 'User-Agent' => USER_AGENT - ]); - - $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/'; - if (isset($parsedUrl['query'])) { - $path .= '?' . $parsedUrl['query']; - } - - $client->get($path); - - if ($client->statusCode !== 200) { - echo "Error fetching from {$data['realUrl']}: HTTP status {$client->statusCode}\n"; - sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], "Bad Gateway: Failed to fetch content from source"); - return; - } - - $isVideo = isset($data['path']) && is_string($data['path']) && strpos($data['path'], '.mp4') !== false; - - // 检查content-length - $contentLength = isset($client->headers['content-length']) ? $client->headers['content-length'] : null; - if ($contentLength) { - // contentLength小于2KB且与缓存文件大小不一致时,重新获取 - if ($contentLength < 2048 && isset($data['headers']['content-length']) && $data['headers']['content-length'] !== $contentLength) { - echo "Warning: content-length is different for the response from: {$data['realUrl']}\n"; - sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], "Bad Gateway: Content-Length mismatch for {$data['realUrl']}"); - return; - } - // 更新data到缓存cacheMetaFile - file_put_contents($cacheMetaFile, json_encode($data)); - } else { - echo "Warning: content-length is undefined for the response from: {$data['realUrl']}\n"; - } - - // 写入临时缓存文件 - file_put_contents($tempCacheContentFile, $client->body); - - // 重命名临时文件为正式缓存文件 - try { - $targetDir = dirname($cacheContentFile); - if (!file_exists($targetDir)) { - mkdir($targetDir, 0777, true); - } - rename($tempCacheContentFile, $cacheContentFile); - echo "Successfully cached: {$cacheContentFile}\n"; - } catch (Exception $e) { - echo "Error renaming temp cache file {$tempCacheContentFile} to {$cacheContentFile}: " . $e->getMessage() . "\n"; - // 如果重命名失败,尝试删除临时文件以避免混乱 - if (file_exists($tempCacheContentFile)) { - unlink($tempCacheContentFile); - } - } - - $baseHeaders = [ - 'Cloud-Type' => $data['cloudtype'] ?? 'unknown', - 'Cloud-Expiration' => $data['expiration'] ?? 0, - 'ETag' => $data['uniqid'] ?? '', - 'Cache-Control' => 'public, max-age=31536000', - 'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT', - 'Accept-Ranges' => 'bytes', - 'Connection' => 'keep-alive', - 'Date' => gmdate('D, d M Y H:i:s') . ' GMT', - 'Last-Modified' => isset($data['headers']['last-modified']) ? - $data['headers']['last-modified'] : - gmdate('D, d M Y H:i:s', filemtime($cacheMetaFile)) . ' GMT', - ]; - - $responseHeaders = array_merge($baseHeaders, [ - 'Content-Type' => isset($client->headers['content-type']) ? $client->headers['content-type'] : ($isVideo ? 'video/mp4' : 'application/octet-stream'), - ], $data['headers']); - - foreach ($responseHeaders as $key => $value) { - $res->header($key, $value); - } - - $res->status(HTTP_STATUS['OK']); - $res->end($client->body); -} - -// 尝试从过期缓存提供服务或返回错误 -function tryServeFromStaleCacheOrError(string $uniqidhex, $res, string $errorMessage = null) { - global $pathIndex, $cacheDir; - - if (isset($pathIndex[$uniqidhex])) { - $cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta'; - $cacheContentFile = $cacheDir . '/' . $pathIndex[$uniqidhex]['uniqid'] . '.content'; - - if (file_exists($cacheMetaFile) && file_exists($cacheContentFile)) { - echo "API call failed or returned non-200. Serving stale cache for {$uniqidhex}\n"; - - try { - $cacheData = json_decode(file_get_contents($cacheMetaFile), true); - serveFromCache($cacheData, $cacheContentFile, $cacheMetaFile, $res); - return; - } catch (Exception $e) { - echo "Error parsing stale meta file {$cacheMetaFile}: " . $e->getMessage() . "\n"; - // 如果过期缓存也损坏,则返回通用错误 - } - } - } - - sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], $errorMessage ?: 'Bad Gateway'); -} - -// 处理主请求 -function handleMainRequest($req, $res) { - global $pathIndex, $cacheDir, $viewsInfo; - - $url = preg_replace('/\/{2,}/', '/', $req->server['request_uri']); - $parsedUrl = parse_url($url); - $queryParams = []; - - if (isset($parsedUrl['query'])) { - parse_str($parsedUrl['query'], $queryParams); - } - - $sign = $queryParams['sign'] ?? ''; - $pathParts = explode('/', trim($parsedUrl['path'], '/')); - $reqPath = $pathParts[0] ?? ''; - $token = implode('/', array_slice($pathParts, 1)); - - if ($reqPath === 'favicon.ico') { - return handleFavicon($req, $res); - } - - if ($reqPath === 'endpoint') { - return handleEndpoint($req, $res, $queryParams); - } - - if (!$token && $reqPath) { - $token = $reqPath; - $reqPath = 'app'; // 默认为'app',如果只提供了一个路径段 - } - - $allowedPaths = ['avatar', 'go', 'bbs', 'www', 'url', 'thumb', 'app']; - if (!in_array($reqPath, $allowedPaths) || !$token) { - return sendErrorResponse($res, HTTP_STATUS['BAD_REQUEST'], "Bad Request: Invalid path or missing token."); - } - - $viewsInfo['request']++; - $uniqidhex = md5($reqPath . $token . $sign); - $cacheMetaFile = ''; - $cacheContentFile = ''; - - if (isset($pathIndex[$uniqidhex])) { - $cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta'; - $cacheContentFile = $cacheDir . '/' . $pathIndex[$uniqidhex]['uniqid'] . '.content'; - } - - if (isset($pathIndex[$uniqidhex]) && isCacheValid($cacheMetaFile, $cacheContentFile)) { - $cacheResult = checkCacheHeaders($req, $cacheMetaFile); - - if ($cacheResult['isNotModified']) { - $res->status(HTTP_STATUS['NOT_MODIFIED']); - $res->end(); - } else { - $viewsInfo['cacheHit']++; - serveFromCache($cacheResult['cacheData'], $cacheContentFile, $cacheMetaFile, $res); - } - } else { - try { - $viewsInfo['apiCall']++; - $apiData = fetchApiData($reqPath, $token, $sign); - - if (isset($apiData['code']) && ($apiData['code'] === HTTP_STATUS['REDIRECT'] || $apiData['code'] === 301)) { - return handleApiRedirect($res, $apiData); - } - - if (isset($apiData['code']) && $apiData['code'] === HTTP_STATUS['OK'] && isset($apiData['data']) && isset($apiData['data']['url'])) { - $data = [ - 'realUrl' => $apiData['data']['url'], - 'cloudtype' => $apiData['data']['cloudtype'] ?? '', - 'expiration' => isset($apiData['data']['expiration']) ? $apiData['data']['expiration'] * 1000 : 0, - 'path' => $apiData['data']['path'] ?? '', - 'headers' => $apiData['data']['headers'] ?? [], - 'uniqid' => $apiData['data']['uniqid'] ?? '', - 'thumb' => $apiData['data']['thumb'] ?? '' - ]; - - $pathIndex[$uniqidhex] = ['uniqid' => $data['uniqid'], 'timestamp' => time() * 1000]; - $cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta'; - $cacheContentFile = $cacheDir . '/' . $data['uniqid'] . '.content'; - $tempCacheContentFile = $cacheDir . '/' . $data['uniqid'] . '_' . bin2hex(random_bytes(16)) . '.temp'; - - try { - file_put_contents($cacheMetaFile, json_encode($data)); - } catch (Exception $e) { - echo "Error writing meta file {$cacheMetaFile}: " . $e->getMessage() . "\n"; - sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], 'Failed to write cache metadata.'); - return; - } - - if (file_exists($cacheContentFile)) { - $contentLength = filesize($cacheContentFile); - if ($contentLength < 2048 && isset($data['headers']['content-length']) && intval($data['headers']['content-length']) !== $contentLength) { - echo "Content length mismatch for {$cacheContentFile}. API: {$data['headers']['content-length']}, Cache: {$contentLength}. Re-fetching.\n"; - fetchAndServe($data, $tempCacheContentFile, $cacheContentFile, $cacheMetaFile, $res); - } else { - serveFromCache($data, $cacheContentFile, $cacheMetaFile, $res); - } - } else { - fetchAndServe($data, $tempCacheContentFile, $cacheContentFile, $cacheMetaFile, $res); - } - } else { - $viewsInfo['fetchApiWarning']++; - tryServeFromStaleCacheOrError($uniqidhex, $res, $apiData['message'] ?? null); - } - } catch (Exception $e) { - $viewsInfo['fetchApiError']++; - echo "Error in API call or processing: " . $e->getMessage() . "\n"; - tryServeFromStaleCacheOrError($uniqidhex, $res, "Bad Gateway: API request failed. " . $e->getMessage()); - } - } -} - -// 定时清理过期缓存数据 -function cleanupExpiredCache() { - global $pathIndex; - - $currentTime = time() * 1000; - foreach ($pathIndex as $key => $value) { - if ($currentTime - $value['timestamp'] > CACHE_EXPIRY_MS) { - unset($pathIndex[$key]); - } - } -} - -// 主函数 -function main() { - global $port; - - initializeApp(); - - // 创建服务器 - $server = new Server('0.0.0.0', $port); - - echo "Server started at http://0.0.0.0:{$port}\n"; - - // 设置定时器清理过期缓存 - Swoole\Timer::tick(CACHE_CLEANUP_INTERVAL_MS, function () { - cleanupExpiredCache(); - }); - - // 处理请求 - $server->handle('/', function ($req, $res) { - handleMainRequest($req, $res); - }); - - $server->start(); -} - -// 启动服务器 -run(function () { - main(); -}); diff --git a/new/new.js b/new.js similarity index 98% rename from new/new.js rename to new.js index a19be50..6f8d0c6 100644 --- a/new/new.js +++ b/new.js @@ -8,7 +8,7 @@ const EventEmitter = require('events'); // Configuration const PORT = 9520; -const API_BASE = 'http://183.6.121.121:9519/api'; +const API_BASE = 'http://127.0.0.1:9558/api'; const CACHE_DIR = path.join(__dirname, '.cache'); // Ensure cache directory exists @@ -170,7 +170,8 @@ function downloadAndServe(req, res, apiData, cachePaths, key) { } // Save meta - fs.writeFileSync(cachePaths.meta, JSON.stringify(apiData)); + const metaPath = cachePaths.meta.replace('.meta', '.thumb.meta'); + fs.writeFileSync(metaPath, JSON.stringify(apiData)); const totalSize = parseInt(proxyRes.headers['content-length'] || '0', 10); task.totalSize = totalSize; diff --git a/new/fastify.js b/new/fastify.js deleted file mode 100644 index 7653723..0000000 --- a/new/fastify.js +++ /dev/null @@ -1,363 +0,0 @@ -const fastify = require('fastify')({ - logger: false, // 关闭默认日志,极大提升吞吐量 - disableRequestLogging: true, // 关闭请求日志 - connectionTimeout: 30000, // 快速释放死连接 - keepAliveTimeout: 5000, // 调整 Keep-Alive -}); -const { request } = require('undici'); // High-performance HTTP client -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); -const EventEmitter = require('events'); - -// Configuration -const PORT = 9520; -const API_BASE = 'http://183.6.121.121:9519/api'; -const CACHE_DIR = path.join(__dirname, '.cache'); - -// Ensure cache directory exists -if (!fs.existsSync(CACHE_DIR)) { - fs.mkdirSync(CACHE_DIR, { recursive: true }); -} - -// Active downloads manager -const activeDownloads = new Map(); - -// Helper to fetch JSON from API using Undici (Faster than http.get) -async function fetchApi(token) { - const apiUrl = new URL(API_BASE); - if (token) { - apiUrl.searchParams.set('token', token); - } - - const { statusCode, body } = await request(apiUrl, { - method: 'GET', - headers: { 'Connection': 'keep-alive' }, - bodyTimeout: 5000, - headersTimeout: 5000 - }); - - if (statusCode !== 200) { - throw new Error(`API Status Code: ${statusCode}`); - } - - const data = await body.json(); - return data; -} - -function getCacheKey(apiData) { - if (apiData.data && apiData.data.uniqid) { - return apiData.data.uniqid; - } - if (apiData.data && apiData.data.url) { - return crypto.createHash('md5').update(apiData.data.url).digest('hex'); - } - return null; -} - -function getCachePaths(key) { - const subDir = key.substring(0, 1); - const dir = path.join(CACHE_DIR, subDir); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - return { - content: path.join(dir, `${key}.data`), - meta: path.join(dir, `${key}.meta`) - }; -} - -// Serve existing file -function serveCompletedCache(reply, cachePaths, apiData) { - const { content } = cachePaths; - const responseHeaders = { ...apiData.data.headers }; - - if (!responseHeaders['Access-Control-Allow-Origin']) { - responseHeaders['Access-Control-Allow-Origin'] = '*'; - } - - // Fastify handles Range requests automatically if we send the stream? - // Actually, for full control over custom headers + Range, we often need manual handling or plugins. - // But serving a raw stream in Fastify usually just pipes it. - // For "High Performance", letting nginx handle static files is best, but here we do it in Node. - // We will stick to the manual Range logic for consistency with previous "growing file" support. - - // To support Range properly with Fastify + Stream, we can set headers and return stream. - // But for "growing" files, we need our custom pump logic. - // For completed files, we can use fs.createReadStream. - - const range = reply.request.headers.range; - const stat = fs.statSync(content); // Sync is okay for startup/metadata, but Async preferred in high-perf. - // In strict high-perf, use fs.promises.stat or cache stats. - - const totalSize = stat.size; - - if (range) { - const parts = range.replace(/bytes=/, "").split("-"); - const start = parseInt(parts[0], 10); - const end = parts[1] ? parseInt(parts[1], 10) : totalSize - 1; - - responseHeaders['Content-Range'] = `bytes ${start}-${end}/${totalSize}`; - responseHeaders['Accept-Ranges'] = 'bytes'; - responseHeaders['Content-Length'] = (end - start) + 1; - - reply.code(206).headers(responseHeaders); - return fs.createReadStream(content, { start, end }); - } else { - responseHeaders['Content-Length'] = totalSize; - responseHeaders['Accept-Ranges'] = 'bytes'; - reply.code(200).headers(responseHeaders); - return fs.createReadStream(content); - } -} - -// Download and Serve logic -function downloadAndServe(reply, apiData, cachePaths, key) { - let task = activeDownloads.get(key); - - if (!task) { - task = { - emitter: new EventEmitter(), - currentSize: 0, - totalSize: 0, - path: cachePaths.content, - done: false, - error: null - }; - task.emitter.setMaxListeners(0); - activeDownloads.set(key, task); - - // Start Download - const targetUrl = apiData.data.url; - - // Use Undici stream for high performance download - // stream() is efficient for piping - const { stream } = require('undici'); - - stream(targetUrl, { method: 'GET', opaque: task }, ({ statusCode, headers }) => { - if (statusCode !== 200) { - // handle error - const err = new Error(`Upstream ${statusCode}`); - task.error = err; - task.emitter.emit('error', err); - activeDownloads.delete(key); - // We need to return a Writable to undici - // return new Writable(...) or simple dummy - return fs.createWriteStream('/dev/null'); - } - - // Save meta - fs.writeFileSync(cachePaths.meta, JSON.stringify(apiData)); - - task.totalSize = parseInt(headers['content-length'] || '0', 10); - - // Return the write stream to file - const fileStream = fs.createWriteStream(task.path); - - // Monitor writing - // We need to intercept the stream to update currentSize - // PassThrough stream adds overhead. - // Better to wrap the write? - // Or just fs.watchFile? (Slow). - // Let's use a custom Writable wrapper or event listener on fileStream 'drain'/'finish' isn't granular enough. - // Undici stream factory returns a Writable. - - // Let's stick to a simple approach: - // We can't easily hook into fs.WriteStream bytesWritten in real-time without polling or proxying. - // Proxying: - const originalWrite = fileStream.write.bind(fileStream); - fileStream.write = (chunk, encoding, cb) => { - const ret = originalWrite(chunk, encoding, cb); - task.currentSize += chunk.length; - task.emitter.emit('progress', task.currentSize); - return ret; - }; - - return fileStream; - - }, ({ opaque }) => { - // Finished - opaque.done = true; - opaque.emitter.emit('done'); - activeDownloads.delete(key); - }, (err, { opaque }) => { - // Error - if (err) { - opaque.error = err; - opaque.emitter.emit('error', err); - activeDownloads.delete(key); - fs.unlink(opaque.path, () => { }); - } - }); - } - - // Serve growing file - return serveGrowingFile(reply, task, apiData); -} - -function serveGrowingFile(reply, task, apiData) { - const responseHeaders = { ...apiData.data.headers }; - if (!responseHeaders['Access-Control-Allow-Origin']) responseHeaders['Access-Control-Allow-Origin'] = '*'; - - const range = reply.request.headers.range; - let start = 0; - - if (range) { - const parts = range.replace(/bytes=/, "").split("-"); - start = parseInt(parts[0], 10) || 0; - responseHeaders['Accept-Ranges'] = 'bytes'; - if (task.totalSize) { - responseHeaders['Content-Range'] = `bytes ${start}-${task.totalSize - 1}/${task.totalSize}`; - responseHeaders['Content-Length'] = task.totalSize - start; - } - reply.code(206); - } else { - if (task.totalSize) responseHeaders['Content-Length'] = task.totalSize; - reply.code(200); - } - - reply.headers(responseHeaders); - - // Custom stream to pump data from file to response - const { Readable } = require('stream'); - - return new Readable({ - read(size) { - const self = this; - let bytesSent = start; // State needs to be per-stream instance. - // Wait, 'read' is called multiple times. We need to store state outside or on 'this'. - if (this._bytesSent === undefined) this._bytesSent = start; - - pump(this); - - function pump(stream) { - if (stream.destroyed) return; - - if (task.error) { - stream.destroy(task.error); - return; - } - - // Open FD if needed - if (!stream._fd) { - fs.open(task.path, 'r', (err, fd) => { - if (err) { - if (err.code === 'ENOENT' && !task.done) { - setTimeout(() => pump(stream), 100); - } else { - stream.destroy(err); - } - return; - } - stream._fd = fd; - pump(stream); - }); - return; - } - - const available = task.currentSize - stream._bytesSent; - - if (available > 0) { - const buffer = Buffer.alloc(Math.min(available, 64 * 1024)); - fs.read(stream._fd, buffer, 0, buffer.length, stream._bytesSent, (err, bytesRead) => { - if (err) { - stream.destroy(err); - return; - } - if (bytesRead > 0) { - stream._bytesSent += bytesRead; - const keepPushing = stream.push(buffer.slice(0, bytesRead)); - // If push returns false, we should stop and wait for _read again? - // Actually Node streams: if push returns true, we can push more. - // But here we just push what we have and wait for next _read call or event? - // Standard implementation: push until it returns false. - // But for "live" tailing, we might want to just push what we have and exit, - // expecting _read to be called again by consumer. - } else { - wait(stream); - } - }); - } else { - if (task.done) { - fs.close(stream._fd, () => { }); - stream.push(null); // EOF - } else { - wait(stream); - } - } - } - - function wait(stream) { - // Wait for progress - const onProgress = () => { - pump(stream); - }; - task.emitter.once('progress', onProgress); - task.emitter.once('done', onProgress); // Check done state - - // If stream destroyed, remove listeners? - // Readable.read is active, so stream is active. - } - }, - destroy(err, cb) { - if (this._fd) fs.close(this._fd, () => { }); - cb(err); - } - }); -} - -// Routes -fastify.get('/favicon.ico', async (request, reply) => { - reply.code(204); - return ''; -}); - -fastify.get('/*', async (request, reply) => { - const token = request.url.substring(1); - if (!token) { - reply.code(400); - return 'Missing token'; - } - - try { - // 1. Fetch API - // Note: fetchApi is async, so we await - const apiData = await fetchApi(token); - - if (apiData.code !== 200 || !apiData.data || !apiData.data.url) { - reply.code(404); - return 'Invalid API response'; - } - - const key = getCacheKey(apiData); - if (!key) { - reply.code(500); - return 'Key Error'; - } - - const cachePaths = getCachePaths(key); - - // 2. Serve - if (activeDownloads.has(key)) { - return downloadAndServe(reply, apiData, cachePaths, key); - } else if (fs.existsSync(cachePaths.content) && fs.existsSync(cachePaths.meta)) { - return serveCompletedCache(reply, cachePaths, apiData); - } else { - return downloadAndServe(reply, apiData, cachePaths, key); - } - - } catch (err) { - request.log.error(err); - reply.code(502); - return 'Gateway Error: ' + err.message; - } -}); - -// Run -fastify.listen({ port: PORT, host: '0.0.0.0' }, (err, address) => { - if (err) { - console.error(err); - process.exit(1); - } - console.log(`Fastify Server running at ${address}`); -}); diff --git a/obfuscate.js b/obfuscate.js deleted file mode 100644 index 212277f..0000000 --- a/obfuscate.js +++ /dev/null @@ -1,21 +0,0 @@ -const fs = require('fs'); -const JavaScriptObfuscator = require('javascript-obfuscator'); - -const inputFilePath = './source.js'; // 替换为你的文件名 -const outputFilePath = './index.js'; - -const code = fs.readFileSync(inputFilePath, 'utf8'); - -const obfuscatedCode = JavaScriptObfuscator.obfuscate(code, { - compact: true, // 压缩代码 - controlFlowFlattening: true, // 控制流混淆 - deadCodeInjection: true, // 死代码注入 - numbersToExpressions: true, // 将数字转换为表达式 - renameGlobals: true, // 重命名全局变量 - simplify: true, // 简化代码 - splitStrings: true, // 将字符串拆分为多个字符串 - stringArray: true, // 将字符串转换为数组 -}).getObfuscatedCode(); - -fs.writeFileSync(outputFilePath, obfuscatedCode); -console.log('Code has been obfuscated and saved to', outputFilePath); diff --git a/package.json b/package.json index 404e50b..183219a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "dependencies": { "fastify": "^5.6.2", "javascript-obfuscator": "^4.1.1", - "undici": "^7.16.0" + "sharp": "^0.34.5", + "undici": "^7.16.0", + "fluent-ffmpeg": "^2.1.3" }, "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" } diff --git a/proxy.js b/proxy.js deleted file mode 100644 index 9a8c6b6..0000000 --- a/proxy.js +++ /dev/null @@ -1,1212 +0,0 @@ -const http = require('http'); -const https = require('https'); -const url = require('url'); -const querystring = require('querystring'); -const fs = require('fs'); -const pathModule = require('path'); -const crypto = require('crypto'); - -const CACHE_DIR_NAME = '.cache'; -const DEFAULT_PORT = 9001; -const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9519/'; - -// 创建缓存目录结构 -const cacheDir = pathModule.join(__dirname, CACHE_DIR_NAME); -if (!fs.existsSync(cacheDir)) { - fs.mkdirSync(cacheDir, { recursive: true }); - console.log(`Cache directory created: ${cacheDir}`); -} - -// 根据缓存键创建子目录,避免单个目录文件过多 -function getCacheSubDir(cacheKey) { - // 使用缓存键的前两个字符作为子目录名 - const subDir = cacheKey.substring(0, 2); - const fullPath = pathModule.join(cacheDir, subDir); - if (!fs.existsSync(fullPath)) { - fs.mkdirSync(fullPath, { recursive: true }); - } - return fullPath; -} - -// 缓存配置 -const META_CACHE_EXPIRY_MS = 30 * 60 * 1000; // 30分钟 -const metaCache = {}; // 存储meta信息的内存缓存 -const contentCache = {}; // 存储内容的内存索引 - -// 下载任务管理和并发控制 -const MAX_CONCURRENT_DOWNLOADS = 5; // 最大并发下载数 -const MAX_QUEUE_SIZE = 50; // 最大队列长度 -const MAX_CLIENTS_PER_TASK = 10; // 每个任务最大客户端数 -const REQUEST_TIMEOUT = 30000; // 请求超时时间(30秒) -const downloadTasks = new Map(); // 下载任务管理器 -const downloadQueue = []; // 下载队列 -let activeDownloads = 0; // 当前活跃下载数 - - - -// 服务器启动时间 -const serverStartTime = Date.now(); - -// 下载任务状态 -const DOWNLOAD_STATUS = { - PENDING: 'pending', - DOWNLOADING: 'downloading', - COMPLETED: 'completed', - FAILED: 'failed', - PAUSED: 'paused' -}; - -// 下载任务类 -class DownloadTask { - constructor(url, cacheKey, metaCacheFile, contentCacheFile) { - this.id = `${cacheKey}_${Date.now()}`; - this.url = url; - this.cacheKey = cacheKey; - this.metaCacheFile = metaCacheFile; - this.contentCacheFile = contentCacheFile; - this.status = DOWNLOAD_STATUS.PENDING; - this.waitingClients = []; - this.error = null; - this.createdAt = Date.now(); - - // 进度跟踪 - this.progress = { - totalSize: 0, - downloadedSize: 0, - percentage: 0, - speed: 0, // bytes/second - eta: 0, // estimated time remaining in seconds - startTime: null, - lastUpdateTime: null, - lastDownloadedSize: 0 - }; - - // 状态管理 - this.retryCount = 0; - this.maxRetries = 3; - this.lastError = null; - this.pauseRequested = false; - } - - addClient(res, rangeHeader) { - this.waitingClients.push({ res, rangeHeader, addedAt: Date.now() }); - } - - removeClient(res) { - this.waitingClients = this.waitingClients.filter(client => client.res !== res); - } - - hasClients() { - return this.waitingClients.length > 0; - } - - // 更新下载进度 - updateProgress(downloadedBytes, totalBytes) { - const now = Date.now(); - - if (!this.progress.startTime) { - this.progress.startTime = now; - } - - this.progress.totalSize = totalBytes; - this.progress.downloadedSize = downloadedBytes; - this.progress.percentage = totalBytes > 0 ? (downloadedBytes / totalBytes) * 100 : 0; - - // 计算下载速度 - if (this.progress.lastUpdateTime) { - const timeDiff = (now - this.progress.lastUpdateTime) / 1000; // seconds - const sizeDiff = downloadedBytes - this.progress.lastDownloadedSize; - - if (timeDiff > 0) { - this.progress.speed = sizeDiff / timeDiff; - - // 计算预计剩余时间 - const remainingBytes = totalBytes - downloadedBytes; - this.progress.eta = this.progress.speed > 0 ? remainingBytes / this.progress.speed : 0; - } - } - - this.progress.lastUpdateTime = now; - this.progress.lastDownloadedSize = downloadedBytes; - } - - // 获取进度信息 - getProgressInfo() { - return { - id: this.id, - url: this.url, - status: this.status, - progress: { ...this.progress }, - clientCount: this.waitingClients.length, - retryCount: this.retryCount, - error: this.lastError, - createdAt: this.createdAt - }; - } - - // 设置错误状态 - setError(error) { - this.lastError = error; - this.error = error; - this.status = DOWNLOAD_STATUS.FAILED; - } - - // 请求暂停 - requestPause() { - this.pauseRequested = true; - } - - // 检查是否应该重试 - shouldRetry() { - return this.retryCount < this.maxRetries && this.status === DOWNLOAD_STATUS.FAILED; - } - - // 增加重试次数 - incrementRetry() { - this.retryCount++; - } -} - -// 下载任务管理器函数 -function getOrCreateDownloadTask(url, cacheKey, metaCacheFile, contentCacheFile) { - let task = downloadTasks.get(cacheKey); - if (!task) { - task = new DownloadTask(url, cacheKey, metaCacheFile, contentCacheFile); - downloadTasks.set(cacheKey, task); - } - return task; -} - -function startNextDownload() { - if (activeDownloads >= MAX_CONCURRENT_DOWNLOADS || downloadQueue.length === 0) { - return; - } - - const task = downloadQueue.shift(); - if (task && task.status === DOWNLOAD_STATUS.PENDING) { - activeDownloads++; - task.status = DOWNLOAD_STATUS.DOWNLOADING; - console.log(`Starting download: ${task.url} (${activeDownloads}/${MAX_CONCURRENT_DOWNLOADS})`); - executeDownload(task); - } -} - -function completeDownload(task, success = true) { - activeDownloads = Math.max(0, activeDownloads - 1); - task.status = success ? DOWNLOAD_STATUS.COMPLETED : DOWNLOAD_STATUS.FAILED; - - if (!success) { - // 清理失败的下载任务 - downloadTasks.delete(task.cacheKey); - } - - console.log(`Download ${success ? 'completed' : 'failed'}: ${task.url} (${activeDownloads}/${MAX_CONCURRENT_DOWNLOADS})`); - - // 启动下一个下载任务 - setTimeout(startNextDownload, 100); -} - -function cleanupExpiredTasks() { - const now = Date.now(); - const expiredTasks = []; - - downloadTasks.forEach((task, key) => { - // 清理超过1小时且没有客户端的已完成任务 - if (task.status === DOWNLOAD_STATUS.COMPLETED && - !task.hasClients() && - (now - task.startTime) > 3600000) { - expiredTasks.push(key); - } - // 清理超过10分钟的失败任务 - else if (task.status === DOWNLOAD_STATUS.FAILED && - (now - task.startTime) > 600000) { - expiredTasks.push(key); - } - }); - - expiredTasks.forEach(key => { - downloadTasks.delete(key); - }); - - if (expiredTasks.length > 0) { - console.log(`Cleaned up ${expiredTasks.length} expired download tasks`); - } -} - -// 定期清理过期任务 -setInterval(cleanupExpiredTasks, 300000); // 每5分钟清理一次 - -// 创建HTTP服务器 -const server = http.createServer(async (req, res) => { - const parsedUrl = url.parse(req.url, true); - const path = parsedUrl.pathname; - const clientIP = req.connection.remoteAddress || req.socket.remoteAddress || '127.0.0.1'; - - // 处理favicon请求 - if (path === '/favicon.ico') { - res.writeHead(204); - res.end(); - return; - } - - // 处理状态查询请求 - if (path === '/status') { - const tasks = Array.from(downloadTasks.values()).map(task => task.getProgressInfo()); - const status = { - activeDownloads, - queueLength: downloadQueue.length, - totalTasks: downloadTasks.size, - tasks: tasks.slice(0, 20), // 只返回前20个任务 - serverInfo: { - maxConcurrentDownloads: MAX_CONCURRENT_DOWNLOADS, - maxQueueSize: MAX_QUEUE_SIZE, - maxClientsPerTask: MAX_CLIENTS_PER_TASK, - requestTimeout: REQUEST_TIMEOUT, - uptime: Date.now() - serverStartTime - } - }; - - res.writeHead(200, { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - }); - res.end(JSON.stringify(status, null, 2)); - return; - } - - // 处理任务控制请求 - if (path.startsWith('/control/')) { - const action = path.split('/')[2]; // pause, resume, cancel - const taskId = parsedUrl.query.taskId; - - if (!taskId) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Missing taskId parameter' })); - return; - } - - const task = downloadTasks.get(taskId); - if (!task) { - res.writeHead(404, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Task not found' })); - return; - } - - let result = { success: false, message: '' }; - - switch (action) { - case 'pause': - if (task.status === DOWNLOAD_STATUS.DOWNLOADING) { - task.requestPause(); - result = { success: true, message: 'Pause requested' }; - } else { - result = { success: false, message: 'Task is not downloading' }; - } - break; - - case 'resume': - if (task.status === DOWNLOAD_STATUS.PAUSED || task.status === DOWNLOAD_STATUS.FAILED) { - task.status = DOWNLOAD_STATUS.PENDING; - task.pauseRequested = false; - if (!downloadQueue.includes(task)) { - downloadQueue.push(task); - } - startNextDownload(); - result = { success: true, message: 'Resume requested' }; - } else { - result = { success: false, message: 'Task cannot be resumed' }; - } - break; - - case 'cancel': - task.requestPause(); - task.status = DOWNLOAD_STATUS.FAILED; - task.setError('Cancelled by user'); - // 通知所有等待的客户端 - task.waitingClients.forEach(({ res: clientRes }) => { - if (!clientRes.headersSent && !clientRes.destroyed) { - clientRes.writeHead(499, { 'Content-Type': 'text/plain' }); - clientRes.end('Download cancelled'); - } - }); - task.waitingClients = []; - result = { success: true, message: 'Task cancelled' }; - break; - - default: - result = { success: false, message: 'Invalid action' }; - } - - res.writeHead(200, { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - }); - res.end(JSON.stringify(result)); - return; - } - - - - // 设置请求超时 - const timeout = setTimeout(() => { - if (!res.headersSent) { - res.writeHead(408, { 'Content-Type': 'text/plain' }); - res.end('Request Timeout'); - } - }, REQUEST_TIMEOUT); - - // 设置CORS头,自动获取Origin - const origin = req.headers.origin || req.headers.referer || '*'; - res.setHeader('Access-Control-Allow-Origin', origin); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, HEAD'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin'); - res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Max-Age', '86400'); - - // 处理预检请求 - if (req.method === 'OPTIONS') { - res.writeHead(200); - res.end(); - return; - } - - // 生成缓存键并创建分层目录结构 - const cacheKey = crypto.createHash('md5').update(req.url).digest('hex'); - const cacheSubDir = getCacheSubDir(cacheKey); - const metaCacheFile = pathModule.join(cacheSubDir, `${cacheKey}.meta`); - const contentCacheFile = pathModule.join(cacheSubDir, `${cacheKey}.content`); - - // 检查meta缓存是否有效 - const now = Date.now(); - let metaInfo = metaCache[cacheKey]; - let metaExpired = !metaInfo || (now - metaInfo.timestamp > META_CACHE_EXPIRY_MS); - - // 如果meta缓存过期,尝试从文件读取 - if (metaExpired && fs.existsSync(metaCacheFile)) { - try { - const metaData = JSON.parse(fs.readFileSync(metaCacheFile, 'utf8')); - if (now - metaData.timestamp <= META_CACHE_EXPIRY_MS) { - metaInfo = metaData; - metaCache[cacheKey] = metaInfo; - metaExpired = false; - console.log(`Meta cache loaded from file for: ${req.url}`); - } - } catch (err) { - console.error(`Error reading meta cache file: ${err.message}`); - } - } - - // 如果meta缓存有效且内容缓存存在,直接返回缓存内容 - if (!metaExpired && fs.existsSync(contentCacheFile)) { - console.log(`Serving from cache: ${req.url}`); - const rangeHeader = req.headers.range; - - // 检查是否可以从完整缓存文件提供Range请求 - if (rangeHeader && fs.existsSync(contentCacheFile)) { - try { - const stat = fs.statSync(contentCacheFile); - const expectedSize = parseInt(metaInfo.headers['content-length'] || '0', 10); - - if (stat.size === expectedSize) { - const ranges = parseRange(rangeHeader, expectedSize); - if (ranges) { - console.log(`Serving range request from complete cache: ${contentCacheFile}`); - serveFromCache(res, metaInfo, contentCacheFile, rangeHeader, req); - return; - } - } else if (stat.size > 0 && stat.size < expectedSize) { - // 部分下载的文件,检查是否可以提供请求的范围 - const ranges = parseRange(rangeHeader, expectedSize); - if (ranges && !Array.isArray(ranges)) { - const { start, end } = ranges; - if (end < stat.size) { - // 请求的范围在已下载的部分内 - console.log(`Serving range request from partial cache: ${contentCacheFile}`); - serveFromCache(res, metaInfo, contentCacheFile, rangeHeader); - return; - } - } - } - } catch (err) { - console.error(`Error checking cached file for range request: ${err.message}`); - } - } - - serveFromCache(res, metaInfo, contentCacheFile, rangeHeader, req); - return; - } - - // 需要请求原始API - 使用下载任务管理器 - console.log(`Fetching from API: ${req.url}`); - const rangeHeader = req.headers.range; - - // 获取或创建下载任务 - const task = getOrCreateDownloadTask(req.url, cacheKey, metaCacheFile, contentCacheFile); - - // 检查队列是否已满 - if (downloadQueue.length >= MAX_QUEUE_SIZE && task.status === DOWNLOAD_STATUS.PENDING) { - console.log(`Download queue full, rejecting request: ${req.url}`); - clearTimeout(timeout); - res.writeHead(503, { 'Content-Type': 'text/plain' }); - res.end('Service Unavailable - Queue Full'); - return; - } - - // 检查任务客户端数量限制 - if (task.waitingClients.length >= MAX_CLIENTS_PER_TASK) { - console.log(`Too many clients for task, rejecting: ${req.url}`); - clearTimeout(timeout); - res.writeHead(503, { 'Content-Type': 'text/plain' }); - res.end('Service Unavailable - Too Many Clients'); - return; - } - - // 添加客户端到等待列表 - task.addClient(res, rangeHeader); - - // 处理客户端断开连接 - res.on('close', () => { - clearTimeout(timeout); - task.removeClient(res); - console.log(`Client disconnected from task: ${task.id}`); - }); - - // 处理响应完成 - res.on('finish', () => { - clearTimeout(timeout); - }); - - if (task.status === DOWNLOAD_STATUS.COMPLETED) { - // 任务已完成,直接从缓存提供内容 - console.log(`Serving completed download from cache: ${req.url}`); - serveFromCache(res, metaCache[cacheKey], contentCacheFile, rangeHeader, req); - task.removeClient(res); - } else if (task.status === DOWNLOAD_STATUS.DOWNLOADING) { - // 任务正在下载中,客户端等待 - console.log(`Client waiting for ongoing download: ${req.url}`); - } else if (task.status === DOWNLOAD_STATUS.PENDING) { - // 将任务加入下载队列 - if (!downloadQueue.includes(task)) { - downloadQueue.push(task); - console.log(`Added to download queue: ${req.url}`); - } - startNextDownload(); - } else if (task.status === DOWNLOAD_STATUS.FAILED) { - // 任务失败,重新尝试 - task.status = DOWNLOAD_STATUS.PENDING; - task.error = null; - if (!downloadQueue.includes(task)) { - downloadQueue.push(task); - } - startNextDownload(); - } -}); - -// 从缓存提供内容,支持多段Range和HTTP缓存 -function serveFromCache(res, metaInfo, contentCacheFile, rangeHeader, req) { - // 设置响应头,保持CORS头 - const headers = { ...metaInfo.headers }; - // 确保CORS头不被覆盖 - if (!headers['access-control-allow-origin']) { - headers['access-control-allow-origin'] = res.getHeader('access-control-allow-origin') || '*'; - } - - // 确保Content-Type正确设置,特别是对于视频文件 - if (!headers['content-type']) { - const ext = pathModule.extname(contentCacheFile).toLowerCase(); - const mimeTypes = { - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.ogg': 'video/ogg', - '.avi': 'video/x-msvideo', - '.mov': 'video/quicktime', - '.wmv': 'video/x-ms-wmv', - '.flv': 'video/x-flv', - '.mkv': 'video/x-matroska', - '.m4v': 'video/mp4', - '.3gp': 'video/3gpp', - '.ts': 'video/mp2t' - }; - headers['content-type'] = mimeTypes[ext] || 'application/octet-stream'; - } - - // 确保Accept-Ranges头存在,这对视频播放很重要 - headers['accept-ranges'] = 'bytes'; - - // 处理Range请求 - if (rangeHeader && fs.existsSync(contentCacheFile)) { - const stat = fs.statSync(contentCacheFile); - const fileSize = stat.size; - - // 生成ETag和Last-Modified - const etag = `"${stat.mtime.getTime()}-${fileSize}"`; - const lastModified = stat.mtime.toUTCString(); - - // 设置缓存相关头 - headers['etag'] = etag; - headers['last-modified'] = lastModified; - headers['cache-control'] = 'public, max-age=3600, must-revalidate'; // 1小时缓存,必须重新验证 - headers['vary'] = 'Accept-Encoding, Range'; // 根据编码和范围请求变化 - - // 检查条件请求 - const ifNoneMatch = req && req.headers['if-none-match']; - const ifModifiedSince = req && req.headers['if-modified-since']; - - // 304 Not Modified 检查 - if ((ifNoneMatch && ifNoneMatch === etag) || - (ifModifiedSince && new Date(ifModifiedSince) >= stat.mtime)) { - res.writeHead(304, headers); - res.end(); - return; - } - - // 解析Range头 - const ranges = parseRange(rangeHeader, fileSize); - if (ranges) { - // 单段Range请求 - if (!Array.isArray(ranges)) { - const { start, end } = ranges; - const chunkSize = (end - start) + 1; - - // 设置206 Partial Content响应头 - headers['content-range'] = `bytes ${start}-${end}/${fileSize}`; - headers['accept-ranges'] = 'bytes'; - headers['content-length'] = chunkSize; - - res.writeHead(206, headers); - - // 创建范围读取流 - const fileStream = fs.createReadStream(contentCacheFile, { start, end }); - fileStream.pipe(res); - - fileStream.on('error', (err) => { - console.error(`Error reading cache file: ${err.message}`); - res.statusCode = 500; - res.end('Internal Server Error'); - }); - - return; - } - - // 多段Range请求 - 暂时不支持,返回完整文件 - console.log('Multi-range request detected, serving full file instead'); - } - } - - // 普通请求或Range解析失败 - const stat = fs.statSync(contentCacheFile); - - // 生成ETag和Last-Modified - const etag = `"${stat.mtime.getTime()}-${stat.size}"`; - const lastModified = stat.mtime.toUTCString(); - - // 设置缓存相关头 - headers['etag'] = etag; - headers['last-modified'] = lastModified; - headers['cache-control'] = 'public, max-age=3600, must-revalidate'; // 1小时缓存,必须重新验证 - headers['vary'] = 'Accept-Encoding, Range'; // 根据编码和范围请求变化 - - // 检查条件请求 - const ifNoneMatch = req && req.headers['if-none-match']; - const ifModifiedSince = req && req.headers['if-modified-since']; - - // 304 Not Modified 检查 - if ((ifNoneMatch && ifNoneMatch === etag) || - (ifModifiedSince && new Date(ifModifiedSince) >= stat.mtime)) { - res.writeHead(304, headers); - res.end(); - return; - } - - res.writeHead(200, headers); - - // 创建文件读取流并直接管道到响应 - const fileStream = fs.createReadStream(contentCacheFile); - fileStream.pipe(res); - - // 处理错误 - fileStream.on('error', (err) => { - console.error(`Error reading cache file: ${err.message}`); - res.statusCode = 500; - res.end('Internal Server Error'); - }); -} - -// 获取Content-Type -function getContentType(url) { - const ext = pathModule.extname(url).toLowerCase(); - const mimeTypes = { - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.ogg': 'video/ogg', - '.avi': 'video/x-msvideo', - '.mov': 'video/quicktime', - '.wmv': 'video/x-ms-wmv', - '.flv': 'video/x-flv', - '.mkv': 'video/x-matroska', - '.m4v': 'video/mp4', - '.3gp': 'video/3gpp', - '.ts': 'video/mp2t' - }; - return mimeTypes[ext] || 'application/octet-stream'; -} - -// 解析Range头,支持多段Range -function parseRange(rangeHeader, fileSize) { - if (!rangeHeader || !rangeHeader.startsWith('bytes=')) { - return null; - } - - const rangeSpec = rangeHeader.substring(6); - const ranges = []; - - // 支持多个范围,如 bytes=0-499,1000-1499 - const rangeParts = rangeSpec.split(','); - - for (const part of rangeParts) { - const trimmed = part.trim(); - const [start, end] = trimmed.split('-').map(s => s ? parseInt(s, 10) : undefined); - - let rangeStart, rangeEnd; - - if (start !== undefined && end !== undefined) { - // 完整范围: 0-499 - rangeStart = start; - rangeEnd = end; - } else if (start !== undefined && end === undefined) { - // 从某位置到结尾: 500- - rangeStart = start; - rangeEnd = fileSize - 1; - } else if (start === undefined && end !== undefined) { - // 最后几个字节: -500 - rangeStart = Math.max(0, fileSize - end); - rangeEnd = fileSize - 1; - } else { - continue; // 无效范围 - } - - // 验证范围有效性 - if (rangeStart >= fileSize || rangeEnd >= fileSize || rangeStart > rangeEnd || rangeStart < 0) { - continue; - } - - ranges.push({ start: rangeStart, end: rangeEnd }); - } - - return ranges.length > 0 ? (ranges.length === 1 ? ranges[0] : ranges) : null; -} - -// 执行下载任务 -function executeDownload(task) { - const targetUrl = new URL(task.url, DEFAULT_API_ENDPOINT); - const protocol = targetUrl.protocol === 'https:' ? https : http; - - const options = { - method: 'GET', - headers: { - 'User-Agent': 'Mozilla/5.0 (compatible; AlistProxy/1.0)' - } - }; - - // 检查是否有缓存文件,如果有则添加条件请求头 - if (fs.existsSync(task.contentCacheFile)) { - try { - const stat = fs.statSync(task.contentCacheFile); - const etag = `"${stat.mtime.getTime()}-${stat.size}"`; - const lastModified = stat.mtime.toUTCString(); - - // 添加条件请求头,让上游服务器判断是否需要重新下载 - options.headers['if-none-match'] = etag; - options.headers['if-modified-since'] = lastModified; - } catch (err) { - console.log(`Error reading cache file stats: ${err.message}`); - } - } - - const proxyReq = protocol.request(targetUrl, options, (proxyRes) => { - console.log(`Response status: ${proxyRes.statusCode} for ${task.url}`); - - // 处理304 Not Modified响应 - if (proxyRes.statusCode === 304) { - console.log(`304 Not Modified, serving from cache: ${task.url}`); - - // 从缓存提供内容 - if (fs.existsSync(task.contentCacheFile) && fs.existsSync(task.metaCacheFile)) { - try { - const cachedMeta = JSON.parse(fs.readFileSync(task.metaCacheFile, 'utf8')); - - // 为所有等待的客户端提供缓存内容 - task.waitingClients.forEach(({ res: clientRes, rangeHeader }) => { - if (!clientRes.headersSent && !clientRes.destroyed) { - serveFromCache(clientRes, cachedMeta, task.contentCacheFile, rangeHeader, null); - } - }); - - task.status = DOWNLOAD_STATUS.COMPLETED; - completeDownload(task, true); - return; - } catch (err) { - console.error(`Error serving 304 from cache: ${err.message}`); - } - } - - // 如果缓存文件不存在,继续正常下载 - console.log(`Cache file missing for 304 response, continuing download`); - } - - const metaInfo = { - timestamp: Date.now(), - headers: proxyRes.headers, - statusCode: proxyRes.statusCode - }; - - // 更新内存缓存 - metaCache[task.cacheKey] = metaInfo; - - // 写入meta缓存文件 - fs.writeFile(task.metaCacheFile, JSON.stringify(metaInfo), (err) => { - if (err) console.error(`Error writing meta cache: ${err.message}`); - }); - - if (proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) { - const contentLength = parseInt(proxyRes.headers['content-length'] || '0', 10); - task.updateProgress(0, contentLength); - - // 创建临时文件用于断点续传 - const tempFile = `${task.contentCacheFile}.tmp`; - const fileStream = fs.createWriteStream(tempFile); - let downloadedBytes = 0; - - // 进度跟踪变量 - let lastLoggedSize = 0; - let lastLogTime = Date.now(); - - // 监听数据流 - proxyRes.on('data', (chunk) => { - downloadedBytes += chunk.length; - task.updateProgress(downloadedBytes, contentLength); - - // 每1MB或每5秒输出一次进度日志 - const now = Date.now(); - if (downloadedBytes - lastLoggedSize >= 1024 * 1024 || now - lastLogTime >= 5000) { - const progress = task.getProgressInfo(); - console.log(`Download progress [${task.id}]: ${progress.progress.percentage.toFixed(1)}% ` + - `(${formatBytes(progress.progress.downloadedSize)}/${formatBytes(progress.progress.totalSize)}) ` + - `Speed: ${formatBytes(progress.progress.speed)}/s ` + - `ETA: ${formatTime(progress.progress.eta)}`); - lastLoggedSize = downloadedBytes; - lastLogTime = now; - } - - // 检查是否请求暂停 - if (task.pauseRequested) { - console.log(`Download paused for task: ${task.id}`); - task.status = DOWNLOAD_STATUS.PAUSED; - proxyRes.destroy(); - if (fileStream && !fileStream.destroyed) { - fileStream.end(); - } - return; - } - - // 检查等待的Range请求客户端 - const clientsToServe = []; - task.waitingClients.forEach((client, index) => { - if (client.res.destroyed) { - return; - } - - if (client.rangeHeader) { - const range = parseRange(client.rangeHeader, contentLength); - if (range && !Array.isArray(range)) { - const { start, end } = range; - // 检查请求的范围是否已经下载完成 - if (downloadedBytes > end) { - console.log(`Serving range to waiting client: ${start}-${end} (downloaded: ${downloadedBytes})`); - clientsToServe.push({ client, range, index }); - } - } - } - }); - - // 为可以服务的客户端提供Range响应 - clientsToServe.forEach(({ client, range, index }) => { - try { - const { start, end } = range; - const chunkSize = (end - start) + 1; - - // 设置响应头 - const headers = { - 'content-type': getContentType(task.url), - 'accept-ranges': 'bytes', - 'content-range': `bytes ${start}-${end}/${contentLength}`, - 'content-length': chunkSize, - 'access-control-allow-origin': '*', - 'cache-control': 'public, max-age=3600' - }; - - client.res.writeHead(206, headers); - - // 直接从临时文件读取指定范围的数据 - const rangeStream = fs.createReadStream(tempFile, { start, end }); - rangeStream.pipe(client.res); - - rangeStream.on('error', (err) => { - console.error(`Error reading range from temp file: ${err.message}`); - if (!client.res.headersSent) { - client.res.writeHead(500); - client.res.end('Internal Server Error'); - } - }); - - // 从等待列表中移除该客户端 - task.waitingClients.splice(index, 1); - console.log(`Served range ${start}-${end} from temp file to waiting client`); - } catch (err) { - console.error(`Error serving range to client: ${err.message}`); - } - }); - - // 向非Range请求的等待客户端发送数据 - task.waitingClients.forEach(client => { - if (!client.res.destroyed && !client.rangeHeader) { - // 设置响应头(仅第一次) - if (!client.headersSent) { - const headers = { ...proxyRes.headers }; - if (!headers['access-control-allow-origin']) { - headers['access-control-allow-origin'] = client.res.getHeader('access-control-allow-origin') || '*'; - } - headers['accept-ranges'] = 'bytes'; - client.res.writeHead(200, headers); - client.headersSent = true; - } - - // 发送数据块 - try { - client.res.write(chunk); - } catch (err) { - console.error(`Error writing to client: ${err.message}`); - task.removeClient(client.res); - } - } - }); - }); - - // 将数据写入文件 - proxyRes.pipe(fileStream); - - proxyRes.on('end', () => { - // 结束所有客户端响应 - task.waitingClients.forEach(client => { - if (!client.res.destroyed) { - try { - client.res.end(); - } catch (err) { - console.error(`Error ending client response: ${err.message}`); - } - } - }); - task.waitingClients = []; - - // 完成文件写入 - fileStream.end(); - }); - - fileStream.on('finish', () => { - fs.rename(tempFile, task.contentCacheFile, (err) => { - if (err) { - console.error(`Error saving cache file: ${err.message}`); - completeDownload(task, false); - } else { - console.log(`Cache saved for: ${task.url}`); - completeDownload(task, true); - } - }); - }); - - fileStream.on('error', (err) => { - console.error(`Error writing cache file: ${err.message}`); - fs.unlink(tempFile, () => {}); - completeDownload(task, false); - }); - - } else { - // 非成功响应 - task.waitingClients.forEach(client => { - if (!client.res.destroyed) { - const headers = { ...proxyRes.headers }; - if (!headers['access-control-allow-origin']) { - headers['access-control-allow-origin'] = client.res.getHeader('access-control-allow-origin') || '*'; - } - client.res.writeHead(proxyRes.statusCode, headers); - proxyRes.pipe(client.res); - } - }); - completeDownload(task, false); - } - }); - - proxyReq.on('error', (err) => { - console.error(`Download request error: ${err.message}`); - task.error = err.message; - - // 向所有等待的客户端发送错误响应 - task.waitingClients.forEach(client => { - if (!client.res.destroyed) { - client.res.statusCode = 502; - client.res.end('Bad Gateway'); - } - }); - task.waitingClients = []; - - completeDownload(task, false); - }); - - proxyReq.end(); -} - -// 从API获取内容(保留用于非大文件请求) -function fetchFromApi(req, res, cacheKey, metaCacheFile, contentCacheFile, rangeHeader) { - // 构建目标URL - const targetUrl = new URL(req.url, DEFAULT_API_ENDPOINT); - - // 选择合适的协议模块 - const protocol = targetUrl.protocol === 'https:' ? https : http; - - // 准备请求选项 - const options = { - method: req.method, - headers: { ...req.headers } - }; - - // 删除可能导致问题的头信息 - delete options.headers.host; - - // 检查是否有缓存文件,如果有则添加条件请求头 - if (fs.existsSync(contentCacheFile)) { - try { - const stat = fs.statSync(contentCacheFile); - const etag = `"${stat.mtime.getTime()}-${stat.size}"`; - const lastModified = stat.mtime.toUTCString(); - - // 添加条件请求头,让上游服务器判断是否需要重新下载 - options.headers['if-none-match'] = etag; - options.headers['if-modified-since'] = lastModified; - } catch (err) { - console.log(`Error reading cache file stats: ${err.message}`); - } - } - - // 检查是否有正在进行的下载任务 - const existingTask = downloadTasks.get(cacheKey); - if (existingTask && rangeHeader) { - const range = parseRange(rangeHeader, existingTask.totalSize || Number.MAX_SAFE_INTEGER); - if (range && !Array.isArray(range)) { - const { start, end } = range; - - // 检查请求的范围是否已经下载 - if (fs.existsSync(contentCacheFile)) { - const stat = fs.statSync(contentCacheFile); - if (stat.size > end) { - // 请求的范围已经下载完成,从部分文件提供内容 - console.log(`Serving range from partial download: ${req.url} (${start}-${end}, downloaded: ${stat.size})`); - - // 创建临时的meta信息 - const tempMeta = { - headers: { - 'content-type': getContentType(req.url), - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*' - }, - statusCode: 200 - }; - - serveFromCache(res, tempMeta, contentCacheFile, rangeHeader, req); - return; - } - } - - // 如果请求的范围还没下载完,将客户端添加到等待列表 - console.log(`Adding client to waiting list for ongoing download: ${req.url}`); - existingTask.addClient(res, rangeHeader); - return; - } - } - - // 如果是Range请求且缓存文件已存在,检查是否可以从缓存提供部分内容 - if (rangeHeader && fs.existsSync(contentCacheFile)) { - const stat = fs.statSync(contentCacheFile); - const range = parseRange(rangeHeader, stat.size); - if (range) { - // 尝试从缓存读取meta信息来验证文件完整性 - try { - const metaData = JSON.parse(fs.readFileSync(metaCacheFile, 'utf8')); - if (metaData.headers && metaData.headers['content-length']) { - const expectedSize = parseInt(metaData.headers['content-length'], 10); - if (stat.size === expectedSize) { - // 缓存文件完整,直接提供Range响应 - console.log(`Serving range from complete cache: ${req.url}`); - serveFromCache(res, metaData, contentCacheFile, rangeHeader, req); - return; - } - } - } catch (err) { - // 忽略meta文件读取错误,继续正常流程 - } - } - } - - // 创建请求 - const proxyReq = protocol.request(targetUrl, options, (proxyRes) => { - // 处理304 Not Modified响应 - if (proxyRes.statusCode === 304) { - console.log(`304 Not Modified, serving from cache: ${req.url}`); - - // 从缓存提供内容 - if (fs.existsSync(contentCacheFile) && fs.existsSync(metaCacheFile)) { - try { - const cachedMeta = JSON.parse(fs.readFileSync(metaCacheFile, 'utf8')); - serveFromCache(res, cachedMeta, contentCacheFile, rangeHeader, req); - return; - } catch (err) { - console.error(`Error serving 304 from cache: ${err.message}`); - } - } - - // 如果缓存文件不存在,返回304给客户端 - res.writeHead(304, { - 'Access-Control-Allow-Origin': res.getHeader('access-control-allow-origin') || '*', - 'Cache-Control': 'public, max-age=3600' - }); - res.end(); - return; - } - - // 记录meta信息 - const metaInfo = { - timestamp: Date.now(), - headers: proxyRes.headers, - statusCode: proxyRes.statusCode - }; - - // 更新内存缓存 - metaCache[cacheKey] = metaInfo; - - // 写入meta缓存文件 - fs.writeFile(metaCacheFile, JSON.stringify(metaInfo), (err) => { - if (err) console.error(`Error writing meta cache: ${err.message}`); - }); - - // 设置响应头,保持CORS头 - const responseHeaders = { ...proxyRes.headers }; - // 确保CORS头不被覆盖 - if (!responseHeaders['access-control-allow-origin']) { - responseHeaders['access-control-allow-origin'] = res.getHeader('access-control-allow-origin') || '*'; - } - - // 确保Content-Type正确设置,特别是对于视频文件 - if (!responseHeaders['content-type']) { - const ext = pathModule.extname(req.url).toLowerCase(); - const mimeTypes = { - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.ogg': 'video/ogg', - '.avi': 'video/x-msvideo', - '.mov': 'video/quicktime', - '.wmv': 'video/x-ms-wmv', - '.flv': 'video/x-flv', - '.mkv': 'video/x-matroska', - '.m4v': 'video/mp4', - '.3gp': 'video/3gpp', - '.ts': 'video/mp2t' - }; - responseHeaders['content-type'] = mimeTypes[ext] || 'application/octet-stream'; - } - - // 确保Accept-Ranges头存在 - responseHeaders['accept-ranges'] = 'bytes'; - - res.writeHead(proxyRes.statusCode, responseHeaders); - - // 如果是成功的响应,缓存内容 - if (proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) { - // 对于Range请求的206响应,不进行缓存,直接转发 - if (proxyRes.statusCode === 206 || rangeHeader) { - console.log(`Streaming range response: ${req.url}`); - proxyRes.pipe(res); - } else { - // 完整内容响应,进行缓存 - const tempFile = `${contentCacheFile}.tmp`; - const fileStream = fs.createWriteStream(tempFile); - - // 将响应写入文件并同时发送给客户端 - proxyRes.pipe(fileStream); - proxyRes.pipe(res); - - // 完成后重命名临时文件 - fileStream.on('finish', () => { - fs.rename(tempFile, contentCacheFile, (err) => { - if (err) console.error(`Error saving cache file: ${err.message}`); - else console.log(`Cache saved for: ${req.url}`); - }); - }); - - // 处理错误 - fileStream.on('error', (err) => { - console.error(`Error writing cache file: ${err.message}`); - fs.unlink(tempFile, () => {}); // 删除临时文件 - }); - } - } else { - // 非成功响应直接转发,不缓存内容 - proxyRes.pipe(res); - } - }); - - // 处理请求错误 - proxyReq.on('error', (err) => { - console.error(`Proxy request error: ${err.message}`); - res.statusCode = 502; - res.end('Bad Gateway'); - }); - - // 如果原始请求有body,转发它 - if (['POST', 'PUT', 'PATCH'].includes(req.method)) { - req.pipe(proxyReq); - } else { - proxyReq.end(); - } -} - -// 格式化字节数 -function formatBytes(bytes) { - if (bytes === 0) return '0 B'; - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -// 格式化时间(秒) -function formatTime(seconds) { - if (!seconds || seconds === Infinity) return 'Unknown'; - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const secs = Math.floor(seconds % 60); - - if (hours > 0) { - return `${hours}h ${minutes}m ${secs}s`; - } else if (minutes > 0) { - return `${minutes}m ${secs}s`; - } else { - return `${secs}s`; - } -} - -// 启动服务器 -server.listen(DEFAULT_PORT, () => { - console.log(`Proxy server running at http://localhost:${DEFAULT_PORT}/`); - console.log(`Proxying requests to: ${DEFAULT_API_ENDPOINT}`); - console.log(`Cache directory: ${cacheDir}`); - console.log(`Meta cache expiry: ${META_CACHE_EXPIRY_MS / 60000} minutes`); -}); diff --git a/source.js b/source.js deleted file mode 100644 index d40803a..0000000 --- a/source.js +++ /dev/null @@ -1,805 +0,0 @@ -const http = require('http'); -const https = require('https'); -const url = require('url'); -const querystring = require('querystring'); -const fs = require('fs'); -const pathModule = require('path'); -const crypto = require('crypto'); - -const CACHE_DIR_NAME = '.cache'; -const DEFAULT_PORT = 9001; -const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9519/api'; - -const cacheDir = pathModule.join(__dirname, CACHE_DIR_NAME); -const pathIndex = {}; - -// 访问计数器 -const viewsInfo = { - request: 0, - cacheHit: 0, - apiCall: 0, - cacheCall: 0, - cacheReadError: 0, - fetchApiError: 0, - fetchApiWarning: 0, - increment: function (key) { - if (this.hasOwnProperty(key)) { - this[key]++; - } - } -}; - -let port = DEFAULT_PORT; -let apiEndpoint = DEFAULT_API_ENDPOINT; - -// 解析命令行参数函数 -function parseArguments() { - const args = process.argv.slice(2); - args.forEach(arg => { - const cleanArg = arg.startsWith('--') ? arg.substring(2) : arg; - const [key, value] = cleanArg.split('='); - if (key === 'port' && value) { - const parsedPort = parseInt(value, 10); - if (!isNaN(parsedPort)) { - port = parsedPort; - } - } else if (key === 'api' && value) { - apiEndpoint = value; - } - }); -} - -// 初始化函数,包含参数解析和目录创建 -function initializeApp() { - parseArguments(); - if (!fs.existsSync(cacheDir)) { - try { - fs.mkdirSync(cacheDir, { recursive: true }); - console.log(`Cache directory created: ${cacheDir}`); - } catch (err) { - console.error(`Error creating cache directory ${cacheDir}:`, err); - process.exit(1); // Exit if cache directory cannot be created - } - } -} - -initializeApp(); - -const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours -const CACHE_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour -const HTTP_STATUS = { - OK: 200, - NO_CONTENT: 204, - REDIRECT: 302, - NOT_MODIFIED: 304, - BAD_REQUEST: 400, - NOT_FOUND: 404, - INTERNAL_SERVER_ERROR: 500, - BAD_GATEWAY: 502, -}; - -// 定时清理过期缓存数据 -setInterval(async () => { - const currentTime = Date.now(); - const keysToDelete = []; - const filesToDelete = []; - - // 第一步:收集需要删除的键和文件 - for (const key in pathIndex) { - if (currentTime - pathIndex[key].timestamp > CACHE_EXPIRY_MS) { - keysToDelete.push(key); - const cacheMetaFile = pathModule.join(cacheDir, `${key}.meta`); - const cacheContentFile = pathModule.join(cacheDir, `${pathIndex[key].uniqid}.content`); - filesToDelete.push(cacheMetaFile, cacheContentFile); - } - } - - // 第二步:从内存中删除过期索引 - keysToDelete.forEach(key => delete pathIndex[key]); - - // 第三步:异步删除文件系统中的缓存文件 - if (filesToDelete.length > 0) { - console.log(`Cleaning up ${keysToDelete.length} expired cache entries`); - - // 并行删除文件,但限制并发数为10 - const deletePromises = filesToDelete.map(file => - fs.promises.unlink(file).catch(err => { - if (err.code !== 'ENOENT') { // 忽略文件不存在的错误 - console.warn(`Failed to delete cache file ${file}:`, err.message); - } - }) - ); - - // 使用Promise.all处理所有删除操作 - await Promise.all(deletePromises); - } -}, CACHE_CLEANUP_INTERVAL_MS); - -// 统一发送错误响应 -function sendErrorResponse(res, statusCode, message) { - if (!res.headersSent) { - res.writeHead(statusCode, { 'Content-Type': 'text/plain;charset=UTF-8' }); - res.end(message); - } -} - -// --- Request Handling Logic --- - -async function handleFavicon(req, res) { - res.writeHead(HTTP_STATUS.NO_CONTENT); - res.end(); -} - -async function handleEndpoint(req, res, parsedUrl) { - if (parsedUrl.query.api) { - const urlRegex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w.-]*)*\/?$/; - if (urlRegex.test(parsedUrl.query.api)) { - apiEndpoint = parsedUrl.query.api; - console.log(`API endpoint updated to: ${apiEndpoint}`); - } - } - res.writeHead(HTTP_STATUS.OK, { 'Content-Type': 'application/json; charset=utf-8' }); - res.end(JSON.stringify({ - code: HTTP_STATUS.OK, - data: { - api: apiEndpoint, - port: port, - cacheDir: cacheDir, - pathIndexCount: Object.keys(pathIndex).length, - viewsInfo: { - request: viewsInfo.request, - cacheHit: viewsInfo.cacheHit, - apiCall: viewsInfo.apiCall, - cacheCall: viewsInfo.cacheCall, - cacheReadError: viewsInfo.cacheReadError, - fetchApiError: viewsInfo.fetchApiError, - fetchApiWarning: viewsInfo.fetchApiWarning, - } - } - })); -} - -async function handleApiRedirect(res, apiData) { - res.writeHead(HTTP_STATUS.REDIRECT, { Location: apiData.data.url }); - res.end(); -} - -async function processSuccessfulApiData(apiData, uniqidhex, reqPath, token, sign, res, req) { - const { url: realUrl, cloudtype, expiration, path: apiPath, headers, uniqid, thumb } = apiData.data; - const data = { realUrl, cloudtype, expiration: expiration * 1000, path: apiPath, headers, uniqid, thumb }; - - pathIndex[uniqidhex] = { uniqid: data.uniqid, timestamp: Date.now() }; - const cacheMetaFile = pathModule.join(cacheDir, `${uniqidhex}.meta`); - const cacheContentFile = pathModule.join(cacheDir, `${data.uniqid}.content`); - const tempCacheContentFile = pathModule.join(cacheDir, `${data.uniqid}_${crypto.randomBytes(16).toString('hex')}.temp`); - - try { - fs.writeFileSync(cacheMetaFile, JSON.stringify(data)); - } catch (writeError) { - console.error(`Error writing meta file ${cacheMetaFile}:`, writeError); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to write cache metadata.'); - return; - } - - if (fs.existsSync(cacheContentFile)) { - const stats = fs.statSync(cacheContentFile); - const contentLength = stats.size; - if (contentLength < 2048 && data.headers['content-length'] && parseInt(data.headers['content-length'], 10) !== contentLength) { - console.warn(`Content length mismatch for ${cacheContentFile}. API: ${data.headers['content-length']}, Cache: ${contentLength}. Re-fetching.`); - fetchAndServe(data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req); - } else { - serveFromCache(data, cacheContentFile, cacheMetaFile, res, reqPath, token, sign, uniqidhex, req); - } - } else { - fetchAndServe(data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req); - } -} - -async function tryServeFromStaleCacheOrError(uniqidhex, res, errorMessage, req) { - if (pathIndex[uniqidhex]) { - const cacheMetaFile = pathModule.join(cacheDir, `${uniqidhex}.meta`); - const cacheContentFile = pathModule.join(cacheDir, `${pathIndex[uniqidhex].uniqid}.content`); - if (fs.existsSync(cacheMetaFile) && fs.existsSync(cacheContentFile)) { - console.warn(`API call failed or returned non-200. Serving stale cache for ${uniqidhex}`); - try { - const cacheData = JSON.parse(fs.readFileSync(cacheMetaFile, 'utf8')); - serveFromCache(cacheData, cacheContentFile, cacheMetaFile, res, null, null, null, uniqidhex, req); - return; - } catch (parseError) { - console.error(`Error parsing stale meta file ${cacheMetaFile}:`, parseError); - // Fall through to generic error if stale cache is also broken - } - } - } - sendErrorResponse(res, HTTP_STATUS.BAD_GATEWAY, errorMessage || 'Bad Gateway'); -} - -async function handleMainRequest(req, res) { - // 处理OPTIONS请求,支持跨域预检 - if (req.method === 'OPTIONS') { - res.writeHead(200, { - 'Access-Control-Allow-Origin': req.headers.origin || '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type', - 'Access-Control-Max-Age': '86400' - }); - res.end(); - return; - } - - req.url = req.url.replace(/\/{2,}/g, '/'); - const parsedUrl = url.parse(req.url, true); - const sign = parsedUrl.query.sign || ''; - let reqPath = parsedUrl.pathname.split('/')[1] || ''; // Ensure reqPath is not undefined - let token = parsedUrl.pathname.split('/').slice(2).join('/'); - - if (reqPath === 'favicon.ico') return handleFavicon(req, res); - if (reqPath === 'endpoint') return handleEndpoint(req, res, parsedUrl); - - if (!token && reqPath) { // If token is empty but reqPath is not, assume reqPath is the token - token = reqPath; - reqPath = 'app'; // Default to 'app' if only one path segment is provided - } - - const ALLOWED_PATHS = ['avatar', 'go', 'bbs', 'www', 'url', 'thumb', 'app', 'qrcode', 'school']; - if (!ALLOWED_PATHS.includes(reqPath) || !token) { - return sendErrorResponse(res, HTTP_STATUS.BAD_REQUEST, `Bad Request: Invalid path or missing token.`); - } - - viewsInfo.increment('request'); - const uniqidhex = crypto.createHash('md5').update(reqPath + token + sign).digest('hex'); - let cacheMetaFile = ''; - let cacheContentFile = ''; - - if (pathIndex[uniqidhex]) { - cacheMetaFile = pathModule.join(cacheDir, `${uniqidhex}.meta`); - cacheContentFile = pathModule.join(cacheDir, `${pathIndex[uniqidhex].uniqid}.content`); - } - - if (pathIndex[uniqidhex] && isCacheValid(cacheMetaFile, cacheContentFile)) { - const { cacheData, isNotModified } = await checkCacheHeaders(req, cacheMetaFile); - if (isNotModified) { - res.writeHead(HTTP_STATUS.NOT_MODIFIED); - res.end(); - } else { - viewsInfo.increment('cacheHit'); - serveFromCache(cacheData, cacheContentFile, cacheMetaFile, res, reqPath, token, sign, uniqidhex, req); - } - } else { - try { - viewsInfo.increment('apiCall'); - const apiData = await fetchApiData(reqPath, token, sign); - - if (apiData.code === HTTP_STATUS.REDIRECT || apiData.code === 301) { - return handleApiRedirect(res, apiData); - } - - if (apiData.code === HTTP_STATUS.OK && apiData.data && apiData.data.url) { - await processSuccessfulApiData(apiData, uniqidhex, reqPath, token, sign, res, req); - } else { - viewsInfo.increment('fetchApiWarning'); - await tryServeFromStaleCacheOrError(uniqidhex, res, apiData.message, req); - } - } catch (error) { - viewsInfo.increment('fetchApiError'); - console.error('Error in API call or processing:', error); - await tryServeFromStaleCacheOrError(uniqidhex, res, `Bad Gateway: API request failed. ${error.message}`, req); - } - } -} - -const server = http.createServer(handleMainRequest); - -// 检查缓存头并返回是否为304 -async function checkCacheHeaders(req, cacheMetaFile) { - try { - const metaContent = await fs.promises.readFile(cacheMetaFile, 'utf8'); - const cacheData = JSON.parse(metaContent); - const ifNoneMatch = req.headers['if-none-match']; - const ifModifiedSince = req.headers['if-modified-since']; - - // Check ETag first - if (ifNoneMatch && cacheData.uniqid && ifNoneMatch === cacheData.uniqid) { - return { cacheData, isNotModified: true }; - } - - // Check If-Modified-Since - if (ifModifiedSince && cacheData.headers && cacheData.headers['last-modified']) { - try { - const lastModifiedDate = new Date(cacheData.headers['last-modified']); - const ifModifiedSinceDate = new Date(ifModifiedSince); - // The time resolution of an HTTP date is one second. - // If If-Modified-Since is at least as new as Last-Modified, send 304. - if (lastModifiedDate.getTime() <= ifModifiedSinceDate.getTime()) { - return { cacheData, isNotModified: true }; - } - } catch (dateParseError) { - console.warn(`Error parsing date for cache header check (${cacheMetaFile}):`, dateParseError); - // Proceed as if not modified check failed if dates are invalid - } - } - return { cacheData, isNotModified: false }; - } catch (error) { - console.error(`Error reading or parsing cache meta file ${cacheMetaFile} in checkCacheHeaders:`, error); - return { cacheData: null, isNotModified: false }; // Indicate failure to load cacheData - } -} - -// 检查缓存是否有效 -async function isCacheValid(cacheMetaFile, cacheContentFile) { - try { - // 使用Promise.all并行检查文件是否存在 - const [metaExists, contentExists] = await Promise.all([ - fs.promises.access(cacheMetaFile).then(() => true).catch(() => false), - fs.promises.access(cacheContentFile).then(() => true).catch(() => false) - ]); - - if (!metaExists || !contentExists) { - return false; - } - - const metaContent = await fs.promises.readFile(cacheMetaFile, 'utf8'); - const cacheData = JSON.parse(metaContent); - // Ensure expiration is a number and in the future - return typeof cacheData.expiration === 'number' && cacheData.expiration > Date.now(); - } catch (error) { - console.warn(`Error reading or parsing cache meta file ${cacheMetaFile} for validation:`, error); - return false; // If meta file is corrupt or unreadable, cache is not valid - } -} - - -// 从 API 获取数据 -const API_TIMEOUT_MS = 5000; -const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'; - -async function fetchApiData(reqPath, token, sign) { - const queryParams = querystring.stringify({ - type: reqPath, - sign: sign - }); - const apiUrl = `${apiEndpoint}?${queryParams}`; - const parsedApiUrl = new URL(apiUrl); - const protocol = parsedApiUrl.protocol === 'https:' ? https : http; - - const options = { - method: 'GET', - headers: { - 'Accept': 'application/json; charset=utf-8', - 'User-Agent': USER_AGENT, - 'token': token - }, - timeout: API_TIMEOUT_MS, - rejectUnauthorized: false, // Allow self-signed certificates, use with caution - }; - - return new Promise((resolve, reject) => { - const apiReq = protocol.request(apiUrl, options, (apiRes) => { - let responseData = ''; - apiRes.setEncoding('utf8'); - apiRes.on('data', chunk => responseData += chunk); - apiRes.on('end', () => { - try { - if (apiRes.statusCode >= 400) { - // Treat HTTP errors from API as rejections for easier handling - console.error(`API request to ${apiUrl} failed with status ${apiRes.statusCode}: ${responseData}`); - // Attempt to parse for a message, but prioritize status code for error type - let errorPayload = { code: apiRes.statusCode, message: `API Error: ${apiRes.statusCode}` }; - try { - const parsedError = JSON.parse(responseData); - if (parsedError && parsedError.message) errorPayload.message = parsedError.message; - } catch (e) { /* Ignore if response is not JSON */ } - resolve(errorPayload); // Resolve with error structure for consistency - return; - } - resolve(JSON.parse(responseData)); - } catch (parseError) { - console.error(`Error parsing JSON response from ${apiUrl}:`, parseError, responseData); - reject(new Error(`Failed to parse API response: ${parseError.message}`)); - } - }); - }); - - apiReq.on('timeout', () => { - apiReq.destroy(); // Destroy the request to free up resources - console.error(`API request to ${apiUrl} timed out after ${API_TIMEOUT_MS}ms`); - reject(new Error('API request timed out')); - }); - - apiReq.on('error', (networkError) => { - console.error(`API request to ${apiUrl} failed:`, networkError); - reject(networkError); - }); - - apiReq.end(); - }); -} - -// 从真实 URL 获取数据并写入缓存 -const REAL_URL_FETCH_TIMEOUT_MS = 0; // 0 means no timeout for the actual file download - -const fetchAndServe = async (data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req) => { - const protocol = data.realUrl.startsWith('https:') ? https : http; - - protocol.get(data.realUrl, { timeout: REAL_URL_FETCH_TIMEOUT_MS, rejectUnauthorized: false }, (realRes) => { - const cacheStream = fs.createWriteStream(tempCacheContentFile, { flags: 'w', highWaterMark: 64 * 1024 }); // 增加缓冲区大小到64KB - - let isVideo = data.path && typeof data.path === 'string' && data.path.includes('.mp4'); - // 确保 content-length 是有效的 - const contentLength = realRes.headers['content-length']; - if (contentLength) { - data.headers['content-length'] = contentLength; - // 异步更新 data 到缓存 cacheMetaFile - fs.promises.writeFile(cacheMetaFile, JSON.stringify(data)) - .catch(err => console.error(`Error writing meta file ${cacheMetaFile}:`, err)); - } else { - console.warn('Warning: content-length is undefined for the response from:', data.realUrl); - } - - const baseHeaders = { - 'Cloud-Type': data.cloudtype, - 'ETag': data.uniqid || '', - 'Cache-Control': 'public, max-age=31536000', // 1 year - 'Expires': new Date(Date.now() + 31536000000).toUTCString(), - 'Accept-Ranges': 'bytes', - 'Connection': 'keep-alive', - 'Date': new Date().toUTCString(), - 'Last-Modified': data.headers['last-modified'] || new Date().toUTCString(), - 'Access-Control-Allow-Origin': req.headers.origin || '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type', - }; - const responseHeaders = { - ...baseHeaders, - 'Content-Type': realRes.headers['content-type'] || (isVideo ? 'video/mp4' : 'application/octet-stream'), - ...data.headers, - }; - - res.writeHead(realRes.statusCode, responseHeaders); - - // 使用管道优化流传输 - const pipeline = require('stream').pipeline; - - // 创建一个流分支,同时写入缓存和响应 - const { PassThrough } = require('stream'); - const passThrough = new PassThrough(); - - passThrough.pipe(cacheStream); - passThrough.pipe(res); - - // 使用pipeline处理流错误 - pipeline( - realRes, - passThrough, - (err) => { - if (err) { - console.error(`Pipeline error for ${data.realUrl}:`, err); - handleResponseError(res, tempCacheContentFile, data.realUrl); - return; - } - - // 流处理完成后,重命名临时文件 - fs.promises.access(tempCacheContentFile) - .then(() => { - // 确保目标目录存在 - return fs.promises.mkdir(pathModule.dirname(cacheContentFile), { recursive: true }) - .then(() => fs.promises.rename(tempCacheContentFile, cacheContentFile)) - .then(() => console.log(`Successfully cached: ${cacheContentFile}`)) - .catch(renameError => { - console.error(`Error renaming temp cache file ${tempCacheContentFile} to ${cacheContentFile}:`, renameError); - return fs.promises.unlink(tempCacheContentFile).catch(() => {}); - }); - }) - .catch(() => { - console.warn(`Temp cache file ${tempCacheContentFile} not found after stream end for ${data.realUrl}`); - }); - } - ); - }).on('error', (requestError) => { - console.error(`Error making GET request to ${data.realUrl}:`, requestError); - handleResponseError(res, tempCacheContentFile, data.realUrl); - }); -}; - -// 从缓存中读取数据并返回 -async function serveFromCache(cacheData, cacheContentFile, cacheMetaFile, res, reqPath, token, sign, uniqidhex, req) { - if (!cacheData) { // 缓存数据不可用,尝试重新获取 - console.warn(`Cache metadata unavailable for ${cacheContentFile}, attempting to fetch fresh data`); - - // 如果提供了请求参数,尝试重新获取数据 - if (reqPath && token) { - try { - viewsInfo.increment('apiCall'); - const apiData = await fetchApiData(reqPath, token, sign); - - if (apiData.code === HTTP_STATUS.REDIRECT || apiData.code === 301) { - res.writeHead(HTTP_STATUS.REDIRECT, { Location: apiData.data.url }); - res.end(); - return; - } - - if (apiData.code === HTTP_STATUS.OK && apiData.data && apiData.data.url) { - const { url: realUrl, cloudtype, expiration, path: apiPath, headers, uniqid, thumb } = apiData.data; - const data = { realUrl, cloudtype, expiration: expiration * 1000, path: apiPath, headers, uniqid, thumb }; - - // 更新索引 - pathIndex[uniqidhex] = { uniqid: data.uniqid, timestamp: Date.now() }; - - // 写入新的元数据 - await fs.promises.mkdir(pathModule.dirname(cacheMetaFile), { recursive: true }); - await fs.promises.writeFile(cacheMetaFile, JSON.stringify(data)); - - // 获取并提供新数据 - const tempCacheContentFile = pathModule.join(cacheDir, `${data.uniqid}_${crypto.randomBytes(16).toString('hex')}.temp`); - fetchAndServe(data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req); - return; - } else { - viewsInfo.increment('fetchApiWarning'); - sendErrorResponse(res, HTTP_STATUS.BAD_GATEWAY, apiData.message || 'Failed to fetch data from API'); - return; - } - } catch (error) { - viewsInfo.increment('fetchApiError'); - console.error('Error fetching fresh data:', error); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, `Failed to fetch fresh data: ${error.message}`); - return; - } - } else { - // 如果没有提供请求参数,无法重新获取 - console.error(`serveFromCache called with null cacheData and insufficient request info for ${cacheContentFile}`); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Cache metadata unavailable and cannot fetch fresh data.'); - return; - } - } - - // 使用异步方式获取ETag和Last-Modified - let etag = cacheData.uniqid; - let lastModified = cacheData.headers && cacheData.headers['last-modified']; - - if (!etag || !lastModified) { - try { - const [fileStats, metaStats] = await Promise.all([ - fs.promises.stat(cacheContentFile).catch(() => null), - fs.promises.stat(cacheMetaFile).catch(() => null) - ]); - - if (!etag && fileStats) { - // 使用文件大小和修改时间作为ETag的一部分,避免读取整个文件计算MD5 - etag = crypto.createHash('md5') - .update(`${fileStats.size}-${fileStats.mtime.getTime()}`) - .digest('hex'); - } - - if (!lastModified && metaStats) { - lastModified = new Date(metaStats.mtime).toUTCString(); - } - } catch (error) { - console.warn(`Error getting file stats for cache: ${error.message}`); - } - } - - const baseHeaders = { - 'Cloud-Type': cacheData.cloudtype || 'unknown', - 'ETag': etag || '', - 'Cache-Control': 'public, max-age=31536000', // 1 year - 'Expires': new Date(Date.now() + 31536000000).toUTCString(), - 'Accept-Ranges': 'bytes', - 'Connection': 'keep-alive', - 'Date': new Date().toUTCString(), - 'Last-Modified': lastModified || new Date().toUTCString(), - 'Access-Control-Allow-Origin': req.headers.origin || '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type', - }; - - viewsInfo.increment('cacheCall'); - - // 先检查缓存文件是否存在且可读 - try { - await fs.promises.access(cacheContentFile, fs.constants.R_OK); - } catch (error) { - console.warn(`Cache content file ${cacheContentFile} not accessible: ${error.message}`); - - // 如果提供了请求参数,尝试重新获取数据 - if (reqPath && token) { - console.log(`Attempting to fetch fresh data for ${cacheContentFile}`); - try { - viewsInfo.increment('apiCall'); - const apiData = await fetchApiData(reqPath, token, sign); - - if (apiData.code === HTTP_STATUS.OK && apiData.data && apiData.data.url) { - const { url: realUrl, cloudtype, expiration, path: apiPath, headers, uniqid, thumb } = apiData.data; - const data = { realUrl, cloudtype, expiration: expiration * 1000, path: apiPath, headers, uniqid, thumb }; - - // 更新索引 - pathIndex[uniqidhex] = { uniqid: data.uniqid, timestamp: Date.now() }; - - // 获取并提供新数据 - const tempCacheContentFile = pathModule.join(cacheDir, `${data.uniqid}_${crypto.randomBytes(16).toString('hex')}.temp`); - fetchAndServe(data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req); - return; - } else { - sendErrorResponse(res, HTTP_STATUS.BAD_GATEWAY, apiData.message || 'Failed to fetch data from API'); - return; - } - } catch (fetchError) { - console.error(`Error fetching fresh data: ${fetchError.message}`); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, `Failed to fetch fresh data: ${fetchError.message}`); - return; - } - } else { - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Unable to read cache content file and cannot fetch fresh data.'); - return; - } - } - - const readStream = fs.createReadStream(cacheContentFile, { highWaterMark: 64 * 1024 }); // 增加读取缓冲区大小 - const isVideo = cacheData.path && typeof cacheData.path === 'string' && cacheData.path.includes('.mp4'); - - let currentContentLength = cacheData.headers && cacheData.headers['content-length'] ? parseInt(cacheData.headers['content-length'], 10) : 0; - - if (!currentContentLength || currentContentLength === 0) { - try { - const stats = fs.statSync(cacheContentFile); - currentContentLength = stats.size; - if (currentContentLength > 0) { - if (!cacheData.headers) cacheData.headers = {}; - cacheData.headers['content-length'] = currentContentLength.toString(); - // Update meta file if content-length was missing or zero - fs.writeFileSync(cacheMetaFile, JSON.stringify(cacheData)); - console.log(`Updated content-length in ${cacheMetaFile} to ${currentContentLength}`); - } else { - console.warn(`Cached content file ${cacheContentFile} has size 0 or stat failed.`); - // Potentially treat as an error or serve as is if 0 length is valid for some files - } - } catch (statError) { - console.error(`Error stating cache content file ${cacheContentFile}:`, statError); - handleCacheReadError(res, cacheContentFile, reqPath, token, sign, uniqidhex); // Treat stat error as read error - return; - } - } - - readStream.on('open', () => { - - const responseHeaders = { - ...baseHeaders, - 'Content-Type': (cacheData.headers && cacheData.headers['content-type']) || (isVideo ? 'video/mp4' : 'application/octet-stream'), - // Merge other headers from cacheData.headers, letting them override base if necessary - // but ensure our critical headers like Content-Length (if updated) are preserved. - ...(cacheData.headers || {}), - }; - - res.writeHead(HTTP_STATUS.OK, responseHeaders); - readStream.pipe(res); - }); - - readStream.on('error', (err) => { - console.error(`Read stream error for ${cacheContentFile}:`, err); - - // 如果提供了请求参数,尝试重新获取数据而不是直接报错 - if (reqPath && token) { - console.log(`Read stream error, attempting to fetch fresh data for ${cacheContentFile}`); - viewsInfo.increment('apiCall'); - - fetchApiData(reqPath, token, sign) - .then(apiData => { - if (apiData.code === HTTP_STATUS.OK && apiData.data && apiData.data.url) { - const { url: realUrl, cloudtype, expiration, path: apiPath, headers, uniqid, thumb } = apiData.data; - const data = { realUrl, cloudtype, expiration: expiration * 1000, path: apiPath, headers, uniqid, thumb }; - - // 更新索引 - pathIndex[uniqidhex] = { uniqid: data.uniqid, timestamp: Date.now() }; - - // 获取并提供新数据 - const tempCacheContentFile = pathModule.join(cacheDir, `${data.uniqid}_${crypto.randomBytes(16).toString('hex')}.temp`); - fetchAndServe(data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req); - } else { - viewsInfo.increment('fetchApiWarning'); - sendErrorResponse(res, HTTP_STATUS.BAD_GATEWAY, apiData.message || 'Failed to fetch data from API'); - } - }) - .catch(fetchError => { - viewsInfo.increment('fetchApiError'); - console.error(`Error fetching fresh data after read stream error: ${fetchError.message}`); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, `Failed to fetch fresh data: ${fetchError.message}`); - }); - } else { - // 如果没有提供请求参数,使用原始的错误处理 - handleCacheReadError(res, cacheContentFile, reqPath, token, sign, uniqidhex); - } - }); - - // Handle cases where client closes connection prematurely - res.on('close', () => { - if (!res.writableEnded) { - console.log(`Client closed connection prematurely for ${cacheContentFile}. Destroying read stream.`); - readStream.destroy(); - } - }); -} - - -// 处理响应错误 -const handleResponseError = (res, tempCacheContentFile, realUrl) => { - viewsInfo.increment('fetchApiError'); - console.error(`Error fetching from real URL: ${realUrl}`); - sendErrorResponse(res, HTTP_STATUS.BAD_GATEWAY, `Bad Gateway: Failed to fetch from ${realUrl}`); - if (fs.existsSync(tempCacheContentFile)) { - try { - fs.unlinkSync(tempCacheContentFile); - } catch (unlinkErr) { - console.error(`Error unlinking temp file ${tempCacheContentFile}:`, unlinkErr); - } - } -}; - -// 处理缓存读取错误 -const handleCacheReadError = (res, filePath, reqPath, token, sign, uniqidhex) => { - viewsInfo.increment('cacheReadError'); - console.error(`Error reading cache file: ${filePath}`); - - // 如果提供了请求参数,尝试重新获取数据 - if (reqPath && token) { - console.log(`Cache read error, attempting to fetch fresh data for ${filePath}`); - viewsInfo.increment('apiCall'); - - fetchApiData(reqPath, token, sign) - .then(apiData => { - if (apiData.code === HTTP_STATUS.OK && apiData.data && apiData.data.url) { - const { url: realUrl, cloudtype, expiration, path: apiPath, headers, uniqid, thumb } = apiData.data; - const data = { realUrl, cloudtype, expiration: expiration * 1000, path: apiPath, headers, uniqid, thumb }; - - // 更新索引 - if (uniqidhex) { - pathIndex[uniqidhex] = { uniqid: data.uniqid, timestamp: Date.now() }; - } - - // 获取并提供新数据 - const cacheMetaFile = pathModule.join(cacheDir, `${uniqidhex}.meta`); - const cacheContentFile = pathModule.join(cacheDir, `${data.uniqid}.content`); - const tempCacheContentFile = pathModule.join(cacheDir, `${data.uniqid}_${crypto.randomBytes(16).toString('hex')}.temp`); - - // 写入新的元数据 - fs.promises.mkdir(pathModule.dirname(cacheMetaFile), { recursive: true }) - .then(() => fs.promises.writeFile(cacheMetaFile, JSON.stringify(data))) - .then(() => { - fetchAndServe(data, tempCacheContentFile, cacheContentFile, cacheMetaFile, res, req); - }) - .catch(writeError => { - console.error(`Error writing meta file after cache read error: ${writeError.message}`); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to write cache metadata'); - }); - } else { - viewsInfo.increment('fetchApiWarning'); - sendErrorResponse(res, HTTP_STATUS.BAD_GATEWAY, apiData.message || 'Failed to fetch data from API'); - } - }) - .catch(fetchError => { - viewsInfo.increment('fetchApiError'); - console.error(`Error fetching fresh data after cache read error: ${fetchError.message}`); - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, `Failed to fetch fresh data: ${fetchError.message}`); - }); - } else { - // 如果没有提供请求参数,返回错误 - sendErrorResponse(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal Server Error: Unable to read cache content file'); - } -}; - -// 启动服务器 -server.listen(port, () => { - console.log(`Proxy server is running on http://localhost:${port}`); -}); - -// 处理 SIGINT 信号(Ctrl+C) -process.on('SIGINT', () => { - console.log('Received SIGINT. Shutting down gracefully...'); - server.close(() => { - console.log('Server closed.'); - process.exit(0); - }); - - setTimeout(() => { - console.error('Forcing shutdown...'); - process.exit(1); - }, 10000); -}); \ No newline at end of file