const express = require('express'); const cors = require('cors'); const { Octokit } = require('@octokit/rest'); const app = express(); app.use(cors({ origin: '*' })); app.use(express.json({ limit: '200mb' })); const PORT = process.env.PORT || 7860; const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); const REPO_OWNER = 'Nour774'; const REPO_NAME = 's'; const BRANCH = 'main'; // طابور العمليات let operationQueue = []; let novelIndex = []; // 🎯 الكاش السحري: تخزين ملفات الروايات بالكامل في RAM هجينج فيس لمنع طلبها من جيثب تكراراً let localNovelsCache = {}; let isIndexLoaded = false; /** * دالة مساعدة تجلب الملفات مع كاش محلي صارم */ async function fetchFileContentWithCache(path) { try { const { data } = await octokit.repos.getContent({ owner: REPO_OWNER, repo: REPO_NAME, path, ref: BRANCH }); return Buffer.from(data.content, 'base64').toString('utf8'); } catch (e) { return null; } } // مسارات استقبال الطلبات (إضافة رواية / إضافة فصل) app.post('/api/add-novel', (req, res) => { operationQueue.push({ type: 'ADD_NOVEL', data: req.body }); res.status(200).json({ status: 'queued' }); }); app.post('/api/add-chapter', (req, res) => { operationQueue.push({ type: 'ADD_CHAPTER', data: req.body }); res.status(200).json({ status: 'queued' }); }); /** * 🔄 محرك الضخ الذكي الخالي من استهلاك طلبات الـ API */ setInterval(async () => { if (operationQueue.length === 0) return; const currentOps = [...operationQueue]; operationQueue = []; console.log(`[⚡ Engine] بدء تصفية وحقن حزمة تحتوي على ${currentOps.length} عملية بالذاكرة...`); try { let finalTreeChanges = []; const timestamp = Math.floor(Date.now() / 1000); // 1. جلب الفهرس العام مرة واحدة فقط عند بدء تشغيل السيرفر الفعلي وتخزينه بالذاكرة if (!isIndexLoaded) { console.log("[📥 Cache] جلب الفهرس العام لأول مرة من جيثب..."); let searchIndexContent = await fetchFileContentWithCache('novel/search_index.js'); if (searchIndexContent) { let novelIndexMatch = searchIndexContent.match(/window\.novelIndex\s*=\s*([\s\S]*?);/); novelIndex = novelIndexMatch ? eval(novelIndexMatch[1]) : []; } isIndexLoaded = true; } // مجموعة لتتبع الروايات التي تم تعديلها في هذه الدفعة لرفعها let alteredNovelIds = new Set(); for (const op of currentOps) { if (op.type === 'ADD_NOVEL') { const d = op.data; const nextId = novelIndex.length > 0 ? novelIndex[novelIndex.length - 1][0] + 1 : 1; // تحديث مصفوفة الفهرس بالذاكرة novelIndex.push([ nextId, d.englishName, d.arabicName, d.slug, d.genres, parseInt(d.status), 10.0, 0, timestamp, 0, d.author, d.coverType ]); // حقن الرواية الجديدة مباشرة في كاش الذاكرة (0 طلبات لجيثب!) localNovelsCache[nextId] = { id: nextId, slug: d.slug, uploader_id: parseInt(d.uploaderId), uploader_username: d.uploaderUsername, description: d.description, chapters: [] }; alteredNovelIds.add(nextId); } else if (op.type === 'ADD_CHAPTER') { const d = op.data; const novelId = parseInt(d.novelId); // إذا لم تكن الرواية في كاش الذاكرة، نسحبها من جيثب مرة واحدة فقط ونحفظها أبدياً في الكاش if (!localNovelsCache[novelId]) { console.log(`[📥 Cache Missing] جلب وصف الرواية ${novelId} لتخزينه في الذاكرة...`); let novelDocContent = await fetchFileContentWithCache(`novels/${novelId}.js`); if (novelDocContent) { let docMatch = novelDocContent.match(/window\.currentNovelData\s*=\s*([\s\S]*?);/); if (docMatch) { localNovelsCache[novelId] = eval(`(${docMatch[1]})`); } } } let cachedNovel = localNovelsCache[novelId]; if (cachedNovel) { // حقن الفصل الجديد في كاش الذاكرة (0 طلبات لجيثب!) const existingCh = cachedNovel.chapters.find(c => c.num === parseFloat(d.chapterNum)); if (!existingCh) { cachedNovel.chapters.push({ num: parseFloat(d.chapterNum), title: d.chapterTitle, type: "text", contributor: d.contributor }); cachedNovel.chapters.sort((a, b) => a.num - b.num); } alteredNovelIds.add(novelId); // تحديث عداد الفهرس العام في الذاكرة let row = novelIndex.find(r => r[0] === novelId); if (row) { row[9] = cachedNovel.chapters.length; row[8] = timestamp; } } // 2. إنشاء كائن الفصل المنفصل وضخه في الشجرة finalTreeChanges.push({ path: `chapters/${novelId}/${d.chapterNum}.js`, mode: '100644', type: 'blob', content: `window.currentChapterData = {\n "novelId": ${novelId},\n "num": ${d.chapterNum},\n "title": "${d.chapterTitle}",\n "contributor": "${d.contributor}",\n "content": "${d.content.replace(/"/g, '\\"')}"\n};` }); } } // 3. تحويل كافة الروايات المعدلة من الكاش الميموري إلى ملفات شجرة جيثب بضربة واحدة alteredNovelIds.forEach(id => { finalTreeChanges.push({ path: `novels/${id}.js`, mode: '100644', type: 'blob', content: `window.currentNovelData = ${JSON.stringify(localNovelsCache[id], null, 2)};` }); }); // 4. بناء ملف الفهرس المحدث من الذاكرة وضخه const updatedSearchIndexContent = `window.blackSkyConfig = window.blackSkyConfig || {};\nwindow.novelIndex = ${JSON.stringify(novelIndex, null, 4)};`; finalTreeChanges.push({ path: 'novel/search_index.js', mode: '100644', type: 'blob', content: updatedSearchIndexContent }); // 5. عملية دفع الـ Commit المجمع إلى جيثب (تستهلك طلب API واحد فقط للشجرة ككل!) console.log(`[📤 GitHub Sync] جاري شحن الشجرة المجمعة (${finalTreeChanges.length} ملفاً بالتزامن)...`); const { data: refData } = await octokit.git.getRef({ owner: REPO_OWNER, repo: REPO_NAME, ref: `heads/${BRANCH}` }); const { data: commitData } = await octokit.git.getCommit({ owner: REPO_OWNER, repo: REPO_NAME, commit_sha: refData.object.sha }); const { data: newTreeData } = await octokit.git.createTree({ owner: REPO_OWNER, repo: REPO_NAME, base_tree: commitData.tree.sha, tree: finalTreeChanges }); const { data: newCommitData } = await octokit.git.createCommit({ owner: REPO_OWNER, repo: REPO_NAME, message: `🤖 تحسين الأداء: دمج وضخ حزمة ضخمة (${finalTreeChanges.length} ملفاً) بطلب API واحد`, tree: newTreeData.sha, parents: [refData.object.sha] }); await octokit.git.updateRef({ owner: REPO_OWNER, repo: REPO_NAME, ref: `heads/${BRANCH}`, sha: newCommitData.sha }); console.log(`[🟢 Success] تم الدمج الكلي بنجاح تام وحماية خطة جيثب!`); } catch (error) { console.error('[🔴 Engine Error] حدث خطأ في حزمة الضخ:', error.message); // في حال حدوث خطأ، نعيد العمليات للطابور لإعادة المحاولة لاحقاً operationQueue = [...currentOps, ...operationQueue]; } }, 15000); app.listen(PORT, () => console.log(`سيرفر محاكاة الضغط المطور مستعد على المنفذ ${PORT}`));