Spaces:
Sleeping
Sleeping
Download server.js from Nour774/blacksky-api: direct link, hf CLI and curl.
- Browser
- Download file 9.13 kB
-
https://huggingface.co/spaces/Nour774/blacksky-api/resolve/main/server.js
- Command line
-
hf download hf://spaces/Nour774/blacksky-api/server.js
-
curl -L -o server.js https://huggingface.co/spaces/Nour774/blacksky-api/resolve/main/server.js
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}`)); |