CODE KÍCH 1
// Nguyên liệu:
// +Via 1: Cầm tkqc có nút call support
// +Via 2: cầm BM
// B1: Kết bạn 2 Via 1 và Via 2 với nhau
// B2: Nhập danh sách tkqc tách, chạy chức năng 1 trên Via 1 (check nút share tkqc sang Via 2)
// B2: Nhập danh sách tkqc share thành công và danh sách BM có trên Via 2, chạy chức năng số 2 trên Via 2 (Nhét tkqc vào BM)
// B3: Nhập tkqc nhét BM thành công ở bước 2, chạy chức năng số 3 trên Via 1 (call support theo ads ID)
// Globals
const v = "maxvia88";
// BỎ QUA CHECK NÚT CALL SUPPORT
const SKIP_CALL_SUPPORT_CHECK = true; // bật/tắt
const FALLBACK_CMS_ID = "834096378082416"; // CMS mặc định của Meta Marketing Pro
const FALLBACK_SHORT_DESC =
"Analyse your ad account performance and Page stats with your Meta Marketing Pro and get strategic recommendations to help drive your ROI.";
// === Fallback slots khi API không trả về lịch ===
const FALLBACK_DATE_ISO = "2025-09-22"; // YYYY-MM-DD
const FALLBACK_TIMES = ["11:15","11:45","12:00","12:30","10:30","11:15","15:30","16:15","17:00"];
const FALLBACK_TZ_OFFSET = "+07:00"; // Asia/Jakarta / Asia/Bangkok
const FALLBACK_DURATION_MIN = 45; // cuộc gọi 45 phút
function buildFallbackSlotsFrom(dateISO = FALLBACK_DATE_ISO,
times = FALLBACK_TIMES,
durationMin = FALLBACK_DURATION_MIN,
tzOffset = FALLBACK_TZ_OFFSET) {
const nowSec = Math.floor(Date.now() / 1000);
const out = [];
for (const hhmm of times) {
const iso = `${dateISO}T${hhmm}:00${tzOffset}`; // ví dụ 2025-08-29T09:00:00+07:00
const startSec = Math.floor(new Date(iso).getTime() / 1000);
const endSec = startSec + durationMin * 60;
if (startSec > nowSec) {
out.push({
time_frame: {
start_time: { timestamp: startSec },
end_time: { timestamp: endSec },
preferred: true,
}
});
}
}
return out;
}
// Try to safely get Facebook context variables
let actor_id, fb_dtsgg, accessToken;
try {
actor_id = require("CurrentUserInitialData")?.USER_ID || "";
fb_dtsgg = require("DTSGInitData")?.token || "";
accessToken = require("WebApiApplication")?.getAccessToken() || "";
} catch (e) {
console.error("Error initializing Facebook context:", e);
}
/**
* Performs an API call with retry logic and error handling
*
* @param {string} url - The API endpoint URL
* @param {Object} options - Fetch options
* @param {number} retries - Number of retry attempts
* @param {function} validateSuccess - Function to validate success response
* @returns {Object} Response with status and data/error
*/
async function apiCall(url, options = {}, retries = 1, validateSuccess = null) {
let lastError = null;
for (let i = 0; i < retries; i++) {
if (window.stopProcessing || window.forceStopAllOperations) {
throw new Error("Operation canceled by user");
}
const localController = new AbortController();
const globalSignal = window.currentAbortController?.signal;
if (globalSignal && typeof globalSignal.addEventListener === 'function') {
const onAbort = () => localController.abort();
globalSignal.addEventListener('abort', onAbort, { once: true });
}
try {
const fetchOptions = { ...options, credentials: 'include', signal: localController.signal };
const startedAt = performance.now();
// fetch + xử lý
const fetchAndProcess = (async () => {
const response = await fetch(url, fetchOptions);
if (response.status === 500) {
return { raced: 'fetch', result: { status: true, data: null } };
}
if (!response.ok) {
return {
raced: 'fetch',
result: { status: false, error: `HTTP error: ${response.status} ${response.statusText}` }
};
}
const data = await response.json();
if (validateSuccess && !validateSuccess(data)) {
const errMsg = data?.errors?.[0]?.description || JSON.stringify(data);
return { raced: 'fetch', result: { status: false, error: errMsg, data } };
}
return { raced: 'fetch', result: { status: true, data } };
})();
// mốc 4.5s
const timeoutPromise = new Promise(resolve =>
setTimeout(() => resolve({ raced: 'timeout' }), 4500)
);
const winner = await Promise.race([fetchAndProcess, timeoutPromise]);
if (winner?.raced === 'timeout') {
// Không abort. Vẫn đợi fetch xong và trả về thành công + tổng thời gian đợi
const final = await fetchAndProcess;
const totalTimeMs = Math.round(performance.now() - startedAt);
const original = final.result;
return {
status: true,
data: original.data ?? null,
longWait: true,
totalTimeMs,
original
};
}
// fetch xong trước 4.5s
const totalTimeMs = Math.round(performance.now() - startedAt);
const { result } = winner;
if (result.status) {
return { ...result, totalTimeMs };
}
lastError = result.error;
} catch (err) {
if (err.name === 'AbortError' || err.message === "Operation canceled by user") {
throw new Error("Operation canceled by user");
}
lastError = err?.message || String(err);
}
if (i < retries - 1) {
const delay = Math.pow(2, i) * 500;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return { status: false, error: lastError };
}
/**
* Schedule a call with the given CMS ID and time frame.
*/
async function makeacall(cms_id, slots, ads_id, short_description = "Analyse your ad account performance and Page stats with your Meta Marketing Pro and get strategic recommendations to help drive your ROI.", sbg_prefill_data, sbg_program_name) {
if (!cms_id || !slots || !ads_id) {
return { status: false, error: "Missing required parameters" };
}
const randomIndex = Math.floor(Math.random() * slots.length);
const time_frame = slots[randomIndex]?.time_frame;
const timeslot_label = getSelectedTimeslotLabel(time_frame.start_time.timestamp)
try {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
if (!actor || !token) {
return { status: false, error: "Missing Facebook context (actor_id or accessToken)" };
}
const timeframe = getRandomTimeframe(time_frame);
const contactInfo = Array.isArray(sbg_prefill_data) ? sbg_prefill_data : [];
const getVal = (i, def) => contactInfo[i]?.value?.length > 5 ? contactInfo[i].value : def;
const name = getVal(2, "Ban Do");
const phone_number = getVal(3, `+197240${randomNumber(5)}`);
const business_name = getVal(0, "Maxvia88 Company");
const email_address = getVal(1, `maxvia88${randomLowercase(5)}@gmail.com`);
const rawJson = {
input: {
client_mutation_id: "1",
actor_id: actor,
channel_type: "SCHEDULE_CALL",
program: "Transactional Incubator",
sub_program: null,
contact: { name, phone_number, business_name, email_address },
source_tracking: {
surface: "ADS_MANAGER",
entry_point: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
lead_source: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET"
},
timeframe: timeframe,
advertiser_id: actor,
advertiser_context: {
agent_title: null,
cms_id: cms_id,
short_description: short_description,
short_title: "Get to know your Meta Marketing Pro",
signal_source_key: "new_to_program",
advertiser_context_id: ads_id
},
ad_account_id: ads_id,
notes: "",
selected_timezone: "Asia/Jakarta",
chat_input: {},
selected_timeslot_label: timeslot_label
}
};
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=BizKitMarketingExpertScheduleCallButtonMutation&doc_id=9512784282180280&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
const result = await apiCall(
url,
{ method: 'GET', credentials: 'include', signal: window.currentAbortController?.signal, headers: { Accept: 'application/json' } },
2,
(data) => data?.data?.sbg_engagement_connection_create?.connection?.connection_details?.status === "SCHEDULED"
);
return { ...result, slot: time_frame, timeslot_label };
} catch (err) {
console.error("Error in makeacall:", err);
return { status: false, error: err.message || "Unknown error in makeacall" };
}
}
function capitalizeWords(str) {
if (!str) return "";
return str
.replace(/_/g, " ") // thay _ bằng khoảng trắng
.toLowerCase() // đưa về thường
.split(" ") // tách theo khoảng trắng
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
/**
* Make a call using Facebook dtsg token instead of access token
*/
async function makeacallfbdt(cms_id = "834096378082416", slots, ads_id, short_description = "Analyse your ad account performance and Page stats with your Meta Marketing Pro and get strategic recommendations to help drive your ROI.", sbg_prefill_data, sbg_program_name) {
const actor = window.actor_id || actor_id || "";
const dtsg = window.fb_dtsgg || fb_dtsgg || "";
if (!cms_id || !slots || !ads_id || !dtsg || !actor) {
return { status: false, error: "Missing required parameters" };
}
const randomIndex = Math.floor(Math.random() * slots.length);
const time_frame = slots[randomIndex]?.time_frame;
const timeslot_label = getSelectedTimeslotLabel(time_frame.start_time.timestamp)
try {
const timeframe = getRandomTimeframe(time_frame);
const contactInfo = Array.isArray(sbg_prefill_data) ? sbg_prefill_data : [];
const getVal = (i, def) => contactInfo[i]?.value?.length > 5 ? contactInfo[i].value : def;
const name = getVal(2, "Ban Do");
const phone_number = getVal(3, `+197240${randomNumber(5)}`);
const business_name = getVal(0, "Maxvia88 Company");
const email_address = getVal(1, `maxvia88${randomLowercase(5)}@gmail.com`);
const rawJson = {
input: {
client_mutation_id: "1",
actor_id: actor,
channel_type: "SCHEDULE_CALL",
program: capitalizeWords(sbg_program_name || "Accelerate Plus"),
sub_program: null,
contact: { name, phone_number, business_name, email_address },
source_tracking: {
surface: "ADS_MANAGER",
entry_point: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
lead_source: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET"
},
timeframe: timeframe,
advertiser_id: actor,
advertiser_context: {
agent_title: "undefined",
cms_id: cms_id,
short_description: short_description,
short_title: "Get to know your Meta Marketing Pro",
signal_source_key: "new_to_program",
advertiser_context_id: ads_id,
},
ad_account_id: ads_id,
notes: "",
selected_timezone: "Asia/Jakarta",
chat_input: {},
selected_timeslot_label: timeslot_label
}
};
// const rawJson = {
// input: {
// client_mutation_id: "1",
// actor_id: actor,
// channel_type: "SCHEDULE_CALL",
// program: "Accelerate Plus",
// sub_program: null,
// contact: { name, phone_number, business_name, email_address },
// source_tracking: {
// surface: "ADS_MANAGER",
// entry_point: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
// lead_source: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET"
// },
// timeframe: timeframe,
// advertiser_id: actor,
// advertiser_context: {
// agent_title: null,
// cms_id: cms_id,
// short_description: short_description,
// short_title: "Get to know your Meta Marketing Pro",
// signal_source_key: "new_to_program",
// advertiser_context_id: ads_id,
// long_description: short_description
// },
// ad_account_id: ads_id,
// notes: "",
// selected_timezone: "Asia/Jakarta",
// chat_input: {},
// selected_timeslot_label: timeslot_label
// }
// };
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://adsmanager.facebook.com/api/graphql/`;
const params = `av=${actor}&__aaid=605774672319432&__user=${actor}&__a=1&__req=2u&__hs=20312.BP%3Aads_manager_comet_pkg.2.0...0&dpr=1&__ccg=UNKNOWN&__rev=1025785809&__s=ci3b3b%3Ado976g%3Atz8q1d&__hsi=7537657817136921378&__dyn=7AgSXgWGgWEjgCu6mudg9omosyUqDBBh96EnK49o9EeUaVoWFGV8kG4VEHoOqqE88lBxeipe9wNWAAzppFuUuGfxW2u5Eiz8WdyU8ryUKrVoS3u7azoV2EK12xqUC8yEScx6bxW7A78O4EgCyku4oS4EWfGUhwyg9p44889EScxyu6UGq13yHGmmUTxJ3rG2PCG9DDl0zlBwyzp8KUWcwxyU29xep3bBAzEW9lpubwIxecAwXzogyo464Xy-cwuEnxaFo5a7EN1O79UCumbz8KiewwBK68eF8pK1Nxebxa4AbxR2V8cE8Q3mbgOUGfgeEmwJCxSegroG48gyHxSi4p8y7rKfxefKaxWi2y2i7Qm4VEhGcx22uexm1fAwLzUS327EG4E945EryrhUK5Ue8Su6Ey3maUjxOfxiFFEC48C9wFjQfyoaoym9yUixi48hyUix6cyopzFEHyU8Uiwj8G1TDV8sw8KmbwVzi1y4fz8coiGQU9Eak49VUWrUlUym5UpwDwXx67HxnwAxx7KAfwxCxSiu5onADzEHxm58G9xq2K3GUixl4wNx5e8wAAAVQEhy8myFUpxCQ48Cq4E8888oAgCi2aawVy8khEkxyEoypopxKU-GoigK6K224kifyohw&__hsdp=gfWsBEeT0l2ezNk4ckNa18AImMX1vOhNkIGXO8aMHtl993Awx8cgug23MaA7cn4IkzNAJFNrwzDflaJoKbwHx6l9Q7FxjglF9ai6k2Iw1jy0xo5O01g9w&__hblp=0Yw_wCyUqxW2SieBx9xq8Km2Gu2S2iUaU9Cm1QzUjOriJePHDNdCQxoGJjNs6YaMx2A9MN4Mhbkj2GNAaR4jPgF2ylsFAhjcR8L9-m8GPzUNVuFoxGF9lG8Dx1pN6z_BAgBJ4LzV7bdghV4OGGCB8K98VoV4AloHnsB8x4mlax94ml7zFudRK4AWz4lm5EzKVelAF2pkpyax0S-WFBhKAm9jgEEmwQxGmmdJ2d0kUWdge2Qii2mip29lO6Gc8WprCKiiEScppAJ3e4pAvTW-mLBDmqvyF8mV8x0mHgGt2KtfCleqbBF3HyLHKEmG-qFV8GfKey8-_x6qt1lohuQrAK49p-ia-bKiu13wDwlEgAzLLFCmaCGQ4aH88V9BzF48rVvGayaixrF-l4QQGRAKmpA-gxkGo2zxfwVJqGtep7F2_l94GbyGglKEG7VScGAbhEtB-qWh8C2e1hyGUgBiV6i9zUy9Bxdaiiex8x-uq9AK-ho99LJeV8LybDChdDxmq584QkUCaAAwTx627GuiELypohK5Hgybx6XLAVpqKHgCWF4iF4UHKFj7xnGmTxjAQF6dhpEjGBlB_BB8mhknt2Xh5eXGGAF688Cz6ngGi-GZ5yep7yWAGdgXhHCKGJ16-y7F4y8ynClGmjuiAheiAjz9GQA9BGAivALCz8tAyGUyvK49pmcyXmmepqAy8x6G-RXquuBHLV9lFxm6uVHiKqHBCAByEFetwFLUW2icAgiyEycGtehaiFKFHyAeAhGgCubK4Vp98WU_BQ6ljDgx-8DVaB-bHGuDGii9hkfXAgoDCgFa9BS67ydBGhaqmuWABAh-Fenl4jvwPFrUhBXXLV9O1IxalvF5ha9jzVHKmF8daHu9UOdnQunuEKWqUGABybyoaGV5BB-qfg450zyu_uEy8hcxk9FKQVV7K9jBDV8DUB4TKjCV6Wijp4ABghCTi8epESLCQmchJ5nRZ7KaSF9p4ayHGoGK7Q8eaFF5jh5CQ5i4cRll-IFHIJqXCRA8ZGiWBmEhQhlpdVFaAlhnRNXF24ua9zk&__comet_req=58&fb_dtsg=${encodeURIComponent(dtsg)}&jazoest=25661&lsd=2tx8xQuV2KJAf9jcGSRzU_&__spin_r=1025785809&__spin_b=trunk&__spin_t=1754997720&__jssesw=1&fb_api_caller_class=RelayModern&fb_api_req_friendly_name=BizKitMarketingExpertScheduleCallButtonMutation&variables=${encodedJson}&server_timestamps=true&doc_id=9512784282180280`;
const result = await apiCall(
url,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: 'application/json' },
body: params,
credentials: "include",
signal: window.currentAbortController?.signal
},
2,
(data) => data?.data?.sbg_engagement_connection_create?.connection?.connection_details?.status === "SCHEDULED"
);
return { ...result, slot: time_frame, timeslot_label };
} catch (err) {
console.error("Error in makeacallfbdt:", err);
return { status: false, error: err.message || "Unknown error in makeacallfbdt" };
}
}
/**
* Get CMS ID and existing connections for scheduling.
*/
async function getidscall(ads_id) {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
if (!ads_id || !actor) {
return { status: false, error: "Missing required parameters (ads_id or actor_id)" };
}
try {
const rawJson = {
input: {
ad_account_id: ads_id,
advertiser_user_id: actor,
instantiated_programs_with_channels: true,
source_tracking: {
entry_point: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
lead_source: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
surface: "ADS_MANAGER"
}
},
shouldUseV1API: true,
isWAChatEnabled: false
};
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=useMetaProEngagementEligibilityQuery&doc_id=9984842741620517&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: { Accept: 'application/json' }
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const existing_connections = data?.data?.sbg_engagement_eligible_programs?.existing_connections;
const cms_id = data?.data?.sbg_engagement_eligible_programs?.instantiated_programs_with_channels?.prioritized_program?.program?.contentv1?.cms_id;
const short_description = data?.data?.sbg_engagement_eligible_programs?.instantiated_programs_with_channels?.prioritized_program?.program?.contentv1?.short_description;
const available_call_ctas = data?.data?.sbg_engagement_eligible_programs?.instantiated_programs_with_channels?.prioritized_program?.program?.contentv1?.available_call_ctas?.[0]?.type === "SCHEDULE_CALL";
if (cms_id && available_call_ctas) {
return { status: true, error: null, cms_id, existing_connections, short_description };
}
return {
status: false,
error: data.errors?.[0]?.description || "No call support available",
errorDetails: data
};
} catch (err) {
console.error("Error in getidscall:", err);
return { status: false, error: err.message || "Unknown error in getidscall" };
}
}
async function getidscallv2(ads_id) {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
if (!ads_id || !actor) {
return { status: false, error: "Missing required parameters (ads_id or actor_id)" };
}
try {
const rawJson = {
"inputData": {
"source_tracking": {
"surface": "ADS_MANAGER",
"entry_point": "OS_START_YOUR_DAY",
"lead_source": "opportunity_score_start_your_day"
},
"advertiser_user_id": actor,
"ad_account_id": ads_id,
}
}
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=MetaProEngagementOSEntryPointQuery&doc_id=9569721913149059&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: { Accept: 'application/json' }
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const programs = data?.data?.sbg_engagement_eligible_programs?.programs || [];
const program0 = programs[0] || {};
const existing_connections = data?.data?.sbg_engagement_eligible_programs?.existing_connections || [];
const cms_id = program0?.content?.cms_id || null;
const sbg_program_name = program0?.sbg_program_name || "";
const short_description = program0?.content?.long_description || program0?.content?.short_title || "";
const available_call_ctas = Array.isArray(program0?.channels)
? program0.channels.some(c => c?.type === "SCHEDULE_CALL")
: false;
if (cms_id && available_call_ctas) {
return { status: true, error: null, cms_id, existing_connections, short_description, sbg_program_name };
}
return {
status: false,
error: data.errors?.[0]?.description || "No call support available",
errorDetails: data
};
} catch (err) {
console.error("Error in getidscall:", err);
return { status: false, error: err.message || "Unknown error in getidscall" };
}
}
async function getcmsid(ads_id) {
const actor = window.actor_id || actor_id || "";
if (!ads_id || !actor) {
return { status: false, error: "Missing required parameters (ads_id or actor_id)" };
}
try {
const url = `https://adsmanager.facebook.com/adsmanager/manage/accounts?act=${ads_id}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8'
}
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const html = await response.text();
// Tìm cms_id trong HTML/inline JSON
const cmsMatch = html.match(/"cms_id"\s*:\s*"?(\d+)"?/);
const descMatch = html.match(/"short_description"\s*:\s*"([^"]*)"/);
const sbg_program_namecMatch = html.match(/"sbg_program_name"\s*:\s*"([^"]*)"/);
const cms_id = cmsMatch?.[1] || null;
const short_description = descMatch?.[1] || "";
const sbg_program_name = sbg_program_namecMatch?.[1] || "";
if (cms_id) {
return { status: true, error: null, cms_id, existing_connections: null, short_description, sbg_program_name };
}
// Fallback: thử lấy qua GraphQL nếu có hàm getidscall
if (typeof getidscall === 'function') {
const fb = await getidscall(ads_id);
if (fb?.status) return fb;
return { status: false, error: fb?.error || "Could not extract cms_id from Ads Manager HTML", errorDetails: fb };
}
return { status: false, error: "Could not extract cms_id from Ads Manager HTML" };
} catch (err) {
console.error("Error in getcmsid:", err);
return { status: false, error: err?.message || "Unknown error in getcmsid" };
}
}
/**
* Get available timeslot for scheduling.
*/
async function gettimeslot(ads_id) {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
if (!ads_id || !actor) {
return { status: false, error: "Missing required parameters (ads_id or actor_id)" };
}
try {
const rawJson = {
inputData: {
source_tracking: {
surface: "ADS_MANAGER",
entry_point: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
lead_source: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET"
},
advertiser_user_id: actor,
ad_account_id: ads_id
}
};
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=MetaProEngagementCalendarModalRootQuery&doc_id=9438056746321540&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: { Accept: 'application/json' }
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const programs = data?.data?.sbg_engagement_eligible_programs?.programs || [];
const program0 = programs[0] || {};
const channels = Array.isArray(program0.channels) ? program0.channels : [];
const scheduleChannels = channels.filter(ch => ch?.type === 'SCHEDULE_CALL');
const slots = scheduleChannels.flatMap(ch =>
(ch?.availabilities || []).flatMap(av => av?.slots || [])
);
const existing_connections = data?.data?.sbg_engagement_eligible_programs?.existing_connections;
let sbg_prefill_data = data?.data?.sbg_prefill_data;
const sbg_program_name = program0?.sbg_program_name;
if (!Array.isArray(slots) || slots.length === 0) {
return {
status: false,
error: "No available time slots",
errorDetails: data
};
}
return {
status: true,
error: null,
slots,
existing_connections,
sbg_prefill_data,
sbg_program_name
};
} catch (err) {
console.error("Error in gettimeslot:", err);
return { status: false, error: err.message || "Unknown error in gettimeslot" };
}
}
/**
* Get available timeslot for scheduling.
*/
async function gettimeslotv2(ads_id) {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
if (!ads_id || !actor) {
return { status: false, error: "Missing required parameters (ads_id or actor_id)" };
}
try {
const rawJson = {
"inputData": {
"source_tracking": {
"surface": "ADS_MANAGER",
"entry_point": "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
"lead_source": "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET"
},
"advertiser_user_id": actor,
"ad_account_id": ads_id
},
"fields": [
"EMAIL",
"FULL_NAME",
"PHONE"
],
"advertiserContext": {
"ad_account_id": ads_id
}
}
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=MetaProEngagement1ClickModalRootQuery&doc_id=23868284372766223&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: { Accept: 'application/json' }
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const programs = data?.data?.sbg_engagement_eligible_programs?.programs || [];
const program0 = programs[0] || {};
const channels = Array.isArray(program0.channels) ? program0.channels : [];
const scheduleChannels = channels.filter(ch => ch?.type === 'SCHEDULE_CALL');
const slots = scheduleChannels.flatMap(ch =>
(ch?.availabilities || []).flatMap(av => av?.slots || [])
);
const existing_connections = data?.data?.sbg_engagement_eligible_programs?.existing_connections;
let sbg_prefill_data = data?.data?.sbg_prefill_data;
const sbg_program_name = program0?.sbg_program_name;
if (!Array.isArray(slots) || slots.length === 0) {
return {
status: false,
error: "No available time slots",
errorDetails: data
};
}
return {
status: true,
error: null,
existing_connections,
sbg_prefill_data,
slots,
sbg_program_name
};
} catch (err) {
console.error("Error in gettimeslot:", err);
return { status: false, error: err.message || "Unknown error in gettimeslot" };
}
}
/**
* Get available timeslot for scheduling.
*/
async function gettimeslotv3(ads_id) {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
if (!ads_id || !actor) {
return { status: false, error: "Missing required parameters (ads_id or actor_id)" };
}
try {
const rawJson = {
inputData: {
source_tracking: {
surface: "ADS_MANAGER",
entry_point: "ADS_MANAGER_MODAL",
lead_source: "IOS_AdsMngr_CampaignsOverView_EntryPoint"
},
advertiser_user_id: actor,
ad_account_id: ads_id
},
fields: ["EMAIL", "FULL_NAME", "PHONE"],
advertiserContext: {
ad_account_id: ads_id
}
};
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=AdsSBGMEEngagementDetailsDialogQuery&doc_id=24205266979145499&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: { Accept: 'application/json' }
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const programs = data?.data?.sbg_engagement_eligible_programs?.programs || [];
const program0 = programs[0] || {};
const channels = Array.isArray(program0.channels) ? program0.channels : [];
const scheduleChannels = channels.filter(ch => ch?.type === 'SCHEDULE_CALL');
const slots = scheduleChannels.flatMap(ch =>
(ch?.availabilities || []).flatMap(av => av?.slots || [])
);
const existing_connections = data?.data?.sbg_engagement_eligible_programs?.existing_connections;
let sbg_prefill_data = data?.data?.sbg_prefill_data;
const sbg_program_name = program0?.sbg_program_name;
if (!Array.isArray(slots) || slots.length === 0) {
return {
status: false,
error: "No available time slots",
errorDetails: data
};
}
return {
status: true,
error: null,
existing_connections,
sbg_prefill_data,
slots,
sbg_program_name
};
} catch (err) {
console.error("Error in gettimeslot:", err);
return { status: false, error: err.message || "Unknown error in gettimeslot" };
}
}
/**
* Format timeframe for scheduling.
*/
function getRandomTimeframe(time_frame) {
return {
start_time: time_frame.start_time.timestamp,
end_time: time_frame.end_time.timestamp,
preferred: time_frame.preferred || false,
};
}
/**
* Format timeslot label for display.
*/
function getSelectedTimeslotLabel(start_time) {
try {
const date = new Date(Number(start_time) * 1000);
const fmt = new Intl.DateTimeFormat('en-US', {
timeZone: 'Asia/Jakarta',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true
});
const parts = fmt.formatToParts(date);
const map = Object.fromEntries(parts.map(p => [p.type, p.value]));
const sep = '\u202F'; // narrow no-break space
const dayPeriod = (map.dayPeriod || '').toUpperCase();
return `${map.month}/${map.day} ${map.hour}:${map.minute}${sep}${dayPeriod}`.trim();
} catch {
const d = new Date(Number(start_time) * 1000);
const h24 = d.toLocaleString('en-US', { hour: 'numeric', hour12: true, timeZone: 'Asia/Jakarta' });
const hour = ((d.getHours() % 12) || 12);
const minute = String(d.getMinutes()).padStart(2, '0');
const ampm = d.getHours() < 12 ? 'AM' : 'PM';
return `${d.getMonth() + 1}/${d.getDate()} ${hour}:${minute}\u202F${ampm}`;
}
}
/**
* Cancel a scheduled call by connection ID.
*/
async function cancelcall(connection_id) {
const actor = window.actor_id || actor_id || "";
const token = window.accessToken || accessToken || "";
const rawJson = {
input: {
client_mutation_id: "3",
advertiser_user_id: actor,
connection_id: connection_id,
source_tracking: {
surface: "ADS_MANAGER",
entry_point: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET",
lead_source: "ADS_MANAGER_START_YOUR_DAY_MARKETING_EXPERT_WIDGET"
}
}
};
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=BizKitMarketingExpertScheduleCallCancelConfirmationModalMutation&doc_id=9750321601731205&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
try {
const response = await fetch(url, { method: 'GET', credentials: 'include', signal: window.currentAbortController?.signal, headers: { Accept: 'application/json' } });
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const CLOSED = data?.data?.sbg_engagement_connection_cancel?.connection?.connection_details?.status === "CLOSED";
if (CLOSED) {
return { status: true, error: null };
}
return { status: false, error: data.errors?.[0]?.description || data };
} catch (err) {
return { status: false, error: err };
}
}
/**
* Add ad account to BM and log result using fetch.
*/
async function nhettkqc(accountId, businessId) {
const actor = window.actor_id || actor_id || "";
const dtsg = window.fb_dtsgg || fb_dtsgg || "";
const url = `https://business.facebook.com/business/objects/add/connections/?business_id=${businessId}&from_id=${businessId}&from_asset_type=brand&to_id=${accountId}&to_asset_type=ad-account`;
const params = `__user=${encodeURIComponent(actor)}&__a=1&__dyn=7xeUmxa2C5rgydwCwRyU8EKnFG2Om2q12wAxuq3mq1FxebzA3aF98Sm4Euxa16xq2WdwJwy-2i13x21FxG9y8Gdz8hwgo5S3a4EuCx62a2q5E9UeUryE5mWyUd8S3bg-3tpUdoK7UC5U7y78jxiUa8522m3K2y3WElUScyo720FoO12Kmu7EK3i2a3Fe6rwnVUao9k2B12ewi8doa84K5E5WUrorx2awCx5e8wxK2efK6F8W1dx-q4VEhwww9O3ifzobEaUiwrUK5Ue8Sp1G3WcwMzUkGum2ym2WE4e8wl8hyVEKu9zUbVEHyU8U3yDwbm1bwzwqpbw&__csr=&__req=r&__hs=19234.BP%3Abrands_pkg.2.0.0.0.0&dpr=1.5&__ccg=EXCELLENT&__rev=1006115252&__s=ne9waj%3Acicyhn%3Aq28x8k&__hsi=7137596782722923131&__comet_req=0&fb_dtsg=${encodeURIComponent(dtsg)}&jazoest=25661&lsd=tqhJ435PyAJ7SnONkDETc0&__spin_r=1006115252&__spin_b=trunk&__spin_t=1661851252&__jssesw=1`;
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: 'text/plain'
},
body: params,
credentials: "include",
signal: window.currentAbortController?.signal
});
if (!response.ok) {
if (response.status === 500) {
return { status: true, error: null };
}
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const text = await response.text();
const json = JSON.parse(text.replace(/^for \(;;\);/, ''));
if (json.payload && json.payload.success === true) {
return { status: true, error: null };
} else {
return { status: false, error: json.errorDescription || json };
}
} catch (e) {
return { status: false, error: e };
}
}
async function sharetkqcbm(accountId, businessId) {
const actor = window.actor_id || actor_id || "";
const dtsg = window.fb_dtsgg || fb_dtsgg || "";
const url = `https://business.facebook.com/business/objects/add/connections/?business_id=${businessId}&from_id=${businessId}&from_asset_type=brand&to_id=${accountId}&to_asset_type=ad-account`;
const params = `__user=${encodeURIComponent(actor)}&__a=1&__dyn=7xeUmxa2C5rgydwCwRyU8EKnFG2Om2q12wAxuq3mq1FxebzA3aF98Sm4Euxa16xq2WdwJwy-2i13x21FxG9y8Gdz8hwgo5S3a4EuCx62a2q5E9UeUryE5mWyUd8S3bg-3tpUdoK7UC5U7y78jxiUa8522m3K2y3WElUScyo720FoO12Kmu7EK3i2a3Fe6rwnVUao9k2B12ewi8doa84K5E5WUrorx2awCx5e8wxK2efK6F8W1dx-q4VEhwww9O3ifzobEaUiwrUK5Ue8Sp1G3WcwMzUkGum2ym2WE4e8wl8hyVEKu9zUbVEHyU8U3yDwbm1bwzwqpbw&__csr=&__req=r&__hs=19234.BP%3Abrands_pkg.2.0.0.0.0&dpr=1.5&__ccg=EXCELLENT&__rev=1006115252&__s=ne9waj%3Acicyhn%3Aq28x8k&__hsi=7137596782722923131&__comet_req=0&fb_dtsg=${encodeURIComponent(dtsg)}&jazoest=25661&lsd=tqhJ435PyAJ7SnONkDETc0&__spin_r=1006115252&__spin_b=trunk&__spin_t=1661851252&__jssesw=1`;
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: 'text/plain'
},
body: params,
credentials: "include",
signal: window.currentAbortController?.signal
});
if (!response.ok) {
if (response.status === 500) {
return { status: true, error: null };
}
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const text = await response.text();
const json = JSON.parse(text.replace(/^for \(;;\);/, ''));
if (json.payload && json.payload.success === true) {
return { status: true, error: null };
} else {
return { status: false, error: json.errorDescription || json };
}
} catch (e) {
return { status: false, error: e };
}
}
async function gettokeneaag() {
const url = `https://business.facebook.com/billing_hub/payment_settings/`;
try {
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
signal: window.currentAbortController?.signal,
headers: {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Upgrade-Insecure-Requests": "1"
}
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const html = await response.text();
// Tìm EAAG an toàn bằng regex (đến trước dấu ")
const match = html.match(/EAAG[^"\\]+/);
if (!match) {
return { status: false, error: 'Không tìm thấy EAAG token trên trang' };
}
const token = match[0];
return { status: true, token };
} catch (err) {
return { status: false, error: err?.message || String(err) };
}
}
async function checkallbm() {
const token = window.accessToken || accessToken || "";
const url = `https://graph.facebook.com/v19.0/me/businesses?fields=is_disabled_for_integrity_reasons,owned_ad_accounts.limit(10)&access_token=${token}&limit=1000`;
try {
const response = await fetch(url, { method: 'GET', credentials: 'include', signal: window.currentAbortController?.signal, headers: { Accept: 'application/json' } });
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const allbm = data?.data;
if (Array.isArray(allbm) && allbm.length > 0) {
return { status: true, error: null, allbm: allbm };
}
return { status: false, error: data.errors?.description || data };
} catch (err) {
return { status: false, error: err };
}
}
/**
* Random Số theo độ dài.
*/
function randomNumber(length) {
let result = '';
for (let i = 0; i < length; i++) {
result += Math.floor(Math.random() * 10);
}
return result;
}
/**
* Random ký tự viết thường theo độ dài.
*/
function randomLowercase(length) {
let result = '';
const chars = 'abcdefghijklmnopqrstuvwxyz';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* Create ads account and return its ID.
*/
async function createtkqc(BMprocesid, number = 1) {
const token = window.accessToken || accessToken || "";
const rawJson = {
businessID: BMprocesid,
adAccountName: `USDL ${number}`,
timezoneID: "1",
currency: "USD",
endAdvertiserID: BMprocesid
};
const encodedJson = encodeURIComponent(JSON.stringify(rawJson));
const url = `https://graph.facebook.com/graphql?method=post&locale=en_US&pretty=false&format=json&fb_api_req_friendly_name=BizKitSettingsCreateAdAccountMutation&doc_id=9236789956426634&fb_api_caller_class=RelayModern&server_timestamps=true&variables=${encodedJson}&access_token=${token}`;
try {
const response = await fetch(url, { method: 'GET', credentials: 'include', signal: window.currentAbortController?.signal, headers: { Accept: 'application/json' } });
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
const ads_id = data?.data?.business_settings_create_ad_account?.id;
if (ads_id) {
return { status: true, error: null, ads_id };
}
return { status: false, error: data.errors?.[0]?.description || data };
} catch (err) {
return { status: false, error: err };
}
}
async function checkBMtype(BMprocesid) {
const dtsg = window.fb_dtsgg || fb_dtsgg || "";
const url = `https://business.facebook.com/business/adaccount/limits/?business_id=${BMprocesid}`;
const params = `__a=1&fb_dtsg=${encodeURIComponent(dtsg)}&lsd=-X13GDdXiR6GDsJsHxJxLG`;
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: 'text/plain',
},
body: params,
credentials: "include",
signal: window.currentAbortController?.signal
});
if (!response.ok) {
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const text = await response.text();
const json = JSON.parse(text.replace(/^for \(;;\);/, ''));
if (json.payload && json.payload.adAccountLimit) {
return { status: true, error: null, adAccountLimit: json.payload.adAccountLimit };
} else {
return { status: false, error: json.errorDescription || json };
}
} catch (e) {
return { status: false, error: e };
}
}
// Inject UI function to be called at the start
function injectUI() {
// Check if UI already exists
if (document.getElementById('fb-tool-container')) {
document.getElementById('fb-tool-container').remove();
}
// Create styles (dedup by id)
const prevStyle = document.getElementById('fb-tool-styles');
if (prevStyle) prevStyle.remove();
const style = document.createElement('style');
style.id = 'fb-tool-styles';
style.textContent = `
#fb-tool-container {
position: fixed;
top: 20px;
left: 20px;
width: 1000px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
padding: 0;
overflow: hidden;
}
.fb-tool-header {
background: linear-gradient(135deg, #4267B2, #3b5998);
color: white;
padding: 15px 20px;
font-weight: bold;
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.fb-tool-close {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.fb-tool-close:hover {
background: rgba(255,255,255,0.4);
}
.fb-tool-body {
padding: 15px 20px;
}
.fb-tool-info {
background: #f7f8fa;
padding: 10px 15px;
margin-bottom: 15px;
border-radius: 6px;
border: 1px solid #dddfe2;
}
.fb-tool-info-row {
display: flex;
margin-bottom: 5px;
align-items: center;
}
.fb-tool-info-row:last-child {
margin-bottom: 0;
}
.fb-tool-info-label {
width: 110px;
font-weight: bold;
font-size: 13px;
color: #444;
}
.fb-tool-info-value {
flex: 1;
background: #fff;
padding: 5px 10px;
border-radius: 4px;
border: 1px solid #dddfe2;
font-family: monospace;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fb-tool-info-copy {
margin-left: 8px;
cursor: pointer;
color: #4267B2;
font-size: 12px;
background: #e7f3ff;
border: none;
padding: 3px 8px;
border-radius: 4px;
}
.fb-tool-info-copy:hover {
background: #d4e5f9;
}
.fb-tool-row {
display: flex;
gap: 15px;
margin-bottom: 15px;
}
.fb-tool-column {
flex: 1;
}
.fb-tool-input-group {
margin-bottom: 15px;
}
.fb-tool-label {
display: block;
font-weight: bold;
margin-bottom: 5px;
font-size: 14px;
}
.fb-tool-textarea {
width: 100%;
height: 120px;
resize: vertical;
padding: 10px;
font-family: monospace;
border: 1px solid #dddfe2;
border-radius: 6px;
font-size: 13px;
}
.fb-tool-textarea:focus {
border-color: #4267B2;
outline: none;
box-shadow: 0 0 0 2px rgba(66, 103, 178, 0.15);
}
.fb-tool-output {
background: #f7f8fa;
height: 100px;
}
.fb-tool-controls {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
background: #f7f8fa;
padding: 12px 15px;
border-radius: 6px;
}
.fb-tool-select {
padding: 8px 12px;
border: 1px solid #dddfe2;
border-radius: 6px;
flex: 1;
font-size: 14px;
max-width: 300px;
}
.fb-tool-button {
background: linear-gradient(to bottom, #4267B2, #365899);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-weight: bold;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 6px;
}
.fb-tool-button:hover {
background: linear-gradient(to bottom, #365899, #2d4373);
}
.fb-tool-button:disabled {
background: #bdc7d8;
cursor: not-allowed;
}
.fb-tool-button-stop {
background: linear-gradient(to bottom, #e74c3c, #c0392b);
}
.fb-tool-button-stop:hover {
background: linear-gradient(to bottom, #c0392b, #a93226);
}
.fb-tool-progress-container {
height: 16px;
background: #f2f2f2;
border-radius: 8px;
margin-bottom: 15px;
overflow: hidden;
}
.fb-tool-progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(to right, #4CAF50, #8BC34A);
border-radius: 8px;
transition: width 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 11px;
font-weight: bold;
}
.fb-tool-status {
background: #f7f8fa;
padding: 10px 15px;
border-radius: 6px;
margin-bottom: 15px;
font-size: 13px;
color: #333;
border-left: 4px solid #4267B2;
}
.fb-tool-footer {
padding: 12px 20px;
background: #f7f8fa;
border-top: 1px solid #dddfe2;
font-size: 12px;
color: #777;
text-align: right;
}
.fb-tool-input-inline {
padding: 8px;
border: 1px solid #dddfe2;
border-radius: 6px;
width: 60px;
text-align: center;
}
.fb-tool-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
.fb-tool-badge-success {
background: #e3f2e1;
color: #2e7d32;
}
.fb-tool-badge-error {
background: #fce8e8;
color: #c62828;
}
`;
document.head.appendChild(style);
// Try to get actor_id and token
let actor_id = "";
let accessToken = "";
let tokenType = "Unknown";
try {
actor_id = require("CurrentUserInitialData").USER_ID || "";
} catch (e) {
console.error("Error getting actor_id:", e);
}
try {
accessToken = require("WebApiApplication").getAccessToken() || "";
if (accessToken.startsWith("EAAG")) {
tokenType = "EAAG (GraphAPI)";
} else if (accessToken.startsWith("EAA")) {
tokenType = "EAA (Standard)";
}
} catch (e) {
console.error("Error getting access token:", e);
}
// Create container
const container = document.createElement('div');
container.id = 'fb-tool-container';
// Build HTML structure
container.innerHTML = `
`;
document.body.appendChild(container);
// lưu token vào dataset để tái sử dụng
const tokenElInit = document.getElementById('fb-tool-token');
if (tokenElInit) tokenElInit.dataset.fullToken = accessToken;
window.accessToken = accessToken;
// Add event listeners for copy buttons instead of inline handlers
document.getElementById('copy-actor-id').addEventListener('click', function () {
navigator.clipboard.writeText(actor_id).then(() => {
this.textContent = 'Copied!';
setTimeout(() => {
this.textContent = 'Copy';
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert('Failed to copy: ' + err);
});
});
document.getElementById('copy-token').addEventListener('click', function () {
const t = document.getElementById('fb-tool-token')?.dataset?.fullToken || window.accessToken || '';
navigator.clipboard.writeText(t).then(() => {
this.textContent = 'Copied!';
setTimeout(() => { this.textContent = 'Copy'; }, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert('Failed to copy: ' + err);
});
});
// Handle close button with improved cleanup
document.getElementById('fb-tool-close').addEventListener('click', () => {
window.stopProcessing = true;
window.forceStopAllOperations = true;
window.currentAbortController?.abort();
const el = document.getElementById('fb-tool-container');
if (el) el.remove();
updateStatus('All operations stopped and UI closed');
});
// Handle function change to show/hide additional fields
document.getElementById('fb-tool-function').addEventListener('change', function () {
const uidContainer = document.getElementById('fb-tool-uid-container');
uidContainer.style.display = this.value === '1' ? 'block' : 'none';
const createadsContainer = document.getElementById('fb-tool-createads-container');
createadsContainer.style.display = this.value === '4' ? 'inline-flex' : 'none';
});
// Initialize UI based on current function
const functionSelect = document.getElementById('fb-tool-function');
document.getElementById('fb-tool-uid-container').style.display =
functionSelect.value === '1' ? 'block' : 'none';
document.getElementById('fb-tool-createads-container').style.display =
functionSelect.value === '4' ? 'inline-flex' : 'none';
// Handle run button
document.getElementById('fb-tool-run').addEventListener('click', startProcess);
// Handle stop button with improved cleanup
document.getElementById('fb-tool-stop').addEventListener('click', function () {
window.stopProcessing = true;
window.forceStopAllOperations = true;
window.currentAbortController?.abort();
updateStatus('Stopping all operations...');
updateProgress(100, 'Stopped');
this.disabled = true;
document.getElementById('fb-tool-run').disabled = false;
});
// Fill textareas with existing values if available
if (window.accountLists) {
document.getElementById('fb-tool-ads-input').value = window.accountLists;
}
if (window.BMLists) {
document.getElementById('fb-tool-bm-input').value = window.BMLists;
}
}
// Create a delay function that respects the stop flag
async function delayWithStopCheck(seconds) {
const startTime = Date.now();
const endTime = startTime + (seconds * 1000);
while (Date.now() < endTime) {
if (window.stopProcessing || window.forceStopAllOperations) {
throw new Error("Operation canceled by user");
}
// Check every 100ms
await new Promise(resolve => setTimeout(resolve, 100));
}
}
// Function to update progress bar
function updateProgress(percentage, text) {
const progressBar = document.getElementById('fb-tool-progress');
if (!progressBar) return;
progressBar.style.width = `${percentage}%`;
progressBar.textContent = text || `${Math.round(percentage)}%`;
// Change color based on progress
if (percentage < 30) {
progressBar.style.background = 'linear-gradient(to right, #f44336, #ff7043)';
} else if (percentage < 70) {
progressBar.style.background = 'linear-gradient(to right, #ff9800, #ffc107)';
} else {
progressBar.style.background = 'linear-gradient(to right, #4CAF50, #8BC34A)';
}
}
// Function to update status
function updateStatus(text) {
const statusElement = document.getElementById('fb-tool-status');
if (!statusElement) return;
statusElement.textContent = text;
// Highlight the status update
statusElement.style.backgroundColor = '#e9f3ff';
setTimeout(() => {
statusElement.style.backgroundColor = '#f7f8fa';
}, 300);
}
// Function to add to output
function addToOutput(isSuccess, text) {
const outputId = isSuccess ? 'fb-tool-success-output' : 'fb-tool-fail-output';
const output = document.getElementById(outputId);
if (!output) return;
// Format with timestamp
const now = new Date().toLocaleTimeString();
output.value += `${text} [${now}]\n`;
output.scrollTop = output.scrollHeight;
}
// Main function to start the process
async function startProcess() {
try {
// Reset outputs and UI
//document.getElementById('fb-tool-success-output').value = '';
//document.getElementById('fb-tool-fail-output').value = '';
updateProgress(0);
updateStatus('Đang khởi tạo...');
// Toggle button states
const runButton = document.getElementById('fb-tool-run');
const stopButton = document.getElementById('fb-tool-stop');
runButton.disabled = true;
stopButton.disabled = false;
// Reset BOTH stop flags
window.stopProcessing = false;
window.forceStopAllOperations = false; // This flag wasn't being reset!
// Get values from UI
const adsInput = document.getElementById('fb-tool-ads-input').value.trim();
const bmInput = document.getElementById('fb-tool-bm-input').value.trim();
const functionValue = document.getElementById('fb-tool-function').value;
const delayValue = parseInt(document.getElementById('fb-tool-delay').value) || 1;
// Validate inputs
if (!adsInput && (functionValue === '1' || functionValue === '3')) {
alert('Vui lòng nhập danh sách TKQC!');
runButton.disabled = false;
return;
}
if (!bmInput && (functionValue === '2' || functionValue === '4')) {
alert('Vui lòng nhập danh sách BM!');
runButton.disabled = false;
return;
}
// Update global variables for the existing backend
window.accountLists = adsInput;
window.BMLists = bmInput;
window.typeselect = parseInt(functionValue);
window.delays = delayValue;
// Special handling for function 1 (share to another account)
if (functionValue === '1') {
window.uidvianhan = document.getElementById('fb-tool-uid-input').value.trim();
// if (!window.uidvianhan) {
// alert('Vui lòng nhập UID via nhận TKQC share!');
// runButton.disabled = false;
// return;
// }
}
// Special handling for function 4 (BM10)
if (functionValue === '4') {
window.createads = parseInt(document.getElementById('fb-tool-createads').value) || 0;
} else {
window.createads = 0;
}
// Call modified main process
await executeProcess();
// Re-enable run button
runButton.disabled = false;
stopButton.disabled = true;
updateStatus('Hoàn tất xử lý!');
} catch (error) {
console.error('Error in startProcess:', error);
document.getElementById('fb-tool-run').disabled = false;
document.getElementById('fb-tool-stop').disabled = true;
updateStatus(`Lỗi: ${error.message}`);
alert(`Đã xảy ra lỗi: ${error.message}`);
}
}
// Main process execution with progress reporting
async function executeProcess() {
const accountIds = window.accountLists.split('\n')
.map(line => line.split('|')[0].trim())
.filter(id => id !== '');
const BMIds = window.BMLists.split('\n')
.map(line => line.split('|')[0].trim())
.filter(id => id !== '');
let actor_id, fb_dtsgg, accessToken;
try {
actor_id = require("CurrentUserInitialData").USER_ID;
if (!actor_id) throw new Error("Actor ID not found");
fb_dtsgg = require("DTSGInitData").token;
if (!fb_dtsgg) throw new Error("DTSG token not found");
accessToken = require("WebApiApplication").getAccessToken();
// ưu tiên token từ UI/localStorage nếu có
const preferred = getTokenFromUIOrStorage();
if (preferred.startsWith('EAAG') && !accessToken.startsWith('EAAG')) {
accessToken = preferred;
}
if (preferred) accessToken = preferred;
if (!accessToken) {
throw new Error("Access token not found");
}
// persist to window.* for helpers
window.actor_id = actor_id;
window.fb_dtsgg = fb_dtsgg;
window.accessToken = accessToken;
} catch (e) {
updateStatus(`Lỗi lấy thông tin người dùng: ${e.message}`);
throw e;
}
updateStatus(`Đã lấy thông tin, bắt đầu xử lý...`);
window.currentAbortController = new AbortController();
if (window.typeselect === 1) {
await processFunction1(accountIds, actor_id, accessToken, window.uidvianhan);
} else if (window.typeselect === 2) {
await processFunction2(accountIds, BMIds, actor_id, accessToken, fb_dtsgg);
} else if (window.typeselect === 3) {
await processFunction3(accountIds, actor_id, accessToken);
} else if (window.typeselect === 4) {
await processFunction4(accountIds, BMIds, actor_id, accessToken, fb_dtsgg);
} else if (window.typeselect === 5) {
await processFunction5(accountIds, BMIds, actor_id, accessToken, fb_dtsgg);
}
}
// Function 1: Check and share TKQC
// Function 1: Check & Share TKQC (KHÔNG dùng timeslot)
async function processFunction1(accountIds, actor_id, accessToken, uidvianhan) {
let processcount = 0;
let successCount = 0;
let failCount = 0;
// Yêu cầu EAAG vì gọi Graph /act_{id}/users
if (!accessToken.startsWith("EAAG")) {
const newToken = prompt("Token không phải EAAG. Vui lòng nhập Token EAAG:");
if (newToken && newToken.startsWith("EAAG")) {
accessToken = newToken;
setTokenAndUI(accessToken);
window.accessToken = accessToken;
} else {
alert("Token không hợp lệ hoặc không phải EAAG. Vui lòng thử lại.");
return;
}
}
updateStatus(`Đang share TKQC cho ${accountIds.length} tài khoản...`);
for (let i = 0; i < accountIds.length; i++) {
if (window.stopProcessing || window.forceStopAllOperations) break;
const adaccountid = (accountIds[i] || "").trim();
if (!adaccountid) continue;
processcount++;
updateProgress((processcount / accountIds.length) * 100);
updateStatus(`Đang xử lý TKQC ${adaccountid} (${processcount}/${accountIds.length})...`);
if (uidvianhan && uidvianhan.length > 0) {
// share thẳng, không cần timeslot/check call support
const shareResult = await sharetkqc(adaccountid, uidvianhan);
if (shareResult.status) {
successCount++;
addToOutput(true, `${adaccountid}|Share thành công cho UID ${uidvianhan}`);
} else {
failCount++;
addToOutput(false, `${adaccountid}|Share thất bại: ${JSON.stringify(shareResult.error)}`);
}
} else {
// Không có UID nhận → chỉ log OK để tiếp bước sau
successCount++;
addToOutput(true, `${adaccountid}|Bỏ qua share (không có UID nhận)`);
}
if (i < accountIds.length - 1 && !window.stopProcessing && !window.forceStopAllOperations) {
updateStatus(`Delay ${window.delays}s trước khi xử lý TKQC tiếp theo...`);
try { await delayWithStopCheck(window.delays); }
catch (e) { if (e.message === "Operation canceled by user") break; throw e; }
}
}
updateStatus(`Hoàn tất! Thành công: ${successCount}, Thất bại: ${failCount}`);
updateProgress(100, `${successCount}/${accountIds.length}`);
}
// Function 2: Add TKQC to BM
async function processFunction2(accountIds, BMIds, actor_id, accessToken, fb_dtsgg) {
let processcount = 0;
let successCount = 0;
let failCount = 0;
let adsnumber = 0;
let successadsList = [];
let successBMList = [];
updateStatus(`Đang nhét TKQC vào ${BMIds.length} BM...`);
try {
for (let i = 0; i < BMIds.length; i++) {
if (window.stopProcessing || window.forceStopAllOperations) break;
const BMprocesid = BMIds[i];
processcount++;
updateProgress((processcount / BMIds.length) * 100);
updateStatus(`Đang xử lý BM ${BMprocesid} (${processcount}/${BMIds.length})...`);
if (adsnumber >= accountIds.length) {
updateStatus("Đã hết TKQC để xử lý, dừng tiến trình");
break;
}
// Get next available ad account
let adaccountid;
for (const adaccountidcheck of accountIds.slice(adsnumber)) {
adsnumber++;
adaccountid = adaccountidcheck;
break;
}
if (!adaccountid) {
updateStatus("Không tìm được TKQC phù hợp, dừng tiến trình");
break;
}
// Add ad account to BM
updateStatus(`Đang nhét TKQC ${adaccountid} vào BM ${BMprocesid}...`);
const nhettkqcResult = await nhettkqc(adaccountid, BMprocesid);
if (nhettkqcResult.status) {
successadsList.push(adaccountid);
successBMList.push(BMprocesid);
successCount++;
addToOutput(true, `${adaccountid}|${BMprocesid}|Nhét TKQC thành công vào BM`);
} else {
failCount++;
addToOutput(false, `${adaccountid}|${BMprocesid}|Nhét tkqc thất bại ${JSON.stringify(nhettkqcResult.error)}`);
continue;
}
if (i < BMIds.length - 1 && !window.stopProcessing && !window.forceStopAllOperations) {
updateStatus(`Delay ${window.delays}s trước khi xử lý BM tiếp theo...`);
try {
await delayWithStopCheck(window.delays);
} catch (e) {
if (e.message === "Operation canceled by user") break;
throw e;
}
}
}
} catch (error) {
console.error("Error in processFunction2:", error);
updateStatus(`Lỗi: ${error.message}`);
}
updateStatus(`Hoàn tất! Thành công: ${successCount}, Thất bại: ${failCount}`);
updateProgress(100, `${successCount}/${BMIds.length}`);
}
// Function 5: Add TKQC to BM
async function processFunction2(accountIds, BMIds, actor_id, accessToken, fb_dtsgg) {
let processcount = 0;
let successCount = 0;
let failCount = 0;
let adsnumber = 0;
let successadsList = [];
let successBMList = [];
updateStatus(`Đang nhét TKQC vào ${BMIds.length} BM...`);
try {
for (let i = 0; i < BMIds.length; i++) {
if (window.stopProcessing || window.forceStopAllOperations) break;
const BMprocesid = BMIds[i];
processcount++;
updateProgress((processcount / BMIds.length) * 100);
updateStatus(`Đang xử lý BM ${BMprocesid} (${processcount}/${BMIds.length})...`);
if (adsnumber >= accountIds.length) {
updateStatus("Đã hết TKQC để xử lý, dừng tiến trình");
break;
}
// Get next available ad account
let adaccountid;
for (const adaccountidcheck of accountIds.slice(adsnumber)) {
adsnumber++;
adaccountid = adaccountidcheck;
break;
}
if (!adaccountid) {
updateStatus("Không tìm được TKQC phù hợp, dừng tiến trình");
break;
}
// Add ad account to BM
updateStatus(`Đang nhét TKQC ${adaccountid} vào BM ${BMprocesid}...`);
const nhettkqcResult = await nhettkqc(adaccountid, BMprocesid);
if (nhettkqcResult.status) {
successadsList.push(adaccountid);
successBMList.push(BMprocesid);
successCount++;
addToOutput(true, `${adaccountid}|${BMprocesid}|Nhét TKQC thành công vào BM`);
} else {
failCount++;
addToOutput(false, `${adaccountid}|${BMprocesid}|Nhét tkqc thất bại ${JSON.stringify(nhettkqcResult.error)}`);
continue;
}
if (i < BMIds.length - 1 && !window.stopProcessing && !window.forceStopAllOperations) {
updateStatus(`Delay ${window.delays}s trước khi xử lý BM tiếp theo...`);
try {
await delayWithStopCheck(window.delays);
} catch (e) {
if (e.message === "Operation canceled by user") break;
throw e;
}
}
}
} catch (error) {
console.error("Error in processFunction2:", error);
updateStatus(`Lỗi: ${error.message}`);
}
updateStatus(`Hoàn tất! Thành công: ${successCount}, Thất bại: ${failCount}`);
updateProgress(100, `${successCount}/${BMIds.length}`);
}
// Function 3: Call Support
// Function 3: Call Support (bỏ qua bước check nút Call Support nếu bật cờ)
async function processFunction3(accountIds, actor_id, accessToken) {
let processcount = 0;
let successCount = 0;
let failCount = 0;
let successBMList = [];
let errorBMList = [];
updateStatus(`Đang call support cho ${accountIds.length} TKQC...`);
// Ưu tiên dùng fb_dtsg khi đang đứng trong Ads Manager
let use_token = true;
if (isOnAdsManagerFacebook()) use_token = false;
// Ép nhập EAAG nếu đang chọn flow dùng token
if (use_token && !accessToken.startsWith("EAAG")) {
const newToken = prompt("Token không phải EAAG. Vui lòng nhập Token EAAG:");
if (newToken && newToken.startsWith("EAAG")) {
accessToken = newToken;
setTokenAndUI(accessToken);
window.accessToken = accessToken;
} else {
alert("Token không hợp lệ hoặc không phải EAAG. Vui lòng thử lại.");
return;
}
}
for (let i = 0; i < accountIds.length; i++) {
if (window.stopProcessing || window.forceStopAllOperations) break;
const adaccountidss = accountIds[i];
const arrayads = adaccountidss.split("|");
let adaccountid = arrayads[0] ? arrayads[0].trim() : "";
let time_start = arrayads[1] ? arrayads[1].trim() : "";
let time_end = arrayads[2] ? arrayads[2].trim() : "";
const adaccountkich = arrayads[3] ? arrayads[3].trim() : "";
let time_frame = {
start_time: { timestamp: time_start },
end_time: { timestamp: time_end },
preferred: true
};
processcount++;
updateProgress((processcount / accountIds.length) * 100);
updateStatus(`Đang xử lý TKQC ${adaccountid} (${processcount}/${accountIds.length})...`);
// --- LẤY CMS (BỎ QUA CHECK NÚT NẾU BẬT CỜ) ---
let result = await getcmsid(adaccountid);
if (!result?.status && SKIP_CALL_SUPPORT_CHECK) {
result = {
status: true,
cms_id: FALLBACK_CMS_ID,
short_description: FALLBACK_SHORT_DESC,
existing_connections: []
};
addToOutput(true, `${adaccountid}| Bỏ qua check nút call support (dùng CMS ${FALLBACK_CMS_ID})`);
} else if (!result?.status) {
addToOutput(false, `${adaccountid}| Không có nút call support`);
failCount++;
errorBMList.push(adaccountid);
// sang TK tiếp theo
if (i < accountIds.length - 1 && !window.stopProcessing && !window.forceStopAllOperations) {
updateStatus(`Delay ${window.delays}s trước TKQC tiếp theo...`);
try { await delayWithStopCheck(window.delays); } catch (e) { if (e.message === "Operation canceled by user") break; throw e; }
}
continue;
}
// Hủy cuộc gọi cũ (nếu API trả về)
if (result.existing_connections?.length) {
updateStatus(`TKQC ${adaccountid}: Hủy cuộc gọi cũ...`);
for (const connection of result.existing_connections) {
if (connection.channel_type) {
const cancelResult = await cancelcall(connection.id);
updateStatus(`TKQC ${adaccountid}: Hủy cũ ${cancelResult.status ? "OK" : "FAIL"}`);
}
}
}
// Lấy timeslot từ API lịch
const timeslotResult = await gettimeslotv3(adaccountid);
// Cho phép thay đổi adaccountid nếu truyền ở cột thứ 4
if (adaccountkich && adaccountkich.length > 10) adaccountid = adaccountkich;
// Có slot từ API -> đặt theo slot trả về
if (timeslotResult.status && Array.isArray(timeslotResult.slots) && timeslotResult.slots.length) {
if (timeslotResult.existing_connections?.length) {
for (const connection of timeslotResult.existing_connections) {
if (connection.channel_type) await cancelcall(connection.id);
}
}
updateStatus(`TKQC ${adaccountid}: Đang đặt lịch theo slot API...`);
let result2;
if (use_token) {
result2 = await makeacall(
result.cms_id,
timeslotResult.slots,
adaccountid,
result.short_description,
timeslotResult.sbg_prefill_data,
timeslotResult.sbg_program_name
);
} else {
result2 = await makeacallfbdt(
result.cms_id,
timeslotResult.slots,
adaccountid,
result.short_description,
timeslotResult.sbg_prefill_data,
timeslotResult.sbg_program_name
);
}
if (result2.status) {
successCount++;
addToOutput(true, `${adaccountid}| đặt lịch thành công`);
} else {
failCount++;
addToOutput(false, `${adaccountid}| đặt lịch thất bại: ${JSON.stringify(result2.error)}`);
}
// KHÔNG có slot -> dùng fallback ngày 2025-08-29 & các giờ bạn chỉ định
} else {
const fallbackSlots = buildFallbackSlotsFrom(); // dùng cấu hình ở trên
if (!fallbackSlots.length) {
failCount++;
addToOutput(false, `${adaccountid}| Không tạo được fallback slot (có thể ngày/giờ đã qua)`);
} else {
updateStatus(`TKQC ${adaccountid}: Không có slot API, thử fallback ${FALLBACK_DATE_ISO}...`);
const cmsvalue = result.cms_id || FALLBACK_CMS_ID;
let result2;
if (use_token) {
result2 = await makeacall(
cmsvalue,
fallbackSlots,
adaccountid,
result.short_description
);
} else {
result2 = await makeacallfbdt(
cmsvalue,
fallbackSlots,
adaccountid,
result.short_description
);
}
if (result2.status) {
successCount++;
addToOutput(true, `${adaccountid}| đặt lịch fallback thành công (${FALLBACK_DATE_ISO})`);
} else {
failCount++;
addToOutput(false, `${adaccountid}| Lỗi đặt lịch fallback: ${JSON.stringify(result2.error)}`);
}
}
}
// Delay giữa các TK
if (i < accountIds.length - 1 && !window.stopProcessing && !window.forceStopAllOperations) {
updateStatus(`Delay ${window.delays}s trước khi xử lý TKQC tiếp theo...`);
try { await delayWithStopCheck(window.delays); } catch (e) { if (e.message === "Operation canceled by user") break; throw e; }
}
}
updateStatus(`Hoàn tất! Thành công: ${successCount}, Thất bại: ${failCount}`);
updateProgress(100, `${successCount}/${accountIds.length}`);
}
// Function 4: Kích BM10
async function processFunction4(accountIds, BMIds, actor_id, accessToken, fb_dtsgg) {
let processcount = 0;
let successCount = 0;
let failCount = 0;
let adsnumber = 0;
let successBMList = [];
let successadsList = [];
if (!accessToken.startsWith("EAAG")) {
const newToken = prompt("Token không phải EAAG. Vui lòng nhập Token EAAG:");
if (newToken && newToken.startsWith("EAAG")) {
accessToken = newToken;
setTokenAndUI(accessToken);
window.accessToken = accessToken; // keep helpers in sync
} else {
alert("Token không hợp lệ hoặc không phải EAAG. Vui lòng thử lại.");
return;
}
}
updateStatus(`Đang kích BM10 cho ${BMIds.length} BM...`);
for (let i = 0; i < BMIds.length; i++) {
if (window.stopProcessing || window.forceStopAllOperations) break;
const BMprocesid = BMIds[i];
processcount++;
updateProgress((processcount / BMIds.length) * 100);
updateStatus(`Đang xử lý BM ${BMprocesid} (${processcount}/${BMIds.length})...`);
if (adsnumber >= accountIds.length) {
updateStatus("Đã hết TKQC để xử lý, dừng tiến trình");
break;
}
// Find ad account with call support
let adaccountid;
let result;
for (const adaccountidcheck of accountIds.slice(adsnumber)) {
adsnumber++;
updateStatus(`Đang kiểm tra nút call support cho TKQC ${adaccountidcheck}...`);
result = await getidscall(adaccountidcheck);
if (!result.status) {
addToOutput(false, `${adaccountidcheck}| Không có nút call support`);
continue;
}
adaccountid = adaccountidcheck;
break;
}
if (!adaccountid) {
addToOutput(false, "Không tìm được tài khoản có nút call support");
updateStatus("Không tìm được TKQC phù hợp, dừng tiến trình");
break;
}
addToOutput(true, `${adaccountid}| Có nút call support: ${result.cms_id}`);
// Cancel existing connections
if (result.existing_connections?.length) {
updateStatus(`TKQC ${adaccountid}: Đang hủy cuộc gọi cũ...`);
for (const connection of result.existing_connections) {
if (connection.channel_type) {
const cancelResult = await cancelcall(connection.id);
updateStatus(`TKQC ${adaccountid}: Hủy cuộc gọi cũ ${cancelResult.status ? 'Thành công' : 'Thất bại'}`);
}
}
}
// Add ad account to BM
updateStatus(`Đang nhét TKQC ${adaccountid} vào BM ${BMprocesid}...`);
const nhettkqcResult = await nhettkqc(adaccountid, BMprocesid);
if (nhettkqcResult.status) {
successadsList.push(adaccountid);
addToOutput(true, `${adaccountid}|${BMprocesid}|Nhét tkqc vào BM thành công`);
} else {
failCount++;
addToOutput(false, `${adaccountid}|${BMprocesid}|Nhét tkqc vào BM thất bại: ${JSON.stringify(nhettkqcResult.error)}`);
continue;
}
// Get timeslot and make call
const timeslotResult = await gettimeslotv3(adaccountid);
if (timeslotResult.status) {
updateStatus(`TKQC ${adaccountid}: Đang đặt lịch cuộc gọi...`);
const result2 = await makeacall(result.cms_id, timeslotResult.slots, adaccountid, result.short_description, timeslotResult.sbg_prefill_data);
if (result2.status) {
successCount++;
// Check BM type
updateStatus(`Đang kiểm tra thông tin BM ${BMprocesid}...`);
const BMtype = await checkBMtype(BMprocesid);
if (BMtype.status && BMtype.adAccountLimit > 1) {
const successEntry = `${adaccountid}|${BMprocesid}|BM${BMtype.adAccountLimit}`;
successBMList.push(successEntry);
addToOutput(true, `${successEntry} Kích BM thành công`);
// Create additional ad accounts
if (window.createads > 0) {
const maxCreate = Math.min(window.createads, BMtype.adAccountLimit);
updateStatus(`Đang tạo ${maxCreate} TKQC mới cho BM ${BMprocesid}...`);
for (let j = 0; j < maxCreate; j++) {
if (window.stopProcessing || window.forceStopAllOperations) break;
updateStatus(`Đang tạo TKQC ${j + 1}/${maxCreate}...`);
const createResult = await createtkqc(BMprocesid, j + 1);
if (createResult.status) {
addToOutput(true, `Tạo TKQC thành công : ${createResult.ads_id}`);
} else {
addToOutput(false, `Lỗi tạo TKQC: ${JSON.stringify(createResult.error)}`);
break;
}
}
}
} else {
failCount++;
addToOutput(false, `Kích BM10 thất bại ko tăng info: ${BMprocesid}`);
}
} else {
failCount++;
addToOutput(false, `Kích BM10 thất bại: ${BMprocesid} - ${JSON.stringify(result2.error)}`);
}
} else {
failCount++;
addToOutput(false, `Lỗi khung giờ: ${adaccountid} - ${JSON.stringify(timeslotResult.error)}`);
}
if (i < BMIds.length - 1 && !window.stopProcessing && !window.forceStopAllOperations) {
updateStatus(`Delay ${window.delays}s trước khi xử lý BM tiếp theo...`);
try {
await delayWithStopCheck(window.delays);
} catch (e) {
if (e.message === "Operation canceled by user") break;
throw e;
}
}
}
updateStatus(`Hoàn tất! Thành công: ${successCount}, Thất bại: ${failCount}`);
updateProgress(100, `${successCount}/${BMIds.length}`);
}
// Initialize global variables for UI access
window.stopProcessing = false;
window.forceStopAllOperations = false;
// Remove early UI injection to avoid duplicate UI
// injectUI(); // removed
async function sharetkqc(ads_id, uidvianhan) {
const url = `https://graph.facebook.com/v22.0/act_${ads_id}/users?locale=en_US&role=1001&uid=${encodeURIComponent(uidvianhan)}&access_token=${window.accessToken || accessToken}`;
try {
const response = await fetch(url, { method: 'POST', credentials: 'include' });
if (!response.ok) {
if (response.status === 500) return { status: true, error: null };
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
if (data.success === true) {
return { status: true, error: null };
}
return { status: false, error: data.error || data };
} catch (err) {
return { status: false, error: err };
}
}
async function removetkqc(ads_id, uidvianhan) {
const url = `https://graph.facebook.com/v22.0/act_${ads_id}/users?locale=en_US&role=1001&uid=${encodeURIComponent(uidvianhan)}&access_token=${window.accessToken || accessToken}`;
try {
const response = await fetch(url, { method: 'POST', credentials: 'include' });
if (!response.ok) {
if (response.status === 500) return { status: true, error: null };
return { status: false, error: `HTTP error: ${response.status} ${response.statusText}` };
}
const data = await response.json();
if (data.success === true) {
return { status: true, error: null };
}
return { status: false, error: data.error || data };
} catch (err) {
return { status: false, error: err };
}
}
// Add safe initialization for require statements to prevent errors
// When running in Chrome Console on Facebook
const initEnvironment = () => {
let actor_id, fb_dtsgg, accessToken;
try {
if (typeof require !== 'undefined') {
try {
actor_id = require("CurrentUserInitialData")?.USER_ID;
fb_dtsgg = require("DTSGInitData")?.token;
accessToken = require("WebApiApplication")?.getAccessToken();
} catch (specificError) {
console.error("Error accessing Facebook modules:", specificError);
return { success: false, error: "Could not access Facebook context. Please ensure you're on a Facebook page and logged in.", details: specificError.message };
}
} else {
// Alert the user that the script needs to run on Facebook
console.error("This script must be run on Facebook's website where 'require' is available");
return { success: false, error: "This script only works on Facebook. Please run it from the Chrome Console while on Facebook.", details: "require is undefined" };
}
// Check if we got valid values
if (!actor_id) {
return { success: false, error: "Could not detect your Facebook User ID", details: "USER_ID is null or undefined" };
}
if (!fb_dtsgg) {
return { success: false, error: "Could not detect Facebook DTSG token", details: "DTSG token is null or undefined" };
}
if (!accessToken) {
return { success: false, error: "Could not detect Facebook access token", details: "Access token is null or undefined" };
}
// Update global variables
window.actor_id = actor_id;
window.fb_dtsgg = fb_dtsgg;
window.accessToken = accessToken;
return { success: true, data: { actor_id, fb_dtsgg, accessToken } };
} catch (e) {
console.error("Error initializing environment:", e);
return { success: false, error: "Unexpected error during initialization", details: e.message };
}
};
// Function to show better error messages to users
function showInitializationError(result) {
const errorContainer = document.createElement('div');
errorContainer.id = 'fb-tool-error-container';
errorContainer.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 10000;
padding: 20px;
max-width: 500px;
width: 90%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
`;
errorContainer.innerHTML = `
Initialization Failed
${result.error}
${result.details}
`;
document.body.appendChild(errorContainer);
document.getElementById('fb-tool-error-close').addEventListener('click', () => {
errorContainer.remove();
});
document.getElementById('fb-tool-error-retry').addEventListener('click', () => {
errorContainer.remove();
initializeAndStartTool();
});
}
// Safer initialization function
function initializeAndStartTool() {
// First clear any existing UI to prevent duplicates
const existingContainer = document.getElementById('fb-tool-container');
if (existingContainer) existingContainer.remove();
const existingErrorContainer = document.getElementById('fb-tool-error-container');
if (existingErrorContainer) existingErrorContainer.remove();
// Then attempt to initialize
const result = initEnvironment();
if (result.success) {
injectUI();
console.log("Facebook Business Manager Tool initialized successfully.");
} else {
showInitializationError(result);
console.error("Initialization failed:", result.error);
}
}
// Execute the initialization when this script runs
try {
initializeAndStartTool();
} catch (e) {
console.error("Fatal error during tool startup:", e);
alert("An unexpected error occurred while starting the tool. See console for details.");
}
function setTokenAndUI(token) {
token = (token || '').trim();
window.accessToken = token;
const tokenEl = document.getElementById('fb-tool-token');
if (tokenEl) {
tokenEl.dataset.fullToken = token;
tokenEl.textContent = token ? token.substring(0, 25) + '...' : 'Not available';
const badge = tokenEl.parentElement?.querySelector('.fb-tool-badge');
if (badge) {
badge.textContent = token
? (token.startsWith('EAAG') ? '✓ EAAG (GraphAPI)' : '✓ EAA (Standard)')
: '✗ Invalid';
badge.classList.toggle('fb-tool-badge-success', !!token);
badge.classList.toggle('fb-tool-badge-error', !token);
}
}
}
function getTokenFromUIOrStorage() {
const el = document.getElementById('fb-tool-token');
const tFromUI = el?.dataset?.fullToken;
if (tFromUI) return tFromUI;
return window.accessToken || '';
}
function isOnAdsManagerFacebook() {
try {
return window.location.hostname.includes('adsmanager.facebook.com')
|| window.location.href.includes('adsmanager.facebook.com');
} catch {
return false;
}
}