blacksky-api / server.js
Nour774's picture
Update server.js
aee961c verified
Raw History Blame Contribute Delete
9.13 kB
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}`));