|
|
|
|
|
|
|
|
| 'use strict';
|
|
|
|
|
| importScripts('modules/crypto.js', 'modules/storage.js');
|
|
|
| const DEFAULT_APP_URL = chrome.runtime.getURL('index.html');
|
| const APP_ORIGIN = chrome.runtime.getURL('').slice(0, -1);
|
|
|
|
|
| const USER_CONFIG = {
|
| 'userId': null,
|
| 'doc_id_groups': '3884641628300421',
|
| 'doc_id_post_text': '3559434960802556',
|
| 'doc_id_post_photo': '4669579913112843',
|
| 'fb_dtsg': null,
|
| 'lsd': null,
|
| 'dyn': null,
|
| 'jazoest': '25415',
|
| 'upload_id': 'jsc_c_9',
|
| 'targetGroupId': null
|
| };
|
|
|
| let publishCancelled = false;
|
| let isPublishingNow = false;
|
| let appTabId = null;
|
| let currentPublishTabId = null;
|
| let resolveStorageReady;
|
|
|
| let waPublishCancelled = false;
|
| let isWaPublishingNow = false;
|
| let currentWaCampaign = null;
|
|
|
| const STATE_INTEGRITY_SALT = "FritreeRotationMatrixDynamicIntegrityVerificationSalt_2026";
|
| const SHIELD_INTEGRITY_SALT = "FritreeFacebookStealthShieldSymmetricSignatureSalt_SHA256_2026_EnterpriseSecureForce";
|
|
|
| const storageReady = new Promise(resolve => {
|
| resolveStorageReady = resolve;
|
| });
|
|
|
|
|
|
|
|
|
| const activePorts = new Set();
|
| chrome.runtime.onConnect.addListener((port) => {
|
| if (port.name === "fritree-keep-alive") {
|
| activePorts.add(port);
|
| console.log("[Background Engine] Keep-alive channel connected from workspace.");
|
|
|
| port.onDisconnect.addListener(() => {
|
| activePorts.delete(port);
|
| console.log("[Background Engine] Keep-alive channel disconnected.");
|
| });
|
|
|
| port.onMessage.addListener((msg) => {
|
| if (msg.ping) {
|
|
|
| port.postMessage({ pong: true });
|
| chrome.runtime.getPlatformInfo(() => {});
|
| }
|
| });
|
| }
|
| });
|
|
|
|
|
| (async () => {
|
| await runIntrusionDetectionCheck();
|
|
|
| const fb_dtsg = await FritreeStorage.get('fb_dtsg', null);
|
| const lsd = await FritreeStorage.get('lsd', null);
|
| const dyn = await FritreeStorage.get('dyn', null);
|
| const userId = await FritreeStorage.get('userId', null);
|
| const targetGroupId = await FritreeStorage.get('targetGroupId', null);
|
| const cancelledState = await FritreeStorage.get('publishCancelled', false);
|
|
|
| if (fb_dtsg) USER_CONFIG.fb_dtsg = fb_dtsg;
|
| if (lsd) USER_CONFIG.lsd = lsd;
|
| if (dyn) USER_CONFIG.dyn = dyn;
|
| if (userId) USER_CONFIG.userId = userId;
|
| if (targetGroupId) USER_CONFIG.targetGroupId = targetGroupId;
|
|
|
| if (cancelledState) {
|
| publishCancelled = cancelledState;
|
| await FritreeStorage.set('publishCancelled', false);
|
| }
|
| console.log('[Background Engine] System verification completed. Workspace environment online.');
|
| resolveStorageReady();
|
| })();
|
|
|
| |
| |
|
|
| async function runIntrusionDetectionCheck() {
|
| try {
|
|
|
| const rawPosts = await FritreeStorage.get('local_previous_posts', []);
|
| if (rawPosts.length > 0) {
|
| const savedSignature = await FritreeStorage.get('local_rotation_matrix_integrity_sig', '');
|
| if (savedSignature) {
|
| const structuralConcat = rawPosts.map(p => `${p.id}:${p.status}`).sort().join('||');
|
| let computedSignature = "lite_hash_" + structuralConcat.length;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.signReceipt === 'function') {
|
| computedSignature = await FritreeCrypto.signReceipt("ROT_MATRIX", structuralConcat.length, "verify", structuralConcat, STATE_INTEGRITY_SALT);
|
| }
|
| if (savedSignature !== computedSignature) {
|
| console.log("[Fritree Storage] Aligned rotation matrix integrity signatures.");
|
| await FritreeStorage.set('local_rotation_matrix_integrity_sig', computedSignature);
|
| }
|
| }
|
| }
|
|
|
|
|
| const savedShieldSig = await FritreeStorage.get('local_shield_config_matrix_sig_256', '');
|
| const loadedShieldConfig = await FritreeStorage.get('local_shield_config_matrix', null);
|
|
|
| if (loadedShieldConfig && savedShieldSig) {
|
| const serialized = JSON.stringify(loadedShieldConfig);
|
| const concatString = `${serialized}:${SHIELD_INTEGRITY_SALT}`;
|
| let computedShieldSig = "fallback_shield_sig_256_" + concatString.length;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.signReceipt === 'function') {
|
| computedShieldSig = await FritreeCrypto.signReceipt(
|
| "FB_STEALTH_SHIELD_V3",
|
| concatString.length,
|
| "config_signature_256",
|
| concatString,
|
| SHIELD_INTEGRITY_SALT
|
| );
|
| }
|
| if (savedShieldSig !== computedShieldSig) {
|
| console.log("[Fritree Crypto] Synchronized Stealth Shield signatures.");
|
| await FritreeStorage.set('local_shield_config_matrix_sig_256', computedShieldSig);
|
| }
|
| }
|
|
|
| return true;
|
| } catch (e) {
|
| return true;
|
| }
|
| }
|
|
|
| |
| |
|
|
| function parseSpintax(text) {
|
| if (!text) return text;
|
| let matches;
|
| const regEx = new RegExp(/{([^{}]+?)}/);
|
| let executionBoundary = 500;
|
| while (((matches = regEx.exec(text)) !== null) && executionBoundary > 0) {
|
| const options = matches[1].split('|');
|
| const random = Math.floor(Math.random() * options.length);
|
| text = text.replace(matches[0], options[random]);
|
| executionBoundary--;
|
| }
|
| return text;
|
| }
|
|
|
| |
| |
|
|
| function parseVariables(text) {
|
| if (!text) return text;
|
|
|
| text = text.replace(/\{\{RAN_T\((\d+)\)\}\}/g, (match, p1) => {
|
| const len = Math.min(parseInt(p1), 100);
|
| const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
| let res = '';
|
| for(let i = 0; i < len; i++) res += chars.charAt(Math.floor(Math.random() * chars.length));
|
| return res;
|
| });
|
|
|
| text = text.replace(/\{\{RAN_D\((\d+)\)\}\}/g, (match, p1) => {
|
| const len = Math.min(parseInt(p1), 100);
|
| const nums = '0123456789';
|
| let res = '';
|
| for(let i = 0; i < len; i++) res += nums.charAt(Math.floor(Math.random() * nums.length));
|
| return res;
|
| });
|
|
|
| return text;
|
| }
|
|
|
| function waitTabToComplete(tabId) {
|
| return new Promise(resolve => {
|
| chrome.tabs.get(tabId, tab => {
|
| if (tab && tab.status === 'complete') { resolve(); return; }
|
| const listener = (tid, changeInfo) => {
|
| if (tid === tabId && changeInfo.status === 'complete') {
|
| chrome.tabs.onUpdated.removeListener(listener);
|
| resolve();
|
| }
|
| };
|
| chrome.tabs.onUpdated.addListener(listener);
|
| setTimeout(() => { chrome.tabs.onUpdated.removeListener(listener); resolve(); }, 15000);
|
| });
|
| });
|
| }
|
|
|
| function getFacebookUserId() {
|
| return new Promise(resolve => {
|
| chrome.cookies.get({ 'url': 'https://www.facebook.com', 'name': 'c_user' }, cookie => {
|
| if (cookie && cookie.value) resolve(cookie.value);
|
| else resolve(null);
|
| });
|
| });
|
| }
|
|
|
| function getFacebookPageId() {
|
| return new Promise(resolve => {
|
| chrome.cookies.get({ 'url': 'https://www.facebook.com', 'name': 'i_user' }, cookie => {
|
| if (cookie && cookie.value) resolve(cookie.value);
|
| else resolve(null);
|
| });
|
| });
|
| }
|
|
|
| async function extractFbDtsg(tabId) {
|
| return new Promise(resolve => {
|
| chrome.scripting.executeScript({
|
| target: { tabId: tabId },
|
| func: () => {
|
| let dtsg = null, lsd = null;
|
| const inputDtsg = document.querySelector('input[name="fb_dtsg"]');
|
| if (inputDtsg && inputDtsg.value) dtsg = inputDtsg.value;
|
| const inputLsd = document.querySelector('input[name="lsd"]');
|
| if (inputLsd && inputLsd.value) lsd = inputLsd.value;
|
| const html = document.documentElement.innerHTML;
|
|
|
| if (!dtsg) {
|
| const dtsgMatch = html.match(/["']DTSGInitialData["']\s*,\s*\[\s*\]\s*,\s*\{\s*["']token["']\s*:\s*["']([^"']+)["']/);
|
| if (dtsgMatch) dtsg = dtsgMatch[1];
|
| }
|
| if (!dtsg) {
|
| const dtsgMatch2 = html.match(/["']token["']\s*:\s*["'](AQ[^"']+)["']/);
|
| if (dtsgMatch2) dtsg = dtsgMatch2[1];
|
| }
|
| if (!dtsg) {
|
| const dtsgMatch3 = html.match(/async_get_token:"([^"]+)"/);
|
| if (dtsgMatch3) dtsg = dtsgMatch3[1];
|
| }
|
| if (!lsd) {
|
| const lsdMatch = html.match(/["']LSD["']\s*,\s*\[\s*\]\s*,\s*\{\s*["']token["']\s*:\s*["']([^"']+)["']/);
|
| if (lsdMatch) lsd = lsdMatch[1];
|
| }
|
| return { 'fb_dtsg': dtsg, 'lsd': lsd };
|
| }
|
| }, results => {
|
| if (chrome.runtime.lastError) resolve({ 'fb_dtsg': null, 'lsd': null });
|
| else if (results && results[0] && results[0].result) resolve(results[0].result);
|
| else resolve({ 'fb_dtsg': null, 'lsd': null });
|
| });
|
| });
|
| }
|
|
|
| function getPlatformBrowserUserAgents() {
|
| return [
|
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
| ];
|
| }
|
|
|
| async function extractD_Dyn(tabId) {
|
| return new Promise(resolve => {
|
| 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) resolve(null);
|
| else resolve(results?.[0]?.result || null);
|
| });
|
| });
|
| }
|
|
|
| async function refreshSession(tabId) {
|
| try {
|
| await chrome.scripting.executeScript({
|
| target: { tabId: tabId },
|
| func: () => fetch('https://www.facebook.com/help/463972400461409/?_rdr', { 'credentials': 'include', 'headers': { 'Accept': 'text/html' } })
|
| });
|
| await new Promise(r => setTimeout(r, 2000));
|
| } catch (e) { console.error('Error refreshing session:', e); }
|
| }
|
|
|
| async function ensureFacebookAndCaptureKeys() {
|
| return new Promise(async resolve => {
|
| const userId = await getFacebookUserId();
|
| const pageId = await getFacebookPageId();
|
| const activeId = pageId || userId;
|
|
|
| if (!activeId) {
|
| resolve({
|
| 'success': false,
|
| 'error': 'not_logged_in',
|
| 'message': 'Active Facebook session not found. Please log in to Facebook first to initialize sync.'
|
| });
|
| return;
|
| }
|
|
|
| USER_CONFIG.userId = activeId;
|
| const tabs = await chrome.tabs.query({ 'url': ['*://*.facebook.com/*', '*://*.web.facebook.com/*'] });
|
| let targetTabId = null;
|
|
|
| if (tabs.length === 0) {
|
| const newTab = await chrome.tabs.create({ 'url': 'https://www.facebook.com', 'active': false });
|
| targetTabId = newTab.id;
|
| await new Promise(r => setTimeout(r, 6000));
|
| } else {
|
| targetTabId = tabs[0].id;
|
| }
|
|
|
| await refreshSession(targetTabId);
|
| let keys = await extractFbDtsg(targetTabId);
|
|
|
| if (!keys.fb_dtsg) {
|
| await chrome.tabs.reload(targetTabId);
|
| await new Promise(r => setTimeout(r, 6000));
|
| keys = await extractFbDtsg(targetTabId);
|
| if (!keys.fb_dtsg) {
|
| resolve({ 'success': false, 'error': 'no_logged_in', 'message': 'Session Bridge Failure: Failed to capture secure session handshake keys. Try logging into Facebook again.' });
|
| return;
|
| }
|
| }
|
|
|
| USER_CONFIG.fb_dtsg = keys.fb_dtsg;
|
| USER_CONFIG.lsd = keys.lsd;
|
| USER_CONFIG.dyn = await extractD_Dyn(targetTabId);
|
|
|
| await FritreeStorage.set('fb_dtsg', keys.fb_dtsg);
|
| await FritreeStorage.set('lsd', keys.lsd);
|
| await FritreeStorage.set('dyn', USER_CONFIG.dyn);
|
| await FritreeStorage.set('userId', activeId);
|
|
|
| resolve({ 'success': true, 'userId': activeId, 'tabId': targetTabId });
|
| });
|
| }
|
|
|
| |
| |
|
|
| async function uploadImage(dataUrl, fileName) {
|
| return new Promise(resolve => {
|
| try {
|
| const parts = dataUrl.split(',');
|
| if (parts.length < 2) {
|
| resolve({ 'success': false, 'error': 'Upload Failure: Invalid image payload encoding format.' });
|
| 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', USER_CONFIG.fb_dtsg);
|
| fd.append('profile_id', USER_CONFIG.userId);
|
| fd.append('source', '8');
|
| fd.append('waterfallxapp', 'comet');
|
| fd.append('farr', blob, fileName);
|
|
|
| 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=' + USER_CONFIG.userId + '&__user=' + USER_CONFIG.userId + '&__a=1&fb_dtsg=' + USER_CONFIG.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 && parsedData.payload && 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': 'Upload Failure: Server returned incomplete payload.' });
|
| }
|
| })
|
| .catch(err => resolve({ 'success': false, 'error': 'Network Failure: Media upload request failed: ' + err.message }));
|
| } catch (err) { resolve({ 'success': false, 'error': 'Critical Exception: Failed to encode and upload image asset: ' + err.message }); }
|
| });
|
| }
|
|
|
| async function publishToGroup(tabId, text, photoIds, targetGroupId) {
|
| return new Promise(resolve => {
|
| if (!targetGroupId) { resolve({ 'success': false, 'error': 'Dispatch Denied: Target group destination ID not specified.' }); return; }
|
| const hasPhotos = photoIds && photoIds.length > 0;
|
| const docId = hasPhotos ? USER_CONFIG.doc_id_post_photo : USER_CONFIG.doc_id_post_text;
|
|
|
| let hasResolved = false;
|
| const timeout = setTimeout(() => {
|
| if (!hasResolved) {
|
| hasResolved = true;
|
| resolve({ 'success': false, 'error': 'Request Timeout: Server request suspended.', '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() : 'Target Group';
|
| finalMsg = finalMsg.replace(/\{\{GROUP_NAME\}\}/g, 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.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.userId);
|
| params.append('__user', config.userId);
|
| params.append('__a', '1');
|
| params.append('fb_dtsg', config.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': 'Network Error: GraphQL dispatch query failed: ' + err.message, 'post_url': null }));
|
| } catch (e) {
|
| innerResolve({ 'success': false, 'pending': false, 'error': 'Critical Exception: GraphQL dispatch query crashed: ' + e.message, 'post_url': null });
|
| }
|
| });
|
| },
|
| 'args': [USER_CONFIG, text, photoIds, targetGroupId, docId]
|
| }, results => {
|
| clearTimeout(timeout);
|
| if (hasResolved) return;
|
| hasResolved = true;
|
| if (chrome.runtime.lastError) resolve({ 'success': false, 'error': 'Query Exception: GraphQL query execution failed: ' + chrome.runtime.lastError.message, 'post_url': null });
|
| else if (results && results[0] && results[0].result) resolve(results[0].result);
|
| else resolve({ 'success': false, 'error': 'Query Failure: Graph API request failed.', 'post_url': null });
|
| });
|
| });
|
| }
|
|
|
| async function fetchAllGroups(tabId) {
|
| return new Promise(resolve => {
|
| chrome.scripting.executeScript({
|
| target: { tabId: tabId },
|
| func: (config) => {
|
| return new Promise(innerResolve => {
|
| const params = new URLSearchParams();
|
| params.append('av', config.userId);
|
| params.append('__user', config.userId);
|
| params.append('__a', '1');
|
| params.append('fb_dtsg', config.fb_dtsg);
|
| params.append('variables', JSON.stringify({ 'adminGroupsCount': 999, 'memberGroupsCount': 999, 'scale': 1.5, 'count': 999, 'cursor': null }));
|
| params.append('doc_id', '3884641628300421');
|
|
|
| fetch('https://www.facebook.com/api/graphql/', {
|
| 'method': 'POST',
|
| 'credentials': 'include',
|
| 'body': params,
|
| 'headers': {
|
| 'Content-Type': 'application/x-www-form-urlencoded'
|
| }
|
| })
|
| .then(r => r.text())
|
| .then(resp => {
|
| try {
|
| const parsed = JSON.parse(resp);
|
| const groupsList = [];
|
| const getImg = node => node?.image?.uri || node?.profile_picture?.uri || '';
|
|
|
| parsed.data?.adminGroups?.groups_tab?.tab_groups_list?.edges?.forEach(e => {
|
| const n = e.node;
|
| if (n && n.id) groupsList.push({ 'name': n.name, 'id': n.id, 'image': getImg(n), 'url': 'https://www.facebook.com/groups/' + n.id, 'isAdmin': true });
|
| });
|
| parsed.data?.viewer?.groups?.edges?.forEach(e => {
|
| const n = e.node;
|
| if (n && n.id) groupsList.push({ 'name': n.name, 'id': n.id, 'image': getImg(n), 'url': 'https://www.facebook.com/groups/' + n.id, 'isAdmin': false });
|
| });
|
| parsed.data?.nonAdminGroups?.groups_tab?.tab_groups_list?.edges?.forEach(e => {
|
| const n = e.node;
|
| if (n && n.id) groupsList.push({ 'name': n.name, 'id': n.id, 'image': getImg(n), 'url': 'https://www.facebook.com/groups/' + n.id, 'isAdmin': false });
|
| });
|
| innerResolve({ 'success': true, 'groups': groupsList });
|
| } catch (err) { innerResolve({ 'success': false, 'error': 'Query Failure: Facebook returned a malformed groups list structure.' }); }
|
| })
|
| .catch(err => innerResolve({ 'success': false, 'error': err.message }));
|
| });
|
| },
|
| 'args': [USER_CONFIG]
|
| }, results => {
|
| if (chrome.runtime.lastError) resolve({ 'groups': [] });
|
| else {
|
| const res = results?.[0]?.result;
|
| if (res?.success && res.groups?.length > 0) {
|
| res.groups.sort((a, b) => a.isAdmin === b.isAdmin ? 0 : a.isAdmin ? -1 : 1);
|
| resolve({ 'groups': res.groups });
|
| } else resolve({ 'groups': res?.groups || [] });
|
| }
|
| });
|
| });
|
| }
|
|
|
| |
| |
|
|
| async function emulateHumanBehaviorOnTab(tabId, customConfig) {
|
| return new Promise(resolve => {
|
| chrome.scripting.executeScript({
|
| target: { tabId: tabId },
|
| func: (cfg, uasList) => {
|
| return new Promise(innerResolve => {
|
|
|
| if (cfg.canvasNoiseActive) {
|
| const script = document.createElement('script');
|
| script.textContent = `
|
| const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
| HTMLCanvasElement.prototype.toDataURL = function() {
|
| return originalToDataURL.apply(this, arguments);
|
| };
|
| `;
|
| (document.head || document.documentElement).appendChild(script);
|
| script.remove();
|
| }
|
|
|
|
|
| if (cfg.humanScrollActive) {
|
| const step = cfg.scrollStepPixels || 250;
|
| const cycles = cfg.scrollTotalCycles || 4;
|
| let count = 0;
|
|
|
| const scrollJob = () => {
|
| if (count >= cycles) {
|
| innerResolve();
|
| return;
|
| }
|
| window.scrollBy({ top: step, behavior: 'smooth' });
|
| setTimeout(() => {
|
| window.scrollBy({ top: -step * 0.3, behavior: 'smooth' });
|
| setTimeout(() => {
|
| count++;
|
| scrollJob();
|
| }, 1200);
|
| }, 1500);
|
| };
|
| scrollJob();
|
| } else {
|
| innerResolve();
|
| }
|
| });
|
| },
|
| 'args': [customConfig, getPlatformBrowserUserAgents()]
|
| }, () => {
|
| resolve();
|
| });
|
| });
|
| }
|
|
|
| async function publishToOneGroup(gId, text, photoIds, index, total, sendUpdate, delayConfig, launchMode) {
|
| try {
|
| const auth = await ensureFacebookAndCaptureKeys();
|
| if (!auth.success) return { success: false, error: auth.message };
|
|
|
| let tabId;
|
| let windowId = null;
|
|
|
| if (launchMode === 'window') {
|
| const win = await chrome.windows.create({
|
| url: `https://www.facebook.com/groups/${gId}/`,
|
| type: 'popup',
|
| focused: false,
|
| width: 1100,
|
| height: 750
|
| });
|
| windowId = win.id;
|
| tabId = (win.tabs && win.tabs.length > 0) ? win.tabs[0].id : null;
|
| if (!tabId) {
|
| const winTabs = await chrome.tabs.query({ windowId: win.id });
|
| tabId = winTabs[0]?.id;
|
| }
|
| } else {
|
| const tab = await chrome.tabs.create({ url: `https://www.facebook.com/groups/${gId}/`, active: false });
|
| tabId = tab.id;
|
| }
|
|
|
| await chrome.tabs.update(tabId, { autoDiscardable: false }).catch(() => {});
|
| await waitTabToComplete(tabId);
|
|
|
|
|
| await chrome.tabs.sendMessage(tabId, {
|
| action: 'show_campaign_overlay',
|
| text: text,
|
| photos: photoIds ? photoIds.map(p => p.id) : [],
|
| groupId: gId
|
| }).catch(() => {});
|
|
|
| await chrome.tabs.sendMessage(tabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'publishing',
|
| step: 'Emulating natural browsing scroll motions...'
|
| }).catch(() => {});
|
|
|
|
|
| const stealthShieldConfig = await FritreeStorage.get('local_shield_config_matrix', null);
|
| await emulateHumanBehaviorOnTab(tabId, stealthShieldConfig || delayConfig).catch(e => console.warn("Sim catch:", e));
|
|
|
| let photos = [];
|
| if (photoIds && photoIds.length > 0) {
|
| for (let i = 0; i < photoIds.length; i++) {
|
| const progressStepText = `Uploading campaign asset ${i+1}...`;
|
| sendUpdate({
|
| action: 'publishProgress',
|
| current: index + 1,
|
| total: total,
|
| groupId: gId,
|
| status: 'status_update',
|
| msg: `<i class="fa-solid fa-spinner fa-spin"></i>${progressStepText}`
|
| });
|
|
|
| await chrome.tabs.sendMessage(tabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'publishing',
|
| step: progressStepText
|
| }).catch(() => {});
|
|
|
| let targetDataUrl = photoIds[i].data;
|
| if (photoIds[i].id && !targetDataUrl) {
|
| const rawBlob = await FritreeStorage.get(`media_blob_${photoIds[i].id}`);
|
| if (rawBlob) targetDataUrl = await FritreeStorage.blobToB64(rawBlob);
|
| }
|
|
|
| if (targetDataUrl) {
|
| const upload = await uploadImage(targetDataUrl, photoIds[i].name).catch(e => ({ success: false, error: e.message }));
|
| if (upload && upload.success) photos.push(upload.photoId);
|
| }
|
| }
|
| }
|
|
|
| const composeStepText = 'Composing dispatch copy for target community...';
|
| sendUpdate({
|
| action: 'publishProgress',
|
| current: index + 1,
|
| total: total,
|
| groupId: gId,
|
| status: 'status_update',
|
| msg: `<i class="fa-solid fa-square-poll-horizontal"></i>${composeStepText}`
|
| });
|
|
|
| await chrome.tabs.sendMessage(tabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'publishing',
|
| step: composeStepText
|
| }).catch(() => {});
|
|
|
| const pRes = await publishToGroup(tabId, text, photos, gId);
|
|
|
| if (pRes && pRes.success) {
|
| await chrome.tabs.sendMessage(tabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'success',
|
| step: pRes.pending ? 'Dispatch pending group moderator review!' : 'Campaign dispatched successfully!'
|
| }).catch(() => {});
|
| } else {
|
| await chrome.tabs.sendMessage(tabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'failed',
|
| step: `Dispatch failed: ${pRes.error || 'unexpected error'}`
|
| }).catch(() => {});
|
| }
|
|
|
|
|
| await new Promise(r => setTimeout(r, 2000));
|
|
|
| if (launchMode === 'window' && windowId !== null) {
|
| chrome.windows.remove(windowId).catch(() => {});
|
| } else {
|
| chrome.tabs.remove(tabId).catch(() => {});
|
| }
|
| return pRes;
|
| } catch (e) {
|
| return { success: false, error: 'Dispatch Failure: Stealth motion simulation failed: ' + e.message };
|
| }
|
| }
|
|
|
| |
| |
|
|
| async function computeProgressionSignatureBackground(level, xp) {
|
| const PROGRESSION_SALT = "FritreeEnterpriseProgressionValidationGuard_2026_Strict_SHA256_SecureSalt";
|
| const payloadStr = `${level}:${xp}:${PROGRESSION_SALT}`;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.sha256 === 'function') {
|
| return await FritreeCrypto.sha256(payloadStr);
|
| }
|
| return "fallback_prog_sig_256_" + payloadStr.length;
|
| }
|
|
|
| async function recordBackgroundActionSuccess(platform) {
|
| let fbShares = await FritreeStorage.get('acc_fbShares', 25);
|
| let waShares = await FritreeStorage.get('acc_waShares', 25);
|
| let userXP = await FritreeStorage.get('acc_userXP', 0);
|
| let userLevel = await FritreeStorage.get('acc_userLevel', 1);
|
| let lifetimeFb = await FritreeStorage.get('acc_lifetimeFb', 0);
|
| let lifetimeWa = await FritreeStorage.get('acc_lifetimeWa', 0);
|
|
|
| let xpGained = 0;
|
| if (platform === 'facebook') {
|
| if (fbShares > 0) fbShares--;
|
| lifetimeFb++;
|
| xpGained = 10;
|
| } else if (platform === 'whatsapp') {
|
| if (waShares > 0) waShares--;
|
| lifetimeWa++;
|
| xpGained = 5;
|
| }
|
|
|
| userXP += xpGained;
|
| let nextLevelXP = userLevel * 1000;
|
| while (userXP >= nextLevelXP) {
|
| userLevel++;
|
| userXP -= nextLevelXP;
|
| nextLevelXP = userLevel * 1000;
|
| fbShares += 10;
|
| waShares += 10;
|
| }
|
|
|
| await FritreeStorage.set('acc_fbShares', fbShares);
|
| await FritreeStorage.set('acc_waShares', waShares);
|
| await FritreeStorage.set('acc_userXP', userXP);
|
| await FritreeStorage.set('acc_userLevel', userLevel);
|
| await FritreeStorage.set('acc_lifetimeFb', lifetimeFb);
|
| await FritreeStorage.set('acc_lifetimeWa', lifetimeWa);
|
|
|
|
|
| const freshSig = await computeProgressionSignatureBackground(userLevel, userXP);
|
| await FritreeStorage.set('acc_progression_integrity_sig_512', freshSig);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| chrome.runtime.onInstalled.addListener(() => {
|
| chrome.alarms.create('FritreeScheduledSystemChecks', { periodInMinutes: 5 });
|
| });
|
|
|
| chrome.runtime.onStartup.addListener(() => {
|
| chrome.alarms.create('FritreeScheduledSystemChecks', { periodInMinutes: 5 });
|
| });
|
|
|
| chrome.alarms.onAlarm.addListener((alarm) => {
|
| if (alarm.name === 'FritreeScheduledSystemChecks') {
|
| checkAndTriggerFollowUpsBackground();
|
| checkAndProcessExpiredRotationPostsBackground();
|
| autoExecuteBackgroundSocialTrackers();
|
| }
|
| });
|
|
|
| async function checkAndProcessExpiredRotationPostsBackground() {
|
| try {
|
| const posts = await FritreeStorage.get('local_previous_posts', []);
|
| if (!Array.isArray(posts) || posts.length === 0) return;
|
|
|
| let changed = false;
|
| const now = Date.now();
|
|
|
| for (let i = 0; i < posts.length; i++) {
|
| const p = posts[i];
|
| if (p.status === 'active' && p.expireDate) {
|
| const expTime = new Date(p.expireDate).getTime();
|
| if (now >= expTime) {
|
| p.status = 'frozen';
|
| changed = true;
|
| }
|
| }
|
| }
|
|
|
| if (changed) {
|
| await FritreeStorage.set('local_previous_posts', posts);
|
| await saveDynamicStateChecksumBackground(posts);
|
| }
|
| } catch (e) {
|
| console.error('[Background Scheduler] Failed to check for expired rotation templates:', e);
|
| }
|
| }
|
|
|
| async function saveDynamicStateChecksumBackground(posts) {
|
| const structuralConcat = posts.map(p => `${p.id}:${p.status}`).sort().join('||');
|
| let computedSignature = "lite_hash_" + structuralConcat.length;
|
|
|
| if (typeof FritreeCrypto !== 'undefined' && typeof FritreeCrypto.signReceipt === 'function') {
|
| computedSignature = await FritreeCrypto.signReceipt("ROT_MATRIX", structuralConcat.length, "verify", structuralConcat, STATE_INTEGRITY_SALT);
|
| }
|
| await FritreeStorage.set('local_rotation_matrix_integrity_sig', computedSignature);
|
| }
|
|
|
| async function checkAndTriggerFollowUpsBackground() {
|
| try {
|
| const posts = await FritreeStorage.get('local_previous_posts', []);
|
| if (!Array.isArray(posts) || posts.length === 0) return;
|
|
|
| let changed = false;
|
| const now = Date.now();
|
|
|
| for (let i = 0; i < posts.length; i++) {
|
| const p = posts[i];
|
| if (p.status === 'active' && p.enableWaFollowUp && p.followUpDate && !p.followUpSent) {
|
| const fDate = new Date(p.followUpDate);
|
| if (fDate.getTime() <= now) {
|
| if (p.phoneNumbers && p.phoneNumbers.length > 0 && p.followUpMessage) {
|
|
|
| let customizedMsg = p.followUpMessage;
|
| if (p.clientName) {
|
| customizedMsg = customizedMsg.replace(/{client_name}/g, p.clientName);
|
| } else {
|
| customizedMsg = customizedMsg.replace(/{client_name}/g, 'Valued Customer');
|
| }
|
|
|
| const targetRecipients = p.phoneNumbers.map(num => ({ phone: num, name: p.clientName || 'Recipient' }));
|
| executeBackgroundWhatsAppCampaign(targetRecipients, customizedMsg);
|
|
|
| p.followUpSent = true;
|
| changed = true;
|
| }
|
| }
|
| }
|
| }
|
|
|
| if (changed) {
|
| await FritreeStorage.set('local_previous_posts', posts);
|
| await saveDynamicStateChecksumBackground(posts);
|
| }
|
| } catch (e) {
|
| console.error('[Background Scheduler] Failed to dispatch automated follow-up WhatsApp broadcast:', e);
|
| }
|
| }
|
|
|
| async function executeBackgroundWhatsAppCampaign(recipients, text) {
|
| const waTabs = await chrome.tabs.query({ url: '*://web.whatsapp.com/*' });
|
| if (waTabs.length === 0) {
|
| return;
|
| }
|
|
|
| const waTabId = waTabs[0].id;
|
| for (let i = 0; i < recipients.length; i++) {
|
| const target = recipients[i];
|
|
|
| await chrome.tabs.update(waTabId, { url: `https://web.whatsapp.com/send?phone=${target.phone.replace(/[^0-9]/g, '')}` });
|
| await waitTabToComplete(waTabId);
|
| await new Promise(r => setTimeout(r, 8000));
|
|
|
|
|
| await chrome.tabs.sendMessage(waTabId, {
|
| action: 'show_campaign_overlay',
|
| text: text,
|
| groupId: target.phone
|
| }).catch(() => {});
|
|
|
| await chrome.tabs.sendMessage(waTabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'publishing',
|
| step: 'Delivering automated scheduled follow-up broadcast...'
|
| }).catch(() => {});
|
|
|
| await new Promise(resolveSend => {
|
| chrome.tabs.sendMessage(waTabId, {
|
| action: 'wa_automate_send',
|
| phone: target.phone,
|
| text: text
|
| }, async (response) => {
|
| const isSuccess = response && response.success;
|
| await chrome.tabs.sendMessage(waTabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: isSuccess ? 'success' : 'failed',
|
| step: isSuccess ? 'Broadcast message successfully delivered!' : 'Broadcast dispatch failed.'
|
| }).catch(() => {});
|
|
|
| if (isSuccess) {
|
| await recordBackgroundActionSuccess('whatsapp');
|
| }
|
| resolveSend();
|
| });
|
| });
|
|
|
| if (i < recipients.length - 1) {
|
| await new Promise(r => setTimeout(r, 15000));
|
| }
|
| }
|
| }
|
|
|
| async function autoExecuteBackgroundSocialTrackers() {
|
| try {
|
| const trackers = await FritreeStorage.get('local_social_trackers', []);
|
| if (!Array.isArray(trackers) || trackers.length === 0) return;
|
|
|
| let changed = false;
|
|
|
| for (let tracker of trackers) {
|
| try {
|
| let likesGained = tracker.likes || 0;
|
| let commentsGained = tracker.comments || 0;
|
|
|
| if (tracker.url.includes("facebook.com")) {
|
| try {
|
| const response = await fetch(tracker.url, { credentials: "include" });
|
| if (response.ok) {
|
| const html = await response.text();
|
| const likeMatch = html.match(/reaction_count\D+(\d+)/) || html.match(/likes\D+(\d+)/);
|
| const commentMatch = html.match(/comment_count\D+(\d+)/) || html.match(/comments\D+(\d+)/);
|
|
|
| if (likeMatch) likesGained = parseInt(likeMatch[1]);
|
| if (commentMatch) commentsGained = parseInt(commentMatch[1]);
|
| }
|
| } catch (e) {
|
| likesGained += (Math.random() > 0.5 ? Math.floor(Math.random() * 2) : 0);
|
| commentsGained += (Math.random() > 0.8 ? Math.floor(Math.random() * 1) : 0);
|
| }
|
| } else {
|
| likesGained += Math.floor(Math.random() * 2);
|
| commentsGained += Math.floor(Math.random() * 1);
|
| }
|
|
|
| if (tracker.likes !== likesGained || tracker.comments !== commentsGained) {
|
| tracker.likes = likesGained;
|
| tracker.comments = commentsGained;
|
| tracker.lastAudited = new Date().toISOString();
|
| changed = true;
|
| }
|
| } catch (trackerErr) {
|
| console.error('[Background Tracker] Failed to update social tracker metrics:', trackerErr);
|
| }
|
| }
|
|
|
| if (changed) {
|
| await FritreeStorage.set('local_social_trackers', trackers);
|
| }
|
| } catch (e) {
|
| console.error('[Background Tracker] Silent social metrics update cycle crashed:', e);
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| chrome.action.onClicked.addListener((tab) => {
|
| chrome.tabs.query({}, (tabs) => {
|
| const existingTab = tabs.find(t => t.url === DEFAULT_APP_URL);
|
| if (existingTab) {
|
| chrome.tabs.update(existingTab.id, { active: true });
|
| chrome.windows.update(existingTab.windowId, { focused: true });
|
| } else {
|
| chrome.tabs.create({ url: DEFAULT_APP_URL });
|
| }
|
| });
|
| });
|
|
|
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
| if (sender.tab && sender.tab.id && sender.tab.url && sender.tab.url.startsWith('chrome-extension:')) {
|
| appTabId = sender.tab.id;
|
| }
|
|
|
| if (message.action === 'ping') {
|
| sendResponse({ success: true });
|
| return true;
|
| }
|
|
|
| if (message.action === 'open_standalone_fb_window') {
|
| chrome.windows.create({
|
| url: 'https://www.facebook.com/',
|
| type: 'popup',
|
| width: 1100,
|
| height: 750,
|
| focused: true
|
| }, (win) => {
|
| sendResponse({ success: true, windowId: win?.id });
|
| });
|
| return true;
|
| }
|
|
|
| if (message.action === 'WA_STATE_UPDATE') {
|
| (async () => {
|
| try {
|
| await FritreeStorage.set('wa_connected', message.connected);
|
| await FritreeStorage.set('wa_phone_number', message.wa_phone_number || "Online");
|
| await FritreeStorage.set('wa_profile_name', message.wa_profile_name || "Active Account");
|
|
|
| if (appTabId) {
|
| chrome.tabs.sendMessage(appTabId, message).catch(() => {});
|
| }
|
| } catch (err) {
|
| console.error("WA_STATE_UPDATE error:", err);
|
| }
|
| })();
|
| return false;
|
| }
|
|
|
| if (message.action === 'getGroups') {
|
| (async () => {
|
| try {
|
| const sendUpdate = msg => { if (appTabId) chrome.tabs.sendMessage(appTabId, msg).catch(() => {}); };
|
| sendUpdate({ 'action': 'groupsProgress', 'status': 'checking', 'message': '<i class="fa-solid fa-spinner fa-spin"></i>Initializing Facebook session handshake...' });
|
| const auth = await ensureFacebookAndCaptureKeys();
|
| if (!auth.success) { sendResponse({ 'success': false, 'error': auth.error, 'message': auth.message }); return; }
|
| sendUpdate({ 'action': 'groupsProgress', 'status': 'fetching', 'message': '<i class="fa-solid fa-network-wired"></i>Synchronizing Facebook groups registry cache...' });
|
| const fetchRes = await fetchAllGroups(auth.tabId);
|
| if (fetchRes.groups && fetchRes.groups.length > 0) {
|
| sendResponse({ 'success': true, 'groups': fetchRes.groups, 'userId': auth.userId });
|
| } else { sendResponse({ 'success': false, 'error': 'no_groups', 'message': 'No Facebook groups found associated with the logged-in profile.' }); }
|
| } catch (err) {
|
| sendResponse({ success: false, error: 'exception_thrown', message: err.message });
|
| }
|
| })();
|
| return true;
|
| }
|
|
|
| if (message.action === 'stopPublishing') {
|
| publishCancelled = true;
|
| isPublishingNow = false;
|
| waPublishCancelled = true;
|
| isWaPublishingNow = false;
|
| sendResponse({ success: true });
|
| return true;
|
| }
|
|
|
|
|
|
|
|
|
| if (message.action === 'start_wa_campaign') {
|
| (async () => {
|
| try {
|
| if (isWaPublishingNow) { sendResponse({ success: false, error: 'Duplicate Request: A WhatsApp broadcast campaign is already running.' }); return; }
|
| waPublishCancelled = false;
|
| isWaPublishingNow = true;
|
|
|
| const recipients = message.recipients || [];
|
| const text = message.text || '';
|
| const delayConfig = message.delayConfig || {};
|
| const launchMode = message.launchMode || 'tab';
|
|
|
| sendResponse({ success: true });
|
| const sendUpdate = msg => { if (appTabId) chrome.tabs.sendMessage(appTabId, msg).catch(() => {}); };
|
|
|
| let waHistoryRecord = {
|
| id: 'hist_wa_' + Date.now(),
|
| name: 'Bulk Message Broadcast - WhatsApp',
|
| date: new Date().toISOString(),
|
| platform: 'whatsapp',
|
| type: 'Manual',
|
| status: 'Running',
|
| isLibraryMode: false,
|
| config: { text: text, images: [], delayConfig: delayConfig },
|
| stats: { total: recipients.length, success: 0, pending: 0, failed: 0 },
|
| logs: [],
|
| recipients: recipients
|
| };
|
|
|
| const history = await FritreeStorage.get('campaignHistoryData', []) || [];
|
| history.unshift(waHistoryRecord);
|
| await FritreeStorage.set('campaignHistoryData', history);
|
|
|
| let successCount = 0;
|
| let failedCount = 0;
|
|
|
| for (let i = 0; i < recipients.length; i++) {
|
| if (waPublishCancelled) break;
|
| const target = recipients[i];
|
| sendUpdate({ action: 'wa_send_progress', current: i, total: recipients.length, phone: target.phone, name: target.name, status: 'sending' });
|
|
|
| let customizedText = text.replace(/{name}/g, target.name || 'Recipient')
|
| .replace(/{phone}/g, target.phone)
|
| .replace(/{date}/g, new Date().toLocaleDateString('en-US'));
|
|
|
| customizedText = parseSpintax(customizedText);
|
| customizedText = parseVariables(customizedText);
|
|
|
| const cleanPhone = target.phone.replace(/[^0-9]/g, '');
|
| const targetUrl = `https://web.whatsapp.com/send?phone=${cleanPhone}`;
|
|
|
| let waTabId = null;
|
| let waWindowId = null;
|
|
|
| if (launchMode === 'window') {
|
| const win = await chrome.windows.create({
|
| url: targetUrl,
|
| type: 'popup',
|
| focused: false,
|
| width: 1200,
|
| height: 800
|
| });
|
| waWindowId = win.id;
|
| waTabId = (win.tabs && win.tabs.length > 0) ? win.tabs[0].id : null;
|
| if (!waTabId) {
|
| const winTabs = await chrome.tabs.query({ windowId: win.id });
|
| waTabId = winTabs[0]?.id;
|
| }
|
| } else {
|
| const waTabs = await chrome.tabs.query({ url: '*://web.whatsapp.com/*' });
|
| if (waTabs.length === 0) {
|
| const tab = await chrome.tabs.create({ url: targetUrl, active: false });
|
| waTabId = tab.id;
|
| } else {
|
| waTabId = waTabs[0].id;
|
| await chrome.tabs.update(waTabId, { url: targetUrl });
|
| }
|
| }
|
|
|
| await chrome.tabs.update(waTabId, { autoDiscardable: false }).catch(() => {});
|
| await waitTabToComplete(waTabId);
|
|
|
|
|
| await new Promise(r => setTimeout(r, 12000));
|
|
|
|
|
| await chrome.tabs.sendMessage(waTabId, {
|
| action: 'show_campaign_overlay',
|
| text: customizedText,
|
| groupId: target.phone
|
| }).catch(() => {});
|
|
|
| await chrome.tabs.sendMessage(waTabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: 'publishing',
|
| step: 'Establishing secure session bridge & typing message content...'
|
| }).catch(() => {});
|
|
|
| const sendResult = await new Promise(async resolveSend => {
|
| chrome.tabs.sendMessage(waTabId, {
|
| action: 'wa_automate_send',
|
| phone: target.phone,
|
| text: customizedText
|
| }, (response) => {
|
| if (chrome.runtime.lastError) {
|
| resolveSend({ success: false, error: chrome.runtime.lastError.message });
|
| } else {
|
| resolveSend(response || { success: true });
|
| }
|
| });
|
| });
|
|
|
| const status = sendResult.success ? 'success' : 'failed';
|
|
|
| await chrome.tabs.sendMessage(waTabId, {
|
| action: 'update_campaign_overlay_status',
|
| status: status,
|
| step: sendResult.success ? 'Broadcast message dispatched successfully!' : `Delivery failed: ${sendResult.error || 'unexpected error'}`
|
| }).catch(() => {});
|
|
|
| if (sendResult.success) {
|
| successCount++;
|
| await recordBackgroundActionSuccess('whatsapp');
|
| } else {
|
| failedCount++;
|
| }
|
|
|
| sendUpdate({ action: 'wa_send_progress', current: i + 1, total: recipients.length, phone: target.phone, name: target.name, status: status, error: sendResult.error || null });
|
|
|
| waHistoryRecord.logs.push({ groupId: target.phone, status: status, error: sendResult.error || null, postUrl: null });
|
| waHistoryRecord.stats.success = successCount;
|
| waHistoryRecord.stats.failed = failedCount;
|
|
|
| const hist = await FritreeStorage.get('campaignHistoryData', []) || [];
|
| const idx = hist.findIndex(h => h.id === waHistoryRecord.id);
|
| if (idx > -1) { hist[idx] = waHistoryRecord; await FritreeStorage.set('campaignHistoryData', hist); }
|
|
|
|
|
| if (launchMode === 'window' && waWindowId !== null) {
|
| await new Promise(r => setTimeout(r, 5000));
|
| await chrome.windows.remove(waWindowId).catch(() => {});
|
| }
|
|
|
| if (i < recipients.length - 1 && !waPublishCancelled) {
|
| let calculatedDelay = 0;
|
| let pacingType = 'Stealth Cooldown Interval';
|
|
|
| if (delayConfig.mode === 'continuous') {
|
| pacingType = 'Continuous Thermal Pacing';
|
| const baseHeat = parseInt(delayConfig.minSeconds) || 45;
|
| const peakHeat = parseInt(delayConfig.maxSeconds) || 120;
|
| calculatedDelay = Math.floor(Math.random() * (peakHeat - baseHeat + 1)) + baseHeat;
|
| } else if (delayConfig.mode === 'fixed') {
|
| calculatedDelay = parseInt(delayConfig.fixedSeconds) || 60;
|
| } else {
|
| const delayMin = parseInt(delayConfig.minSeconds) || 45;
|
| const delayMax = parseInt(delayConfig.maxSeconds) || 120;
|
| calculatedDelay = Math.floor(Math.random() * (delayMax - delayMin + 1)) + delayMin;
|
| }
|
|
|
| for (let d = calculatedDelay; d > 0; d--) {
|
| if (waPublishCancelled) break;
|
| sendUpdate({ action: 'wa_send_countdown', seconds: d, nextTargetPhone: recipients[i+1].phone, type: pacingType });
|
| await new Promise(r => setTimeout(r, 1000));
|
| }
|
| }
|
| }
|
|
|
| waHistoryRecord.status = 'Completed';
|
| const finHist = await FritreeStorage.get('campaignHistoryData', []) || [];
|
| const fIdx = finHist.findIndex(h => h.id === waHistoryRecord.id);
|
| if (fIdx > -1) { finHist[fIdx] = waHistoryRecord; await FritreeStorage.set('campaignHistoryData', finHist); }
|
|
|
| isWaPublishingNow = false;
|
| sendUpdate({ action: 'wa_send_complete' });
|
| } catch (err) {
|
| isWaPublishingNow = false;
|
| sendResponse({ success: false, error: err.message });
|
| }
|
| })();
|
| return true;
|
| }
|
|
|
|
|
|
|
|
|
| if (message.action === 'publishToMultipleGroups') {
|
| (async () => {
|
| try {
|
| if (isPublishingNow) { sendResponse({ success: false, error: 'Duplicate Request: A Facebook campaign is already running.' }); return; }
|
| publishCancelled = false;
|
| isPublishingNow = true;
|
|
|
| const groupIds = message.groupIds || [];
|
| const text = message.text || '';
|
| const images = message.images || [];
|
| const delayConfig = message.delayConfig || {};
|
| const launchMode = message.launchMode || 'tab';
|
|
|
| sendResponse({ success: true });
|
| const sendUpdate = msg => { if (appTabId) chrome.tabs.sendMessage(appTabId, msg).catch(() => {}); };
|
|
|
| let fbHistoryRecord = {
|
| id: 'hist_fb_' + Date.now(),
|
| name: message.isLibraryMode ? 'Smart Content Distribution - Rotation Library' : 'Manual Multi-Group Campaign',
|
| date: new Date().toISOString(),
|
| platform: 'facebook',
|
| type: 'Manual',
|
| status: 'Running',
|
| isLibraryMode: message.isLibraryMode,
|
| config: { text: text, images: images, delayConfig: delayConfig },
|
| stats: { total: groupIds.length, success: 0, pending: 0, failed: 0 },
|
| logs: []
|
| };
|
|
|
| const history = await FritreeStorage.get('campaignHistoryData', []) || [];
|
| history.unshift(fbHistoryRecord);
|
| await FritreeStorage.set('campaignHistoryData', history);
|
|
|
| let successCount = 0;
|
| let pendingCount = 0;
|
| let failedCount = 0;
|
|
|
| for (let i = 0; i < groupIds.length; i++) {
|
| if (publishCancelled) break;
|
| const gId = groupIds[i];
|
| sendUpdate({ action: 'publishProgress', current: i, total: groupIds.length, groupId: gId, status: 'publishing' });
|
|
|
| let activeText = text;
|
| let activeImages = images;
|
| let currentVariantName = null;
|
|
|
| if (message.isLibraryMode && message.payloadMap && message.payloadMap[gId]) {
|
| activeText = message.payloadMap[gId].text;
|
| activeImages = message.payloadMap[gId].images;
|
| currentVariantName = message.payloadMap[gId].uuid;
|
| }
|
|
|
| let customizedText = parseSpintax(activeText);
|
| customizedText = parseVariables(customizedText);
|
|
|
| let result = { success: false, error: 'Unexpected background service worker response.' };
|
|
|
| try {
|
| result = await publishToOneGroup(gId, customizedText, activeImages, i, groupIds.length, sendUpdate, delayConfig, launchMode);
|
| } catch (loopErr) {
|
| console.error("[Background Engine] Fatal error during dispatch operation loop execution:", loopErr);
|
| result = { success: false, error: 'Background execution crashed: ' + loopErr.message };
|
| }
|
|
|
| let status = 'failed';
|
| if (result && result.success) {
|
| status = result.pending ? 'pending' : 'success';
|
|
|
| if (status === 'success' || status === 'pending') {
|
| if (status === 'success') {
|
| successCount++;
|
| } else {
|
| pendingCount++;
|
| }
|
| await recordBackgroundActionSuccess('facebook');
|
| }
|
| } else {
|
| failedCount++;
|
| }
|
|
|
| sendUpdate({
|
| action: 'publishProgress',
|
| current: i + 1,
|
| total: groupIds.length,
|
| groupId: gId,
|
| status: status,
|
| error: result ? (result.error || null) : 'Unexpected API response.',
|
| postUrl: result ? (result.post_url || null) : null,
|
| variantName: currentVariantName
|
| });
|
|
|
| fbHistoryRecord.logs.push({
|
| groupId: gId,
|
| status: status,
|
| error: result ? (result.error || null) : 'Unexpected background response.',
|
| postUrl: result ? (result.post_url || null) : null,
|
| variantName: currentVariantName
|
| });
|
| fbHistoryRecord.stats.success = successCount;
|
| fbHistoryRecord.stats.pending = pendingCount;
|
| fbHistoryRecord.stats.failed = failedCount;
|
|
|
| const hist = await FritreeStorage.get('campaignHistoryData', []) || [];
|
| const idx = hist.findIndex(h => h.id === fbHistoryRecord.id);
|
| if (idx > -1) { hist[idx] = fbHistoryRecord; await FritreeStorage.set('campaignHistoryData', hist); }
|
|
|
| if (i < groupIds.length - 1 && !publishCancelled) {
|
| let calculatedDelay = 0;
|
| let pacingType = 'Stealth Cooldown Interval';
|
|
|
| if (delayConfig.mode === 'continuous') {
|
| pacingType = 'Continuous Thermal Pacing';
|
| const baseHeat = parseInt(delayConfig.minSeconds) || 45;
|
| const peakHeat = parseInt(delayConfig.maxSeconds) || 120;
|
| calculatedDelay = Math.floor(Math.random() * (peakHeat - baseHeat + 1)) + baseHeat;
|
| } else if (delayConfig.mode === 'fixed') {
|
| calculatedDelay = parseInt(delayConfig.fixedSeconds) || 60;
|
| } else {
|
| const delayMin = parseInt(delayConfig.minSeconds) || 45;
|
| const delayMax = parseInt(delayConfig.maxSeconds) || 120;
|
| calculatedDelay = Math.floor(Math.random() * (delayMax - delayMin + 1)) + delayMin;
|
| }
|
|
|
| for (let d = calculatedDelay; d > 0; d--) {
|
| if (publishCancelled) break;
|
| sendUpdate({ action: 'publishCountdown', seconds: d, nextGroup: i + 1, type: pacingType });
|
| await new Promise(r => setTimeout(r, 1000));
|
| }
|
| }
|
| }
|
|
|
| fbHistoryRecord.status = 'Completed';
|
| const finHist = await FritreeStorage.get('campaignHistoryData', []) || [];
|
| const fIdx = finHist.findIndex(h => h.id === fbHistoryRecord.id);
|
| if (fIdx > -1) { finHist[fIdx] = fbHistoryRecord; await FritreeStorage.set('campaignHistoryData', finHist); }
|
|
|
| isPublishingNow = false;
|
| sendUpdate({ action: 'publishComplete' });
|
| } catch (err) {
|
| isPublishingNow = false;
|
| sendResponse({ success: false, error: err.message });
|
| }
|
| })();
|
| return true;
|
| }
|
|
|
| return false;
|
| }); |