Created
August 27, 2025 13:14
-
-
Save dvygolov/4449da3e5aa420d4ecf0b4b37f5e7b82 to your computer and use it in GitHub Desktop.
Script to manage column presets of Ads Manager: import/export columns
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
javascript:eval("(async () => {" + atob("Y2xhc3MgRmlsZVNlbGVjdG9yIHsKICBjb25zdHJ1Y3RvcihmaWxlUHJvY2Vzc29yKSB7CiAgICB0aGlzLmZpbGVQcm9jZXNzb3IgPSBmaWxlUHJvY2Vzc29yOwogIH0KCiAgY3JlYXRlRGl2KCkgewogICAgdGhpcy5kaXYgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTsKICAgIHRoaXMuZGl2LnN0eWxlLnBvc2l0aW9uID0gImZpeGVkIjsKICAgIHRoaXMuZGl2LnN0eWxlLnRvcCA9ICI1MCUiOwogICAgdGhpcy5kaXYuc3R5bGUubGVmdCA9ICI1MCUiOwogICAgdGhpcy5kaXYuc3R5bGUudHJhbnNmb3JtID0gInRyYW5zbGF0ZSgtNTAlLCAtNTAlKSI7CiAgICB0aGlzLmRpdi5zdHlsZS53aWR0aCA9ICIyMDBweCI7IC8vIFVwZGF0ZWQgd2lkdGgKICAgIHRoaXMuZGl2LnN0eWxlLmhlaWdodCA9ICIxMjBweCI7CiAgICB0aGlzLmRpdi5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAieWVsbG93IjsKICAgIHRoaXMuZGl2LnN0eWxlLnpJbmRleCA9ICIxMDAwIjsKICAgIHRoaXMuZGl2LnN0eWxlLmRpc3BsYXkgPSAiZmxleCI7CiAgICB0aGlzLmRpdi5zdHlsZS5mbGV4RGlyZWN0aW9uID0gImNvbHVtbiI7IC8vIFRvIGFsaWduIGl0ZW1zIHZlcnRpY2FsbHkKICAgIHRoaXMuZGl2LnN0eWxlLmFsaWduSXRlbXMgPSAiY2VudGVyIjsKICAgIHRoaXMuZGl2LnN0eWxlLmp1c3RpZnlDb250ZW50ID0gImNlbnRlciI7CiAgICB0aGlzLmRpdi5zdHlsZS5wYWRkaW5nID0gIjEwcHgiOyAvLyBBZGRlZCBwYWRkaW5nCiAgICB0aGlzLmRpdi5zdHlsZS5ib3hTaXppbmcgPSAiYm9yZGVyLWJveCI7IC8vIFRvIGluY2x1ZGUgcGFkZGluZyBpbiB3aWR0aC9oZWlnaHQKICAgIHRoaXMuZGl2LnN0eWxlLmJvcmRlclJhZGl1cyA9ICIxMHB4IjsKCiAgICAvLyBDcmVhdGUgYW5kIHN0eWxlIHRoZSB0aXRsZQogICAgdmFyIHRpdGxlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7CiAgICB0aXRsZS5pbm5lckhUTUwgPQogICAgICAiPGJyPkZCIENvbHVtbiBQcmVzZXQgTWFuYWdlciB2Mi4xPGJyPlRFQU0gRWRpdGlvbjxicj5ieSBZZWxsb3cgV2ViPGJyPjxicj4iOwogICAgdGl0bGUuc3R5bGUudGV4dEFsaWduID0gImNlbnRlciI7CiAgICB0aXRsZS5zdHlsZS5mb250V2VpZ2h0ID0gImJvbGQiOwoKICAgIC8vIENyZWF0ZSBhbmQgc3R5bGUgdGhlIGNsb3NlIGJ1dHRvbgogICAgdmFyIGNsb3NlQnV0dG9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnV0dG9uIik7CiAgICBjbG9zZUJ1dHRvbi5pbm5lckhUTUwgPSAiWCI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS5wb3NpdGlvbiA9ICJhYnNvbHV0ZSI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS50b3AgPSAiNXB4IjsKICAgIGNsb3NlQnV0dG9uLnN0eWxlLnJpZ2h0ID0gIjVweCI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS5ib3JkZXIgPSAibm9uZSI7CiAgICBjbG9zZUJ1dHRvbi5zdHlsZS5iYWNrZ3JvdW5kID0gIm5vbmUiOwogICAgY2xvc2VCdXR0b24uc3R5bGUuY3Vyc29yID0gInBvaW50ZXIiOwogICAgY2xvc2VCdXR0b24ub25jbGljayA9ICgpID0+IHsKICAgICAgZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZCh0aGlzLmRpdik7CiAgICB9OwoKICAgIHRoaXMuZGl2LmFwcGVuZENoaWxkKHRpdGxlKTsKICAgIHRoaXMuZGl2LmFwcGVuZENoaWxkKGNsb3NlQnV0dG9uKTsKICB9CgogIGNyZWF0ZUZpbGVJbnB1dCgpIHsKICAgIC8vIENyZWF0ZSB0aGUgZmlsZSBpbnB1dCBhbmQgaGFuZGxlIGZpbGUgc2VsZWN0aW9uCiAgICB0aGlzLmZpbGVJbnB1dCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImlucHV0Iik7CiAgICB0aGlzLmZpbGVJbnB1dC50eXBlID0gImZpbGUiOwogICAgdGhpcy5maWxlSW5wdXQuYWNjZXB0ID0gIi5qc29uIjsgLy8gQWNjZXB0IG9ubHkgSlNPTiBmaWxlcwogICAgdGhpcy5maWxlSW5wdXQuc3R5bGUuZGlzcGxheSA9ICJub25lIjsKICB9CgogIGNyZWF0ZUJ1dHRvbigpIHsKICAgIC8vIENyZWF0ZSB0aGUgYnV0dG9uCiAgICB0aGlzLmJ1dHRvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImJ1dHRvbiIpOwogICAgdGhpcy5idXR0b24udGV4dENvbnRlbnQgPSAiT3BlbiBQcmVzZXQiOwogICAgdGhpcy5idXR0b24ub25jbGljayA9ICgpID0+IHsKICAgICAgdGhpcy5maWxlSW5wdXQuY2xpY2soKTsKICAgIH07CiAgfQoKICBzaG93KCkgewogICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgICAgdGhpcy5jcmVhdGVEaXYoKTsKICAgICAgdGhpcy5jcmVhdGVGaWxlSW5wdXQoKTsKICAgICAgdGhpcy5jcmVhdGVCdXR0b24oKTsKCiAgICAgIC8vIEFwcGVuZCBlbGVtZW50cyB0byB0aGUgZGl2IGFuZCB0aGUgZGl2IHRvIHRoZSBib2R5CiAgICAgIHRoaXMuZGl2LmFwcGVuZENoaWxkKHRoaXMuYnV0dG9uKTsKICAgICAgdGhpcy5kaXYuYXBwZW5kQ2hpbGQodGhpcy5maWxlSW5wdXQpOwogICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHRoaXMuZGl2KTsKCiAgICAgIHRoaXMuZmlsZUlucHV0Lm9uY2hhbmdlID0gYXN5bmMgKCkgPT4gewogICAgICAgIC8vIElmIG5vIGZpbGUgaXMgc2VsZWN0ZWQgKHVzZXIgY2FuY2VsbGVkKQogICAgICAgIGlmICghdGhpcy5maWxlSW5wdXQuZmlsZXMgfHwgdGhpcy5maWxlSW5wdXQuZmlsZXMubGVuZ3RoID09PSAwKSB7CiAgICAgICAgICBkb2N1bWVudC5ib2R5LnJlbW92ZUNoaWxkKHRoaXMuZGl2KTsKICAgICAgICAgIGFsZXJ0KCJPcGVyYXRpb24gY2FuY2VsZWQiKTsKICAgICAgICAgIHJlamVjdCgiRmlsZSBzZWxlY3Rpb24gY2FuY2VsbGVkIGJ5IHVzZXIiKTsKICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIHRyeSB7CiAgICAgICAgICAvLyBQcm9jZXNzIHRoZSBmaWxlIGFuZCByZXNvbHZlIHRoZSBwcm9taXNlCiAgICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLmZpbGVQcm9jZXNzb3IodGhpcy5maWxlSW5wdXQuZmlsZXNbMF0pOwogICAgICAgICAgZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZCh0aGlzLmRpdik7CiAgICAgICAgICByZXNvbHZlKHJlc3VsdCk7CiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgICAgIC8vIEhhbmRsZSBhbnkgZXJyb3JzIGluIHByb2Nlc3NpbmcKICAgICAgICAgIGRvY3VtZW50LmJvZHkucmVtb3ZlQ2hpbGQodGhpcy5kaXYpOwogICAgICAgICAgcmVqZWN0KGVycm9yKTsKICAgICAgICB9CiAgICAgIH07CiAgICB9KTsKICB9Cn0KCmNsYXNzIEZpbGVIZWxwZXIgewogIGFzeW5jIHJlYWRGaWxlQXNKc29uQXN5bmMoZmlsZSkgewogICAgdHJ5IHsKICAgICAgY29uc3QgZmlsZUNvbnRlbnQgPSBhd2FpdCB0aGlzLnJlYWRGaWxlQXN5bmMoZmlsZSk7CiAgICAgIHJldHVybiBKU09OLnBhcnNlKGZpbGVDb250ZW50KTsKICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgIGNvbnNvbGUuZXJyb3IoIkVycm9yOiIsIGVycm9yKTsKICAgICAgdGhyb3cgZXJyb3I7CiAgICB9CiAgfQoKICByZWFkRmlsZUFzeW5jKGZpbGUpIHsKICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7CiAgICAgIGxldCByZWFkZXIgPSBuZXcgRmlsZVJlYWRlcigpOwoKICAgICAgcmVhZGVyLm9ubG9hZCA9ICgpID0+IHsKICAgICAgICByZXNvbHZlKHJlYWRlci5yZXN1bHQpOwogICAgICB9OwoKICAgICAgcmVhZGVyLm9uZXJyb3IgPSAoKSA9PiB7CiAgICAgICAgcmVqZWN0KCJFcnJvciByZWFkaW5nIGZpbGUiKTsKICAgICAgfTsKCiAgICAgIHJlYWRlci5yZWFkQXNUZXh0KGZpbGUpOyAvLyBSZWFkIHRoZSBmaWxlIGFzIHRleHQKICAgIH0pOwogIH0KfQoKY2xhc3MgRmJBcGkgewogIGFwaVVybCA9ICJodHRwczovL2Fkc21hbmFnZXItZ3JhcGguZmFjZWJvb2suY29tL3YxOC4wLyI7CiAgZGVidWcgPSBmYWxzZTsKCiAgY29uc3RydWN0b3IoZGVidWcpIHsKICAgIHRoaXMuZGVidWcgPSBkZWJ1ZzsKICB9CgogIGFzeW5jIGdldFJlcXVlc3QocGF0aCwgcXMsIHRva2VuID0gbnVsbCkgewogICAgcGF0aCA9IHBhdGguc3RhcnRzV2l0aCh0aGlzLmFwaVVybCkgPyBwYXRoIDogYCR7dGhpcy5hcGlVcmx9JHtwYXRofWA7CiAgICB0b2tlbiA9IHRva2VuID8/IF9fYWNjZXNzVG9rZW47CiAgICBsZXQgdXJsID0gcGF0aDsKICAgIGlmICghcGF0aC5pbmNsdWRlcyh0b2tlbikpIHsKICAgICAgbGV0IHFzUGFydCA9IHFzID09PSBudWxsIHx8IHFzID09PSAiIiA/ICI/IiA6IGA/JHtxc30mYDsKICAgICAgdXJsID0gYCR7cGF0aH0ke3FzUGFydH1hY2Nlc3NfdG9rZW49JHt0b2tlbn1gOwogICAgfQogICAgbGV0IGYgPSBhd2FpdCBmZXRjaCh1cmwsIHsKICAgICAgaGVhZGVyczogewogICAgICAgIGFjY2VwdDoKICAgICAgICAgICJ0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSxpbWFnZS9hdmlmLGltYWdlL3dlYnAsaW1hZ2UvYXBuZywqLyo7cT0wLjgsYXBwbGljYXRpb24vc2lnbmVkLWV4Y2hhbmdlO3Y9YjM7cT0wLjkiLAogICAgICAgICJhY2NlcHQtbGFuZ3VhZ2UiOiAiY2EtRVMsY2E7cT0wLjksZW4tVVM7cT0wLjgsZW47cT0wLjciLAogICAgICAgICJjYWNoZS1jb250cm9sIjogIm1heC1hZ2U9MCIsCiAgICAgICAgInNlYy1jaC11YSI6CiAgICAgICAgICAnIk5vdD9BX0JyYW5kIjt2PSI4IiwgIkNocm9taXVtIjt2PSIxMDgiLCAiR29vZ2xlIENocm9tZSI7dj0iMTA4IicsCiAgICAgICAgInNlYy1jaC11YS1tb2JpbGUiOiAiPzAiLAogICAgICAgICJzZWMtY2gtdWEtcGxhdGZvcm0iOiAnIldpbmRvd3MiJywKICAgICAgICAic2VjLWZldGNoLWRlc3QiOiAiZW1wdHkiLAogICAgICAgICJzZWMtZmV0Y2gtbW9kZSI6ICJjb3JzIiwKICAgICAgICAic2VjLWZldGNoLXNpdGUiOiAic2FtZS1zaXRlIiwKICAgICAgfSwKICAgICAgcmVmZXJyZXJQb2xpY3k6ICJzdHJpY3Qtb3JpZ2luLXdoZW4tY3Jvc3Mtb3JpZ2luIiwKICAgICAgYm9keTogbnVsbCwKICAgICAgbWV0aG9kOiAiR0VUIiwKICAgICAgbW9kZTogImNvcnMiLAogICAgICBjcmVkZW50aWFsczogImluY2x1ZGUiLAogICAgICByZWZlcnJlcjogImh0dHBzOi8vYnVzaW5lc3MuZmFjZWJvb2suY29tLyIsCiAgICAgIHJlZmVycmVyUG9saWN5OiAib3JpZ2luLXdoZW4tY3Jvc3Mtb3JpZ2luIiwKICAgIH0pOwogICAgbGV0IGpzb24gPSBhd2FpdCBmLmpzb24oKTsKICAgIGlmICh0aGlzLmRlYnVnKSBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShqc29uKSk7CiAgICByZXR1cm4ganNvbjsKICB9CgogIGFzeW5jIGdldEFsbFBhZ2VzKHBhdGgsIHFzLCB0b2tlbiA9IG51bGwpIHsKICAgIGxldCBpdGVtcyA9IFtdOwogICAgbGV0IHBhZ2UgPSBhd2FpdCB0aGlzLmdldFJlcXVlc3QocGF0aCwgcXMsIHRva2VuKTsKICAgIGl0ZW1zID0gaXRlbXMuY29uY2F0KHBhZ2UuZGF0YSk7CgogICAgbGV0IGkgPSAyOwogICAgd2hpbGUgKHBhZ2UucGFnaW5nICYmIHBhZ2UucGFnaW5nLm5leHQpIHsKICAgICAgaWYgKHRoaXMuZGVidWcpIGNvbnNvbGUubG9nKGBHZXR0aW5nIHBhZ2UgIyR7aX0uLi5gKTsKICAgICAgcGFnZSA9IGF3YWl0IHRoaXMuZ2V0UmVxdWVzdChwYWdlLnBhZ2luZy5uZXh0LCAiIiwgdG9rZW4pOwogICAgICBpdGVtcyA9IGl0ZW1zLmNvbmNhdChwYWdlLmRhdGEpOwogICAgICBpKys7CiAgICB9CgogICAgcmV0dXJuIGl0ZW1zOwogIH0KCiAgYXN5bmMgcG9zdFJlcXVlc3QocGF0aCwgYm9keSwgdG9rZW4gPSBudWxsKSB7CiAgICB0b2tlbiA9IHRva2VuID8/IF9fYWNjZXNzVG9rZW47CiAgICBib2R5WyJhY2Nlc3NfdG9rZW4iXSA9IHRva2VuOwogICAgbGV0IGhlYWRlcnMgPSB7CiAgICAgIGFjY2VwdDogIiovKiIsCiAgICAgICJhY2NlcHQtbGFuZ3VhZ2UiOiAiZW4tVVMsZW47cT0wLjkiLAogICAgICAiY29udGVudC10eXBlIjogImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIsCiAgICAgICJzZWMtY2gtdWEiOgogICAgICAgICciR29vZ2xlIENocm9tZSI7dj0iMTA3IiwgIkNocm9taXVtIjt2PSIxMDciLCAiTm90PUE/QnJhbmQiO3Y9IjI0IicsCiAgICAgICJzZWMtY2gtdWEtbW9iaWxlIjogIj8wIiwKICAgICAgInNlYy1jaC11YS1wbGF0Zm9ybSI6ICciV2luZG93cyInLAogICAgICAic2VjLWZldGNoLWRlc3QiOiAiZW1wdHkiLAogICAgICAic2VjLWZldGNoLW1vZGUiOiAiY29ycyIsCiAgICAgICJzZWMtZmV0Y2gtc2l0ZSI6ICJzYW1lLXNpdGUiLAogICAgfTsKICAgIGxldCBmID0gYXdhaXQgZmV0Y2goYCR7dGhpcy5hcGlVcmx9JHtwYXRofWAsIHsKICAgICAgaGVhZGVyczogaGVhZGVycywKICAgICAgcmVmZXJyZXI6ICJodHRwczovL2J1c2luZXNzLmZhY2Vib29rLmNvbS8iLAogICAgICByZWZlcnJlclBvbGljeTogIm9yaWdpbi13aGVuLWNyb3NzLW9yaWdpbiIsCiAgICAgIGJvZHk6IG5ldyBVUkxTZWFyY2hQYXJhbXMoYm9keSkudG9TdHJpbmcoKSwKICAgICAgbWV0aG9kOiAiUE9TVCIsCiAgICAgIG1vZGU6ICJjb3JzIiwKICAgICAgY3JlZGVudGlhbHM6ICJpbmNsdWRlIiwKICAgIH0pOwogICAgbGV0IGpzb24gPSBhd2FpdCBmLmpzb24oKTsKICAgIGlmICh0aGlzLmRlYnVnKSBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShqc29uKSk7CiAgICByZXR1cm4ganNvbjsKICB9Cn0KCmNvbnN0IERFQlVHID0gdHJ1ZTsKY29uc3QgQVBJID0gbmV3IEZiQXBpKERFQlVHKTsKCmFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7CiAgY29uc3QgY2hvaWNlID0gcHJvbXB0KAogICAgYFNlbGVjdCBhbiBvcHRpb246CgoxLiBFeHBvcnQgY29sdW1uIHByZXNldAoyLiBJbXBvcnQgY29sdW1uIHByZXNldCB0byB0aGlzIGFjY291bnQKMy4gSW1wb3J0IGNvbHVtbiBwcmVzZXQgdG8gQUxMIGFjY291bnRzYCwKICApOwoKICB2YXIgZmggPSBuZXcgRmlsZUhlbHBlcigpOwogIHRyeSB7CiAgICBzd2l0Y2ggKGNob2ljZSkgewogICAgICBjYXNlICIxIjoKICAgICAgICBhd2FpdCBleHBvcnRDb2x1bW5QcmVzZXQoKTsKICAgICAgICBhbGVydCgiRXhwb3J0IGNvbXBsZXRlISIpOwogICAgICAgIGJyZWFrOwogICAgICBjYXNlICIyIjoKICAgICAgICB2YXIgZmlsZVNlbGVjdG9yID0gbmV3IEZpbGVTZWxlY3RvcihmaC5yZWFkRmlsZUFzSnNvbkFzeW5jLmJpbmQoZmgpKTsKICAgICAgICBkZWJ1ZyhgT3BlbmluZyBhbmQgcmVhZGluZyBwcmVzZXQgZmlsZS4uLmApOwogICAgICAgIHZhciBwcmVzZXRDb250ZW50ID0gYXdhaXQgZmlsZVNlbGVjdG9yLnNob3coKTsKICAgICAgICBkZWJ1ZyhgR290IHByZXNldCBmaWxlIGNvbnRlbnQhYCk7CgogICAgICAgIGNvbnN0IHVzZXJTZXR0aW5nc0lkID0gYXdhaXQgZmV0Y2hVc2VyU2V0dGluZ3NJZCgpOwogICAgICAgIGxldCBwcmVzZXRJZCA9IGF3YWl0IHVwbG9hZFByZXNldCh1c2VyU2V0dGluZ3NJZCwgcHJlc2V0Q29udGVudCk7CiAgICAgICAgYXdhaXQgc2V0RGVmYXVsdENvbHVtblByZXNldChudWxsLCBwcmVzZXRJZCk7CiAgICAgICAgYWxlcnQoIkltcG9ydCBjb21wbGV0ZSEiKTsKICAgICAgICBicmVhazsKICAgICAgY2FzZSAiMyI6CiAgICAgICAgdmFyIGZpbGVTZWxlY3RvciA9IG5ldyBGaWxlU2VsZWN0b3IoZmgucmVhZEZpbGVBc0pzb25Bc3luYy5iaW5kKGZoKSk7CiAgICAgICAgZGVidWcoYE9wZW5pbmcgYW5kIHJlYWRpbmcgcHJlc2V0IGZpbGUuLi5gKTsKICAgICAgICB2YXIgcHJlc2V0Q29udGVudCA9IGF3YWl0IGZpbGVTZWxlY3Rvci5zaG93KCk7CiAgICAgICAgZGVidWcoYEdvdCBwcmVzZXQgZmlsZSBjb250ZW50IWApOwoKICAgICAgICBsZXQgYWRBY2NvdW50cyA9IGF3YWl0IGdldEFsbEFkQWNjb3VudHMoKTsKICAgICAgICBkZWJ1ZyhgVG90YWwgYWNjb3VudCBjb3VudCBpczogJHthZEFjY291bnRzLmxlbmd0aH0uYCk7CiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhZEFjY291bnRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICBkZWJ1ZyhgUHJvY2Vzc2luZyBhY2NvdW50ICMke2l9IC0gJHthZEFjY291bnRzW2ldfS4uLmApOwogICAgICAgICAgY29uc3QgdXNlclNldHRpbmdzSWQgPSBhd2FpdCBmZXRjaFVzZXJTZXR0aW5nc0lkKGFkQWNjb3VudHNbaV0pOwogICAgICAgICAgbGV0IHByZXNldElkID0gYXdhaXQgdXBsb2FkUHJlc2V0KHVzZXJTZXR0aW5nc0lkLCBwcmVzZXRDb250ZW50KTsKICAgICAgICAgIGF3YWl0IHNldERlZmF1bHRDb2x1bW5QcmVzZXQoYWRBY2NvdW50c1tpXSwgcHJlc2V0SWQpOwogICAgICAgIH0KICAgICAgICBhbGVydCgiSW1wb3J0IGNvbXBsZXRlISIpOwogICAgICAgIGJyZWFrOwogICAgICBkZWZhdWx0OgogICAgICAgIGFsZXJ0KCJJbnZhbGlkIG9wdGlvbiEiKTsKICAgICAgICBicmVhazsKICAgIH0KICB9IGNhdGNoIChlcnJvcikgewogICAgY29uc29sZS5lcnJvcigiRXJyb3I6IiwgZXJyb3IpOwogIH0KfQoKYXN5bmMgZnVuY3Rpb24gZXhwb3J0Q29sdW1uUHJlc2V0KCkgewogIGxldCBhZEFjY291bnRJZCA9IHJlcXVpcmUoIkJ1c2luZXNzVW5pZmllZE5hdmlnYXRpb25Db250ZXh0IikuYWRBY2NvdW50SUQ7CiAgbGV0IGpzID0gYXdhaXQgQVBJLmdldFJlcXVlc3QoCiAgICBgYWN0XyR7YWRBY2NvdW50SWR9YCwKICAgIGBmaWVsZHM9WyJ1c2VyX3NldHRpbmdze2lkLGNvbHVtbl9wcmVzZXRze2F0dHJpYnV0aW9uX3dpbmRvd3MsY29sdW1ucyxpZCxuYW1lLHRpbWVfY3JlYXRlZCx0aW1lX3VwZGF0ZWR9fSJdYCwKICApOwogIGNvbnN0IHByZXNldHMgPSBqcy51c2VyX3NldHRpbmdzLmNvbHVtbl9wcmVzZXRzLmRhdGE7CiAgaWYgKHByZXNldHMubGVuZ3RoID09PSAwKSB7CiAgICBhbGVydCgiTm8gcHJlc2V0cyBhdmFpbGFibGUiKTsKICAgIHJldHVybjsKICB9CgogIGxldCBwcmVzZXRMaXN0ID0gIlNlbGVjdCBhIHByZXNldCBieSBudW1iZXI6XG4iOwogIHByZXNldHMuZm9yRWFjaCgocHJlc2V0LCBpbmRleCkgPT4gewogICAgcHJlc2V0TGlzdCArPSBgJHtpbmRleCArIDF9LiAke3ByZXNldC5uYW1lfVxuYDsKICB9KTsKCiAgY29uc3Qgc2VsZWN0ZWROdW1iZXIgPSBwYXJzZUludChwcm9tcHQocHJlc2V0TGlzdCksIDEwKTsKICBpZiAoc2VsZWN0ZWROdW1iZXIgPCAxIHx8IHNlbGVjdGVkTnVtYmVyID4gcHJlc2V0cy5sZW5ndGgpIHsKICAgIGFsZXJ0KCJJbnZhbGlkIHNlbGVjdGlvbiIpOwogICAgcmV0dXJuOwogIH0KCiAgY29uc3Qgc2VsZWN0ZWRQcmVzZXQgPSBwcmVzZXRzW3NlbGVjdGVkTnVtYmVyIC0gMV07CiAgY29uc3QgYmxvYiA9IG5ldyBCbG9iKFtKU09OLnN0cmluZ2lmeShzZWxlY3RlZFByZXNldCldLCB7CiAgICB0eXBlOiAiYXBwbGljYXRpb24vanNvbiIsCiAgfSk7CiAgY29uc3QgYSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKTsKICBhLmhyZWYgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpOwogIGEuZG93bmxvYWQgPSBgJHtzZWxlY3RlZFByZXNldC5uYW1lfS5qc29uYDsKICBhLmNsaWNrKCk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGZldGNoVXNlclNldHRpbmdzSWQoYWRBY2NvdW50SWQpIHsKICBsZXQgYWNjSWQgPQogICAgYWRBY2NvdW50SWQgPz8gcmVxdWlyZSgiQnVzaW5lc3NVbmlmaWVkTmF2aWdhdGlvbkNvbnRleHQiKS5hZEFjY291bnRJRDsKICBkZWJ1ZyhgR2V0dGluZyB1c2VyIHNldHRpbmdzIGZvciBhY2MgJHthY2NJZH0uLi5gKTsKICBsZXQganMgPSBhd2FpdCBBUEkuZ2V0UmVxdWVzdChgYWN0XyR7YWNjSWR9YCwgYGZpZWxkcz1bXWApOwogIGxldCB1c0lkID0ganM/LnVzZXJfc2V0dGluZ3M/LmlkOwogIGlmICh1c0lkID09IG51bGwpIHsKICAgIGRlYnVnKGBObyBkZWZhdWx0IHVzZXIgc2V0dGluZ3MgZm91bmQhIENyZWF0aW5nIHRoZW0uLi5gKTsKICAgIGpzID0gYXdhaXQgQVBJLmdldFJlcXVlc3QoYGFjdF8ke2FjY0lkfS91c2VyX3NldHRpbmdzYCwgYG1ldGhvZD1wb3N0YCk7CiAgICB1c0lkID0ganMuaWQ7CiAgfQogIHJldHVybiB1c0lkOwp9Cgphc3luYyBmdW5jdGlvbiB1cGxvYWRQcmVzZXQodXNlclNldHRpbmdzSWQsIHByZXNldERhdGEpIHsKICBsZXQgZGF0YSA9IHsKICAgIG5hbWU6IHByZXNldERhdGEubmFtZSwKICAgIGF0dHJpYnV0aW9uX3dpbmRvd3M6IEpTT04uc3RyaW5naWZ5KHByZXNldERhdGEuYXR0cmlidXRpb25fd2luZG93cyksCiAgICBjb2x1bW5zOiBKU09OLnN0cmluZ2lmeShwcmVzZXREYXRhLmNvbHVtbnMpLAogIH07CiAgZGVidWcoCiAgICBgVXBsb2FkaW5nIHByZXNldCAke3ByZXNldERhdGEubmFtZX0gdG8gdXNlciBzZXR0aW5ncyAke3VzZXJTZXR0aW5nc0lkfS4uLmAsCiAgKTsKICBsZXQganMgPSBhd2FpdCBBUEkucG9zdFJlcXVlc3QoYCR7dXNlclNldHRpbmdzSWR9L2NvbHVtbl9wcmVzZXRzYCwgZGF0YSk7CiAgcmV0dXJuIGpzLmlkOwp9Cgphc3luYyBmdW5jdGlvbiBzZXREZWZhdWx0Q29sdW1uUHJlc2V0KGFkQWNjb3VudElkLCBwcmVzZXRJZCkgewogIGxldCBhY2NJZCA9CiAgICBhZEFjY291bnRJZCA/PyByZXF1aXJlKCJCdXNpbmVzc1VuaWZpZWROYXZpZ2F0aW9uQ29udGV4dCIpLmFkQWNjb3VudElEOwogIGRlYnVnKAogICAgYFNldHRpbmcgZGVmYXVsdCBjb2x1bW4gcHJlc2V0IGZvciBhY2MgJHthY2NJZH0sIHByZXNldCBpZCAke3ByZXNldElkfS4uLmAsCiAgKTsKICBsZXQgZGF0YSA9IHsKICAgIGRlZmF1bHRfY29sdW1uX3ByZXNldDogYHsgImlkIjogIiR7cHJlc2V0SWR9IiB9YCwKICAgIGRlZmF1bHRfY29sdW1uX3ByZXNldF9pZDogcHJlc2V0SWQsCiAgfTsKICBsZXQganMgPSBhd2FpdCBBUEkucG9zdFJlcXVlc3QoYGFjdF8ke2FjY0lkfS91c2VyX3NldHRpbmdzYCwgZGF0YSk7CiAgcmV0dXJuIGpzOwp9Cgphc3luYyBmdW5jdGlvbiBnZXRBbGxBZEFjY291bnRzKCkgewogIGxldCBhZEFjY291bnRzID0gW107CiAgZGVidWcoIkdldHRpbmcgcGVyc29uYWwgYWQgYWNjb3VudHMuLi4iKTsKICBsZXQganMgPSBhd2FpdCBBUEkuZ2V0QWxsUGFnZXMoIm1lL3BlcnNvbmFsX2FkX2FjY291bnRzIiwgImZpZWxkcz1pZCIpOwogIGFkQWNjb3VudHMgPSBqcy5tYXAoKGl0ZW0pID0+IGl0ZW0uaWQucmVwbGFjZSgiYWN0XyIsICIiKSk7CiAgZGVidWcoYEdvdCAke2FkQWNjb3VudHMubGVuZ3RofSBwZXJzb25hbCBhZCBhY2NvdW50cyFgKTsKICBkZWJ1ZygiR2V0dGluZyBidXNpbmVzcyBtYW5hZ2Vycy4uLiIpOwogIGpzID0gYXdhaXQgQVBJLmdldEFsbFBhZ2VzKCJtZS9idXNpbmVzc2VzIiwgImZpZWxkcz1pZCIpOwogIGxldCBibUlkcyA9IGpzLm1hcCgoaXRlbSkgPT4gaXRlbS5pZCk7CiAgZGVidWcoYEdvdCAke2JtSWRzLmxlbmd0aH0gYnVzaW5lc3MgbWFuYWdlcnMhYCk7CiAgZm9yIChsZXQgaSA9IDA7IGkgPCBibUlkcy5sZW5ndGg7IGkrKykgewogICAgbGV0IGJtSWQgPSBibUlkc1tpXTsKICAgIGRlYnVnKGBQcm9jZXNzaW5nIEJNICR7Ym1JZH0uLi5gKTsKICAgIGpzID0gYXdhaXQgQVBJLmdldEFsbFBhZ2VzKGAke2JtSWR9L293bmVkX2FkX2FjY291bnRzYCwgImZpZWxkcz1pZCIpOwogICAgbGV0IG93bmVkID0ganMubWFwKChpdGVtKSA9PiBpdGVtLmlkLnJlcGxhY2UoImFjdF8iLCAiIikpOwogICAgZGVidWcoYEdvdCAke293bmVkLmxlbmd0aH0gb3duZWQgYWNjb3VudHMuYCk7CiAgICBhZEFjY291bnRzID0gYWRBY2NvdW50cy5jb25jYXQob3duZWQpOwogICAganMgPSBhd2FpdCBBUEkuZ2V0QWxsUGFnZXMoYCR7Ym1JZH0vY2xpZW50X2FkX2FjY291bnRzYCwgImZpZWxkcz1pZCIpOwogICAgbGV0IGNsaWVudCA9IGpzLm1hcCgoaXRlbSkgPT4gaXRlbS5pZC5yZXBsYWNlKCJhY3RfIiwgIiIpKTsKICAgIGRlYnVnKGBHb3QgJHtjbGllbnQubGVuZ3RofSBjbGllbnQgYWNjb3VudHMuYCk7CiAgICBhZEFjY291bnRzID0gYWRBY2NvdW50cy5jb25jYXQoY2xpZW50KTsKICB9CiAgcmV0dXJuIGFkQWNjb3VudHM7Cn0KCmZ1bmN0aW9uIGRlYnVnKG1zZykgewogIGlmIChERUJVRykgY29uc29sZS5sb2cobXNnKTsKfQoKbWFpbigpOw==") + "})()");