Skip to content

Instantly share code, notes, and snippets.

@dvygolov
Created October 20, 2025 11:26
Show Gist options
  • Select an option

  • Save dvygolov/d35dd99713853de00d98f9493f0a6646 to your computer and use it in GitHub Desktop.

Select an option

Save dvygolov/d35dd99713853de00d98f9493f0a6646 to your computer and use it in GitHub Desktop.
This script can import/export blocked users to/from your Meta's Fan Pages. Run it in browser's console in Ads Manager.
class FileHelper {
readTextFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject("Error reading file");
reader.readAsText(file);
});
}
downloadFile(content, filename) {
const blob = new Blob([content], { type: "text/plain" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
}
}
class BlockedUsersAPI {
constructor() {
this.graphUrl = null;
this.apiUrl = "https://adsmanager-graph.facebook.com/v22.0";
}
async privateApiRequest(profileId, variables, docId) {
const graphUrl = `https://www.facebook.com/api/graphql/`;
const lsd = require("LSD").token;
const dtsg = require("DTSGInitialData").token;
const body = {
av: profileId,
__user: profileId,
__a: 1,
__comet_req: 15,
fb_dtsg: dtsg,
lsd: lsd,
fb_api_caller_class: "RelayModern",
variables: JSON.stringify(variables),
server_timestamps: true,
doc_id: docId,
};
const headers = {
accept: "*/*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/x-www-form-urlencoded",
"sec-ch-ua":
'"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-fb-lsd": lsd,
};
const response = await fetch(graphUrl, {
headers: headers,
referrer: "https://www.facebook.com/",
referrerPolicy: "strict-origin-when-cross-origin",
body: new URLSearchParams(body).toString(),
method: "POST",
mode: "cors",
credentials: "include",
});
let text = await response.text();
if (text.startsWith("for (;;);")) text = text.substring(9);
return JSON.parse(text);
}
async switchProfile(fromProfileId, toProfileId) {
let lsd = require("LSD").token;
let dtsg = require("DTSGInitialData").token;
const bodyParams = {
av: fromProfileId,
__user: fromProfileId,
__a: "1",
__req: "1e",
dpr: "1",
__ccg: "EXCELLENT",
__comet_req: "15",
fb_dtsg: dtsg,
lsd: lsd,
__crn: "comet.fbweb.CometHomeRoute",
fb_api_caller_class: "RelayModern",
fb_api_req_friendly_name: "CometProfileSwitchMutation",
server_timestamps: "true",
variables: '{"profile_id":"' + toProfileId + '"}',
doc_id: "29569331136046912"
};
var f = await fetch("https://www.facebook.com/api/graphql/", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/x-www-form-urlencoded",
"priority": "u=1, i",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-model": "\"\"",
"sec-ch-ua-platform": "\"Windows\"",
"sec-ch-ua-platform-version": "\"15.0.0\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-fb-friendly-name": "CometProfileSwitchMutation",
"x-fb-lsd": lsd
},
"referrer": "https://www.facebook.com/index.php",
"body": new URLSearchParams(bodyParams).toString(),
"method": "POST",
"mode": "cors",
"credentials": "include"
});
var t = await f.text();
var resp = JSON.parse(t);
console.log("Switch profile response:", resp);
return resp?.data?.profile_switcher_comet_login;
}
getCurrentProfile() {
const cookie = document.cookie;
const match = cookie.match(/i_user=([^;]+)/);
return match ? match[1] : null;
}
async getPages() {
const headers = {
accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9",
"cache-control": "max-age=0",
"sec-ch-ua":
'"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
};
const response = await fetch(
`${this.apiUrl}/me/accounts?access_token=${__accessToken}&fields=id,name,additional_profile_id,access_token`,
{
headers: headers,
referrerPolicy: "strict-origin-when-cross-origin",
body: null,
method: "GET",
mode: "cors",
credentials: "include",
}
);
const json = await response.json();
return json.data || [];
}
async exportBlockedUsers(profile_id) {
const js = await this.privateApiRequest(
profile_id,
{ settingType: "USER", profile_picture_size: 36 },
5380804175301552
);
let setting = js.data.viewer.privacy_block_settings.setting;
const users = [];
for (const edge of setting.blockees.edges) {
users.push(edge.node.id);
}
while (setting.blockees.page_info.has_next_page) {
const nextPage = await this.privateApiRequest(
profile_id,
{
count: 100,
cursor: setting.blockees.page_info.end_cursor,
profile_picture_size: 36,
id: setting.id,
},
5186554984768632
);
setting = nextPage.data.node;
for (const edge of setting.blockees.edges) {
users.push(edge.node.id);
}
}
return users;
}
async blockUsers(fpId, fpToken, userIds) {
const headers = {
accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9",
"cache-control": "max-age=0",
"sec-ch-ua":
'"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
};
// Format as array of numbers: [id1,id2,id3]
const userArray = `[${userIds.join(",")}]`;
const response = await fetch(
`${this.apiUrl}/${fpId}/blocked?access_token=${fpToken}&method=post&user=${userArray}`,
{
headers: headers,
referrerPolicy: "strict-origin-when-cross-origin",
body: null,
method: "GET",
mode: "cors",
credentials: "include",
}
);
const json = await response.json();
return json;
}
async getMainUser() {
const user = await require("AdsGraphAPI")
.get("22.0")
.me()
.get({ fields: ["name", "id"] });
return user;
}
}
class UIManager {
constructor(api, fileHelper) {
this.api = api;
this.fileHelper = fileHelper;
this.modal = null;
this.pages = [];
this.selectedPage = null;
this.importCancelled = false;
}
createStyles() {
const style = document.createElement("style");
style.textContent = `
.fbblocked-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600px;
max-height: 80vh;
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(255, 193, 7, 0.3);
z-index: 10000;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #ffc107;
overflow-y: auto;
border: 2px solid #ffc107;
}
.fbblocked-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.fbblocked-header-left {
display: flex;
flex-direction: column;
gap: 4px;
}
.fbblocked-credit {
font-size: 11px;
color: #999;
}
.fbblocked-credit a {
color: #ffc107;
text-decoration: none;
transition: color 0.3s;
}
.fbblocked-credit a:hover {
color: #ffeb3b;
text-decoration: underline;
}
.fbblocked-title {
font-size: 20px;
font-weight: bold;
margin: 0;
color: #ffc107;
}
.fbblocked-close {
background: rgba(255, 193, 7, 0.2);
border: 1px solid #ffc107;
color: #ffc107;
font-size: 20px;
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s;
}
.fbblocked-close:hover {
background: #ffc107;
color: #000;
}
.fbblocked-section {
background: rgba(0, 0, 0, 0.4);
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
color: #ffc107;
border: 1px solid rgba(255, 193, 7, 0.3);
}
.fbblocked-section-title {
font-weight: bold;
font-size: 14px;
margin-bottom: 10px;
color: #ffeb3b;
}
.fbblocked-page-selector {
width: 100%;
padding: 10px;
border: 2px solid #ffc107;
border-radius: 6px;
font-size: 14px;
background: #000;
color: #ffc107;
cursor: pointer;
transition: border-color 0.3s;
}
.fbblocked-page-selector:focus {
outline: none;
border-color: #ffeb3b;
}
.fbblocked-page-selector option {
background: #000;
color: #ffc107;
}
.fbblocked-page-option {
padding: 8px;
display: flex;
align-items: center;
gap: 10px;
}
.fbblocked-page-icon {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.fbblocked-page-info {
flex: 1;
}
.fbblocked-page-name {
font-weight: bold;
font-size: 14px;
}
.fbblocked-page-id {
font-size: 11px;
color: #999;
}
.fbblocked-buttons {
display: flex;
gap: 10px;
margin-top: 10px;
}
.fbblocked-btn {
flex: 1;
padding: 12px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.fbblocked-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.fbblocked-btn:active {
transform: translateY(0);
}
.fbblocked-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.fbblocked-btn-export {
background: #ffc107;
color: #000;
border: 2px solid #ffc107;
}
.fbblocked-btn-export:hover {
background: #ffeb3b;
border-color: #ffeb3b;
}
.fbblocked-btn-import {
background: transparent;
color: #ffc107;
border: 2px solid #ffc107;
}
.fbblocked-btn-import:hover {
background: #ffc107;
color: #000;
}
.fbblocked-btn-stop {
background: #ff5722;
color: #000;
border: 2px solid #ff5722;
display: none;
margin-top: 10px;
}
.fbblocked-btn-stop:hover {
background: #ff7043;
border-color: #ff7043;
}
.fbblocked-btn-stop.active {
display: block;
}
.fbblocked-progress-container {
margin-top: 10px;
display: none;
}
.fbblocked-progress-bar {
width: 100%;
height: 24px;
border-radius: 12px;
overflow: hidden;
background: #000;
border: 1px solid #ffc107;
}
.fbblocked-progress-fill {
height: 100%;
background: linear-gradient(90deg, #ffc107 0%, #ffeb3b 100%);
transition: width 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
color: #000;
font-size: 12px;
font-weight: bold;
}
.fbblocked-progress-text {
margin-top: 5px;
font-size: 12px;
text-align: center;
color: #ffc107;
}
.fbblocked-log {
background: #000;
color: #ffc107;
padding: 10px;
border-radius: 6px;
max-height: 200px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid rgba(255, 193, 7, 0.3);
}
.fbblocked-log-entry {
margin: 2px 0;
}
.fbblocked-log-info {
color: #ffc107;
}
.fbblocked-log-error {
color: #ff5722;
}
.fbblocked-log-warning {
color: #ffeb3b;
}
.fbblocked-log-time {
color: #999;
}
.fbblocked-file-input {
display: none;
}
`;
document.head.appendChild(style);
}
async createModal() {
this.createStyles();
this.modal = document.createElement("div");
this.modal.className = "fbblocked-modal";
this.modal.innerHTML = `
<div class="fbblocked-header">
<div class="fbblocked-header-left">
<h2 class="fbblocked-title">FB Blocked Users Manager v1.1</h2>
<div class="fbblocked-credit">by <a href="https://yellowweb.top" target="_blank">Yellow Web</a></div>
</div>
<button class="fbblocked-close">×</button>
</div>
<div class="fbblocked-section">
<div class="fbblocked-section-title">Select Fan Page</div>
<select class="fbblocked-page-selector" id="fbblocked-page-select">
<option value="">Loading pages...</option>
</select>
</div>
<div class="fbblocked-section">
<div class="fbblocked-section-title">Actions</div>
<div class="fbblocked-buttons">
<button class="fbblocked-btn fbblocked-btn-export" id="fbblocked-export-btn">
Export Blocked Users
</button>
<button class="fbblocked-btn fbblocked-btn-import" id="fbblocked-import-btn">
Import Blocked Users
</button>
</div>
<input type="file" class="fbblocked-file-input" id="fbblocked-file-input" accept=".txt">
<button class="fbblocked-btn fbblocked-btn-stop" id="fbblocked-stop-btn">
Stop Import
</button>
<div class="fbblocked-progress-container" id="fbblocked-progress-container">
<div class="fbblocked-progress-bar">
<div class="fbblocked-progress-fill" id="fbblocked-progress-fill">0%</div>
</div>
<div class="fbblocked-progress-text" id="fbblocked-progress-text"></div>
</div>
</div>
<div class="fbblocked-section">
<div class="fbblocked-section-title">Log</div>
<div class="fbblocked-log" id="fbblocked-log">
<div class="fbblocked-log-entry fbblocked-log-info">
<span class="fbblocked-log-time">[${this.getTime()}]</span> Manager initialized
</div>
</div>
</div>
`;
document.body.appendChild(this.modal);
// Event listeners
this.modal.querySelector(".fbblocked-close").onclick = () => this.close();
this.modal
.querySelector("#fbblocked-export-btn")
.onclick = () => this.handleExport();
this.modal
.querySelector("#fbblocked-import-btn")
.onclick = () => this.handleImport();
this.modal
.querySelector("#fbblocked-stop-btn")
.onclick = () => this.cancelImport();
await this.loadPages();
}
async loadPages() {
try {
this.log("Loading fan pages...", "info");
this.pages = await this.api.getPages();
const select = this.modal.querySelector("#fbblocked-page-select");
select.innerHTML = '<option value="">-- Select a Fan Page --</option>';
this.pages.forEach((page) => {
const option = document.createElement("option");
option.value = page.id;
option.textContent = `${page.name} (ID: ${page.id})`;
option.dataset.token = page.access_token;
option.dataset.additional_profile_id = page.additional_profile_id;
select.appendChild(option);
});
this.log(`Loaded ${this.pages.length} fan pages`, "info");
} catch (error) {
this.log(`Error loading pages: ${error.message}`, "error");
}
}
getSelectedPage() {
const select = this.modal.querySelector("#fbblocked-page-select");
const selectedId = select.value;
if (!selectedId) return null;
const selectedOption = select.options[select.selectedIndex];
return {
id: selectedId,
name: select.options[select.selectedIndex].textContent,
access_token: selectedOption.dataset.token,
additional_profile_id: selectedOption.dataset.additional_profile_id,
};
}
async handleExport() {
const page = this.getSelectedPage();
if (!page) {
alert("Please select a fan page first!");
return;
}
const exportBtn = this.modal.querySelector("#fbblocked-export-btn");
const importBtn = this.modal.querySelector("#fbblocked-import-btn");
exportBtn.disabled = true;
importBtn.disabled = true;
try {
this.log(`Starting export for page: ${page.name}`, "info");
// Check current profile
const currentProfile = this.api.getCurrentProfile();
const mainUser = await this.api.getMainUser();
this.log(`Main user: ${mainUser.name} (${mainUser.id})`, "info");
// Switch to page profile
if (currentProfile !== page.additional_profile_id) {
this.log(`Switching to page profile ${page.name}...`, "info");
const switched = await this.api.switchProfile(mainUser.id,page.additional_profile_id);
if (!switched) {
throw new Error("Failed to switch to page profile");
}
this.log(`SWITCHED to page profile successfully`, "info");
// Wait a bit for the switch to complete
await this.sleep(1000);
} else {
this.log(`Already on page profile`, "info");
}
// Export blocked users
this.log(`Fetching blocked users...`, "info");
const users = await this.api.exportBlockedUsers(page.additional_profile_id);
this.log(`Found ${users.length} blocked users`, "info");
this.log(`Switching back to main profile...`, "info");
await this.api.switchProfile(page.additional_profile_id, mainUser.id);
this.log(`SWITCHED back to main profile`, "info");
// Download file
const content = users.join("\n");
const filename = `blocked_users_${page.id}_${Date.now()}.txt`;
this.fileHelper.downloadFile(content, filename);
this.log(`✅ Export complete! File: ${filename}`, "info");
} catch (error) {
this.log(`❌ Export failed: ${error.message}`, "error");
console.error(error);
} finally {
exportBtn.disabled = false;
importBtn.disabled = false;
}
}
async handleImport() {
const page = this.getSelectedPage();
if (!page) {
alert("Please select a fan page first!");
return;
}
const fileInput = this.modal.querySelector("#fbblocked-file-input");
fileInput.click();
fileInput.onchange = async () => {
if (!fileInput.files || fileInput.files.length === 0) {
return;
}
const file = fileInput.files[0];
await this.processImport(page, file);
fileInput.value = ""; // Reset input
};
}
async processImport(page, file) {
const exportBtn = this.modal.querySelector("#fbblocked-export-btn");
const importBtn = this.modal.querySelector("#fbblocked-import-btn");
const stopBtn = this.modal.querySelector("#fbblocked-stop-btn");
const progressContainer = this.modal.querySelector(
"#fbblocked-progress-container"
);
this.importCancelled = false;
exportBtn.disabled = true;
importBtn.disabled = true;
stopBtn.classList.add("active");
progressContainer.style.display = "block";
try {
this.log(`Starting import for page: ${page.name}`, "info");
// Read file
const content = await this.fileHelper.readTextFile(file);
const lines = content.split("\n").filter((line) => line.trim());
// Extract user IDs
const userIds = [];
const idRegex = /\d+/;
for (const line of lines) {
const match = idRegex.exec(line);
if (match) {
userIds.push(match[0]);
}
}
this.log(`Found ${userIds.length} user IDs in file`, "info");
if (userIds.length === 0) {
throw new Error("No valid user IDs found in file");
}
// Ensure we're on main profile
const currentProfile = this.api.getCurrentProfile();
if (currentProfile) {
this.log(`Currently on page profile, switching to main...`, "warning");
const mainUser = await this.api.getMainUser();
await this.api.switchProfile(currentProfile, mainUser.id);
await this.sleep(1000);
this.log(`Switched to main profile`, "info");
}
// Create batches
const batchSize = 100;
const batches = [];
for (let i = 0; i < userIds.length; i += batchSize) {
batches.push(userIds.slice(i, i + batchSize));
}
this.log(`Created ${batches.length} batches (max ${batchSize} users each)`, "info");
// Process batches
let processedUsers = 0;
for (let i = 0; i < batches.length; i++) {
// Check if cancelled
if (this.importCancelled) {
this.log(
`🛑 Import cancelled by user at batch ${i + 1}/${batches.length}`,
"warning"
);
break;
}
const batch = batches[i];
const progress = ((i + 1) / batches.length) * 100;
this.updateProgress(
progress,
`Sending batch ${i + 1} of ${batches.length} (${batch.length} users)`
);
this.log(
`📤 Batch ${i + 1}/${batches.length} sent (${batch.length} users)`,
"info"
);
// Send batch without checking result
try {
await this.api.blockUsers(
page.id,
page.access_token,
batch
);
processedUsers += batch.length;
} catch (error) {
// Log error but continue
this.log(
`⚠️ Batch ${i + 1} API error (continuing anyway): ${error.message}`,
"warning"
);
processedUsers += batch.length;
}
// Small delay between batches
await this.sleep(500);
}
if (this.importCancelled) {
this.log(
`Import stopped. Total users sent: ${processedUsers}`,
"warning"
);
} else {
this.log(
`✅ Import complete! Total users sent: ${processedUsers}`,
"info"
);
}
} catch (error) {
this.log(`❌ Import failed: ${error.message}`, "error");
console.error(error);
} finally {
exportBtn.disabled = false;
importBtn.disabled = false;
stopBtn.classList.remove("active");
progressContainer.style.display = "none";
this.importCancelled = false;
}
}
cancelImport() {
if (confirm("Are you sure you want to stop the import?")) {
this.importCancelled = true;
this.log("Cancellation requested, stopping after current batch...", "warning");
}
}
updateProgress(percentage, text) {
const fill = this.modal.querySelector("#fbblocked-progress-fill");
const textEl = this.modal.querySelector("#fbblocked-progress-text");
fill.style.width = `${percentage}%`;
fill.textContent = `${Math.round(percentage)}%`;
textEl.textContent = text;
}
log(message, type = "info") {
const logEl = this.modal.querySelector("#fbblocked-log");
const entry = document.createElement("div");
entry.className = `fbblocked-log-entry fbblocked-log-${type}`;
entry.innerHTML = `<span class="fbblocked-log-time">[${this.getTime()}]</span> ${message}`;
logEl.appendChild(entry);
logEl.scrollTop = logEl.scrollHeight;
}
getTime() {
const now = new Date();
return now.toLocaleTimeString("en-US", { hour12: false });
}
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
close() {
if (this.modal) {
document.body.removeChild(this.modal);
}
}
async show() {
await this.createModal();
}
}
// Main execution
(async function () {
try {
const api = new BlockedUsersAPI();
const fileHelper = new FileHelper();
const ui = new UIManager(api, fileHelper);
await ui.show();
} catch (error) {
console.error("FB Blocked Manager Error:", error);
alert(`Error: ${error.message}`);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment