Skip to content

Instantly share code, notes, and snippets.

@dvygolov
Created August 27, 2025 13:14
Show Gist options
  • Save dvygolov/4449da3e5aa420d4ecf0b4b37f5e7b82 to your computer and use it in GitHub Desktop.
Save dvygolov/4449da3e5aa420d4ecf0b4b37f5e7b82 to your computer and use it in GitHub Desktop.
Script to manage column presets of Ads Manager: import/export columns
class FileSelector {
constructor(fileProcessor) {
this.fileProcessor = fileProcessor;
}
createDiv() {
this.div = document.createElement("div");
this.div.style.position = "fixed";
this.div.style.top = "50%";
this.div.style.left = "50%";
this.div.style.transform = "translate(-50%, -50%)";
this.div.style.width = "200px"; // Updated width
this.div.style.height = "120px";
this.div.style.backgroundColor = "yellow";
this.div.style.zIndex = "1000";
this.div.style.display = "flex";
this.div.style.flexDirection = "column"; // To align items vertically
this.div.style.alignItems = "center";
this.div.style.justifyContent = "center";
this.div.style.padding = "10px"; // Added padding
this.div.style.boxSizing = "border-box"; // To include padding in width/height
this.div.style.borderRadius = "10px";
// Create and style the title
var title = document.createElement("div");
title.innerHTML =
"<br>FB Column Preset Manager v2.1<br>TEAM Edition<br>by Yellow Web<br><br>";
title.style.textAlign = "center";
title.style.fontWeight = "bold";
// Create and style the close button
var closeButton = document.createElement("button");
closeButton.innerHTML = "X";
closeButton.style.position = "absolute";
closeButton.style.top = "5px";
closeButton.style.right = "5px";
closeButton.style.border = "none";
closeButton.style.background = "none";
closeButton.style.cursor = "pointer";
closeButton.onclick = () => {
document.body.removeChild(this.div);
};
this.div.appendChild(title);
this.div.appendChild(closeButton);
}
createFileInput() {
// Create the file input and handle file selection
this.fileInput = document.createElement("input");
this.fileInput.type = "file";
this.fileInput.accept = ".json"; // Accept only JSON files
this.fileInput.style.display = "none";
}
createButton() {
// Create the button
this.button = document.createElement("button");
this.button.textContent = "Open Preset";
this.button.onclick = () => {
this.fileInput.click();
};
}
show() {
return new Promise((resolve, reject) => {
this.createDiv();
this.createFileInput();
this.createButton();
// Append elements to the div and the div to the body
this.div.appendChild(this.button);
this.div.appendChild(this.fileInput);
document.body.appendChild(this.div);
this.fileInput.onchange = async () => {
// If no file is selected (user cancelled)
if (!this.fileInput.files || this.fileInput.files.length === 0) {
document.body.removeChild(this.div);
alert("Operation canceled");
reject("File selection cancelled by user");
return;
}
try {
// Process the file and resolve the promise
const result = await this.fileProcessor(this.fileInput.files[0]);
document.body.removeChild(this.div);
resolve(result);
} catch (error) {
// Handle any errors in processing
document.body.removeChild(this.div);
reject(error);
}
};
});
}
}
class FileHelper {
async readFileAsJsonAsync(file) {
try {
const fileContent = await this.readFileAsync(file);
return JSON.parse(fileContent);
} catch (error) {
console.error("Error:", error);
throw error;
}
}
readFileAsync(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = () => {
reject("Error reading file");
};
reader.readAsText(file); // Read the file as text
});
}
}
class FbApi {
apiUrl = "https://adsmanager-graph.facebook.com/v20.0/";
debug = false;
constructor(debug) {
this.debug = debug;
}
async getRequest(path, qs, token = null) {
path = path.startsWith(this.apiUrl) ? path : `${this.apiUrl}${path}`;
token = token ?? __accessToken;
let url = path;
if (!path.includes(token)) {
let qsPart = qs === null || qs === "" ? "?" : `?${qs}&`;
url = `${path}${qsPart}access_token=${token}`;
}
let f = await fetch(url, {
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": "ca-ES,ca;q=0.9,en-US;q=0.8,en;q=0.7",
"cache-control": "max-age=0",
"sec-ch-ua":
'"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
},
referrerPolicy: "strict-origin-when-cross-origin",
body: null,
method: "GET",
mode: "cors",
credentials: "include",
referrer: "https://business.facebook.com/",
referrerPolicy: "origin-when-cross-origin",
});
let json = await f.json();
if (this.debug) console.log(JSON.stringify(json));
return json;
}
async getAllPages(path, qs, token = null) {
let items = [];
let page = await this.getRequest(path, qs, token);
items = items.concat(page.data);
let i = 2;
while (page.paging && page.paging.next) {
if (this.debug) console.log(`Getting page #${i}...`);
page = await this.getRequest(page.paging.next, "", token);
items = items.concat(page.data);
i++;
}
return items;
}
async postRequest(path, body, token = null) {
token = token ?? __accessToken;
body["access_token"] = token;
let headers = {
accept: "*/*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/x-www-form-urlencoded",
"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": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
};
let f = await fetch(`${this.apiUrl}${path}`, {
headers: headers,
referrer: "https://business.facebook.com/",
referrerPolicy: "origin-when-cross-origin",
body: new URLSearchParams(body).toString(),
method: "POST",
mode: "cors",
credentials: "include",
});
let json = await f.json();
if (this.debug) console.log(JSON.stringify(json));
return json;
}
}
const DEBUG = true;
const API = new FbApi(DEBUG);
async function main() {
const choice = prompt(
`Select an option:
1. Export column preset
2. Import column preset to this account
3. Import column preset to ALL accounts
4. Import column sizes to this account`,
);
var fh = new FileHelper();
try {
switch (choice) {
case "1":
await exportColumnPreset();
alert("Export complete!");
break;
case "2":
var fileSelector = new FileSelector(fh.readFileAsJsonAsync.bind(fh));
debug(`Opening and reading preset file...`);
var presetContent = await fileSelector.show();
debug(`Got preset file content!`);
const userSettingsId = await fetchUserSettingsId();
let presetId = await uploadPreset(userSettingsId, presetContent.preset);
await setDefaultColumnPreset(null, presetId);
if (confirm("Import complete! Reload page with new preset?"))
reloadPageWithPreset(presetId);
break;
case "3":
var fileSelector = new FileSelector(fh.readFileAsJsonAsync.bind(fh));
debug(`Opening and reading preset file...`);
var presetContent = await fileSelector.show();
debug(`Got preset file content!`);
let adAccounts = await getAllAdAccounts();
debug(`Total account count is: ${adAccounts.length}.`);
for (let i = 0; i < adAccounts.length; i++) {
debug(`Processing account #${i} - ${adAccounts[i]}...`);
const userSettingsId = await fetchUserSettingsId(adAccounts[i]);
let presetId = await uploadPreset(
userSettingsId,
presetContent.preset,
);
await setDefaultColumnPreset(adAccounts[i], presetId);
}
alert("Import complete!");
break;
case "4":
var fileSelector = new FileSelector(fh.readFileAsJsonAsync.bind(fh));
debug(`Opening and reading preset file...`);
var presetContent = await fileSelector.show();
debug(`Got preset file content!`);
for (let i = 0; i < presetContent.sizes.length; i++) {
debug(`Uploading size #${i} to current account...`);
await uploadSize(null, presetContent.sizes[i]);
}
alert("Import complete!");
break;
default:
alert("Invalid option!");
break;
}
} catch (error) {
console.error("Error:", error);
}
}
async function exportColumnPreset() {
let adAccountId = require("BusinessUnifiedNavigationContext").adAccountID;
let js = await API.getRequest(
`act_${adAccountId}`,
`fields=["user_settings{id,column_presets{attribution_windows,columns,id,name,time_created,time_updated}},ad_column_sizes{page,tab,report,view,columns}"]`,
);
const presets = js.user_settings.column_presets.data;
if (presets.length === 0) {
alert("No presets available");
return;
}
let presetList = "Select a preset by number:\n";
presets.forEach((preset, index) => {
presetList += `${index + 1}. ${preset.name}\n`;
});
const selectedNumber = parseInt(prompt(presetList), 10);
if (selectedNumber < 1 || selectedNumber > presets.length) {
alert("Invalid selection");
return;
}
const selectedPreset = presets[selectedNumber - 1];
const jsFile = {
preset: selectedPreset,
sizes: js.ad_column_sizes.data,
};
const blob = new Blob([JSON.stringify(jsFile)], {
type: "application/json",
});
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `${selectedPreset.name}.json`;
a.click();
}
async function fetchUserSettingsId(adAccountId) {
let accId =
adAccountId ?? require("BusinessUnifiedNavigationContext").adAccountID;
debug(`Getting user settings for acc ${accId}...`);
let js = await API.getRequest(`act_${accId}`, `fields=[]`);
let usId = js?.user_settings?.id;
if (usId == null) {
debug(`No default user settings found! Creating them...`);
js = await API.getRequest(`act_${accId}/user_settings`, `method=post`);
usId = js.id;
}
return usId;
}
async function uploadPreset(userSettingsId, presetData) {
let data = {
name: presetData.name,
attribution_windows: JSON.stringify(presetData.attribution_windows),
columns: JSON.stringify(presetData.columns),
};
debug(
`Uploading preset ${presetData.name} to user settings ${userSettingsId}...`,
);
let js = await API.postRequest(`${userSettingsId}/column_presets`, data);
return js.id;
}
async function setDefaultColumnPreset(adAccountId, presetId) {
let accId =
adAccountId ?? require("BusinessUnifiedNavigationContext").adAccountID;
debug(
`Setting default column preset for acc ${accId}, preset id ${presetId}...`,
);
let data = {
default_column_preset: `{ "id": "${presetId}" }`,
default_column_preset_id: presetId,
};
let js = await API.postRequest(`act_${accId}/user_settings`, data);
return js;
}
async function uploadSize(adAccountId, size) {
let accId =
adAccountId ?? require("BusinessUnifiedNavigationContext").adAccountID;
const columns = size.columns.reduce((acc, { key, value }) => {
acc[key] = parseInt(value, 10);
return acc;
}, {});
let data = {
page: size.page,
tab: size.tab,
columns: JSON.stringify(columns),
};
debug(`Uploading sizes to ad account ${accId}...`);
let js = await API.postRequest(`act_${accId}/ad_column_sizes`, data);
const sizeId = js.id;
js = await API.postRequest(sizeId, data);
return js.success;
}
async function getAllAdAccounts() {
let adAccounts = [];
debug("Getting personal ad accounts...");
let js = await API.getAllPages("me/personal_ad_accounts", "fields=id");
adAccounts = js.map((item) => item.id.replace("act_", ""));
debug(`Got ${adAccounts.length} personal ad accounts!`);
debug("Getting business managers...");
js = await API.getAllPages("me/businesses", "fields=id");
let bmIds = js.map((item) => item.id);
debug(`Got ${bmIds.length} business managers!`);
for (let i = 0; i < bmIds.length; i++) {
let bmId = bmIds[i];
debug(`Processing BM ${bmId}...`);
js = await API.getAllPages(`${bmId}/owned_ad_accounts`, "fields=id");
let owned = js.map((item) => item.id.replace("act_", ""));
debug(`Got ${owned.length} owned accounts.`);
adAccounts = adAccounts.concat(owned);
js = await API.getAllPages(`${bmId}/client_ad_accounts`, "fields=id");
let client = js.map((item) => item.id.replace("act_", ""));
debug(`Got ${client.length} client accounts.`);
adAccounts = adAccounts.concat(client);
}
return adAccounts;
}
function reloadPageWithPreset(presetId) {
const urlObj = new URL(window.location.href);
urlObj.searchParams.set("column_preset", presetId);
window.location.href = urlObj.toString();
}
function debug(msg) {
if (DEBUG) console.log(msg);
}
main();
@dvygolov
Copy link
Author

javascript:eval("(async () => {" + atob("Y2xhc3MgRmlsZVNlbGVjdG9yIHsKICBjb25zdHJ1Y3RvcihmaWxlUHJvY2Vzc29yKSB7CiAgICB0aGlzLmZpbGVQcm9jZXNzb3IgPSBmaWxlUHJvY2Vzc29yOwogIH0KCiAgY3JlYXRlRGl2KCkgewogICAgdGhpcy5kaXYgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTsKICAgIHRoaXMuZGl2LnN0eWxlLnBvc2l0aW9uID0gImZpeGVkIjsKICAgIHRoaXMuZGl2LnN0eWxlLnRvcCA9ICI1MCUiOwogICAgdGhpcy5kaXYuc3R5bGUubGVmdCA9ICI1MCUiOwogICAgdGhpcy5kaXYuc3R5bGUudHJhbnNmb3JtID0gInRyYW5zbGF0ZSgtNTAlLCAtNTAlKSI7CiAgICB0aGlzLmRpdi5zdHlsZS53aWR0aCA9ICIyMDBweCI7IC8vIFVwZGF0ZWQgd2lkdGgKICAgIHRoaXMuZGl2LnN0eWxlLmhlaWdodCA9ICIxMjBweCI7CiAgICB0aGlzLmRpdi5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAieWVsbG93IjsKICAgIHRoaXMuZGl2LnN0eWxlLnpJbmRleCA9ICIxMDAwIjsKICAgIHRoaXMuZGl2LnN0eWxlLmRpc3BsYXkgPSAiZmxleCI7CiAgICB0aGlzLmRpdi5zdHlsZS5mbGV4RGlyZWN0aW9uID0gImNvbHVtbiI7IC8vIFRvIGFsaWduIGl0ZW1zIHZlcnRpY2FsbHkKICAgIHRoaXMuZGl2LnN0eWxlLmFsaWduSXRlbXMgPSAiY2VudGVyIjsKICAgIHRoaXMuZGl2LnN0eWxlLmp1c3RpZnlDb250ZW50ID0gImNlbnRlciI7CiAgICB0aGlzLmRpdi5zdHlsZS5wYWRkaW5nID0gIjEwcHgiOyAvLyBBZGRlZCBwYWRkaW5nCiAgICB0aGlzLmRpdi5zdHlsZS5ib3hTaXppbmcgPSAiYm9yZGVyLWJveCI7IC8vIFRvIGluY2x1ZGUgcGFkZGluZyBpbiB3aWR0aC9oZWlnaHQKICAgIHRoaXMuZGl2LnN0eWxlLmJvcmRlclJhZGl1cyA9ICIxMHB4IjsKCiAgICAvLyBDcmVhdGUgYW5kIHN0eWxlIHRoZSB0aXRsZQogICAgdmFyIHRpdGxlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7CiAgICB0aXRsZS5pbm5lckhUTUwgPQogICAgICAiPGJyPkZCIENvbHVtbiBQcmVzZXQgTWFuYWdlciB2Mi4xPGJyPlRFQU0gRWRpdGlvbjxicj5ieSBZZWxsb3cgV2ViPGJyPjxicj4iOwogICAgdGl0bGUuc3R5bGUudGV4dEFsaWduID0gImNlbnRlciI7CiAgICB0aXRsZS5zdHlsZS5mb250V2VpZ2h0ID0gImJvbGQiOwoKICAgIC8vIENyZWF0ZSBhbmQgc3R5bGUgdGhlIGNsb3NlIGJ1dHRvbgogICAgdmFyIGNsb3NlQnV0dG9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnV0dG9uIik7CiAgICBjbG9zZUJ1dHRvbi5pbm5lckhUTUwgPSAiWCI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS5wb3NpdGlvbiA9ICJhYnNvbHV0ZSI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS50b3AgPSAiNXB4IjsKICAgIGNsb3NlQnV0dG9uLnN0eWxlLnJpZ2h0ID0gIjVweCI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS5ib3JkZXIgPSAibm9uZSI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS5iYWNrZ3JvdW5kID0gIm5vbmUiOwogICAgY2xvc2VCdXR0b24uc3R5bGUuY3Vyc29yID0gInBvaW50ZXIiOwogICAgY2xvc2VCdXR0b24ub25jbGljayA9ICgpID0+IHsKICAgICAgZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZCh0aGlzLmRpdik7CiAgICB9OwoKICAgIHRoaXMuZGl2LmFwcGVuZENoaWxkKHRpdGxlKTsKICAgIHRoaXMuZGl2LmFwcGVuZENoaWxkKGNsb3NlQnV0dG9uKTsKICB9CgogIGNyZWF0ZUZpbGVJbnB1dCgpIHsKICAgIC8vIENyZWF0ZSB0aGUgZmlsZSBpbnB1dCBhbmQgaGFuZGxlIGZpbGUgc2VsZWN0aW9uCiAgICB0aGlzLmZpbGVJbnB1dCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImlucHV0Iik7CiAgICB0aGlzLmZpbGVJbnB1dC50eXBlID0gImZpbGUiOwogICAgdGhpcy5maWxlSW5wdXQuYWNjZXB0ID0gIi5qc29uIjsgLy8gQWNjZXB0IG9ubHkgSlNPTiBmaWxlcwogICAgdGhpcy5maWxlSW5wdXQuc3R5bGUuZGlzcGxheSA9ICJub25lIjsKICB9CgogIGNyZWF0ZUJ1dHRvbigpIHsKICAgIC8vIENyZWF0ZSB0aGUgYnV0dG9uCiAgICB0aGlzLmJ1dHRvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImJ1dHRvbiIpOwogICAgdGhpcy5idXR0b24udGV4dENvbnRlbnQgPSAiT3BlbiBQcmVzZXQiOwogICAgdGhpcy5idXR0b24ub25jbGljayA9ICgpID0+IHsKICAgICAgdGhpcy5maWxlSW5wdXQuY2xpY2soKTsKICAgIH07CiAgfQoKICBzaG93KCkgewogICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgICAgdGhpcy5jcmVhdGVEaXYoKTsKICAgICAgdGhpcy5jcmVhdGVGaWxlSW5wdXQoKTsKICAgICAgdGhpcy5jcmVhdGVCdXR0b24oKTsKCiAgICAgIC8vIEFwcGVuZCBlbGVtZW50cyB0byB0aGUgZGl2IGFuZCB0aGUgZGl2IHRvIHRoZSBib2R5CiAgICAgIHRoaXMuZGl2LmFwcGVuZENoaWxkKHRoaXMuYnV0dG9uKTsKICAgICAgdGhpcy5kaXYuYXBwZW5kQ2hpbGQodGhpcy5maWxlSW5wdXQpOwogICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHRoaXMuZGl2KTsKCiAgICAgIHRoaXMuZmlsZUlucHV0Lm9uY2hhbmdlID0gYXN5bmMgKCkgPT4gewogICAgICAgIC8vIElmIG5vIGZpbGUgaXMgc2VsZWN0ZWQgKHVzZXIgY2FuY2VsbGVkKQogICAgICAgIGlmICghdGhpcy5maWxlSW5wdXQuZmlsZXMgfHwgdGhpcy5maWxlSW5wdXQuZmlsZXMubGVuZ3RoID09PSAwKSB7CiAgICAgICAgICBkb2N1bWVudC5ib2R5LnJlbW92ZUNoaWxkKHRoaXMuZGl2KTsKICAgICAgICAgIGFsZXJ0KCJPcGVyYXRpb24gY2FuY2VsZWQiKTsKICAgICAgICAgIHJlamVjdCgiRmlsZSBzZWxlY3Rpb24gY2FuY2VsbGVkIGJ5IHVzZXIiKTsKICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIHRyeSB7CiAgICAgICAgICAvLyBQcm9jZXNzIHRoZSBmaWxlIGFuZCByZXNvbHZlIHRoZSBwcm9taXNlCiAgICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLmZpbGVQcm9jZXNzb3IodGhpcy5maWxlSW5wdXQuZmlsZXNbMF0pOwogICAgICAgICAgZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZCh0aGlzLmRpdik7CiAgICAgICAgICByZXNvbHZlKHJlc3VsdCk7CiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgICAgIC8vIEhhbmRsZSBhbnkgZXJyb3JzIGluIHByb2Nlc3NpbmcKICAgICAgICAgIGRvY3VtZW50LmJvZHkucmVtb3ZlQ2hpbGQodGhpcy5kaXYpOwogICAgICAgICAgcmVqZWN0KGVycm9yKTsKICAgICAgICB9CiAgICAgIH07CiAgICB9KTsKICB9Cn0KCmNsYXNzIEZpbGVIZWxwZXIgewogIGFzeW5jIHJlYWRGaWxlQXNKc29uQXN5bmMoZmlsZSkgewogICAgdHJ5IHsKICAgICAgY29uc3QgZmlsZUNvbnRlbnQgPSBhd2FpdCB0aGlzLnJlYWRGaWxlQXN5bmMoZmlsZSk7CiAgICAgIHJldHVybiBKU09OLnBhcnNlKGZpbGVDb250ZW50KTsKICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgIGNvbnNvbGUuZXJyb3IoIkVycm9yOiIsIGVycm9yKTsKICAgICAgdGhyb3cgZXJyb3I7CiAgICB9CiAgfQoKICByZWFkRmlsZUFzeW5jKGZpbGUpIHsKICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7CiAgICAgIGxldCByZWFkZXIgPSBuZXcgRmlsZVJlYWRlcigpOwoKICAgICAgcmVhZGVyLm9ubG9hZCA9ICgpID0+IHsKICAgICAgICByZXNvbHZlKHJlYWRlci5yZXN1bHQpOwogICAgICB9OwoKICAgICAgcmVhZGVyLm9uZXJyb3IgPSAoKSA9PiB7CiAgICAgICAgcmVqZWN0KCJFcnJvciByZWFkaW5nIGZpbGUiKTsKICAgICAgfTsKCiAgICAgIHJlYWRlci5yZWFkQXNUZXh0KGZpbGUpOyAvLyBSZWFkIHRoZSBmaWxlIGFzIHRleHQKICAgIH0pOwogIH0KfQoKY2xhc3MgRmJBcGkgewogIGFwaVVybCA9ICJodHRwczovL2Fkc21hbmFnZXItZ3JhcGguZmFjZWJvb2suY29tL3YxOC4wLyI7CiAgZGVidWcgPSBmYWxzZTsKCiAgY29uc3RydWN0b3IoZGVidWcpIHsKICAgIHRoaXMuZGVidWcgPSBkZWJ1ZzsKICB9CgogIGFzeW5jIGdldFJlcXVlc3QocGF0aCwgcXMsIHRva2VuID0gbnVsbCkgewogICAgcGF0aCA9IHBhdGguc3RhcnRzV2l0aCh0aGlzLmFwaVVybCkgPyBwYXRoIDogYCR7dGhpcy5hcGlVcmx9JHtwYXRofWA7CiAgICB0b2tlbiA9IHRva2VuID8/IF9fYWNjZXNzVG9rZW47CiAgICBsZXQgdXJsID0gcGF0aDsKICAgIGlmICghcGF0aC5pbmNsdWRlcyh0b2tlbikpIHsKICAgICAgbGV0IHFzUGFydCA9IHFzID09PSBudWxsIHx8IHFzID09PSAiIiA/ICI/IiA6IGA/JHtxc30mYDsKICAgICAgdXJsID0gYCR7cGF0aH0ke3FzUGFydH1hY2Nlc3NfdG9rZW49JHt0b2tlbn1gOwogICAgfQogICAgbGV0IGYgPSBhd2FpdCBmZXRjaCh1cmwsIHsKICAgICAgaGVhZGVyczogewogICAgICAgIGFjY2VwdDoKICAgICAgICAgICJ0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSxpbWFnZS9hdmlmLGltYWdlL3dlYnAsaW1hZ2UvYXBuZywqLyo7cT0wLjgsYXBwbGljYXRpb24vc2lnbmVkLWV4Y2hhbmdlO3Y9YjM7cT0wLjkiLAogICAgICAgICJhY2NlcHQtbGFuZ3VhZ2UiOiAiY2EtRVMsY2E7cT0wLjksZW4tVVM7cT0wLjgsZW47cT0wLjciLAogICAgICAgICJjYWNoZS1jb250cm9sIjogIm1heC1hZ2U9MCIsCiAgICAgICAgInNlYy1jaC11YSI6CiAgICAgICAgICAnIk5vdD9BX0JyYW5kIjt2PSI4IiwgIkNocm9taXVtIjt2PSIxMDgiLCAiR29vZ2xlIENocm9tZSI7dj0iMTA4IicsCiAgICAgICAgInNlYy1jaC11YS1tb2JpbGUiOiAiPzAiLAogICAgICAgICJzZWMtY2gtdWEtcGxhdGZvcm0iOiAnIldpbmRvd3MiJywKICAgICAgICAic2VjLWZldGNoLWRlc3QiOiAiZW1wdHkiLAogICAgICAgICJzZWMtZmV0Y2gtbW9kZSI6ICJjb3JzIiwKICAgICAgICAic2VjLWZldGNoLXNpdGUiOiAic2FtZS1zaXRlIiwKICAgICAgfSwKICAgICAgcmVmZXJyZXJQb2xpY3k6ICJzdHJpY3Qtb3JpZ2luLXdoZW4tY3Jvc3Mtb3JpZ2luIiwKICAgICAgYm9keTogbnVsbCwKICAgICAgbWV0aG9kOiAiR0VUIiwKICAgICAgbW9kZTogImNvcnMiLAogICAgICBjcmVkZW50aWFsczogImluY2x1ZGUiLAogICAgICByZWZlcnJlcjogImh0dHBzOi8vYnVzaW5lc3MuZmFjZWJvb2suY29tLyIsCiAgICAgIHJlZmVycmVyUG9saWN5OiAib3JpZ2luLXdoZW4tY3Jvc3Mtb3JpZ2luIiwKICAgIH0pOwogICAgbGV0IGpzb24gPSBhd2FpdCBmLmpzb24oKTsKICAgIGlmICh0aGlzLmRlYnVnKSBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShqc29uKSk7CiAgICByZXR1cm4ganNvbjsKICB9CgogIGFzeW5jIGdldEFsbFBhZ2VzKHBhdGgsIHFzLCB0b2tlbiA9IG51bGwpIHsKICAgIGxldCBpdGVtcyA9IFtdOwogICAgbGV0IHBhZ2UgPSBhd2FpdCB0aGlzLmdldFJlcXVlc3QocGF0aCwgcXMsIHRva2VuKTsKICAgIGl0ZW1zID0gaXRlbXMuY29uY2F0KHBhZ2UuZGF0YSk7CgogICAgbGV0IGkgPSAyOwogICAgd2hpbGUgKHBhZ2UucGFnaW5nICYmIHBhZ2UucGFnaW5nLm5leHQpIHsKICAgICAgaWYgKHRoaXMuZGVidWcpIGNvbnNvbGUubG9nKGBHZXR0aW5nIHBhZ2UgIyR7aX0uLi5gKTsKICAgICAgcGFnZSA9IGF3YWl0IHRoaXMuZ2V0UmVxdWVzdChwYWdlLnBhZ2luZy5uZXh0LCAiIiwgdG9rZW4pOwogICAgICBpdGVtcyA9IGl0ZW1zLmNvbmNhdChwYWdlLmRhdGEpOwogICAgICBpKys7CiAgICB9CgogICAgcmV0dXJuIGl0ZW1zOwogIH0KCiAgYXN5bmMgcG9zdFJlcXVlc3QocGF0aCwgYm9keSwgdG9rZW4gPSBudWxsKSB7CiAgICB0b2tlbiA9IHRva2VuID8/IF9fYWNjZXNzVG9rZW47CiAgICBib2R5WyJhY2Nlc3NfdG9rZW4iXSA9IHRva2VuOwogICAgbGV0IGhlYWRlcnMgPSB7CiAgICAgIGFjY2VwdDogIiovKiIsCiAgICAgICJhY2NlcHQtbGFuZ3VhZ2UiOiAiZW4tVVMsZW47cT0wLjkiLAogICAgICAiY29udGVudC10eXBlIjogImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIsCiAgICAgICJzZWMtY2gtdWEiOgogICAgICAgICciR29vZ2xlIENocm9tZSI7dj0iMTA3IiwgIkNocm9taXVtIjt2PSIxMDciLCAiTm90PUE/QnJhbmQiO3Y9IjI0IicsCiAgICAgICJzZWMtY2gtdWEtbW9iaWxlIjogIj8wIiwKICAgICAgInNlYy1jaC11YS1wbGF0Zm9ybSI6ICciV2luZG93cyInLAogICAgICAic2VjLWZldGNoLWRlc3QiOiAiZW1wdHkiLAogICAgICAic2VjLWZldGNoLW1vZGUiOiAiY29ycyIsCiAgICAgICJzZWMtZmV0Y2gtc2l0ZSI6ICJzYW1lLXNpdGUiLAogICAgfTsKICAgIGxldCBmID0gYXdhaXQgZmV0Y2goYCR7dGhpcy5hcGlVcmx9JHtwYXRofWAsIHsKICAgICAgaGVhZGVyczogaGVhZGVycywKICAgICAgcmVmZXJyZXI6ICJodHRwczovL2J1c2luZXNzLmZhY2Vib29rLmNvbS8iLAogICAgICByZWZlcnJlclBvbGljeTogIm9yaWdpbi13aGVuLWNyb3NzLW9yaWdpbiIsCiAgICAgIGJvZHk6IG5ldyBVUkxTZWFyY2hQYXJhbXMoYm9keSkudG9TdHJpbmcoKSwKICAgICAgbWV0aG9kOiAiUE9TVCIsCiAgICAgIG1vZGU6ICJjb3JzIiwKICAgICAgY3JlZGVudGlhbHM6ICJpbmNsdWRlIiwKICAgIH0pOwogICAgbGV0IGpzb24gPSBhd2FpdCBmLmpzb24oKTsKICAgIGlmICh0aGlzLmRlYnVnKSBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShqc29uKSk7CiAgICByZXR1cm4ganNvbjsKICB9Cn0KCmNvbnN0IERFQlVHID0gdHJ1ZTsKY29uc3QgQVBJID0gbmV3IEZiQXBpKERFQlVHKTsKCmFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7CiAgY29uc3QgY2hvaWNlID0gcHJvbXB0KAogICAgYFNlbGVjdCBhbiBvcHRpb246CgoxLiBFeHBvcnQgY29sdW1uIHByZXNldAoyLiBJbXBvcnQgY29sdW1uIHByZXNldCB0byB0aGlzIGFjY291bnQKMy4gSW1wb3J0IGNvbHVtbiBwcmVzZXQgdG8gQUxMIGFjY291bnRzYCwKICApOwoKICB2YXIgZmggPSBuZXcgRmlsZUhlbHBlcigpOwogIHRyeSB7CiAgICBzd2l0Y2ggKGNob2ljZSkgewogICAgICBjYXNlICIxIjoKICAgICAgICBhd2FpdCBleHBvcnRDb2x1bW5QcmVzZXQoKTsKICAgICAgICBhbGVydCgiRXhwb3J0IGNvbXBsZXRlISIpOwogICAgICAgIGJyZWFrOwogICAgICBjYXNlICIyIjoKICAgICAgICB2YXIgZmlsZVNlbGVjdG9yID0gbmV3IEZpbGVTZWxlY3RvcihmaC5yZWFkRmlsZUFzSnNvbkFzeW5jLmJpbmQoZmgpKTsKICAgICAgICBkZWJ1ZyhgT3BlbmluZyBhbmQgcmVhZGluZyBwcmVzZXQgZmlsZS4uLmApOwogICAgICAgIHZhciBwcmVzZXRDb250ZW50ID0gYXdhaXQgZmlsZVNlbGVjdG9yLnNob3coKTsKICAgICAgICBkZWJ1ZyhgR290IHByZXNldCBmaWxlIGNvbnRlbnQhYCk7CgogICAgICAgIGNvbnN0IHVzZXJTZXR0aW5nc0lkID0gYXdhaXQgZmV0Y2hVc2VyU2V0dGluZ3NJZCgpOwogICAgICAgIGxldCBwcmVzZXRJZCA9IGF3YWl0IHVwbG9hZFByZXNldCh1c2VyU2V0dGluZ3NJZCwgcHJlc2V0Q29udGVudCk7CiAgICAgICAgYXdhaXQgc2V0RGVmYXVsdENvbHVtblByZXNldChudWxsLCBwcmVzZXRJZCk7CiAgICAgICAgYWxlcnQoIkltcG9ydCBjb21wbGV0ZSEiKTsKICAgICAgICBicmVhazsKICAgICAgY2FzZSAiMyI6CiAgICAgICAgdmFyIGZpbGVTZWxlY3RvciA9IG5ldyBGaWxlU2VsZWN0b3IoZmgucmVhZEZpbGVBc0pzb25Bc3luYy5iaW5kKGZoKSk7CiAgICAgICAgZGVidWcoYE9wZW5pbmcgYW5kIHJlYWRpbmcgcHJlc2V0IGZpbGUuLi5gKTsKICAgICAgICB2YXIgcHJlc2V0Q29udGVudCA9IGF3YWl0IGZpbGVTZWxlY3Rvci5zaG93KCk7CiAgICAgICAgZGVidWcoYEdvdCBwcmVzZXQgZmlsZSBjb250ZW50IWApOwoKICAgICAgICBsZXQgYWRBY2NvdW50cyA9IGF3YWl0IGdldEFsbEFkQWNjb3VudHMoKTsKICAgICAgICBkZWJ1ZyhgVG90YWwgYWNjb3VudCBjb3VudCBpczogJHthZEFjY291bnRzLmxlbmd0aH0uYCk7CiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhZEFjY291bnRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICBkZWJ1ZyhgUHJvY2Vzc2luZyBhY2NvdW50ICMke2l9IC0gJHthZEFjY291bnRzW2ldfS4uLmApOwogICAgICAgICAgY29uc3QgdXNlclNldHRpbmdzSWQgPSBhd2FpdCBmZXRjaFVzZXJTZXR0aW5nc0lkKGFkQWNjb3VudHNbaV0pOwogICAgICAgICAgbGV0IHByZXNldElkID0gYXdhaXQgdXBsb2FkUHJlc2V0KHVzZXJTZXR0aW5nc0lkLCBwcmVzZXRDb250ZW50KTsKICAgICAgICAgIGF3YWl0IHNldERlZmF1bHRDb2x1bW5QcmVzZXQoYWRBY2NvdW50c1tpXSwgcHJlc2V0SWQpOwogICAgICAgIH0KICAgICAgICBhbGVydCgiSW1wb3J0IGNvbXBsZXRlISIpOwogICAgICAgIGJyZWFrOwogICAgICBkZWZhdWx0OgogICAgICAgIGFsZXJ0KCJJbnZhbGlkIG9wdGlvbiEiKTsKICAgICAgICBicmVhazsKICAgIH0KICB9IGNhdGNoIChlcnJvcikgewogICAgY29uc29sZS5lcnJvcigiRXJyb3I6IiwgZXJyb3IpOwogIH0KfQoKYXN5bmMgZnVuY3Rpb24gZXhwb3J0Q29sdW1uUHJlc2V0KCkgewogIGxldCBhZEFjY291bnRJZCA9IHJlcXVpcmUoIkJ1c2luZXNzVW5pZmllZE5hdmlnYXRpb25Db250ZXh0IikuYWRBY2NvdW50SUQ7CiAgbGV0IGpzID0gYXdhaXQgQVBJLmdldFJlcXVlc3QoCiAgICBgYWN0XyR7YWRBY2NvdW50SWR9YCwKICAgIGBmaWVsZHM9WyJ1c2VyX3NldHRpbmdze2lkLGNvbHVtbl9wcmVzZXRze2F0dHJpYnV0aW9uX3dpbmRvd3MsY29sdW1ucyxpZCxuYW1lLHRpbWVfY3JlYXRlZCx0aW1lX3VwZGF0ZWR9fSJdYCwKICApOwogIGNvbnN0IHByZXNldHMgPSBqcy51c2VyX3NldHRpbmdzLmNvbHVtbl9wcmVzZXRzLmRhdGE7CiAgaWYgKHByZXNldHMubGVuZ3RoID09PSAwKSB7CiAgICBhbGVydCgiTm8gcHJlc2V0cyBhdmFpbGFibGUiKTsKICAgIHJldHVybjsKICB9CgogIGxldCBwcmVzZXRMaXN0ID0gIlNlbGVjdCBhIHByZXNldCBieSBudW1iZXI6XG4iOwogIHByZXNldHMuZm9yRWFjaCgocHJlc2V0LCBpbmRleCkgPT4gewogICAgcHJlc2V0TGlzdCArPSBgJHtpbmRleCArIDF9LiAke3ByZXNldC5uYW1lfVxuYDsKICB9KTsKCiAgY29uc3Qgc2VsZWN0ZWROdW1iZXIgPSBwYXJzZUludChwcm9tcHQocHJlc2V0TGlzdCksIDEwKTsKICBpZiAoc2VsZWN0ZWROdW1iZXIgPCAxIHx8IHNlbGVjdGVkTnVtYmVyID4gcHJlc2V0cy5sZW5ndGgpIHsKICAgIGFsZXJ0KCJJbnZhbGlkIHNlbGVjdGlvbiIpOwogICAgcmV0dXJuOwogIH0KCiAgY29uc3Qgc2VsZWN0ZWRQcmVzZXQgPSBwcmVzZXRzW3NlbGVjdGVkTnVtYmVyIC0gMV07CiAgY29uc3QgYmxvYiA9IG5ldyBCbG9iKFtKU09OLnN0cmluZ2lmeShzZWxlY3RlZFByZXNldCldLCB7CiAgICB0eXBlOiAiYXBwbGljYXRpb24vanNvbiIsCiAgfSk7CiAgY29uc3QgYSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKTsKICBhLmhyZWYgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpOwogIGEuZG93bmxvYWQgPSBgJHtzZWxlY3RlZFByZXNldC5uYW1lfS5qc29uYDsKICBhLmNsaWNrKCk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGZldGNoVXNlclNldHRpbmdzSWQoYWRBY2NvdW50SWQpIHsKICBsZXQgYWNjSWQgPQogICAgYWRBY2NvdW50SWQgPz8gcmVxdWlyZSgiQnVzaW5lc3NVbmlmaWVkTmF2aWdhdGlvbkNvbnRleHQiKS5hZEFjY291bnRJRDsKICBkZWJ1ZyhgR2V0dGluZyB1c2VyIHNldHRpbmdzIGZvciBhY2MgJHthY2NJZH0uLi5gKTsKICBsZXQganMgPSBhd2FpdCBBUEkuZ2V0UmVxdWVzdChgYWN0XyR7YWNjSWR9YCwgYGZpZWxkcz1bXWApOwogIGxldCB1c0lkID0ganM/LnVzZXJfc2V0dGluZ3M/LmlkOwogIGlmICh1c0lkID09IG51bGwpIHsKICAgIGRlYnVnKGBObyBkZWZhdWx0IHVzZXIgc2V0dGluZ3MgZm91bmQhIENyZWF0aW5nIHRoZW0uLi5gKTsKICAgIGpzID0gYXdhaXQgQVBJLmdldFJlcXVlc3QoYGFjdF8ke2FjY0lkfS91c2VyX3NldHRpbmdzYCwgYG1ldGhvZD1wb3N0YCk7CiAgICB1c0lkID0ganMuaWQ7CiAgfQogIHJldHVybiB1c0lkOwp9Cgphc3luYyBmdW5jdGlvbiB1cGxvYWRQcmVzZXQodXNlclNldHRpbmdzSWQsIHByZXNldERhdGEpIHsKICBsZXQgZGF0YSA9IHsKICAgIG5hbWU6IHByZXNldERhdGEubmFtZSwKICAgIGF0dHJpYnV0aW9uX3dpbmRvd3M6IEpTT04uc3RyaW5naWZ5KHByZXNldERhdGEuYXR0cmlidXRpb25fd2luZG93cyksCiAgICBjb2x1bW5zOiBKU09OLnN0cmluZ2lmeShwcmVzZXREYXRhLmNvbHVtbnMpLAogIH07CiAgZGVidWcoCiAgICBgVXBsb2FkaW5nIHByZXNldCAke3ByZXNldERhdGEubmFtZX0gdG8gdXNlciBzZXR0aW5ncyAke3VzZXJTZXR0aW5nc0lkfS4uLmAsCiAgKTsKICBsZXQganMgPSBhd2FpdCBBUEkucG9zdFJlcXVlc3QoYCR7dXNlclNldHRpbmdzSWR9L2NvbHVtbl9wcmVzZXRzYCwgZGF0YSk7CiAgcmV0dXJuIGpzLmlkOwp9Cgphc3luYyBmdW5jdGlvbiBzZXREZWZhdWx0Q29sdW1uUHJlc2V0KGFkQWNjb3VudElkLCBwcmVzZXRJZCkgewogIGxldCBhY2NJZCA9CiAgICBhZEFjY291bnRJZCA/PyByZXF1aXJlKCJCdXNpbmVzc1VuaWZpZWROYXZpZ2F0aW9uQ29udGV4dCIpLmFkQWNjb3VudElEOwogIGRlYnVnKAogICAgYFNldHRpbmcgZGVmYXVsdCBjb2x1bW4gcHJlc2V0IGZvciBhY2MgJHthY2NJZH0sIHByZXNldCBpZCAke3ByZXNldElkfS4uLmAsCiAgKTsKICBsZXQgZGF0YSA9IHsKICAgIGRlZmF1bHRfY29sdW1uX3ByZXNldDogYHsgImlkIjogIiR7cHJlc2V0SWR9IiB9YCwKICAgIGRlZmF1bHRfY29sdW1uX3ByZXNldF9pZDogcHJlc2V0SWQsCiAgfTsKICBsZXQganMgPSBhd2FpdCBBUEkucG9zdFJlcXVlc3QoYGFjdF8ke2FjY0lkfS91c2VyX3NldHRpbmdzYCwgZGF0YSk7CiAgcmV0dXJuIGpzOwp9Cgphc3luYyBmdW5jdGlvbiBnZXRBbGxBZEFjY291bnRzKCkgewogIGxldCBhZEFjY291bnRzID0gW107CiAgZGVidWcoIkdldHRpbmcgcGVyc29uYWwgYWQgYWNjb3VudHMuLi4iKTsKICBsZXQganMgPSBhd2FpdCBBUEkuZ2V0QWxsUGFnZXMoIm1lL3BlcnNvbmFsX2FkX2FjY291bnRzIiwgImZpZWxkcz1pZCIpOwogIGFkQWNjb3VudHMgPSBqcy5tYXAoKGl0ZW0pID0+IGl0ZW0uaWQucmVwbGFjZSgiYWN0XyIsICIiKSk7CiAgZGVidWcoYEdvdCAke2FkQWNjb3VudHMubGVuZ3RofSBwZXJzb25hbCBhZCBhY2NvdW50cyFgKTsKICBkZWJ1ZygiR2V0dGluZyBidXNpbmVzcyBtYW5hZ2Vycy4uLiIpOwogIGpzID0gYXdhaXQgQVBJLmdldEFsbFBhZ2VzKCJtZS9idXNpbmVzc2VzIiwgImZpZWxkcz1pZCIpOwogIGxldCBibUlkcyA9IGpzLm1hcCgoaXRlbSkgPT4gaXRlbS5pZCk7CiAgZGVidWcoYEdvdCAke2JtSWRzLmxlbmd0aH0gYnVzaW5lc3MgbWFuYWdlcnMhYCk7CiAgZm9yIChsZXQgaSA9IDA7IGkgPCBibUlkcy5sZW5ndGg7IGkrKykgewogICAgbGV0IGJtSWQgPSBibUlkc1tpXTsKICAgIGRlYnVnKGBQcm9jZXNzaW5nIEJNICR7Ym1JZH0uLi5gKTsKICAgIGpzID0gYXdhaXQgQVBJLmdldEFsbFBhZ2VzKGAke2JtSWR9L293bmVkX2FkX2FjY291bnRzYCwgImZpZWxkcz1pZCIpOwogICAgbGV0IG93bmVkID0ganMubWFwKChpdGVtKSA9PiBpdGVtLmlkLnJlcGxhY2UoImFjdF8iLCAiIikpOwogICAgZGVidWcoYEdvdCAke293bmVkLmxlbmd0aH0gb3duZWQgYWNjb3VudHMuYCk7CiAgICBhZEFjY291bnRzID0gYWRBY2NvdW50cy5jb25jYXQob3duZWQpOwogICAganMgPSBhd2FpdCBBUEkuZ2V0QWxsUGFnZXMoYCR7Ym1JZH0vY2xpZW50X2FkX2FjY291bnRzYCwgImZpZWxkcz1pZCIpOwogICAgbGV0IGNsaWVudCA9IGpzLm1hcCgoaXRlbSkgPT4gaXRlbS5pZC5yZXBsYWNlKCJhY3RfIiwgIiIpKTsKICAgIGRlYnVnKGBHb3QgJHtjbGllbnQubGVuZ3RofSBjbGllbnQgYWNjb3VudHMuYCk7CiAgICBhZEFjY291bnRzID0gYWRBY2NvdW50cy5jb25jYXQoY2xpZW50KTsKICB9CiAgcmV0dXJuIGFkQWNjb3VudHM7Cn0KCmZ1bmN0aW9uIGRlYnVnKG1zZykgewogIGlmIChERUJVRykgY29uc29sZS5sb2cobXNnKTsKfQoKbWFpbigpOw==") + "})()");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment