Nour774 commited on
Commit
db3071a
·
verified ·
1 Parent(s): 4c107a9

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +192 -93
server.js CHANGED
@@ -3,127 +3,226 @@ const cors = require('cors');
3
  const { Octokit } = require('@octokit/rest');
4
 
5
  const app = express();
6
- app.use(cors()); // 2.
7
- app.use(express.json()); // لتمكين السيرفر من قراءة بيانات JSON المرسلة
8
 
9
- // المنفذ الإجباري الخاص بـ Hugging Face Spaces
10
  const PORT = process.env.PORT || 7860;
11
 
12
- // تهيئة الاتصال بـ GitHub API باستخدام الـ Token السري الخاص بك
13
- const octokit = new Octokit({
14
- auth: process.env.GITHUB_TOKEN // سيتم تمريره كـ Environment Variable في Hugging Face
15
- });
16
-
17
- // إعدادات المستودع
18
- const REPO_OWNER = 'Nour774';
19
- const REPO_NAME = 's'; // أو 'novels-repo' حسب المستودع
20
  const BRANCH = 'main';
21
 
22
- // 📥 طابور الذاكرة المؤقت لتجميع العمليات خلال الـ 15 ثانية
23
- let actionQueue = [];
 
 
 
24
 
25
  /**
26
- * API إستقبال العمليات من المساهمين
27
- * يدعم أنواع العمليات: 'create', 'update', 'delete'
28
  */
29
- app.post('/api/sync', (req, res) => {
30
- const { action, filePath, content } = req.body;
31
-
32
- if (!action || !filePath) {
33
- return res.status(400).json({ error: 'البيانات المرسلة غير كاملة.' });
34
- }
35
-
36
- // تجهيز الكائن المتوافق مع Git Trees API
37
- const treeObject = {
38
- path: filePath,
39
- mode: '100644', // ملف نصي عادي
40
- type: 'blob'
41
- };
42
-
43
- if (action === 'delete') {
44
- // في الـ Git Trees API الحذف يتم بتمرير sha كـ null أو استثنائه تماماً من الشجرة الأساسية
45
- // سنحدد هنا علامة للحذف لتتعامل معها الدالة الدورية
46
- treeObject.sha = null;
47
- } else {
48
- // في حالة الإضافة أو التعديل نضع المحتوى الجديد للملف
49
- treeObject.content = content;
50
- }
51
-
52
- // ضخ العملية في الطابور اللحظي
53
- actionQueue.push(treeObject);
54
-
55
- // نرد على المساهم فوراً بأنه تم الاستلام لكي لا ينتظر جيثب
56
- res.status(200).json({ status: 'queued', message: 'تم جدولة طلبك، سيتم النشر خلال ثوانٍ.' });
57
  });
58
 
59
  /**
60
- * 🔄 المايسترو الدوري: يفرغ الطابور ويدفع شجرة Git المجمعة كل 15 ثانية
61
  */
62
- setInterval(async () => {
63
- if (actionQueue.length === 0) return;
64
-
65
- // أخذ لقطة من الطابور وتفريغه فوراً لاستقبال العمليات الجديدة
66
- const currentBatch = [...actionQueue];
67
- actionQueue = [];
 
 
 
 
68
 
69
- console.log(`[Sync] جاري معالجة حزمة تحتوي على ${currentBatch.length} عملية مجمعة...`);
 
 
 
 
 
 
 
 
 
 
 
 
70
 
 
 
 
 
71
  try {
72
- // 1. جلب الـ SHA الخاص بآخر الالتزام (Commit) الحالي على الفرع
73
- const { data: refData } = await octokit.git.getRef({
74
  owner: REPO_OWNER,
75
  repo: REPO_NAME,
76
- ref: `heads/${BRANCH}`
 
77
  });
78
- const lastCommitSha = refData.object.sha;
 
 
 
 
79
 
80
- // 2. جلب الـ SHA الخاص بالشجرة الأساسية (Base Tree) المنبثقة من آخر Commit
81
- const { data: commitData } = await octokit.git.getCommit({
82
- owner: REPO_OWNER,
83
- repo: REPO_NAME,
84
- commit_sha: lastCommitSha
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  });
86
- const baseTreeSha = commitData.tree.sha;
87
 
88
- // 3. بناء الشجرة الجديدة (New Tree) عبر دمج العمليات الحالية مع الشجرة الأساسية
89
- // جيثب هنا يتكفل بالإضافات والتعديلات والحجوزات دفعة واحدة بطلب واحد!
 
 
 
90
  const { data: newTreeData } = await octokit.git.createTree({
91
- owner: REPO_OWNER,
92
- repo: REPO_NAME,
93
- base_tree: baseTreeSha,
94
- tree: currentBatch
95
  });
96
 
97
- // 4. إنشاء الالتزام الجديد (New Commit) وربطه بالشجرة الجديدة والالتزام السابق
98
  const { data: newCommitData } = await octokit.git.createCommit({
99
- owner: REPO_OWNER,
100
- repo: REPO_NAME,
101
- message: `🤖 السماء السوداء: تحديث مجمع آلي (${currentBatch.length} ملفات)`,
102
- tree: newTreeData.sha,
103
- parents: [lastCommitSha]
104
  });
105
 
106
- // 5. تحريك إشارة الفرع (Update Reference) لتشير إلى الالتزام الجديد ليرى العالم التحديثات
107
- await octokit.git.updateRef({
108
- owner: REPO_OWNER,
109
- repo: REPO_NAME,
110
- ref: `heads/${BRANCH}`,
111
- sha: newCommitData.sha
112
- });
113
-
114
- console.log(`[Success] تم تحديث جيثب بنجاح بـ Commit واحد مجمع!`);
115
-
116
- // 6. [هنا يمكنك استدعاء دالة إسقاط كاش كلوفلير صامتاً]
117
- // await purgeCloudflareCache();
118
 
119
  } catch (error) {
120
- console.error('[Error] فشلت عملية المزام��ة المجمعة مع جيثب:', error);
121
- // في حال الفشل، نعيد الحزمة إلى الطابور حتى لا تضيع بيانات الفصول
122
- actionQueue = [...currentBatch, ...actionQueue];
123
  }
124
- }, 15000); // تيك تاك.. كل 15 ثانية
125
 
126
- // تشغيل السيرفر والاستماع للمنفذ
127
- app.listen(PORT, () => {
128
- console.log(`سيرفر السماء السوداء يعمل بنجاح على المنفذ ${PORT}`);
129
- });
 
3
  const { Octokit } = require('@octokit/rest');
4
 
5
  const app = express();
6
+ app.use(cors({ origin: '*' }));
7
+ app.use(express.json({ limit: '50mb' })); // لضمان استقبال محتويات الفصول والصور الكبيرة
8
 
 
9
  const PORT = process.env.PORT || 7860;
10
 
11
+ const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
12
+ const REPO_OWNER = 'Nour774';
13
+ const REPO_NAME = 's'; // أو novels-repo
 
 
 
 
 
14
  const BRANCH = 'main';
15
 
16
+ // طابور الذاكرة المؤقت للعمليات الذكية
17
+ let operationQueue = [];
18
+
19
+ // مسار ترحيبي
20
+ app.get('/', (req, res) => res.send('🌌 محرك السماء السوداء الذكي يعمل ومستعد لمعالجة المصفوفات!'));
21
 
22
  /**
23
+ * 1. API إضافة عمل جديد (رواية أو مانجا)
 
24
  */
25
+ app.post('/api/add-novel', (req, res) => {
26
+ const { englishName, arabicName, slug, genres, status, rating, views, author, coverType, coverUrl, uploaderId, uploaderUsername, description } = req.body;
27
+
28
+ operationQueue.push({
29
+ type: 'ADD_NOVEL',
30
+ data: { englishName, arabicName, slug, genres, status, rating, views, author, coverType, coverUrl, uploaderId, uploaderUsername, description }
31
+ });
32
+
33
+ res.status(200).json({ status: 'queued', message: 'جاري إنشاء العمل وتوليد المعرف والفهرسة صامتاً...' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  });
35
 
36
  /**
37
+ * 2. API إضافة فصل جديد لعمل موجود
38
  */
39
+ app.post('/api/add-chapter', (req, res) => {
40
+ const { novelId, chapterNum, chapterTitle, chapterType, contributor, chapterData } = req.body;
41
+
42
+ operationQueue.push({
43
+ type: 'ADD_CHAPTER',
44
+ data: { novelId: parseInt(novelId), chapterNum, chapterTitle, chapterType, contributor, chapterData }
45
+ });
46
+
47
+ res.status(200).json({ status: 'queued', message: 'جاري حقن الفصل وتحديث الفهارس والعدادات...' });
48
+ });
49
 
50
+ /**
51
+ * 3. API التعديل السريع (الحالة أو الاسم)
52
+ */
53
+ app.post('/api/update-info', (req, res) => {
54
+ const { novelId, field, newValue } = req.body; // field قد يكون 'status' أو 'arabicName'.. إلخ
55
+
56
+ operationQueue.push({
57
+ type: 'UPDATE_INFO',
58
+ data: { novelId: parseInt(novelId), field, newValue }
59
+ });
60
+
61
+ res.status(200).json({ status: 'queued', message: 'تم جدولة تعديل البيانات البسيطة...' });
62
+ });
63
 
64
+ /**
65
+ * دالة مساعدة لجلب محتوى ملف نصي مباشرة من جيثب
66
+ */
67
+ async function fetchFileContent(path) {
68
  try {
69
+ const { data } = await octokit.repos.getContent({
 
70
  owner: REPO_OWNER,
71
  repo: REPO_NAME,
72
+ path: path,
73
+ ref: BRANCH
74
  });
75
+ return Buffer.from(data.content, 'base64').toString('utf8');
76
+ } catch (e) {
77
+ return null; // الملف غير موجود بعد
78
+ }
79
+ }
80
 
81
+ /**
82
+ * 🔄 محرك المعالجة والدمج الذكي كل 15 ثانية
83
+ */
84
+ setInterval(async () => {
85
+ if (operationQueue.length === 0) return;
86
+
87
+ const currentOps = [...operationQueue];
88
+ operationQueue = [];
89
+
90
+ console.log(`[Engine] جاري تطبيق ${currentOps.length} عملية ذكية على الفهارس...`);
91
+
92
+ try {
93
+ // حزمة الملفات النهائية التي سنرسلها لـ Git Tree
94
+ let finalTreeChanges = [];
95
+
96
+ // 1. جلب ملف الفهرس الحالي من جيثب وتفكيكه
97
+ let searchIndexContent = await fetchFileContent('novel/search_index.js');
98
+ if (!searchIndexContent) {
99
+ // كود مبدئي إذا كان الملف فارغاً تماماً في البداية
100
+ searchIndexContent = `window.blackSkyConfig = window.blackSkyConfig || {}; window.novelIndex = [];`;
101
+ }
102
+
103
+ // عزل المصفوفة المستهدفة برمجياً للتعامل معها كـ JavaScript حقيقي
104
+ let novelIndexMatch = searchIndexContent.match(/window\.novelIndex\s*=\s*([\s\S]*?);/);
105
+ let novelIndex = novelIndexMatch ? eval(novelIndexMatch[1]) : [];
106
+
107
+ // 2. معالجة العمليات واحدة تلو الأخرى داخل الذاكرة
108
+ for (const op of currentOps) {
109
+ const timestamp = Math.floor(Date.now() / 1000);
110
+
111
+ if (op.type === 'ADD_NOVEL') {
112
+ const d = op.data;
113
+ // توليد الـ ID تلقائياً بناءً على آخر عنصر
114
+ const nextId = novelIndex.length > 0 ? novelIndex[novelIndex.length - 1][0] + 1 : 1;
115
+
116
+ // تحديد قيمة معرف الصورة (رابط كامل أو صيغة فقط)
117
+ const coverValue = d.coverUrl ? d.coverUrl : d.coverType; // 'png' أو 'jpg' أو الرابط الكامل
118
+
119
+ // إضافة السطر الجديد للمصوفة الشاملة
120
+ // [ID, الاسم الإنجليزي, الاسم العربي, Slug, [التصنيفات], الحالة, التقييم, المشاهدات, الـ Timestamp, عدد_الفصول, المؤلف, معرف_الغلاف]
121
+ novelIndex.push([
122
+ nextId, d.englishName, d.arabicName, d.slug, d.genres,
123
+ parseInt(d.status), parseFloat(d.rating), parseInt(d.views),
124
+ timestamp, 0, d.author, coverValue
125
+ ]);
126
+
127
+ // توليد ملف الوصف الفردي الجديد للعمل (novels/ID.js)
128
+ const novelDescriptionContent = `window.currentNovelData = {
129
+ "id": ${nextId},
130
+ "slug": "${d.slug}",
131
+ "uploader_id": ${d.uploaderId},
132
+ "uploader_username": "${d.uploaderUsername}",
133
+ "description": "${d.description.replace(/"/g, '\\"')}",
134
+ "chapters": []
135
+ };`;
136
+ finalTreeChanges.push({
137
+ path: `novels/${nextId}.js`,
138
+ mode: '100644',
139
+ type: 'blob',
140
+ content: novelDescriptionContent
141
+ });
142
+
143
+ } else if (op.type === 'ADD_CHAPTER') {
144
+ const d = op.data;
145
+
146
+ // تحديث ملف وصف الرواية (novels/ID.js)
147
+ let novelDocContent = await fetchFileContent(`novels/${d.novelId}.js`);
148
+ if (novelDocContent) {
149
+ let docMatch = novelDocContent.match(/window\.currentNovelData\s*=\s*([\s\S]*?);/);
150
+ if (docMatch) {
151
+ let novelData = eval(`(${docMatch[1]})`);
152
+
153
+ // إضافة الفصل الجديد للمصفوفة داخل ملف الوصف
154
+ novelData.chapters.push({
155
+ num: d.chapterNum,
156
+ title: d.chapterTitle,
157
+ type: d.chapterType,
158
+ contributor: d.contributor,
159
+ data: d.chapterData // كود الاستدعاء أو الحزمة
160
+ });
161
+
162
+ finalTreeChanges.push({
163
+ path: `novels/${d.novelId}.js`,
164
+ mode: '100644',
165
+ type: 'blob',
166
+ content: `window.currentNovelData = ${JSON.stringify(novelData, null, 2)};`
167
+ });
168
+
169
+ // تحديث عداد الفصول والـ Timestamp في ملف الفهرس العام (search_index.js)
170
+ let row = novelIndex.find(r => r[0] === d.novelId);
171
+ if (row) {
172
+ row[9] = novelData.chapters.length; // تحديث عدد الفصول
173
+ row[8] = timestamp; // تحديث الطابع الزمني لآخر تحديث فصول
174
+ }
175
+ }
176
+ }
177
+
178
+ } else if (op.type === 'UPDATE_INFO') {
179
+ const d = op.data;
180
+ let row = novelIndex.find(r => r[0] === d.novelId);
181
+ if (row) {
182
+ // خريطة لتحديد مكان العمود المراد تعديله في المصفوفة
183
+ const fieldMap = { 'englishName': 1, 'arabicName': 2, 'slug': 3, 'status': 5, 'rating': 6, 'views': 7, 'author': 10 };
184
+ const colIndex = fieldMap[d.field];
185
+ if (colIndex !== undefined) {
186
+ row[colIndex] = d.newValue;
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // 3. إعادة بناء كود ملف الفهرس بالبيانات الجديدة
193
+ // سنحافظ على الهيكل الثابت للتهيئة والـ Config ونستبدل فقط مصفوفة الـ novelIndex
194
+ const headerPart = searchIndexContent.split('window.novelIndex =')[0];
195
+ const updatedSearchIndexContent = `${headerPart}window.novelIndex = ${JSON.stringify(novelIndex, null, 4)};`;
196
+
197
+ finalTreeChanges.push({
198
+ path: 'novel/search_index.js',
199
+ mode: '100644',
200
+ type: 'blob',
201
+ content: updatedSearchIndexContent
202
  });
 
203
 
204
+ // 4. ضخ كل التعديلات الذكية المجمعة إلى جيثب بـ Commit واحد حاسم!
205
+ const { data: refData } = await octokit.git.getRef({ owner: REPO_OWNER, repo: REPO_NAME, ref: `heads/${BRANCH}` });
206
+ const lastCommitSha = refData.object.sha;
207
+ const { data: commitData } = await octokit.git.getCommit({ owner: REPO_OWNER, repo: REPO_NAME, commit_sha: lastCommitSha });
208
+
209
  const { data: newTreeData } = await octokit.git.createTree({
210
+ owner: REPO_OWNER, repo: REPO_NAME, base_tree: commitData.tree.sha, tree: finalTreeChanges
 
 
 
211
  });
212
 
 
213
  const { data: newCommitData } = await octokit.git.createCommit({
214
+ owner: REPO_OWNER, repo: REPO_NAME,
215
+ message: `🤖 محرك السماء السوداء: معالجة مصفوفات ودمج آلي (${finalTreeChanges.length} ملفات معدلة)`,
216
+ tree: newTreeData.sha, parents: [lastCommitSha]
 
 
217
  });
218
 
219
+ await octokit.git.updateRef({ owner: REPO_OWNER, repo: REPO_NAME, ref: `heads/${BRANCH}`, sha: newCommitData.sha });
220
+ console.log(`[Success Engine] تم تحديث وفهرسة وتوليد المعرفات للبيانات بنجاح!`);
 
 
 
 
 
 
 
 
 
 
221
 
222
  } catch (error) {
223
+ console.error('[Engine Error] فشلت معالجة المصفوفات الحيوية:', error);
224
+ operationQueue = [...currentOps, ...operationQueue]; // حفظ البيانات من الضياع
 
225
  }
226
+ }, 15000);
227
 
228
+ app.listen(PORT, () => console.log(`المحرك الذكي مستعد على المنفذ ${PORT}`));