Skip to content

Instantly share code, notes, and snippets.

@kenzouno1
Last active August 9, 2025 01:53
Show Gist options
  • Save kenzouno1/5660ecbca5f65b08e0c03b0d254bb9c0 to your computer and use it in GitHub Desktop.
Save kenzouno1/5660ecbca5f65b08e0c03b0d254bb9c0 to your computer and use it in GitHub Desktop.
const script = document.createElement("script");
script.textContent = `
// CENTRALIZED SETTINGS MANAGER
window.syncViaSettingsManager = {
_settings: null,
_listeners: [],
// Default settings
defaults: {
mainSync: true,
autoResolve: true,
},
// Get settings (cached)
get() {
if (this._settings === null) {
this.load();
}
return { ...this._settings }; // Return copy to prevent mutation
},
// Load from localStorage
load() {
try {
const saved = localStorage.getItem('sync-via-settings');
if (saved) {
const parsed = JSON.parse(saved);
// Validate settings structure
this._settings = { ...this.defaults, ...parsed };
console.log('Settings loaded:', this._settings);
} else {
this._settings = { ...this.defaults };
console.log('Using default settings:', this._settings);
}
} catch (error) {
console.error('Error loading settings:', error);
this._settings = { ...this.defaults };
}
return this._settings;
},
// Save to localStorage
save(newSettings) {
try {
// Merge with existing settings
this._settings = { ...this._settings, ...newSettings };
localStorage.setItem('sync-via-settings', JSON.stringify(this._settings));
console.log('Settings saved:', this._settings);
// Notify listeners
this._notifyListeners(this._settings);
return true;
} catch (error) {
console.error('Error saving settings:', error);
return false;
}
},
// Update specific setting
set(key, value) {
const currentSettings = this.get();
currentSettings[key] = value;
return this.save(currentSettings);
},
// Add listener for settings changes
addListener(callback) {
this._listeners.push(callback);
},
// Remove listener
removeListener(callback) {
const index = this._listeners.indexOf(callback);
if (index > -1) {
this._listeners.splice(index, 1);
}
},
// Notify all listeners
_notifyListeners(settings) {
this._listeners.forEach(callback => {
try {
callback(settings);
} catch (error) {
console.error('Error in settings listener:', error);
}
});
}
};
(async () => {
try {
// Create syncViaControls immediately
window.syncViaControls = {
startMainSync: () => console.log("Main sync started"),
stopMainSync: () => console.log("Main sync stopped"),
startAutoResolve: () => console.log("AutoResolve started"),
stopAutoResolve: () => console.log("AutoResolve stopped"),
};
console.log('syncViaControls created immediately');
const readLocalStorage = async (key, json = true) => {
const account = localStorage.getItem(key);
if (json) {
return JSON.parse(account)
}
return account
}
const statuses = ["Other Blocked", "Need Login", "Block 282", "Block 956"]
const account = readLocalStorage("account");
const getGroups = async (statusList = []) => {
let groups = await window.electronAPI.crudGroup(JSON.stringify({
action: "getAll",
isCloud: false
}))
if (statusList.length > 0) {
for (const statusName of statusList) {
const group = groups.find(x => x.name == statusName)
if (!group) {
console.log("Tạo group mới:", statusName)
await window.electronAPI.crudGroup(JSON.stringify({
action: "create",
groupData: statusName,
isCloud: false
}))
}
}
}
groups = await window.electronAPI.crudGroup(JSON.stringify({
action: "getAll",
isCloud: false
}))
return groups
}
const groups = await getGroups(statuses);
const needLoginGroup = groups.find(group => group.name == "Need Login")
const otherBlockedGroup = groups.find(group => group.name == "Other Blocked")
const block282Group = groups.find(group => group.name == "Block 282")
const block956Group = groups.find(group => group.name == "Block 956")
const fetchVias = async () => {
const response = await fetch("https://crawlmanageapi.tongkhobds.com/fbvia", {
headers: {
"x-api-key": "C2fvbErrov102oUer0"
}
})
const { data } = await response.json()
return data
}
const defaultProfileParams = {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
"profile_name": "",
"new_fingerprint": "",
"cpu": {
"type": "option",
"value": "20"
},
"group": "noGroup",
"browser": {
"type": "Chrome",
"version": "136"
},
"os": {
"type": "macOS",
"version": "macos14"
},
"proxy": {
"type": "not_use",
"host": "",
"port": "",
"user_name": "",
"password": ""
},
"country": "Vietnam",
"locationMethod": "country",
"time_zone": {
"use_ip": false,
"value": "Asia/Bangkok"
},
"web_rtc": "disable",
"screen_resolution": {
"type": "random",
"value": "1600x900"
},
"canvas": "noise",
"client_rects": "noise",
"audio_context": "noise",
"fonts": {
"type": "default",
"value": ""
},
"webgl": "noise",
"webgl_metadata": {
"type": "option",
"webgl_vendor": "nvidia",
"webgl_renderer": "ANGLE (NVIDIA, NVIDIA GeForce GT 730 (0x00001287) Direct3D11 vs_5_0 ps_5_0, D3D11)"
},
"start_url": "",
"chrome_argument": "",
"enable_extension": false,
"enable_location_tracking": true,
"use_ip_time_zone": true,
"location": {
"type": "block",
"use_ip": false,
"lat": 21.03462547164227,
"long": 105.41526841839915,
"accuracy": 91
},
"device_name": {
"type": "option",
"value": "WDO21-10771"
},
"mac_address": {
"type": "option",
"value": "54:52:00:89:BB:13"
},
"flash": "allow",
"tracking": "allow",
"language": {
"use_ip": false,
"value": [
"vi",
"en"
]
},
"speech_voice": "noise",
"port_protection": "allow",
"acceleration": "allow",
"image_display": "allow",
"memory": {
"type": "option",
"value": "16"
},
"media_device": "noise",
"cookie": "[]",
"note": "",
"isScheduling": false,
"proxy_status": "uncheck"
}
const getProfiles = async (userpackage) => {
const profiles = await window.electronAPI.getProfile({
userPackage: userpackage,
})
return profiles.data
}
const createProfile = async (via, userpackage, pathSave) => {
const v = await window.electronAPI.randomUserAgent(JSON.stringify({
os: defaultProfileParams.os,
ua_version: defaultProfileParams.browser.version + "",
webgl_metadata: defaultProfileParams.webgl_metadata,
device_name: defaultProfileParams.device_name,
mac_address: defaultProfileParams.mac_address,
country: defaultProfileParams.country,
location: defaultProfileParams.location
}));
defaultProfileParams.user_agent = v.data.user_agent,
defaultProfileParams.mac_address.value = v.data.mac_address,
defaultProfileParams.webgl_metadata.webgl_renderer = v.data.webgl_render,
defaultProfileParams.device_name.value = v.data.device_name,
defaultProfileParams.screen_resolution.value = v.data.screen_resolution,
defaultProfileParams.cpu.value = v.data.cpu,
defaultProfileParams.memory.value = v.data.memory,
defaultProfileParams.time_zone.value = v.data.time_zone,
defaultProfileParams.location = v.data.location,
//setup via
defaultProfileParams.profile_name = via.username;
const resources = [{
"resourceKey": "Username|Password|2Fa",
"platform": "Facebook",
"account": via.username + "|" + via.password + "|" + via.twoFA,
"status": "Need Login", "note": "Live"
},
];
if (via.mail) {
resources.push({
"resourceKey": "Username|Password", "platform": "Outlook",
"account": via.mail + "|" + via.mailPassword,
"status": "Unknown",
"note": ""
})
}
await window.electronAPI.addProfile(JSON.stringify({
profile: defaultProfileParams,
pathSave: pathSave,
userPackage: userpackage,
isCloud: false
}))
const profiles = await getProfiles(userpackage);
const id = profiles[profiles.length - 1].id;
await window.electronAPI.updateResource({
profileId: id,
resources: JSON.stringify(resources),
isCloud: false
})
}
let runningIds = []
// Interval variables to control start/stop
let mainInterval = null;
let autoResolveInterval = null;
let autoCheckInterval = null;
const getStateScript = async (id)=>{
const resp = await fetch("http://localhost:1010/api/scripts/check-status/" + id, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
const json = await resp.json();
return json.is_running;
}
const autoCheck = async ()=>{
if(runningIds.length==0) return;
for(const id of runningIds){
const result = await getStateScript(id)
if(!result){
runningIds = runningIds.filter(id => id !== id)
}
}
}
const autoResolve = async ()=>{
console.log("autoResolve")
const scripts = Object.values(await window.electronAPI.crudScript(JSON.stringify({cloud:false,action:"getAll"})))
const loginScripts = scripts.filter(script => script.name == "login_fb");
console.log(loginScripts)
if(loginScripts.length==0 || runningIds.length==5) return;
loginScripts.sort((a,b)=> new Date(a.createdAt) - new Date(b.createdAt))
const loginScript = loginScripts[0];
let profiles = await getProfiles(account.userPackage);
const needLoginProfiles = profiles.filter(profile => profile.group_name == "Need Login")
const otherBlockedProfiles = profiles.filter(profile => profile.group_name == "Other Blocked")
const block282Profiles = profiles.filter(profile => profile.group_name == "Block 282")
const block956Profiles = profiles.filter(profile => profile.group_name == "Block 956")
const groupRun = [...needLoginProfiles,...otherBlockedProfiles,...block282Profiles,...block956Profiles]
if(groupRun.length >5- runningIds.length){
groupRun.length = 5- runningIds.length;
}
for(const profile of groupRun){
const result = await runScript(loginScript.id,profile.id)
console.log("run script",profile,result)
}
}
const runScript = async (scriptId,profileId)=>{
const resp = await fetch("http://localhost:1010/api/scripts/execute/" + scriptId, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
profileId: profileId,
closeBrowser: false
})
});
const json= await resp.json();
runningIds.push(json.id);
return json;
}
const main = async () => {
let profiles = await getProfiles(account.userPackage);
const vias = await fetchVias();
for (const profile of profiles.filter(profile => profile.group_name == "Need Login" || profile.group_name == "Other Blocked" || profile.group_name == "Block 282" || profile.group_name == "Block 956")) {
const via = vias.find(via => via.username === profile.name)
if (!via || !["other_blocked","need_login","blocked_282","blocked_956"].includes(via.status)) {
console.log("delete profile",profile,via)
await window.electronAPI.deleteProfile(JSON.stringify({
profileIds: [profile.id],
pathSave: profile.pathSave,
isCloud: false
}));
}
}
for (const via of vias) {
let profile = profiles.find(profile => profile.name === via.username)
if (!profile && ["other_blocked","need_login","blocked_282","blocked_956"].includes(via.status)) {
const pathSave = readLocalStorage("pathSave");
await createProfile(via, account.userPackage, pathSave)
profiles = await getProfiles(account.userPackage);
profile = profiles.find(profile => profile.name === via.username)
}
if(!profile) continue;
if (via.status == 'other_blocked') {
await window.electronAPI.crudGroup(JSON.stringify({
action: "assignProfilesGroup",
groupId: otherBlockedGroup.id,
profiles: [profile.id],
isCloud: false
}))
}
if (via.status == 'need_login') {
await window.electronAPI.crudGroup(JSON.stringify({
action: "assignProfilesGroup",
groupId: needLoginGroup.id,
profiles: [profile.id],
isCloud: false
}))
}
if (via.status == 'blocked_282') {
await window.electronAPI.crudGroup(JSON.stringify({
action: "assignProfilesGroup",
groupId: block282Group.id,
profiles: [profile.id],
isCloud: false
}))
}
if (via.status == 'blocked_956') {
await window.electronAPI.crudGroup(JSON.stringify({
action: "assignProfilesGroup",
groupId: block956Group.id,
profiles: [profile.id],
isCloud: false
}))
}
}
}
// Functions to control intervals
const startMainSync = () => {
// Stop any existing interval first
stopMainSync();
// Then start new one
mainInterval = setInterval(() => main(), 60 * 1000);
console.log("Main sync started");
}
const stopMainSync = () => {
if (mainInterval) {
clearInterval(mainInterval);
mainInterval = null;
console.log("Main sync stopped");
}
}
const startAutoResolve = () => {
// Stop any existing interval first
stopAutoResolve();
// Then start new one
autoResolveInterval = setInterval(() => autoResolve(), 60 * 1000 * 5);
autoCheckInterval = setInterval(() => autoCheck(), 60 * 1000);
}
const stopAutoResolve = () => {
if (autoResolveInterval) {
clearInterval(autoResolveInterval);
autoResolveInterval = null;
console.log("AutoResolve stopped");
}
// Also clear any pending autoResolve calls
if (window.autoResolveTimeout) {
clearTimeout(window.autoResolveTimeout);
window.autoResolveTimeout = null;
}
if (autoCheckInterval) {
clearInterval(autoCheckInterval);
autoCheckInterval = null;
console.log("AutoCheck stopped");
}
runningIds = []
}
// Expose control functions globally
window.syncViaControls = {
startMainSync,
stopMainSync,
startAutoResolve,
stopAutoResolve,
};
console.log('syncViaControls created and available');
// Load settings and apply them
const loadAndApplySettings = () => {
const settings = window.syncViaSettingsManager.get();
console.log('Loading settings in main script:', settings);
// Apply settings - ensure proper start/stop
if (settings.mainSync) {
startMainSync();
} else {
stopMainSync();
}
if (settings.autoResolve) {
startAutoResolve();
} else {
stopAutoResolve();
}
return settings;
};
// Listen for settings changes
window.syncViaSettingsManager.addListener((settings) => {
console.log('Settings changed, applying:', settings);
loadAndApplySettings();
});
// Initialize with settings
const initialSettings = loadAndApplySettings();
// Run main once regardless of settings
await main();
} catch (error) {
console.error('Error in sync-via.js script:', error);
// Optionally, you can re-throw or handle the error differently
}
function createCollapseOption(){
// Check if option panel already exists
if (document.getElementById('sync-via-options')) {
return;
}
// Create the collapsible option panel
const optionPanel = document.createElement('div');
optionPanel.id = 'sync-via-options';
optionPanel.style.cssText = \`
position: fixed;
bottom: 20px;
right: 20px;
width: 300px;
background: #2c3e50;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 10000;
font-family: Arial, sans-serif;
color: white;
overflow: hidden;
transition: all 0.3s ease;
\`;
// Create header
const header = document.createElement('div');
header.style.cssText = \`
background: #34495e;
padding: 12px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
\`;
header.innerHTML = \`
<span style='font-weight: bold; font-size: 14px;'>Sync Via Controls</span>
<span id='collapse-arrow' style='font-size: 12px; transition: transform 0.3s ease;'>▼</span>
\`;
// Create content
const content = document.createElement('div');
content.id = 'options-content';
content.style.cssText = \`
padding: 16px;
transition: all 0.3s ease;
max-height: 400px;
overflow: hidden;
\`;
// Load settings from centralized manager
let settings = window.syncViaSettingsManager.get();
// Create toggle switch component
const createToggle = (label, key, description) => {
const container = document.createElement('div');
container.style.cssText = \`
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #3f5a7a;
\`;
const labelDiv = document.createElement('div');
labelDiv.style.cssText = \`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
\`;
const labelText = document.createElement('span');
labelText.textContent = label;
labelText.style.cssText = \`
font-weight: bold;
font-size: 13px;
\`;
const toggle = document.createElement('div');
toggle.style.cssText = \`
width: 44px;
height: 24px;
background: \${settings[key] ? '#27ae60' : '#7f8c8d'};
border-radius: 12px;
position: relative;
cursor: pointer;
transition: background 0.3s ease;
\`;
const slider = document.createElement('div');
slider.style.cssText = \`
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
position: absolute;
top: 2px;
left: \${settings[key] ? '22px' : '2px'};
transition: left 0.3s ease;
\`;
toggle.appendChild(slider);
const desc = document.createElement('div');
desc.textContent = description;
desc.style.cssText = \`
font-size: 11px;
color: #bdc3c7;
line-height: 1.4;
\`;
// Toggle functionality
toggle.addEventListener('click', () => {
try {
const newValue = !settings[key];
// Update via settings manager
window.syncViaSettingsManager.set(key, newValue);
// Update local reference
settings = window.syncViaSettingsManager.get();
console.log(\`Toggling \${key} to: \${newValue}\`);
// Update UI
toggle.style.background = newValue ? '#27ae60' : '#7f8c8d';
slider.style.left = newValue ? '22px' : '2px';
} catch (error) {
console.error('Error in toggle:', error);
// Refresh settings and revert UI
settings = window.syncViaSettingsManager.get();
toggle.style.background = settings[key] ? '#27ae60' : '#7f8c8d';
slider.style.left = settings[key] ? '22px' : '2px';
}
});
labelDiv.appendChild(labelText);
labelDiv.appendChild(toggle);
container.appendChild(labelDiv);
container.appendChild(desc);
return container;
};
// Add toggles
content.appendChild(createToggle(
'Main Sync',
'mainSync',
'Synchronizes profiles every 60 seconds with the API'
));
content.appendChild(createToggle(
'Auto Resolve',
'autoResolve',
'Automatically runs login scripts every 5 minutes'
));
// Add refresh button
const refreshDiv = document.createElement('div');
refreshDiv.style.cssText = \`
margin-top: 12px;
text-align: center;
\`;
// Collapse functionality
let isCollapsed = false;
header.addEventListener('click', () => {
isCollapsed = !isCollapsed;
const arrow = document.getElementById('collapse-arrow');
if (isCollapsed) {
content.style.maxHeight = '0px';
content.style.padding = '0 16px';
arrow.style.transform = 'rotate(-90deg)';
} else {
content.style.maxHeight = '400px';
content.style.padding = '16px';
arrow.style.transform = 'rotate(0deg)';
}
});
// Assemble the panel
optionPanel.appendChild(header);
optionPanel.appendChild(content);
document.body.appendChild(optionPanel);
console.log('Sync Via Options panel created successfully!');
}
// Ensure DOM is ready before creating the panel
function initializeOptionsPanel() {
try {
let attempts = 0;
const maxAttempts = 50; // 10 seconds max (50 * 200ms)
// Wait for syncViaControls to be available
const waitForControls = () => {
attempts++;
if (window.syncViaControls) {
console.log('syncViaControls available, creating panel...');
createCollapseOption();
} else if (attempts >= maxAttempts) {
console.error('syncViaControls not available after maximum attempts, creating panel anyway...');
createCollapseOption();
} else {
console.log("Waiting for syncViaControls... (attempt " + attempts + "/" + maxAttempts + ")");
setTimeout(waitForControls, 500);
}
};
// Start waiting immediately
waitForControls();
} catch (error) {
console.error('Error creating options panel:', error);
// Retry after a delay
setTimeout(() => {
try {
createCollapseOption();
} catch (retryError) {
console.error('Retry failed:', retryError);
}
}, 2000);
}
}
// Initialize the options panel with a slight delay to ensure syncViaControls is ready
setTimeout(() => {
initializeOptionsPanel();
}, 500);
// Add cleanup functionality
window.addEventListener('beforeunload', () => {
if (window.syncViaControls) {
window.syncViaControls.stopMainSync();
window.syncViaControls.stopAutoResolve();
console.log('Cleanup: All intervals stopped');
}
});
// Also add a manual trigger function
window.showSyncViaOptions = createCollapseOption;
// Add debug function
window.checkSyncViaStatus = () => {
console.log('Checking syncViaControls status...');
console.log('window.syncViaControls exists:', !!window.syncViaControls);
console.log('document.readyState:', document.readyState);
console.log('Panel exists:', !!document.getElementById('sync-via-options'));
console.log('Settings:', window.syncViaSettingsManager.get());
if (window.syncViaControls) {
console.log('syncViaControls functions:', Object.keys(window.syncViaControls));
}
return {
controlsAvailable: !!window.syncViaControls,
panelExists: !!document.getElementById('sync-via-options'),
readyState: document.readyState,
settings: window.syncViaSettingsManager.get()
};
};
})();
`;
(document.head || document.documentElement).appendChild(script);
// inject html collapse option toggle autoResolve and autoCheck
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment