/** * @fileoverview Facebook Background Service, Full 600+ Groups Deep Harvester & Session Authenticator * @module background/fb-background-service * @description محرك جلب كافة مجموعات فيسبوك بالكامل (All 600+ Joined Groups) عبر التمرير القسري العميق لصفحة joins ومسح الـ Relay Store واستخراج الأسماء والصور الحقيقية 100%. */ 'use strict'; (global => { const FB_CONFIG = { doc_id_groups_shortcuts: '3884641628300421', doc_id_joined_groups_full: '5007421822699884', doc_id_post_text: '3559434960802556', doc_id_post_photo: '4669579913112843', jazoest: '25450', upload_id: 'jsc_c_9', captured: { userId: null, fb_dtsg: null, lsd: null, dyn: null } }; /** * جلب معرّف المستخدم (c_user) من ملفات تعريف الارتباط */ function getFacebookUserId() { return new Promise(resolve => { if (typeof chrome === 'undefined' || !chrome.cookies) { const storedId = global.FritreeStorage ? global.FritreeStorage.get('userId', null) : null; resolve(storedId); return; } const domains = ['.facebook.com', 'facebook.com', 'web.facebook.com', 'm.facebook.com']; let resolved = false; const checkNextDomain = (index) => { if (index >= domains.length) { if (!resolved) { chrome.cookies.getAll({ name: 'c_user' }, allCookies => { const found = (allCookies || []).find(c => c.domain && c.domain.includes('facebook')); resolve(found ? found.value : null); }); } return; } chrome.cookies.getAll({ domain: domains[index], name: 'c_user' }, cookies => { if (cookies && cookies.length > 0 && cookies[0].value) { resolved = true; resolve(cookies[0].value); } else { checkNextDomain(index + 1); } }); }; checkNextDomain(0); }); } function getFacebookPageId() { return new Promise(resolve => { if (typeof chrome === 'undefined' || !chrome.cookies) { resolve(null); return; } chrome.cookies.getAll({ domain: '.facebook.com', name: 'i_user' }, cookies => { if (cookies && cookies.length > 0 && cookies[0].value) { resolve(cookies[0].value); } else { chrome.cookies.get({ url: 'https://www.facebook.com', name: 'i_user' }, cookie => { resolve(cookie && cookie.value ? cookie.value : null); }); } }); }); } async function resolveActiveUserId() { const pageId = await getFacebookPageId(); if (pageId) return pageId; return await getFacebookUserId(); } async function extractFbDtsg(tabId) { return new Promise(resolve => { if (typeof chrome === 'undefined' || !chrome.scripting) { resolve({ fb_dtsg: null, lsd: null }); return; } chrome.scripting.executeScript({ target: { tabId: tabId }, func: () => { let dtsg = null, lsd = null; try { if (window.DTSGInitialData && window.DTSGInitialData.token) dtsg = window.DTSGInitialData.token; if (window.DTSGInitData && window.DTSGInitData.token) dtsg = window.DTSGInitData.token; if (window.LSD && window.LSD.token) lsd = window.LSD.token; } catch (e) {} if (!dtsg) { const inputDtsg = document.querySelector('input[name="fb_dtsg"]') || document.querySelector('[data-dtsg]'); if (inputDtsg) dtsg = inputDtsg.value || inputDtsg.getAttribute('data-dtsg'); } if (!lsd) { const inputLsd = document.querySelector('input[name="lsd"]'); if (inputLsd && inputLsd.value) lsd = inputLsd.value; } const html = document.documentElement.innerHTML; if (!dtsg) { const m = html.match(/["']DTSGInitialData["']\s*,\s*\[\s*\]\s*,\s*\{\s*["']token["']\s*:\s*["']([^"']+)["']/) || html.match(/["']token["']\s*:\s*["'](AQ[^"']+)["']/) || html.match(/["']token["']\s*:\s*["'](NA[^"']+)["']/) || html.match(/async_get_token:"([^"]+)"/) || html.match(/"dtsg"\s*:\s*\{\s*"token"\s*:\s*"([^"]+)"/); if (m) dtsg = m[1]; } if (!lsd) { const lm = html.match(/["']LSD["']\s*,\s*\[\s*\]\s*,\s*\{\s*["']token["']\s*:\s*["']([^"']+)["']/) || html.match(/"lsd"\s*:\s*"([^"]+)"/); if (lm) lsd = lm[1]; } return { fb_dtsg: dtsg, lsd: lsd }; } }, results => { if (chrome.runtime.lastError || !results || !results[0]) { resolve({ fb_dtsg: null, lsd: null }); } else { resolve(results[0].result || { fb_dtsg: null, lsd: null }); } }); }); } async function extractD_Dyn(tabId) { return new Promise(resolve => { if (typeof chrome === 'undefined' || !chrome.scripting) { resolve(null); return; } chrome.scripting.executeScript({ target: { tabId: tabId }, func: () => { const dyn = document.querySelector('input[name="__dyn"]')?.value; if (dyn && dyn.length > 20) return dyn; const html = document.documentElement.innerHTML; const m = html.match(/__dyn\s*:\s*"([^"]+)"/); return m ? m[1] : null; } }, results => { if (chrome.runtime.lastError || !results || !results[0]) { resolve(null); } else { resolve(results[0].result); } }); }); } async function refreshFacebookSession(tabId) { try { await chrome.scripting.executeScript({ target: { tabId: tabId }, func: () => fetch('https://www.facebook.com/groups/joins/', { credentials: 'include', headers: { Accept: 'text/html' } }) }); await new Promise(r => setTimeout(r, 600)); } catch (e) { console.warn('[Fritree Facebook Bg] Session ping note:', e.message); } } async function ensureFacebookAndCaptureKeys() { const activeId = await resolveActiveUserId(); if (!activeId) { return { success: false, error: 'not_logged_in', message: 'لم يتم العثور على جلسة فيسبوك نشطة. سجل دخول لحسابك على فيسبوك في المتصفح أولاً.' }; } FB_CONFIG.captured.userId = activeId; const tabs = await chrome.tabs.query({ url: ['*://*.facebook.com/*', '*://web.facebook.com/*'] }); let targetTabId = null; // فتح صفحة دليل المجموعات الكامل joins مباشرة لضمان تحميل الـ 600 مجموعة if (tabs.length === 0) { const newTab = await chrome.tabs.create({ url: 'https://www.facebook.com/groups/joins/', active: false }); targetTabId = newTab.id; await new Promise(r => setTimeout(r, 3500)); } else { targetTabId = tabs[0].id; const currentTabUrl = tabs[0].url || ''; if (!currentTabUrl.includes('/groups/joins')) { await chrome.tabs.update(targetTabId, { url: 'https://www.facebook.com/groups/joins/' }); await new Promise(r => setTimeout(r, 2500)); } } await refreshFacebookSession(targetTabId); let keys = await extractFbDtsg(targetTabId); if (!keys || !keys.fb_dtsg) { await chrome.tabs.reload(targetTabId); await new Promise(r => setTimeout(r, 3000)); keys = await extractFbDtsg(targetTabId); } if (keys && keys.fb_dtsg) { FB_CONFIG.captured.fb_dtsg = keys.fb_dtsg; FB_CONFIG.captured.lsd = keys.lsd; FB_CONFIG.captured.dyn = await extractD_Dyn(targetTabId); if (global.FritreeStorage) { await global.FritreeStorage.set('fb_dtsg', keys.fb_dtsg); await global.FritreeStorage.set('lsd', keys.lsd); await global.FritreeStorage.set('dyn', FB_CONFIG.captured.dyn); await global.FritreeStorage.set('userId', activeId); } } return { success: true, userId: activeId, tabId: targetTabId }; } /** * محرك السحب القسري الشامل لجميع المجموعات (الـ 600+ كاملة بدون أي استثناء) */ async function fetchAllFacebookGroups(tabId) { return new Promise(resolve => { if (typeof chrome === 'undefined' || !chrome.scripting) { resolve({ success: false, groups: [] }); return; } chrome.scripting.executeScript({ target: { tabId: tabId }, func: (config) => { return new Promise(async (innerResolve) => { const groupsMap = new Map(); function parseFacebookGroupMembersText(text) { if (!text || typeof text !== 'string') return 0; let cleaned = text.replace(/[٠-٩]/g, d => '٠١٢٣٤٥٦٧٨٩'.indexOf(d)); cleaned = cleaned.replace(/٫/g, '.'); let millionMatch = cleaned.match(/([\d.]+)\s*(مليون|m|مليار)/i); if (millionMatch) return Math.round(parseFloat(millionMatch[1]) * 1000000); let thousandMatch = cleaned.match(/([\d.]+)\s*(ألف|الف|آلاف|الاف|k)/i); if (thousandMatch) return Math.round(parseFloat(thousandMatch[1]) * 1000); let plainMatch = cleaned.match(/([\d,.]+)\s*(عضو|اعضاء|أعضاء|من الأعضاء|members|member)/i); if (plainMatch) { const val = parseFloat(plainMatch[1].replace(/,/g, '')); return isNaN(val) ? 0 : Math.round(val); } return 0; } const getImg = node => node?.image?.uri || node?.profile_picture?.uri || node?.group_icon?.uri || node?.custom_icon?.uri || ''; const addGroupToMap = (id, name, img, isAdmin, memberCount) => { if (!id) return; const cleanId = String(id).replace(/[^0-9a-zA-Z._-]/g, ''); const blacklistedTerms = ['feed', 'discover', 'joins', 'create', 'search', 'notifications', 'categories', 'chats', 'saved', 'events', 'members', 'about']; if (blacklistedTerms.includes(cleanId) || cleanId.length < 2) return; const cleanName = (name || '').split('\n')[0].trim(); if (!cleanName || cleanName.startsWith('مجموعة (') || cleanName === cleanId) return; if (!groupsMap.has(cleanId)) { groupsMap.set(cleanId, { id: cleanId, name: cleanName, image: img || '', url: `https://www.facebook.com/groups/${cleanId}`, isAdmin: Boolean(isAdmin), memberCount: parseInt(memberCount, 10) || 0 }); } else { const existing = groupsMap.get(cleanId); if (isAdmin) existing.isAdmin = true; if (!existing.image && img) existing.image = img; if ((!existing.name || existing.name.includes('(')) && cleanName) existing.name = cleanName; if (!existing.memberCount && memberCount) existing.memberCount = parseInt(memberCount, 10); } }; const processEdges = (edges, isAdmin) => { if (!edges || !Array.isArray(edges)) return; edges.forEach(e => { const n = e.node || e; if (n && (n.id || n.group_id)) { const gId = n.id || n.group_id; const gName = n.name || n.title; const mCount = n.group_members?.count || n.members?.count || n.member_count || 0; if (gName) { addGroupToMap(gId, gName, getImg(n), isAdmin, mCount); } } }); }; // 1. المسح الشامل والعميق لكافة سكريبتات الـ JSON في الذاكرة (Relay Modern Cache) try { const scripts = document.querySelectorAll('script[type="application/json"]'); scripts.forEach(s => { const content = s.textContent || ''; if (content.includes('"Group"') || content.includes('"tab_groups_list"') || content.includes('"all_joined_groups"')) { try { const parsed = JSON.parse(content); const scanObject = (obj) => { if (!obj || typeof obj !== 'object') return; if ((obj.__typename === 'Group' || obj.__typename === 'CometGroup') && obj.id && (obj.name || obj.title)) { const mCount = obj.group_members?.count || obj.members?.count || obj.member_count || 0; addGroupToMap(obj.id, obj.name || obj.title, getImg(obj), false, mCount); } if (obj.tab_groups_list?.edges) processEdges(obj.tab_groups_list.edges, false); if (obj.all_joined_groups?.edges) processEdges(obj.all_joined_groups.edges, false); if (obj.viewer?.groups?.edges) processEdges(obj.viewer.groups.edges, false); if (obj.admin_groups?.edges) processEdges(obj.admin_groups.edges, true); Object.values(obj).forEach(val => { if (typeof val === 'object') scanObject(val); }); }; scanObject(parsed); } catch (e) {} } }); } catch (rErr) {} // 2. التمرير القسري المستمر في صفحة دليل المجموعات حتى تحميل الـ 600+ مجموعة بالكامل try { const harvestDOM = () => { const anchors = document.querySelectorAll('a[href*="/groups/"]'); anchors.forEach(a => { const href = a.getAttribute('href') || ''; const match = href.match(/\/groups\/([0-9a-zA-Z._-]+)/); if (match && match[1]) { const gId = match[1].replace(/[^0-9a-zA-Z._-]/g, ''); let groupName = a.innerText?.trim() || a.getAttribute('aria-label') || ''; if (groupName && groupName.length > 1 && !groupName.includes('http') && !groupName.includes('إنشاء')) { const imgEl = a.querySelector('img') || a.closest('div')?.querySelector('img'); const cardText = a.closest('[role="listitem"], [role="article"], div')?.innerText || ''; const mCount = parseFacebookGroupMembersText(cardText); addGroupToMap(gId, groupName, imgEl ? imgEl.src : '', false, mCount); } } }); }; harvestDOM(); // تمرير عميق متتابع حتى نهاية الصفحة let lastHeight = 0; let noChangeCounter = 0; for (let i = 0; i < 40; i++) { window.scrollTo(0, document.body.scrollHeight); await new Promise(r => setTimeout(r, 180)); harvestDOM(); const currentHeight = document.body.scrollHeight; if (currentHeight === lastHeight) { noChangeCounter++; if (noChangeCounter >= 4) break; // تم الوصول لآخر مجموعة بالصفحة } else { noChangeCounter = 0; lastHeight = currentHeight; } } } catch (domErr) {} // 3. استعلام GraphQL بدوران المؤشر لكافة المجموعات if (config.captured.fb_dtsg) { const executeGraphQLQueryLoop = async (docId, queryVariables) => { let hasNextPage = true; let currentCursor = null; let pageCounter = 0; const maxPages = 150; while (hasNextPage && pageCounter < maxPages) { pageCounter++; try { const vars = { ...queryVariables, cursor: currentCursor }; const params = new URLSearchParams(); params.append('av', config.captured.userId); params.append('__user', config.captured.userId); params.append('__a', '1'); params.append('fb_dtsg', config.captured.fb_dtsg); params.append('variables', JSON.stringify(vars)); params.append('doc_id', docId); const r = await fetch('https://www.facebook.com/api/graphql/', { method: 'POST', credentials: 'include', body: params, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); if (r.ok) { const resp = await r.text(); const parsed = JSON.parse(resp); const adminEdges = parsed.data?.adminGroups?.groups_tab?.tab_groups_list?.edges || parsed.data?.viewer?.admin_groups?.edges; const viewerEdges = parsed.data?.viewer?.groups?.edges || parsed.data?.viewer?.all_joined_groups?.edges || parsed.data?.viewer?.joined_groups?.edges; const nonAdminEdges = parsed.data?.nonAdminGroups?.groups_tab?.tab_groups_list?.edges || parsed.data?.viewer?.member_groups?.edges; processEdges(adminEdges, true); processEdges(viewerEdges, false); processEdges(nonAdminEdges, false); const pageInfo = parsed.data?.viewer?.all_joined_groups?.page_info || parsed.data?.viewer?.groups?.page_info || parsed.data?.adminGroups?.groups_tab?.tab_groups_list?.page_info; if (pageInfo && pageInfo.has_next_page && pageInfo.end_cursor && pageInfo.end_cursor !== currentCursor) { currentCursor = pageInfo.end_cursor; } else { hasNextPage = false; } } else { hasNextPage = false; } } catch (err) { hasNextPage = false; } } }; await executeGraphQLQueryLoop(config.doc_id_groups_shortcuts, { adminGroupsCount: 500, memberGroupsCount: 500, scale: 1.5, count: 500 }); } const resultList = Array.from(groupsMap.values()); resultList.sort((a, b) => (a.isAdmin === b.isAdmin ? 0 : (a.isAdmin ? -1 : 1))); console.log(`[Fritree Deep Harvester] Total 100% Real Groups Harvested: ${resultList.length}`); innerResolve({ success: resultList.length > 0, groups: resultList }); }); }, args: [FB_CONFIG] }, results => { if (chrome.runtime.lastError || !results || !results[0]) { resolve({ success: false, groups: [] }); } else { const res = results[0].result; resolve(res || { success: false, groups: [] }); } }); }); } async function uploadFacebookImage(dataUrl, fileName) { return new Promise(resolve => { try { const parts = dataUrl.split(','); if (parts.length < 2) { resolve({ success: false, error: 'صيغة الصورة غير صالحة للرفع.' }); return; } const binary = atob(parts[1]); const mimeType = parts[0].split(':')[1].split(';')[0]; const arrayBuffer = new ArrayBuffer(binary.length); const uint8Array = new Uint8Array(arrayBuffer); for (let i = 0; i < binary.length; i++) { uint8Array[i] = binary.charCodeAt(i); } const blob = new Blob([arrayBuffer], { type: mimeType }); const fd = new FormData(); fd.append('fb_dtsg', FB_CONFIG.captured.fb_dtsg); fd.append('profile_id', FB_CONFIG.captured.userId); fd.append('source', '8'); fd.append('waterfallxapp', 'comet'); fd.append('farr', blob, fileName || 'upload.jpg'); const dynamicUploadId = Math.floor(Math.random() * 1000000000000000).toString(); fd.append('upload_id', dynamicUploadId); fd.append('jazoest', (25000 + Math.floor(Math.random() * 100)).toString()); const uploadUrl = `https://upload.facebook.com/ajax/react_composer/attachments/photo/upload?av=${FB_CONFIG.captured.userId}&__user=${FB_CONFIG.captured.userId}&__a=1&fb_dtsg=${FB_CONFIG.captured.fb_dtsg}`; fetch(uploadUrl, { method: 'POST', credentials: 'include', body: fd, headers: { Accept: '*/*' } }) .then(async r => { const responseText = await r.text(); let photoId = null; try { const cleanText = responseText.replace("for (;;);", "").trim(); const parsedData = JSON.parse(cleanText); if (parsedData?.payload?.photoID) photoId = parsedData.payload.photoID; } catch (jsonErr) { const m1 = responseText.match(/"photoID"\s*:\s*"?(\d+)"?/) || responseText.match(/"fbid"\s*:\s*"?(\d+)"?/); if (m1) photoId = m1[1]; } if (photoId) { resolve({ success: true, photoId: photoId }); } else { resolve({ success: false, error: 'لم تتمكن خوادم فيسبوك من إكمال رفع الصورة.' }); } }) .catch(err => resolve({ success: false, error: 'خطأ شبكة أثناء رفع الصورة: ' + err.message })); } catch (err) { resolve({ success: false, error: 'خطأ في معالجة الصورة: ' + err.message }); } }); } async function publishToGroup(tabId, text, photoIds, targetGroupId) { return new Promise(resolve => { if (!targetGroupId) { resolve({ success: false, error: 'كود المجموعة المستهدفة غير محدد.' }); return; } const hasPhotos = photoIds && photoIds.length > 0; const docId = hasPhotos ? FB_CONFIG.doc_id_post_photo : FB_CONFIG.doc_id_post_text; let hasResolved = false; const timeout = setTimeout(() => { if (!hasResolved) { hasResolved = true; resolve({ success: false, error: 'مهلة الاتصال انتهت: خوادم فيسبوك لم تستجب.', post_url: null }); } }, 30000); chrome.scripting.executeScript({ target: { tabId: tabId }, func: (config, postText, photos, groupId, doc_id) => { return new Promise(innerResolve => { try { let finalMsg = postText || ''; const groupRealName = document.title ? document.title.replace(/\s*[-|\|].*$/, '').trim() : 'المجموعة'; finalMsg = finalMsg.replace(/\{\{\s*GROUP_NAME\s*\}\}/gi, groupRealName); const randomSuffix = '\n\u200B\n\u200B\n\u200B\n\u200B\n\u200B\n' + config.jazoest; const variables = { input: { client_mutation_id: "1", actor_id: config.captured.userId, source: "WWW", audience: { to_id: groupId }, message: { ranges: [], text: finalMsg + randomSuffix }, attachments: photos && photos.length > 0 ? photos.map(id => ({ photo: { id: id } })) : [], inline_activities: [], explicit_place_id: "0", tracking: [null], comment_setting: "ANYONE", closed_discussion: false } }; const params = new URLSearchParams(); params.append('av', config.captured.userId); params.append('__user', config.captured.userId); params.append('__a', '1'); params.append('fb_dtsg', config.captured.fb_dtsg); params.append('variables', JSON.stringify(variables)); params.append('doc_id', doc_id); params.append('jazoest', config.jazoest); fetch('https://www.facebook.com/api/graphql/', { method: 'POST', credentials: 'include', body: params, headers: { Accept: '*/*', 'Content-Type': 'application/x-www-form-urlencoded' } }) .then(r => r.text()) .then(resp => { let storyId = null, permalink = null, postUrl = null; const m1 = resp.match(/["']legacy_story_hideable_id["']\s*:\s*["'](\d+)["']/); if (m1) { storyId = m1[1]; postUrl = `https://www.facebook.com/groups/${groupId}/posts/${storyId}/`; } if (!storyId) { const m2 = resp.match(/["']story_id["']\s*:\s*["']([^"']+)["']/); if (m2) { permalink = m2[1]; postUrl = `https://www.facebook.com/groups/${groupId}/permalink/${permalink}/`; } } const isStoryCreated = resp.includes("story") || resp.includes("story_id"); const isPending = resp.includes('pending_post_footer_title') || resp.includes('pending_post_header') || resp.includes('pending_post_footer'); let success = false, pending = false; if (postUrl) { success = true; pending = false; } else { if (isStoryCreated) { success = true; pending = true; } else if (isPending) { success = false; pending = true; } } innerResolve({ success: success, pending: pending, story_id: storyId || permalink, post_url: postUrl }); }) .catch(err => innerResolve({ success: false, pending: false, error: 'خطأ شبكة: ' + err.message, post_url: null })); } catch (e) { innerResolve({ success: false, pending: false, error: 'خطأ برمجي: ' + e.message, post_url: null }); } }); }, args: [FB_CONFIG, text, photoIds, targetGroupId, docId] }, results => { clearTimeout(timeout); if (hasResolved) return; hasResolved = true; if (chrome.runtime.lastError) { resolve({ success: false, error: 'خطأ: ' + chrome.runtime.lastError.message, post_url: null }); } else if (results && results[0] && results[0].result) { resolve(results[0].result); } else { resolve({ success: false, error: 'لم يتم استلام استجابة صالحة من فيسبوك.', post_url: null }); } }); }); } global.FritreeFacebookBg = { getUserId: getFacebookUserId, getPageId: getFacebookPageId, resolveActiveUserId, ensureSession: ensureFacebookAndCaptureKeys, uploadImage: uploadFacebookImage, publishToGroup, fetchGroups: fetchAllFacebookGroups, refreshSession: refreshFacebookSession, config: FB_CONFIG }; })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);