diff --git a/public/js/admin.js b/public/js/admin.js index 0629857..120b20c 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -1,320 +1 @@ -/* Newbury Nights — admin console */ -const $ = (s, r = document) => r.querySelector(s); -const $$ = (s, r = document) => [...r.querySelectorAll(s)]; - -let TOKEN = null; -let GHOSTS = []; -let SETS = []; - -const api = (path, opts = {}) => - fetch(path, { - ...opts, - headers: { - ...(opts.body && !(opts.body instanceof FormData) ? { 'Content-Type': 'application/json' } : {}), - ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}), - ...(opts.headers || {}), - }, - }); - -/* ---------- Auth ---------- */ -$('#lg-go').addEventListener('click', login); -$('#lg-pass').addEventListener('keydown', (e) => { if (e.key === 'Enter') login(); }); - -async function login() { - const username = $('#lg-user').value.trim(); - const password = $('#lg-pass').value; - const err = $('#lg-error'); - err.classList.add('hidden'); - const res = await api('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) }); - if (!res.ok) { - err.textContent = 'Invalid username or password.'; - err.classList.remove('hidden'); - return; - } - const data = await res.json(); - TOKEN = data.token; - $('#acct-user').textContent = data.user.username; - $('#login').classList.add('hidden'); - $('#app').classList.remove('hidden'); - await Promise.all([loadGhosts(), loadSets()]); -} - -$('#logout').addEventListener('click', async () => { - await api('/auth/logout', { method: 'POST' }); - location.reload(); -}); - -/* ---------- Tabs ---------- */ -$$('.tab').forEach((t) => - t.addEventListener('click', () => { - $$('.tab').forEach((x) => x.classList.remove('active')); - t.classList.add('active'); - $$('.tab-panel').forEach((p) => p.classList.add('hidden')); - $(`#tab-${t.dataset.tab}`).classList.remove('hidden'); - }) -); - -/* ---------- Ghosts ---------- */ -async function loadGhosts() { - const res = await api('/api/admin/ghosts'); - GHOSTS = (await res.json()).ghosts; - renderGhosts(); -} - -$('#ghost-search').addEventListener('input', renderGhosts); - -function renderGhosts() { - const q = $('#ghost-search').value.toLowerCase(); - const rows = GHOSTS.filter( - (g) => !q || g.name.toLowerCase().includes(q) || (g.ability || '').toLowerCase().includes(q) - ).map(ghostRow).join(''); - $('#ghosts-table tbody').innerHTML = rows; - - $$('#ghosts-table .edit').forEach((b) => - b.addEventListener('click', () => openGhost(+b.dataset.id))); - $$('#ghosts-table .del').forEach((b) => - b.addEventListener('click', () => deleteGhost(+b.dataset.id))); - $$('#ghosts-table .toggle').forEach((b) => - b.addEventListener('click', () => toggleGhost(+b.dataset.id))); -} - -function ghostRow(g) { - const img = g.image_path ? `` : ''; - const setRef = g.set_number ? `${g.set_number}` : '—'; - return ` - ${img} -
${esc(g.name)}${g.is_boss ? ' boss' : ''}
- ${g.display_name && g.display_name !== g.name ? `
↳ ${esc(g.display_name)}
` : ''} - ${g.type} - ${'★'.repeat(g.rarity)} - ${g.health}${g.damage} - ${esc(g.ability || '—')} - ${setRef} - ${g.enabled ? '◉' : '◯'} -
- - -
- `; -} - -async function toggleGhost(id) { - const g = GHOSTS.find((x) => x.id === id); - await api(`/api/admin/ghosts/${id}`, { method: 'PATCH', body: JSON.stringify({ enabled: !g.enabled }) }); - await loadGhosts(); -} - -async function deleteGhost(id) { - const g = GHOSTS.find((x) => x.id === id); - if (!confirm(`Delete "${g.name}"? This also removes it from any set rosters.`)) return; - await api(`/api/admin/ghosts/${id}`, { method: 'DELETE' }); - await Promise.all([loadGhosts(), loadSets()]); -} - -/* ghost modal */ -let editingGhostId = null; -$('#new-ghost').addEventListener('click', () => openGhost(null)); - -function openGhost(id) { - editingGhostId = id; - const g = id ? GHOSTS.find((x) => x.id === id) : null; - $('#gm-title').textContent = g ? 'Edit ghost' : 'New ghost'; - $('#gm-name').value = g?.name || ''; - $('#gm-display').value = g?.display_name || ''; - $('#gm-type').value = g?.type || 'red'; - $('#gm-rarity').value = g?.rarity ?? 1; - $('#gm-health').value = g?.health ?? 300; - $('#gm-damage').value = g?.damage ?? 150; - $('#gm-speed').value = g?.speed ?? 3; - $('#gm-range').value = g?.range ?? 3; - $('#gm-charge').value = g?.charge_shot ?? 2; - $('#gm-ability').value = g?.ability || ''; - $('#gm-setnum').value = g?.set_number || ''; - $('#gm-setname').value = g?.set_name || ''; - $('#gm-boss').checked = !!g?.is_boss; - $('#gm-enabled').checked = g ? !!g.enabled : true; - $('#gm-file').value = ''; - const prev = $('#gm-preview'); - if (g?.image_path) { prev.src = `/uploads/${g.image_path}`; prev.classList.remove('hidden'); } - else prev.classList.add('hidden'); - $('#ghost-modal').classList.remove('hidden'); -} - -$('#gm-save').addEventListener('click', saveGhost); - -async function saveGhost() { - const payload = { - name: $('#gm-name').value.trim(), - displayName: $('#gm-display').value.trim() || $('#gm-name').value.trim(), - type: $('#gm-type').value, - rarity: +$('#gm-rarity').value, - health: +$('#gm-health').value, - damage: +$('#gm-damage').value, - speed: +$('#gm-speed').value, - range: +$('#gm-range').value, - chargeShot: +$('#gm-charge').value, - ability: $('#gm-ability').value.trim() || null, - setNumber: $('#gm-setnum').value.trim() || null, - setName: $('#gm-setname').value.trim() || null, - isBoss: $('#gm-boss').checked, - enabled: $('#gm-enabled').checked, - }; - if (!payload.name) { alert('Name is required.'); return; } - - let id = editingGhostId; - if (id) { - await api(`/api/admin/ghosts/${id}`, { method: 'PATCH', body: JSON.stringify(payload) }); - } else { - const res = await api('/api/admin/ghosts', { method: 'POST', body: JSON.stringify(payload) }); - id = (await res.json()).id; - } - - const file = $('#gm-file').files[0]; - if (file && id) { - const fd = new FormData(); - fd.append('image', file); - await api(`/api/admin/ghosts/${id}/image`, { method: 'POST', body: fd }); - } - - $('#ghost-modal').classList.add('hidden'); - await Promise.all([loadGhosts(), loadSets()]); -} - -/* ---------- Sets ---------- */ -async function loadSets() { - const res = await api('/api/admin/sets'); - SETS = (await res.json()).sets; - renderSets(); -} - -function renderSets() { - $('#sets-list').innerHTML = SETS.map(setCard).join('') || - '

No sets yet. Create one to wire a scan code to a ghost roster.

'; - $$('#sets-list .edit-set').forEach((b) => - b.addEventListener('click', () => openSet(+b.dataset.id))); - $$('#sets-list .del-set').forEach((b) => - b.addEventListener('click', () => deleteSet(+b.dataset.id))); -} - -function setCard(s) { - const chips = s.roster.map((r) => - `${esc(r.name)}${r.is_boss ? ' ★' : ''}` - ).join(''); - return `
-
- ${esc(s.code)} - ${esc(s.set_name)} - ${s.set_number ? `#${esc(s.set_number)}` : ''} - - - -
-
${chips || 'no roster'}
-
`; -} - -async function deleteSet(id) { - const s = SETS.find((x) => x.id === id); - if (!confirm(`Delete set "${s.set_name}" (${s.code})?`)) return; - await api(`/api/admin/sets/${id}`, { method: 'DELETE' }); - await loadSets(); -} - -/* set modal */ -let editingSetId = null; -$('#new-set').addEventListener('click', () => openSet(null)); -$('#sm-roster-search').addEventListener('input', filterRosterChecklist); - -function openSet(id) { - editingSetId = id; - const s = id ? SETS.find((x) => x.id === id) : null; - $('#sm-title').textContent = s ? 'Edit set' : 'New set'; - $('#sm-code').value = s?.code || ''; - $('#sm-setnum').value = s?.set_number || ''; - $('#sm-setname').value = s?.set_name || ''; - $('#sm-enabled').checked = s ? !!s.enabled : true; - - // boss dropdown - const bossSel = $('#sm-boss'); - bossSel.innerHTML = '' + - GHOSTS.filter((g) => g.is_boss).map((g) => - ``).join(''); - bossSel.value = s?.boss_ghost_id || ''; - - // roster checklist - const selected = new Set((s?.roster || []).map((r) => r.id)); - $('#sm-roster').innerHTML = GHOSTS.map((g) => - ``).join(''); - $('#sm-roster-search').value = ''; - $('#set-modal').classList.remove('hidden'); -} - -function filterRosterChecklist() { - const q = $('#sm-roster-search').value.toLowerCase(); - $$('#sm-roster .rcheck').forEach((el) => { - el.style.display = !q || el.dataset.name.includes(q) ? '' : 'none'; - }); -} - -$('#sm-save').addEventListener('click', saveSet); - -async function saveSet() { - const payload = { - code: $('#sm-code').value.trim(), - setNumber: $('#sm-setnum').value.trim() || null, - setName: $('#sm-setname').value.trim(), - bossGhostId: $('#sm-boss').value ? +$('#sm-boss').value : null, - enabled: $('#sm-enabled').checked, - }; - if (!payload.code || !payload.setName) { alert('Scan code and set name are required.'); return; } - - let id = editingSetId; - if (id) { - await api(`/api/admin/sets/${id}`, { method: 'PATCH', body: JSON.stringify(payload) }); - } else { - const res = await api('/api/admin/sets', { method: 'POST', body: JSON.stringify(payload) }); - if (res.status === 409) { alert('That scan code already exists.'); return; } - id = (await res.json()).id; - } - - const ghostIds = $$('#sm-roster input:checked').map((i) => +i.value); - await api(`/api/admin/sets/${id}/roster`, { method: 'PUT', body: JSON.stringify({ ghostIds }) }); - - $('#set-modal').classList.add('hidden'); - await loadSets(); -} - -/* ---------- Account ---------- */ -$('#cp-go').addEventListener('click', async () => { - const msg = $('#cp-msg'); - msg.classList.add('hidden'); msg.classList.remove('ok'); - const res = await api('/auth/change-password', { - method: 'POST', - body: JSON.stringify({ - currentPassword: $('#cp-current').value, - newPassword: $('#cp-new').value, - }), - }); - const data = await res.json().catch(() => ({})); - if (res.ok) { - msg.textContent = 'Password updated.'; msg.classList.add('ok'); - } else { - msg.textContent = data.error || 'Could not update password.'; - } - msg.classList.remove('hidden'); - $('#cp-current').value = ''; $('#cp-new').value = ''; -}); - -/* ---------- modal close ---------- */ -$$('[data-close]').forEach((b) => - b.addEventListener('click', () => $$('.modal').forEach((m) => m.classList.add('hidden')))); -$$('.modal').forEach((m) => - m.addEventListener('click', (e) => { if (e.target === m) m.classList.add('hidden'); })); - -function esc(s) { - return String(s ?? '').replace(/[&<>"']/g, (c) => - ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); -} +LyogTmV3YnVyeSBOaWdodHMg4oCUIGFkbWluIGNvbnNvbGUgKi8KY29uc3QgJCA9IChzLCByID0gZG9jdW1lbnQpID0+IHIucXVlcnlTZWxlY3RvcihzKTsKY29uc3QgJCQgPSAocywgciA9IGRvY3VtZW50KSA9PiBbLi4uci5xdWVyeVNlbGVjdG9yQWxsKHMpXTsKCmxldCBUT0tFTiA9IG51bGw7CmxldCBHSE9TVFMgPSBbXTsKbGV0IFNFVFMgPSBbXTsKCmNvbnN0IGFwaSA9IChwYXRoLCBvcHRzID0ge30pID0+CiAgZmV0Y2gocGF0aCwgewogICAgLi4ub3B0cywKICAgIGhlYWRlcnM6IHsKICAgICAgLi4uKG9wdHMuYm9keSAmJiAhKG9wdHMuYm9keSBpbnN0YW5jZW9mIEZvcm1EYXRhKSA/IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9IDoge30pLAogICAgICAuLi4oVE9LRU4gPyB7IEF1dGhvcml6YXRpb246IGBCZWFyZXIgJHtUT0tFTn1gIH0gOiB7fSksCiAgICAgIC4uLihvcHRzLmhlYWRlcnMgfHwge30pLAogICAgfSwKICB9KTsKCi8qIC0tLS0tLS0tLS0gQXV0aCAtLS0tLS0tLS0tICovCiQoJyNsZy1nbycpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgbG9naW4pOwokKCcjbGctcGFzcycpLmFkZEV2ZW50TGlzdGVuZXIoJ2tleWRvd24nLCAoZSkgPT4geyBpZiAoZS5rZXkgPT09ICdFbnRlcicpIGxvZ2luKCk7IH0pOwoKYXN5bmMgZnVuY3Rpb24gbG9naW4oKSB7CiAgY29uc3QgdXNlcm5hbWUgPSAkKCcjbGctdXNlcicpLnZhbHVlLnRyaW0oKTsKICBjb25zdCBwYXNzd29yZCA9ICQoJyNsZy1wYXNzJykudmFsdWU7CiAgY29uc3QgZXJyID0gJCgnI2xnLWVycm9yJyk7CiAgZXJyLmNsYXNzTGlzdC5hZGQoJ2hpZGRlbicpOwogIGNvbnN0IHJlcyA9IGF3YWl0IGFwaSgnL2F1dGgvbG9naW4nLCB7IG1ldGhvZDogJ1BPU1QnLCBib2R5OiBKU09OLnN0cmluZ2lmeSh7IHVzZXJuYW1lLCBwYXNzd29yZCB9KSB9KTsKICBpZiAoIXJlcy5vaykgewogICAgZXJyLnRleHRDb250ZW50ID0gJ0ludmFsaWQgdXNlcm5hbWUgb3IgcGFzc3dvcmQuJzsKICAgIGVyci5jbGFzc0xpc3QucmVtb3ZlKCdoaWRkZW4nKTsKICAgIHJldHVybjsKICB9CiAgY29uc3QgZGF0YSA9IGF3YWl0IHJlcy5qc29uKCk7CiAgVE9LRU4gPSBkYXRhLnRva2VuOwogICQoJyNhY2N0LXVzZXInKS50ZXh0Q29udGVudCA9IGRhdGEudXNlci51c2VybmFtZTsKICAkKCcjbG9naW4nKS5jbGFzc0xpc3QuYWRkKCdoaWRkZW4nKTsKICAkKCcjYXBwJykuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7CiAgYXdhaXQgUHJvbWlzZS5hbGwoW2xvYWRHaG9zdHMoKSwgbG9hZFNldHMoKV0pOwp9CgokKCcjbG9nb3V0JykuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBhc3luYyAoKSA9PiB7CiAgYXdhaXQgYXBpKCcvYXV0aC9sb2dvdXQnLCB7IG1ldGhvZDogJ1BPU1QnIH0pOwogIGxvY2F0aW9uLnJlbG9hZCgpOwp9KTsKCi8qIC0tLS0tLS0tLS0gVGFicyAtLS0tLS0tLS0tICovCiQkKCcudGFiJykuZm9yRWFjaCgodCkgPT4KICB0LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gewogICAgJCQoJy50YWInKS5mb3JFYWNoKCh4KSA9PiB4LmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpKTsKICAgIHQuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7CiAgICAkJCgnLnRhYi1wYW5lbCcpLmZvckVhY2goKHApID0+IHAuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJykpOwogICAgJChgI3RhYi0ke3QuZGF0YXNldC50YWJ9YCkuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7CiAgfSkKKTsKCi8qIC0tLS0tLS0tLS0gR2hvc3RzIC0tLS0tLS0tLS0gKi8KYXN5bmMgZnVuY3Rpb24gbG9hZEdob3N0cygpIHsKICBjb25zdCByZXMgPSBhd2FpdCBhcGkoJy9hcGkvYWRtaW4vZ2hvc3RzJyk7CiAgR0hPU1RTID0gKGF3YWl0IHJlcy5qc29uKCkpLmdob3N0czsKICByZW5kZXJHaG9zdHMoKTsKfQoKJCgnI2dob3N0LXNlYXJjaCcpLmFkZEV2ZW50TGlzdGVuZXIoJ2lucHV0JywgcmVuZGVyR2hvc3RzKTsKCmZ1bmN0aW9uIHJlbmRlckdob3N0cygpIHsKICBjb25zdCBxID0gJCgnI2dob3N0LXNlYXJjaCcpLnZhbHVlLnRvTG93ZXJDYXNlKCk7CiAgY29uc3Qgcm93cyA9IEdIT1NUUy5maWx0ZXIoCiAgICAoZykgPT4gIXEgfHwgZy5uYW1lLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMocSkgfHwgKGcuYWJpbGl0eSB8fCAnJykudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKQogICkubWFwKGdob3N0Um93KS5qb2luKCcnKTsKICAkKCcjZ2hvc3RzLXRhYmxlIHRib2R5JykuaW5uZXJIVE1MID0gcm93czsKCiAgJCQoJyNnaG9zdHMtdGFibGUgLmVkaXQnKS5mb3JFYWNoKChiKSA9PgogICAgYi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IG9wZW5HaG9zdCgrYi5kYXRhc2V0LmlkKSkpOwogICQkKCcjZ2hvc3RzLXRhYmxlIC5kZWwnKS5mb3JFYWNoKChiKSA9PgogICAgYi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IGRlbGV0ZUdob3N0KCtiLmRhdGFzZXQuaWQpKSk7CiAgJCQoJyNnaG9zdHMtdGFibGUgLnRvZ2dsZScpLmZvckVhY2goKGIpID0+CiAgICBiLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gdG9nZ2xlR2hvc3QoK2IuZGF0YXNldC5pZCkpKTsKfQoKZnVuY3Rpb24gZ2hvc3RSb3coZykgewogIGNvbnN0IGltZyA9IGcuaW1hZ2VfcGF0aAogICAgPyBgPGltZyBzcmM9Ii91cGxvYWRzLyR7Zy5pbWFnZV9wYXRofSIgYWx0PSIiPmAKICAgIDogKGcud2VibV9wYXRoCiAgICAgICAgPyBgPHZpZGVvIHNyYz0iL3VwbG9hZHMvJHtnLndlYm1fcGF0aH0iIG11dGVkIGxvb3AgYXV0b3BsYXkgcGxheXNpbmxpbmU+PC92aWRlbz5gCiAgICAgICAgOiAnJyk7CiAgY29uc3Qgc2V0UmVmID0gZy5zZXRfbnVtYmVyID8gYCR7Zy5zZXRfbnVtYmVyfWAgOiAn4oCUJzsKICByZXR1cm4gYDx0cj4KICAgIDx0ZCBjbGFzcz0idGh1bWItY2VsbCI+JHtpbWd9PC90ZD4KICAgIDx0ZD48ZGl2PiR7ZXNjKGcubmFtZSl9JHtnLmlzX2Jvc3MgPyAnIDxzcGFuIGNsYXNzPSJwaWxsIj5ib3NzPC9zcGFuPicgOiAnJ308L2Rpdj4KICAgICAgICAke2cuZGlzcGxheV9uYW1lICYmIGcuZGlzcGxheV9uYW1lICE9PSBnLm5hbWUgPyBgPGRpdiBjbGFzcz0ic3ViIj7ihrMgJHtlc2MoZy5kaXNwbGF5X25hbWUpfTwvZGl2PmAgOiAnJ308L3RkPgogICAgPHRkPjxzcGFuIGNsYXNzPSJkb3QgJHtnLnR5cGV9Ij48L3NwYW4+ICR7Zy50eXBlfTwvdGQ+CiAgICA8dGQgY2xhc3M9InN0YXJzIj4keyfimIUnLnJlcGVhdChnLnJhcml0eSl9PC90ZD4KICAgIDx0ZD4ke2cuaGVhbHRofTwvdGQ+PHRkPiR7Zy5kYW1hZ2V9PC90ZD4KICAgIDx0ZD4ke2VzYyhnLmFiaWxpdHkgfHwgJ+KAlCcpfTwvdGQ+CiAgICA8dGQgY2xhc3M9Im1vbm8iPiR7c2V0UmVmfTwvdGQ+CiAgICA8dGQ+PHNwYW4gY2xhc3M9InRvZ2dsZSAke2cuZW5hYmxlZCA/ICdvbicgOiAnb2ZmJ30iIGRhdGEtaWQ9IiR7Zy5pZH0iPiR7Zy5lbmFibGVkID8gJ+KXiScgOiAn4pevJ308L3NwYW4+PC90ZD4KICAgIDx0ZD48ZGl2IGNsYXNzPSJyb3ctYWN0aW9ucyI+CiAgICAgIDxidXR0b24gY2xhc3M9ImVkaXQiIGRhdGEtaWQ9IiR7Zy5pZH0iPkVkaXQ8L2J1dHRvbj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iZGFuZ2VyIGRlbCIgZGF0YS1pZD0iJHtnLmlkfSI+RGVsPC9idXR0b24+CiAgICA8L2Rpdj48L3RkPgogIDwvdHI+YDsKfQoKYXN5bmMgZnVuY3Rpb24gdG9nZ2xlR2hvc3QoaWQpIHsKICBjb25zdCBnID0gR0hPU1RTLmZpbmQoKHgpID0+IHguaWQgPT09IGlkKTsKICBhd2FpdCBhcGkoYC9hcGkvYWRtaW4vZ2hvc3RzLyR7aWR9YCwgeyBtZXRob2Q6ICdQQVRDSCcsIGJvZHk6IEpTT04uc3RyaW5naWZ5KHsgZW5hYmxlZDogIWcuZW5hYmxlZCB9KSB9KTsKICBhd2FpdCBsb2FkR2hvc3RzKCk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGRlbGV0ZUdob3N0KGlkKSB7CiAgY29uc3QgZyA9IEdIT1NUUy5maW5kKCh4KSA9PiB4LmlkID09PSBpZCk7CiAgaWYgKCFjb25maXJtKGBEZWxldGUgIiR7Zy5uYW1lfSI/IFRoaXMgYWxzbyByZW1vdmVzIGl0IGZyb20gYW55IHNldCByb3N0ZXJzLmApKSByZXR1cm47CiAgYXdhaXQgYXBpKGAvYXBpL2FkbWluL2dob3N0cy8ke2lkfWAsIHsgbWV0aG9kOiAnREVMRVRFJyB9KTsKICBhd2FpdCBQcm9taXNlLmFsbChbbG9hZEdob3N0cygpLCBsb2FkU2V0cygpXSk7Cn0KCi8qIGdob3N0IG1vZGFsICovCmxldCBlZGl0aW5nR2hvc3RJZCA9IG51bGw7CiQoJyNuZXctZ2hvc3QnKS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IG9wZW5HaG9zdChudWxsKSk7CgpmdW5jdGlvbiBvcGVuR2hvc3QoaWQpIHsKICBlZGl0aW5nR2hvc3RJZCA9IGlkOwogIGNvbnN0IGcgPSBpZCA/IEdIT1NUUy5maW5kKCh4KSA9PiB4LmlkID09PSBpZCkgOiBudWxsOwogICQoJyNnbS10aXRsZScpLnRleHRDb250ZW50ID0gZyA/ICdFZGl0IGdob3N0JyA6ICdOZXcgZ2hvc3QnOwogICQoJyNnbS1uYW1lJykudmFsdWUgPSBnPy5uYW1lIHx8ICcnOwogICQoJyNnbS1kaXNwbGF5JykudmFsdWUgPSBnPy5kaXNwbGF5X25hbWUgfHwgJyc7CiAgJCgnI2dtLXR5cGUnKS52YWx1ZSA9IGc/LnR5cGUgfHwgJ3JlZCc7CiAgJCgnI2dtLXJhcml0eScpLnZhbHVlID0gZz8ucmFyaXR5ID8/IDE7CiAgJCgnI2dtLWhlYWx0aCcpLnZhbHVlID0gZz8uaGVhbHRoID8/IDMwMDsKICAkKCcjZ20tZGFtYWdlJykudmFsdWUgPSBnPy5kYW1hZ2UgPz8gMTUwOwogICQoJyNnbS1zcGVlZCcpLnZhbHVlID0gZz8uc3BlZWQgPz8gMzsKICAkKCcjZ20tcmFuZ2UnKS52YWx1ZSA9IGc/LnJhbmdlID8/IDM7CiAgJCgnI2dtLWNoYXJnZScpLnZhbHVlID0gZz8uY2hhcmdlX3Nob3QgPz8gMjsKICAkKCcjZ20tYWJpbGl0eScpLnZhbHVlID0gZz8uYWJpbGl0eSB8fCAnJzsKICAkKCcjZ20tc2V0bnVtJykudmFsdWUgPSBnPy5zZXRfbnVtYmVyIHx8ICcnOwogICQoJyNnbS1zZXRuYW1lJykudmFsdWUgPSBnPy5zZXRfbmFtZSB8fCAnJzsKICAkKCcjZ20tYm9zcycpLmNoZWNrZWQgPSAhIWc/LmlzX2Jvc3M7CiAgJCgnI2dtLWVuYWJsZWQnKS5jaGVja2VkID0gZyA/ICEhZy5lbmFibGVkIDogdHJ1ZTsKICAkKCcjZ20tZmlsZScpLnZhbHVlID0gJyc7CiAgLy8gU2hvdyB0aGUgZXhpc3Rpbmcgc3RvcmVkIG1lZGlhOiBwcmVmZXIgdGhlIFdlYk0gdmlkZW8sIGVsc2UgYSBzdGlsbCBpbWFnZQogIC8vIChpbWFnZV9wYXRoIGlzIHRoZSBHSUYvUE5HLCBvciB0aGUgV2ViUCB0aHVtYm5haWwgZm9yIGNvbnZlcnRlZCBnaG9zdHMpLgogIGlmIChnPy53ZWJtX3BhdGgpIHNob3dQcmV2aWV3KGAvdXBsb2Fkcy8ke2cud2VibV9wYXRofWAsICd2aWRlbycpOwogIGVsc2UgaWYgKGc/LmltYWdlX3BhdGgpIHNob3dQcmV2aWV3KGAvdXBsb2Fkcy8ke2cuaW1hZ2VfcGF0aH1gLCAnaW1hZ2UnKTsKICBlbHNlIGlmIChnPy53ZWJwX3BhdGgpIHNob3dQcmV2aWV3KGAvdXBsb2Fkcy8ke2cud2VicF9wYXRofWAsICdpbWFnZScpOwogIGVsc2UgaGlkZVByZXZpZXcoKTsKICAkKCcjZ2hvc3QtbW9kYWwnKS5jbGFzc0xpc3QucmVtb3ZlKCdoaWRkZW4nKTsKfQoKLy8gU3dhcCB0aGUgbW9kYWwgcHJldmlldyBiZXR3ZWVuIGFuIDxpbWc+IGFuZCBhIDx2aWRlbz4gZGVwZW5kaW5nIG9uIG1lZGlhIGtpbmQuCmZ1bmN0aW9uIHNob3dQcmV2aWV3KHNyYywga2luZCkgewogIGNvbnN0IGltZyA9ICQoJyNnbS1wcmV2aWV3Jyk7CiAgY29uc3QgdmlkID0gJCgnI2dtLXByZXZpZXctdmlkJyk7CiAgaWYgKGtpbmQgPT09ICd2aWRlbycpIHsKICAgIGltZy5jbGFzc0xpc3QuYWRkKCdoaWRkZW4nKTsgaW1nLnJlbW92ZUF0dHJpYnV0ZSgnc3JjJyk7CiAgICB2aWQuc3JjID0gc3JjOyB2aWQuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7CiAgICB2aWQucGxheT8uKCkuY2F0Y2goKCkgPT4ge30pOwogIH0gZWxzZSB7CiAgICB2aWQuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IHZpZC5wYXVzZT8uKCk7IHZpZC5yZW1vdmVBdHRyaWJ1dGUoJ3NyYycpOwogICAgaW1nLnNyYyA9IHNyYzsgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2hpZGRlbicpOwogIH0KfQoKZnVuY3Rpb24gaGlkZVByZXZpZXcoKSB7CiAgY29uc3QgaW1nID0gJCgnI2dtLXByZXZpZXcnKTsKICBjb25zdCB2aWQgPSAkKCcjZ20tcHJldmlldy12aWQnKTsKICBpbWcuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IGltZy5yZW1vdmVBdHRyaWJ1dGUoJ3NyYycpOwogIHZpZC5jbGFzc0xpc3QuYWRkKCdoaWRkZW4nKTsgdmlkLnBhdXNlPy4oKTsgdmlkLnJlbW92ZUF0dHJpYnV0ZSgnc3JjJyk7Cn0KCi8vIExpdmUgbG9jYWwgcHJldmlldyB3aGVuIGEgZmlsZSBpcyBjaG9zZW4gKGJlZm9yZSB1cGxvYWQpLgokKCcjZ20tZmlsZScpLmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsICgpID0+IHsKICBjb25zdCBmaWxlID0gJCgnI2dtLWZpbGUnKS5maWxlc1swXTsKICBpZiAoIWZpbGUpIHJldHVybjsKICBjb25zdCB1cmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGZpbGUpOwogIGNvbnN0IGlzVmlkZW8gPSAvXC4obXA0fHdlYm0pJC9pLnRlc3QoZmlsZS5uYW1lKSB8fCBmaWxlLnR5cGUuc3RhcnRzV2l0aCgndmlkZW8vJyk7CiAgc2hvd1ByZXZpZXcodXJsLCBpc1ZpZGVvID8gJ3ZpZGVvJyA6ICdpbWFnZScpOwp9KTsKCiQoJyNnbS1zYXZlJykuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBzYXZlR2hvc3QpOwoKYXN5bmMgZnVuY3Rpb24gc2F2ZUdob3N0KCkgewogIGNvbnN0IHBheWxvYWQgPSB7CiAgICBuYW1lOiAkKCcjZ20tbmFtZScpLnZhbHVlLnRyaW0oKSwKICAgIGRpc3BsYXlOYW1lOiAkKCcjZ20tZGlzcGxheScpLnZhbHVlLnRyaW0oKSB8fCAkKCcjZ20tbmFtZScpLnZhbHVlLnRyaW0oKSwKICAgIHR5cGU6ICQoJyNnbS10eXBlJykudmFsdWUsCiAgICByYXJpdHk6ICskKCcjZ20tcmFyaXR5JykudmFsdWUsCiAgICBoZWFsdGg6ICskKCcjZ20taGVhbHRoJykudmFsdWUsCiAgICBkYW1hZ2U6ICskKCcjZ20tZGFtYWdlJykudmFsdWUsCiAgICBzcGVlZDogKyQoJyNnbS1zcGVlZCcpLnZhbHVlLAogICAgcmFuZ2U6ICskKCcjZ20tcmFuZ2UnKS52YWx1ZSwKICAgIGNoYXJnZVNob3Q6ICskKCcjZ20tY2hhcmdlJykudmFsdWUsCiAgICBhYmlsaXR5OiAkKCcjZ20tYWJpbGl0eScpLnZhbHVlLnRyaW0oKSB8fCBudWxsLAogICAgc2V0TnVtYmVyOiAkKCcjZ20tc2V0bnVtJykudmFsdWUudHJpbSgpIHx8IG51bGwsCiAgICBzZXROYW1lOiAkKCcjZ20tc2V0bmFtZScpLnZhbHVlLnRyaW0oKSB8fCBudWxsLAogICAgaXNCb3NzOiAkKCcjZ20tYm9zcycpLmNoZWNrZWQsCiAgICBlbmFibGVkOiAkKCcjZ20tZW5hYmxlZCcpLmNoZWNrZWQsCiAgfTsKICBpZiAoIXBheWxvYWQubmFtZSkgeyBhbGVydCgnTmFtZSBpcyByZXF1aXJlZC4nKTsgcmV0dXJuOyB9CgogIGxldCBpZCA9IGVkaXRpbmdHaG9zdElkOwogIGlmIChpZCkgewogICAgYXdhaXQgYXBpKGAvYXBpL2FkbWluL2dob3N0cy8ke2lkfWAsIHsgbWV0aG9kOiAnUEFUQ0gnLCBib2R5OiBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSB9KTsKICB9IGVsc2UgewogICAgY29uc3QgcmVzID0gYXdhaXQgYXBpKCcvYXBpL2FkbWluL2dob3N0cycsIHsgbWV0aG9kOiAnUE9TVCcsIGJvZHk6IEpTT04uc3RyaW5naWZ5KHBheWxvYWQpIH0pOwogICAgaWQgPSAoYXdhaXQgcmVzLmpzb24oKSkuaWQ7CiAgfQoKICBjb25zdCBmaWxlID0gJCgnI2dtLWZpbGUnKS5maWxlc1swXTsKICBpZiAoZmlsZSAmJiBpZCkgewogICAgY29uc3QgZmQgPSBuZXcgRm9ybURhdGEoKTsKICAgIGZkLmFwcGVuZCgnaW1hZ2UnLCBmaWxlKTsKICAgIGF3YWl0IGFwaShgL2FwaS9hZG1pbi9naG9zdHMvJHtpZH0vaW1hZ2VgLCB7IG1ldGhvZDogJ1BPU1QnLCBib2R5OiBmZCB9KTsKICB9CgogICQoJyNnaG9zdC1tb2RhbCcpLmNsYXNzTGlzdC5hZGQoJ2hpZGRlbicpOwogIGF3YWl0IFByb21pc2UuYWxsKFtsb2FkR2hvc3RzKCksIGxvYWRTZXRzKCldKTsKfQoKLyogLS0tLS0tLS0tLSBTZXRzIC0tLS0tLS0tLS0gKi8KYXN5bmMgZnVuY3Rpb24gbG9hZFNldHMoKSB7CiAgY29uc3QgcmVzID0gYXdhaXQgYXBpKCcvYXBpL2FkbWluL3NldHMnKTsKICBTRVRTID0gKGF3YWl0IHJlcy5qc29uKCkpLnNldHM7CiAgcmVuZGVyU2V0cygpOwp9CgpmdW5jdGlvbiByZW5kZXJTZXRzKCkgewogICQoJyNzZXRzLWxpc3QnKS5pbm5lckhUTUwgPSBTRVRTLm1hcChzZXRDYXJkKS5qb2luKCcnKSB8fAogICAgJzxwIGNsYXNzPSJtdXRlZCI+Tm8gc2V0cyB5ZXQuIENyZWF0ZSBvbmUgdG8gd2lyZSBhIHNjYW4gY29kZSB0byBhIGdob3N0IHJvc3Rlci48L3A+JzsKICAkJCgnI3NldHMtbGlzdCAuZWRpdC1zZXQnKS5mb3JFYWNoKChiKSA9PgogICAgYi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IG9wZW5TZXQoK2IuZGF0YXNldC5pZCkpKTsKICAkJCgnI3NldHMtbGlzdCAuZGVsLXNldCcpLmZvckVhY2goKGIpID0+CiAgICBiLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gZGVsZXRlU2V0KCtiLmRhdGFzZXQuaWQpKSk7Cn0KCmZ1bmN0aW9uIHNldENhcmQocykgewogIGNvbnN0IGNoaXBzID0gcy5yb3N0ZXIubWFwKChyKSA9PgogICAgYDxzcGFuIGNsYXNzPSJyY2hpcCI+PHNwYW4gY2xhc3M9ImRvdCAke3IudHlwZX0iPjwvc3Bhbj4ke2VzYyhyLm5hbWUpfSR7ci5pc19ib3NzID8gJyDimIUnIDogJyd9PC9zcGFuPmAKICApLmpvaW4oJycpOwogIHJldHVybiBgPGRpdiBjbGFzcz0ic2V0LWNhcmQgJHtzLmVuYWJsZWQgPyAnJyA6ICdvZmYnfSI+CiAgICA8ZGl2IGNsYXNzPSJzZXQtaGVhZCI+CiAgICAgIDxzcGFuIGNsYXNzPSJjb2RlIj4ke2VzYyhzLmNvZGUpfTwvc3Bhbj4KICAgICAgPHNwYW4gY2xhc3M9InNuYW1lIj4ke2VzYyhzLnNldF9uYW1lKX08L3NwYW4+CiAgICAgICR7cy5zZXRfbnVtYmVyID8gYDxzcGFuIGNsYXNzPSJtb25vIG11dGVkIj4jJHtlc2Mocy5zZXRfbnVtYmVyKX08L3NwYW4+YCA6ICcnfQogICAgICA8c3BhbiBjbGFzcz0iZ3JvdyI+PC9zcGFuPgogICAgICA8YnV0dG9uIGNsYXNzPSJlZGl0LXNldCIgZGF0YS1pZD0iJHtzLmlkfSI+RWRpdDwvYnV0dG9uPgogICAgICA8YnV0dG9uIGNsYXNzPSJkYW5nZXIgZGVsLXNldCIgZGF0YS1pZD0iJHtzLmlkfSI+RGVsZXRlPC9idXR0b24+CiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9InNldC1yb3N0ZXIiPiR7Y2hpcHMgfHwgJzxzcGFuIGNsYXNzPSJtdXRlZCI+bm8gcm9zdGVyPC9zcGFuPid9PC9kaXY+CiAgPC9kaXY+YDsKfQoKYXN5bmMgZnVuY3Rpb24gZGVsZXRlU2V0KGlkKSB7CiAgY29uc3QgcyA9IFNFVFMuZmluZCgoeCkgPT4geC5pZCA9PT0gaWQpOwogIGlmICghY29uZmlybShgRGVsZXRlIHNldCAiJHtzLnNldF9uYW1lfSIgKCR7cy5jb2RlfSk/YCkpIHJldHVybjsKICBhd2FpdCBhcGkoYC9hcGkvYWRtaW4vc2V0cy8ke2lkfWAsIHsgbWV0aG9kOiAnREVMRVRFJyB9KTsKICBhd2FpdCBsb2FkU2V0cygpOwp9CgovKiBzZXQgbW9kYWwgKi8KbGV0IGVkaXRpbmdTZXRJZCA9IG51bGw7CiQoJyNuZXctc2V0JykuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiBvcGVuU2V0KG51bGwpKTsKJCgnI3NtLXJvc3Rlci1zZWFyY2gnKS5hZGRFdmVudExpc3RlbmVyKCdpbnB1dCcsIGZpbHRlclJvc3RlckNoZWNrbGlzdCk7CgpmdW5jdGlvbiBvcGVuU2V0KGlkKSB7CiAgZWRpdGluZ1NldElkID0gaWQ7CiAgY29uc3QgcyA9IGlkID8gU0VUUy5maW5kKCh4KSA9PiB4LmlkID09PSBpZCkgOiBudWxsOwogICQoJyNzbS10aXRsZScpLnRleHRDb250ZW50ID0gcyA/ICdFZGl0IHNldCcgOiAnTmV3IHNldCc7CiAgJCgnI3NtLWNvZGUnKS52YWx1ZSA9IHM/LmNvZGUgfHwgJyc7CiAgJCgnI3NtLXNldG51bScpLnZhbHVlID0gcz8uc2V0X251bWJlciB8fCAnJzsKICAkKCcjc20tc2V0bmFtZScpLnZhbHVlID0gcz8uc2V0X25hbWUgfHwgJyc7CiAgJCgnI3NtLWVuYWJsZWQnKS5jaGVja2VkID0gcyA/ICEhcy5lbmFibGVkIDogdHJ1ZTsKCiAgLy8gYm9zcyBkcm9wZG93bgogIGNvbnN0IGJvc3NTZWwgPSAkKCcjc20tYm9zcycpOwogIGJvc3NTZWwuaW5uZXJIVE1MID0gJzxvcHRpb24gdmFsdWU9IiI+4oCUIG5vbmUg4oCUPC9vcHRpb24+JyArCiAgICBHSE9TVFMuZmlsdGVyKChnKSA9PiBnLmlzX2Jvc3MpLm1hcCgoZykgPT4KICAgICAgYDxvcHRpb24gdmFsdWU9IiR7Zy5pZH0iPiR7ZXNjKGcubmFtZSl9PC9vcHRpb24+YCkuam9pbignJyk7CiAgYm9zc1NlbC52YWx1ZSA9IHM/LmJvc3NfZ2hvc3RfaWQgfHwgJyc7CgogIC8vIHJvc3RlciBjaGVja2xpc3QKICBjb25zdCBzZWxlY3RlZCA9IG5ldyBTZXQoKHM/LnJvc3RlciB8fCBbXSkubWFwKChyKSA9PiByLmlkKSk7CiAgJCgnI3NtLXJvc3RlcicpLmlubmVySFRNTCA9IEdIT1NUUy5tYXAoKGcpID0+CiAgICBgPGxhYmVsIGNsYXNzPSJyY2hlY2siIGRhdGEtbmFtZT0iJHtlc2MoZy5uYW1lLnRvTG93ZXJDYXNlKCkpfSI+CiAgICAgIDxpbnB1dCB0eXBlPSJjaGVja2JveCIgdmFsdWU9IiR7Zy5pZH0iICR7c2VsZWN0ZWQuaGFzKGcuaWQpID8gJ2NoZWNrZWQnIDogJyd9PgogICAgICA8c3BhbiBjbGFzcz0iZG90ICR7Zy50eXBlfSI+PC9zcGFuPiAke2VzYyhnLm5hbWUpfSAkeyfimIUnLnJlcGVhdChnLnJhcml0eSl9CiAgICA8L2xhYmVsPmApLmpvaW4oJycpOwogICQoJyNzbS1yb3N0ZXItc2VhcmNoJykudmFsdWUgPSAnJzsKICAkKCcjc2V0LW1vZGFsJykuY2xhc3NMaXN0LnJlbW92ZSgnaGlkZGVuJyk7Cn0KCmZ1bmN0aW9uIGZpbHRlclJvc3RlckNoZWNrbGlzdCgpIHsKICBjb25zdCBxID0gJCgnI3NtLXJvc3Rlci1zZWFyY2gnKS52YWx1ZS50b0xvd2VyQ2FzZSgpOwogICQkKCcjc20tcm9zdGVyIC5yY2hlY2snKS5mb3JFYWNoKChlbCkgPT4gewogICAgZWwuc3R5bGUuZGlzcGxheSA9ICFxIHx8IGVsLmRhdGFzZXQubmFtZS5pbmNsdWRlcyhxKSA/ICcnIDogJ25vbmUnOwogIH0pOwp9CgokKCcjc20tc2F2ZScpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgc2F2ZVNldCk7Cgphc3luYyBmdW5jdGlvbiBzYXZlU2V0KCkgewogIGNvbnN0IHBheWxvYWQgPSB7CiAgICBjb2RlOiAkKCcjc20tY29kZScpLnZhbHVlLnRyaW0oKSwKICAgIHNldE51bWJlcjogJCgnI3NtLXNldG51bScpLnZhbHVlLnRyaW0oKSB8fCBudWxsLAogICAgc2V0TmFtZTogJCgnI3NtLXNldG5hbWUnKS52YWx1ZS50cmltKCksCiAgICBib3NzR2hvc3RJZDogJCgnI3NtLWJvc3MnKS52YWx1ZSA/ICskKCcjc20tYm9zcycpLnZhbHVlIDogbnVsbCwKICAgIGVuYWJsZWQ6ICQoJyNzbS1lbmFibGVkJykuY2hlY2tlZCwKICB9OwogIGlmICghcGF5bG9hZC5jb2RlIHx8ICFwYXlsb2FkLnNldE5hbWUpIHsgYWxlcnQoJ1NjYW4gY29kZSBhbmQgc2V0IG5hbWUgYXJlIHJlcXVpcmVkLicpOyByZXR1cm47IH0KCiAgbGV0IGlkID0gZWRpdGluZ1NldElkOwogIGlmIChpZCkgewogICAgYXdhaXQgYXBpKGAvYXBpL2FkbWluL3NldHMvJHtpZH1gLCB7IG1ldGhvZDogJ1BBVENIJywgYm9keTogSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkgfSk7CiAgfSBlbHNlIHsKICAgIGNvbnN0IHJlcyA9IGF3YWl0IGFwaSgnL2FwaS9hZG1pbi9zZXRzJywgeyBtZXRob2Q6ICdQT1NUJywgYm9keTogSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkgfSk7CiAgICBpZiAocmVzLnN0YXR1cyA9PT0gNDA5KSB7IGFsZXJ0KCdUaGF0IHNjYW4gY29kZSBhbHJlYWR5IGV4aXN0cy4nKTsgcmV0dXJuOyB9CiAgICBpZCA9IChhd2FpdCByZXMuanNvbigpKS5pZDsKICB9CgogIGNvbnN0IGdob3N0SWRzID0gJCQoJyNzbS1yb3N0ZXIgaW5wdXQ6Y2hlY2tlZCcpLm1hcCgoaSkgPT4gK2kudmFsdWUpOwogIGF3YWl0IGFwaShgL2FwaS9hZG1pbi9zZXRzLyR7aWR9L3Jvc3RlcmAsIHsgbWV0aG9kOiAnUFVUJywgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBnaG9zdElkcyB9KSB9KTsKCiAgJCgnI3NldC1tb2RhbCcpLmNsYXNzTGlzdC5hZGQoJ2hpZGRlbicpOwogIGF3YWl0IGxvYWRTZXRzKCk7Cn0KCi8qIC0tLS0tLS0tLS0gQWNjb3VudCAtLS0tLS0tLS0tICovCiQoJyNjcC1nbycpLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgYXN5bmMgKCkgPT4gewogIGNvbnN0IG1zZyA9ICQoJyNjcC1tc2cnKTsKICBtc2cuY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IG1zZy5jbGFzc0xpc3QucmVtb3ZlKCdvaycpOwogIGNvbnN0IHJlcyA9IGF3YWl0IGFwaSgnL2F1dGgvY2hhbmdlLXBhc3N3b3JkJywgewogICAgbWV0aG9kOiAnUE9TVCcsCiAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7CiAgICAgIGN1cnJlbnRQYXNzd29yZDogJCgnI2NwLWN1cnJlbnQnKS52YWx1ZSwKICAgICAgbmV3UGFzc3dvcmQ6ICQoJyNjcC1uZXcnKS52YWx1ZSwKICAgIH0pLAogIH0pOwogIGNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpLmNhdGNoKCgpID0+ICh7fSkpOwogIGlmIChyZXMub2spIHsKICAgIG1zZy50ZXh0Q29udGVudCA9ICdQYXNzd29yZCB1cGRhdGVkLic7IG1zZy5jbGFzc0xpc3QuYWRkKCdvaycpOwogIH0gZWxzZSB7CiAgICBtc2cudGV4dENvbnRlbnQgPSBkYXRhLmVycm9yIHx8ICdDb3VsZCBub3QgdXBkYXRlIHBhc3N3b3JkLic7CiAgfQogIG1zZy5jbGFzc0xpc3QucmVtb3ZlKCdoaWRkZW4nKTsKICAkKCcjY3AtY3VycmVudCcpLnZhbHVlID0gJyc7ICQoJyNjcC1uZXcnKS52YWx1ZSA9ICcnOwp9KTsKCi8qIC0tLS0tLS0tLS0gbW9kYWwgY2xvc2UgLS0tLS0tLS0tLSAqLwokJCgnW2RhdGEtY2xvc2VdJykuZm9yRWFjaCgoYikgPT4KICBiLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4gJCQoJy5tb2RhbCcpLmZvckVhY2goKG0pID0+IG0uY2xhc3NMaXN0LmFkZCgnaGlkZGVuJykpKSk7CiQkKCcubW9kYWwnKS5mb3JFYWNoKChtKSA9PgogIG0uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4geyBpZiAoZS50YXJnZXQgPT09IG0pIG0uY2xhc3NMaXN0LmFkZCgnaGlkZGVuJyk7IH0pKTsKCmZ1bmN0aW9uIGVzYyhzKSB7CiAgcmV0dXJuIFN0cmluZyhzID8/ICcnKS5yZXBsYWNlKC9bJjw+IiddL2csIChjKSA9PgogICAgKHsgJyYnOiAnJmFtcDsnLCAnPCc6ICcmbHQ7JywgJz4nOiAnJmd0OycsICciJzogJyZxdW90OycsICInIjogJyYjMzk7JyB9W2NdKSk7Cn0K \ No newline at end of file