ESX = nil
local DisptachRequestId = 0
local PhoneNumbers = {}
TriggerEvent('esx:getSharedObject', function(obj)
ESX = obj
end)
function GenerateUniquePhoneNumber()
local foundNumber = false
local phoneNumber = nil
while not foundNumber do
phoneNumber = math.random(10000, 99999)
local result = MySQL.Sync.fetchAll(
'SELECT COUNT(*) as count FROM users WHERE phone_number = @phoneNumber',
{
['@phoneNumber'] = phoneNumber
}
)
local count = tonumber(result[1].count)
if count == 0 then
foundNumber = true
end
end
return phoneNumber
end
function GetDistpatchRequestId()
local requestId = DisptachRequestId
if DisptachRequestId < 65535 then
DisptachRequestId = DisptachRequestId + 1
else
DisptachRequestId = 0
end
return requestId
end
function EndCall(source, channel, target)
local xPlayer = ESX.GetPlayerFromId(source)
print('PHONE CALL END => ' .. channel)
xPlayer.set('onCall', nil)
TriggerClientEvent('esx_phone:endCall', source)
if target ~= nil then
local targetXPlayer = ESX.GetPlayerFromId(target)
if targetXPlayer ~= nil then
targetXPlayer.set('onCall', nil)
TriggerClientEvent('esx_phone:endCall', target)
end
end
end
RegisterServerEvent('esx_phone:startCall')
AddEventHandler('esx_phone:startCall', function(number)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
local xPlayers = ESX.GetPlayers()
local channel = _source + 1000
local foundPlayer = false
for i=1, #xPlayers, 1 do
local targetXPlayer = ESX.GetPlayerFromId(xPlayers[i])
if targetXPlayer.get('phoneNumber') == tonumber(number) then
foundPlayer = targetXPlayer
break
end
end
if foundPlayer ~= false then
xPlayer .set('onCall', {channel = channel, target = foundPlayer.source})
foundPlayer.set('onCall', {channel = channel, target = xPlayer.source})
print('PHONE CALL START => ' .. xPlayer.name .. '@' .. foundPlayer.name .. ' => ' .. channel)
TriggerClientEvent('esx_phone:incomingCall', foundPlayer.source, xPlayer.source, channel, xPlayer.get('phoneNumber'))
else
TriggerClientEvent('esx_phone:endCall', _source, 'De persoon is niet online')
end
end)
RegisterServerEvent('esx_phone:acceptCall')
AddEventHandler('esx_phone:acceptCall', function(target, channel)
local _source = source
print('PHONE CALL ACCEPT => ' .. channel)
TriggerClientEvent('esx_phone:onAcceptCall', target, channel, _source)
end)
RegisterServerEvent('esx_phone:endCall')
AddEventHandler('esx_phone:endCall', function(channel, target)
EndCall(source, channel, target)
end)
AddEventHandler('esx_phone:getDistpatchRequestId', function(cb)
cb(GetDistpatchRequestId())
end)
AddEventHandler('onResourceStart', function(ressource)
if ressource == 'DDFoon' then
TriggerEvent('esx_phone:ready')
end
end)
AddEventHandler('esx:playerLoaded', function(source)
local xPlayer = ESX.GetPlayerFromId(source)
for num,v in pairs(PhoneNumbers) do
if tonumber(num) == num then -- If phonenumber is a player phone number
for src,_ in pairs(v.sources) do
TriggerClientEvent('esx_phone:setPhoneNumberSource', source, num, tonumber(src))
end
end
end
MySQL.Async.fetchAll(
'SELECT * FROM users WHERE identifier = @identifier',
{
['@identifier'] = xPlayer.identifier
},
function(result)
local phoneNumber = result[1].phone_number
if phoneNumber == nil then
phoneNumber = GenerateUniquePhoneNumber()
MySQL.Async.execute(
'UPDATE users SET phone_number = @phone_number WHERE identifier = @identifier',
{
['@identifier'] = xPlayer.identifier,
['@phone_number'] = phoneNumber
}
)
end
TriggerClientEvent('esx_phone:setPhoneNumberSource', -1, phoneNumber, source)
PhoneNumbers[phoneNumber] = {
type = 'player',
hashDispatch = false,
sharePos = false,
hideNumber = false,
hidePosIfAnon = false,
sources = {[source] = true}
}
xPlayer.set('phoneNumber', phoneNumber)
if PhoneNumbers[xPlayer.job.name] ~= nil then
TriggerEvent('esx_phone:addSource', xPlayer.job.name, source)
end
local contacts = {}
MySQL.Async.fetchAll(
'SELECT * FROM user_contacts WHERE identifier = @identifier ORDER BY name ASC',
{
['@identifier'] = xPlayer.identifier
},
function(result2)
for i=1, #result2, 1 do
table.insert(contacts, {
name = result2[i].name,
number = result2[i].number,
})
end
xPlayer.set('contacts', contacts)
TriggerClientEvent('esx_phone:loaded', source, phoneNumber, contacts)
end
)
end
)
end)
AddEventHandler('esx:playerDropped', function(source)
local xPlayer = ESX.GetPlayerFromId(source)
local onCall = xPlayer.get('onCall')
TriggerClientEvent('esx_phone:setPhoneNumberSource', -1, xPlayer.get('phoneNumber'), -1)
PhoneNumbers[xPlayer.get('phoneNumber')] = nil
if PhoneNumbers[xPlayer.job.name] ~= nil then
TriggerEvent('esx_phone:removeSource', xPlayer.job.name, source)
end
if onCall ~= nil then
EndCall(source, onCall.channel, onCall.target)
end
end)
AddEventHandler('esx:setJob', function(source, job, lastJob)
if PhoneNumbers[lastJob.name] ~= nil then
TriggerEvent('esx_phone:removeSource', lastJob.name, source)
end
if PhoneNumbers[job.name] ~= nil then
TriggerEvent('esx_phone:addSource', job.name, source)
end
end)
RegisterServerEvent('esx_phone:reload')
AddEventHandler('esx_phone:reload', function(phoneNumber)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
local contacts = xPlayer.get('contacts') or {}
TriggerClientEvent('esx_phone:loaded', _source, phoneNumber, contacts)
end)
RegisterServerEvent('esx_phone:send')
AddEventHandler('esx_phone:send', function(phoneNumber, message, anon, position)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
print('MESSAGE => ' .. xPlayer.name .. '@' .. phoneNumber .. ' : ' .. message)
if PhoneNumbers[phoneNumber] ~= nil then
for k,v in pairs(PhoneNumbers[phoneNumber].sources) do
local numType = PhoneNumbers[phoneNumber].type
local numHasDispatch = PhoneNumbers[phoneNumber].hasDispatch
local numHide = PhoneNumbers[phoneNumber].hideNumber
local numHidePosIfAnon = PhoneNumbers[phoneNumber].hidePosIfAnon
local numPosition = (PhoneNumbers[phoneNumber].sharePos and position or false)
local numSource = tonumber(k)
if numHidePosIfAnon and anon then
numPosition = false
end
if (phoneNumber == "police") or (phoneNumber == "mecano") or (phoneNumber == "ambulance") or (phoneNumber == "walid") then
TriggerEvent('Inklokken:GetKlok', function(Klokdienst)
if (Klokdienst[numSource] ~= nil) and (Klokdienst[numSource]) then
if numHasDispatch then
TriggerClientEvent('esx_phone:onMessage', numSource, xPlayer.get('phoneNumber'), message, numPosition, (numHide and true or anon), numType, GetDistpatchRequestId())
else
TriggerClientEvent('esx_phone:onMessage', numSource, xPlayer.get('phoneNumber'), message, numPosition, (numHide and true or anon), numType, false)
end
end
end)
else
if numHasDispatch then
TriggerClientEvent('esx_phone:onMessage', numSource, xPlayer.get('phoneNumber'), message, numPosition, (numHide and true or anon), numType, GetDistpatchRequestId())
else
TriggerClientEvent('esx_phone:onMessage', numSource, xPlayer.get('phoneNumber'), message, numPosition, (numHide and true or anon), numType, false)
end
end
end
end
end)
AddEventHandler('esx_phone:registerNumber', function(number, type, sharePos, hasDispatch, hideNumber, hidePosIfAnon)
local hideNumber = hideNumber or false
local hidePosIfAnon = hidePosIfAnon or false
PhoneNumbers[number] = {
type = type,
sharePos = sharePos,
hasDispatch = (hasDispatch or false),
hideNumber = hideNumber,
hidePosIfAnon = hidePosIfAnon,
sources = {}
}
end)
AddEventHandler('esx_phone:addSource', function(number, source)
PhoneNumbers[number].sources[tostring(source)] = true
end)
AddEventHandler('esx_phone:removeSource', function(number, source)
PhoneNumbers[number].sources[tostring(source)] = nil
end)
RegisterServerEvent('esx_phone:addPlayerContact')
AddEventHandler('esx_phone:addPlayerContact', function(phoneNumber, contactName)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
local foundNumber = false
local foundPlayer = nil
MySQL.Async.fetchAll(
'SELECT phone_number FROM users WHERE phone_number = @number',
{
['@number'] = phoneNumber
},
function(result)
if result[1] ~= nil then
foundNumber = true
end
if foundNumber then
if phoneNumber == xPlayer.get('phoneNumber') then
TriggerClientEvent('esx:showNotification', _source, 'Je kunt jezelf niet toevoegen')
else
local hasAlreadyAdded = false
local contacts = xPlayer.get('contacts')
for i=1, #contacts, 1 do
if contacts[i].number == phoneNumber then
hasAlreadyAdded = true
end
end
if hasAlreadyAdded then
TriggerClientEvent('esx:showNotification', _source, 'Dit contact staat al in je contacten')
else
table.insert(contacts, {
name = contactName,
number = phoneNumber,
})
xPlayer.set('contacts', contacts)
MySQL.Async.execute(
'INSERT INTO user_contacts (identifier, name, number) VALUES (@identifier, @name, @number)',
{
['@identifier'] = xPlayer.identifier,
['@name'] = contactName,
['@number'] = phoneNumber
},
function(rowsChanged)
TriggerClientEvent('esx:showNotification', _source, 'Contact toegevoegd')
TriggerClientEvent('esx_phone:addContact', _source, contactName, phoneNumber)
end
)
end
end
else
TriggerClientEvent('esx:showNotification', source, 'Dit nummer bestaat niet')
end
end
)
end)
RegisterServerEvent('esx_phone:removePlayerContact')
AddEventHandler('esx_phone:removePlayerContact', function(phoneNumber)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
local foundNumber = false
local foundPlayer = nil
MySQL.Async.fetchAll(
'SELECT phone_number FROM users WHERE phone_number = @number',
{
['@number'] = phoneNumber
},
function(result)
if result[1] ~= nil then
foundNumber = true
end
if foundNumber then
if phoneNumber == xPlayer.get('phoneNumber') then
TriggerClientEvent('esx:showNotification', _source, 'Je kunt jezelf niet verwijderen')
else
local hasAlreadyAdded = false
local contacts = xPlayer.get('contacts')
local added = nil
for i=1, #contacts, 1 do
if contacts[i].number == phoneNumber then
hasAlreadyAdded = true
added = i
end
end
if hasAlreadyAdded then
-- TriggerClientEvent('esx:showNotification', _source, 'Dit contact staat al in je contacten')
table.remove(contacts, added)
xPlayer.set('contacts', contacts)
MySQL,Async.execute('DELETE FROM `user_contacts` WHERE `identifier`="'..xPlayer.identifier..'" AND `number`="'..phoneNumber..'" ', { },
function(rowsChanged)
TriggerClientEvent('esx:showNotification', _source, 'Contact ververwijderd')
TriggerClientEvent('esx_phone:removedContact', _source, phoneNumber)
end
)
else
TriggerClientEvent('esx:showNotification', _source, 'Hij stond niet in je contacten')
end
end
else
TriggerClientEvent('esx:showNotification', source, 'Dit nummer bestaat niet')
end
end
)
end)
RegisterServerEvent('esx_phone:stopDispatch')
AddEventHandler('esx_phone:stopDispatch', function(dispatchRequestId)
TriggerClientEvent('esx_phone:stopDispatch', -1, dispatchRequestId, GetPlayerName(source))
end)
RegisterServerEvent('esx_phone:billCall')
AddEventHandler('esx_phone:billCall', function(duration)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
local amount = math.floor(duration * 0.34)
xPlayer.removeAccountMoney('bank', amount)
TriggerClientEvent('esx:showNotification', _source, 'Deze oproep heeft ~r~€' .. amount .. '~s~ gekost')
end)
RegisterServerEvent('esx_phone:bankTransfer')
AddEventHandler('esx_phone:bankTransfer', function(target, amount)
local _source = source
local xPlayer = ESX.GetPlayerFromId(_source)
local targetXPlayer = ESX.GetPlayerFromId(target)
if amount > 0 and amount <= xPlayer.getAccount('bank').money then
xPlayer .removeAccountMoney('bank', amount)
targetXPlayer.addAccountMoney ('bank', amount)
TriggerClientEvent('esx:showNotification', _source, 'Je hebt ~g~€' .. amount .. '~s~ overgemaakt aan ' .. targetXPlayer.name)
TriggerEvent("LOGGER:SimpleEmbedHook", "LegaleLogs", "wit", xPlayer.name, xPlayer.name..'Heeft ' .. amount .. ' overgemaakt aan ' .. targetXPlayer.name)
else
TriggerClientEvent('esx:showNotification', _source, 'Ongeldige hoeveelheid')
end
end)
RVNYICAgICAgICAgICAgICAgICAgICAgICA9IG5pbApsb2NhbCBEaXNwdGFjaFJlcXVlc3RJZCAgID0gMApsb2NhbCBQaG9uZU51bWJlcnMgICAgICAgID0ge30KClRyaWdnZXJFdmVudCgnZXN4OmdldFNoYXJlZE9iamVjdCcsIGZ1bmN0aW9uKG9iaikKICBFU1ggPSBvYmoKZW5kKQoKZnVuY3Rpb24gR2VuZXJhdGVVbmlxdWVQaG9uZU51bWJlcigpCgogIGxvY2FsIGZvdW5kTnVtYmVyID0gZmFsc2UKICBsb2NhbCBwaG9uZU51bWJlciA9IG5pbAoKICB3aGlsZSBub3QgZm91bmROdW1iZXIgZG8KCiAgICBwaG9uZU51bWJlciA9IG1hdGgucmFuZG9tKDEwMDAwLCA5OTk5OSkKCiAgICBsb2NhbCByZXN1bHQgPSBNeVNRTC5TeW5jLmZldGNoQWxsKAogICAgICAnU0VMRUNUIENPVU5UKCopIGFzIGNvdW50IEZST00gdXNlcnMgV0hFUkUgcGhvbmVfbnVtYmVyID0gQHBob25lTnVtYmVyJywKICAgICAgewogICAgICAgIFsnQHBob25lTnVtYmVyJ10gPSBwaG9uZU51bWJlcgogICAgICB9CiAgICApCgogICAgbG9jYWwgY291bnQgID0gdG9udW1iZXIocmVzdWx0WzFdLmNvdW50KQoKICAgIGlmIGNvdW50ID09IDAgdGhlbgogICAgICBmb3VuZE51bWJlciA9IHRydWUKICAgIGVuZAoKICBlbmQKCiAgcmV0dXJuIHBob25lTnVtYmVyCmVuZAoKZnVuY3Rpb24gR2V0RGlzdHBhdGNoUmVxdWVzdElkKCkKCiAgbG9jYWwgcmVxdWVzdElkID0gRGlzcHRhY2hSZXF1ZXN0SWQKCiAgaWYgRGlzcHRhY2hSZXF1ZXN0SWQgPCA2NTUzNSB0aGVuCiAgICBEaXNwdGFjaFJlcXVlc3RJZCA9IERpc3B0YWNoUmVxdWVzdElkICsgMQogIGVsc2UKICAgIERpc3B0YWNoUmVxdWVzdElkID0gMAogIGVuZAoKICByZXR1cm4gcmVxdWVzdElkCgplbmQKCmZ1bmN0aW9uIEVuZENhbGwoc291cmNlLCBjaGFubmVsLCB0YXJnZXQpCgogIGxvY2FsIHhQbGF5ZXIgPSBFU1guR2V0UGxheWVyRnJvbUlkKHNvdXJjZSkKCiAgcHJpbnQoJ1BIT05FIENBTEwgRU5EID0+ICcgLi4gY2hhbm5lbCkKCiAgeFBsYXllci5zZXQoJ29uQ2FsbCcsIG5pbCkKCiAgVHJpZ2dlckNsaWVudEV2ZW50KCdlc3hfcGhvbmU6ZW5kQ2FsbCcsIHNvdXJjZSkKCiAgaWYgdGFyZ2V0IH49IG5pbCB0aGVuCgogICAgbG9jYWwgdGFyZ2V0WFBsYXllciA9IEVTWC5HZXRQbGF5ZXJGcm9tSWQodGFyZ2V0KQoKICAgIGlmIHRhcmdldFhQbGF5ZXIgfj0gbmlsIHRoZW4KICAgICAgdGFyZ2V0WFBsYXllci5zZXQoJ29uQ2FsbCcsIG5pbCkKICAgICAgVHJpZ2dlckNsaWVudEV2ZW50KCdlc3hfcGhvbmU6ZW5kQ2FsbCcsIHRhcmdldCkKICAgIGVuZAoKICBlbmQKCmVuZAoKUmVnaXN0ZXJTZXJ2ZXJFdmVudCgnZXN4X3Bob25lOnN0YXJ0Q2FsbCcpCkFkZEV2ZW50SGFuZGxlcignZXN4X3Bob25lOnN0YXJ0Q2FsbCcsIGZ1bmN0aW9uKG51bWJlcikKCiAgbG9jYWwgX3NvdXJjZSAgICAgPSBzb3VyY2UKICBsb2NhbCB4UGxheWVyICAgICA9IEVTWC5HZXRQbGF5ZXJGcm9tSWQoX3NvdXJjZSkKICBsb2NhbCB4UGxheWVycyAgICA9IEVTWC5HZXRQbGF5ZXJzKCkKICBsb2NhbCBjaGFubmVsICAgICA9IF9zb3VyY2UgKyAxMDAwCiAgbG9jYWwgZm91bmRQbGF5ZXIgPSBmYWxzZQoKICBmb3IgaT0xLCAjeFBsYXllcnMsIDEgZG8KCiAgICBsb2NhbCB0YXJnZXRYUGxheWVyID0gRVNYLkdldFBsYXllckZyb21JZCh4UGxheWVyc1tpXSkKCiAgICBpZiB0YXJnZXRYUGxheWVyLmdldCgncGhvbmVOdW1iZXInKSA9PSB0b251bWJlcihudW1iZXIpIHRoZW4KICAgICAgZm91bmRQbGF5ZXIgPSB0YXJnZXRYUGxheWVyCiAgICAgIGJyZWFrCiAgICBlbmQKCiAgZW5kCgogIGlmIGZvdW5kUGxheWVyIH49IGZhbHNlIHRoZW4KCiAgICB4UGxheWVyICAgIC5zZXQoJ29uQ2FsbCcsIHtjaGFubmVsID0gY2hhbm5lbCwgdGFyZ2V0ID0gZm91bmRQbGF5ZXIuc291cmNlfSkKICAgIGZvdW5kUGxheWVyLnNldCgnb25DYWxsJywge2NoYW5uZWwgPSBjaGFubmVsLCB0YXJnZXQgPSB4UGxheWVyLnNvdXJjZX0pCgogICAgcHJpbnQoJ1BIT05FIENBTEwgU1RBUlQgPT4gJyAuLiB4UGxheWVyLm5hbWUgLi4gJ0AnIC4uIGZvdW5kUGxheWVyLm5hbWUgLi4gJyA9PiAnIC4uIGNoYW5uZWwpCiAgIAogICAgVHJpZ2dlckNsaWVudEV2ZW50KCdlc3hfcGhvbmU6aW5jb21pbmdDYWxsJywgZm91bmRQbGF5ZXIuc291cmNlLCB4UGxheWVyLnNvdXJjZSwgY2hhbm5lbCwgeFBsYXllci5nZXQoJ3Bob25lTnVtYmVyJykpCiAKICBlbHNlCiAgICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTplbmRDYWxsJywgX3NvdXJjZSwgJ0RlIHBlcnNvb24gaXMgbmlldCBvbmxpbmUnKQogIGVuZAoKCmVuZCkKClJlZ2lzdGVyU2VydmVyRXZlbnQoJ2VzeF9waG9uZTphY2NlcHRDYWxsJykKQWRkRXZlbnRIYW5kbGVyKCdlc3hfcGhvbmU6YWNjZXB0Q2FsbCcsIGZ1bmN0aW9uKHRhcmdldCwgY2hhbm5lbCkKCiAgbG9jYWwgX3NvdXJjZSA9IHNvdXJjZQoKICBwcmludCgnUEhPTkUgQ0FMTCBBQ0NFUFQgPT4gJyAuLiBjaGFubmVsKQoKICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTpvbkFjY2VwdENhbGwnLCB0YXJnZXQsIGNoYW5uZWwsIF9zb3VyY2UpCiAgCmVuZCkKClJlZ2lzdGVyU2VydmVyRXZlbnQoJ2VzeF9waG9uZTplbmRDYWxsJykKQWRkRXZlbnRIYW5kbGVyKCdlc3hfcGhvbmU6ZW5kQ2FsbCcsIGZ1bmN0aW9uKGNoYW5uZWwsIHRhcmdldCkKICBFbmRDYWxsKHNvdXJjZSwgY2hhbm5lbCwgdGFyZ2V0KQplbmQpCgpBZGRFdmVudEhhbmRsZXIoJ2VzeF9waG9uZTpnZXREaXN0cGF0Y2hSZXF1ZXN0SWQnLCBmdW5jdGlvbihjYikKICBjYihHZXREaXN0cGF0Y2hSZXF1ZXN0SWQoKSkKZW5kKQoKQWRkRXZlbnRIYW5kbGVyKCdvblJlc291cmNlU3RhcnQnLCBmdW5jdGlvbihyZXNzb3VyY2UpCiAgaWYgcmVzc291cmNlID09ICdEREZvb24nIHRoZW4KICAgIFRyaWdnZXJFdmVudCgnZXN4X3Bob25lOnJlYWR5JykKICBlbmQKZW5kKQoKQWRkRXZlbnRIYW5kbGVyKCdlc3g6cGxheWVyTG9hZGVkJywgZnVuY3Rpb24oc291cmNlKQoKICBsb2NhbCB4UGxheWVyID0gRVNYLkdldFBsYXllckZyb21JZChzb3VyY2UpCgogIGZvciBudW0sdiBpbiBwYWlycyhQaG9uZU51bWJlcnMpIGRvCiAgICBpZiB0b251bWJlcihudW0pID09IG51bSB0aGVuIC0tIElmIHBob25lbnVtYmVyIGlzIGEgcGxheWVyIHBob25lIG51bWJlcgogICAgICBmb3Igc3JjLF8gaW4gcGFpcnModi5zb3VyY2VzKSBkbwogICAgICAgIFRyaWdnZXJDbGllbnRFdmVudCgnZXN4X3Bob25lOnNldFBob25lTnVtYmVyU291cmNlJywgc291cmNlLCBudW0sIHRvbnVtYmVyKHNyYykpCiAgICAgIGVuZAogICAgZW5kCiAgZW5kCgogIE15U1FMLkFzeW5jLmZldGNoQWxsKAogICAgJ1NFTEVDVCAqIEZST00gdXNlcnMgV0hFUkUgaWRlbnRpZmllciA9IEBpZGVudGlmaWVyJywKICAgIHsKICAgICAgWydAaWRlbnRpZmllciddID0geFBsYXllci5pZGVudGlmaWVyCiAgICB9LAogICAgZnVuY3Rpb24ocmVzdWx0KQoKICAgICAgbG9jYWwgcGhvbmVOdW1iZXIgPSByZXN1bHRbMV0ucGhvbmVfbnVtYmVyCgogICAgICBpZiBwaG9uZU51bWJlciA9PSBuaWwgdGhlbgoKICAgICAgICBwaG9uZU51bWJlciA9IEdlbmVyYXRlVW5pcXVlUGhvbmVOdW1iZXIoKQoKICAgICAgICBNeVNRTC5Bc3luYy5leGVjdXRlKAogICAgICAgICAgJ1VQREFURSB1c2VycyBTRVQgcGhvbmVfbnVtYmVyID0gQHBob25lX251bWJlciBXSEVSRSBpZGVudGlmaWVyID0gQGlkZW50aWZpZXInLAogICAgICAgICAgewogICAgICAgICAgICBbJ0BpZGVudGlmaWVyJ10gICA9IHhQbGF5ZXIuaWRlbnRpZmllciwKICAgICAgICAgICAgWydAcGhvbmVfbnVtYmVyJ10gPSBwaG9uZU51bWJlcgogICAgICAgICAgfQogICAgICAgICkKICAgICAgZW5kCgogICAgICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTpzZXRQaG9uZU51bWJlclNvdXJjZScsIC0xLCBwaG9uZU51bWJlciwgc291cmNlKQoKICAgICAgUGhvbmVOdW1iZXJzW3Bob25lTnVtYmVyXSA9IHsKICAgICAgICB0eXBlICAgICAgICAgID0gJ3BsYXllcicsCiAgICAgICAgaGFzaERpc3BhdGNoICA9IGZhbHNlLAogICAgICAgIHNoYXJlUG9zICAgICAgPSBmYWxzZSwKICAgICAgICBoaWRlTnVtYmVyICAgID0gZmFsc2UsCiAgICAgICAgaGlkZVBvc0lmQW5vbiA9IGZhbHNlLAogICAgICAgIHNvdXJjZXMgICAgICAgPSB7W3NvdXJjZV0gPSB0cnVlfQogICAgICB9CgogICAgICB4UGxheWVyLnNldCgncGhvbmVOdW1iZXInLCBwaG9uZU51bWJlcikKCiAgICAgIGlmIFBob25lTnVtYmVyc1t4UGxheWVyLmpvYi5uYW1lXSB+PSBuaWwgdGhlbgogICAgICAgIFRyaWdnZXJFdmVudCgnZXN4X3Bob25lOmFkZFNvdXJjZScsIHhQbGF5ZXIuam9iLm5hbWUsIHNvdXJjZSkKICAgICAgZW5kCgogICAgICBsb2NhbCBjb250YWN0cyA9IHt9CgogICAgICBNeVNRTC5Bc3luYy5mZXRjaEFsbCgKICAgICAgICAnU0VMRUNUICogRlJPTSB1c2VyX2NvbnRhY3RzIFdIRVJFIGlkZW50aWZpZXIgPSBAaWRlbnRpZmllciBPUkRFUiBCWSBuYW1lIEFTQycsCiAgICAgICAgewogICAgICAgICAgWydAaWRlbnRpZmllciddID0geFBsYXllci5pZGVudGlmaWVyCiAgICAgICAgfSwKICAgICAgICBmdW5jdGlvbihyZXN1bHQyKQoKICAgICAgICAgIGZvciBpPTEsICNyZXN1bHQyLCAxIGRvCgogICAgICAgICAgICB0YWJsZS5pbnNlcnQoY29udGFjdHMsIHsKICAgICAgICAgICAgICBuYW1lICAgPSByZXN1bHQyW2ldLm5hbWUsCiAgICAgICAgICAgICAgbnVtYmVyID0gcmVzdWx0MltpXS5udW1iZXIsCiAgICAgICAgICAgIH0pCiAgICAgICAgICBlbmQKCiAgICAgICAgICB4UGxheWVyLnNldCgnY29udGFjdHMnLCBjb250YWN0cykKCiAgICAgICAgICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTpsb2FkZWQnLCBzb3VyY2UsIHBob25lTnVtYmVyLCBjb250YWN0cykKCiAgICAgICAgZW5kCiAgICAgICkKCiAgICBlbmQKICApCgplbmQpCgpBZGRFdmVudEhhbmRsZXIoJ2VzeDpwbGF5ZXJEcm9wcGVkJywgZnVuY3Rpb24oc291cmNlKQoKICBsb2NhbCB4UGxheWVyID0gRVNYLkdldFBsYXllckZyb21JZChzb3VyY2UpCiAgbG9jYWwgb25DYWxsICA9IHhQbGF5ZXIuZ2V0KCdvbkNhbGwnKQoKICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTpzZXRQaG9uZU51bWJlclNvdXJjZScsIC0xLCB4UGxheWVyLmdldCgncGhvbmVOdW1iZXInKSwgLTEpCgogIFBob25lTnVtYmVyc1t4UGxheWVyLmdldCgncGhvbmVOdW1iZXInKV0gPSBuaWwKCiAgaWYgUGhvbmVOdW1iZXJzW3hQbGF5ZXIuam9iLm5hbWVdIH49IG5pbCB0aGVuCiAgICBUcmlnZ2VyRXZlbnQoJ2VzeF9waG9uZTpyZW1vdmVTb3VyY2UnLCB4UGxheWVyLmpvYi5uYW1lLCBzb3VyY2UpCiAgZW5kCgogIGlmIG9uQ2FsbCB+PSBuaWwgdGhlbgogICAgRW5kQ2FsbChzb3VyY2UsIG9uQ2FsbC5jaGFubmVsLCBvbkNhbGwudGFyZ2V0KQogIGVuZAoKZW5kKQoKQWRkRXZlbnRIYW5kbGVyKCdlc3g6c2V0Sm9iJywgZnVuY3Rpb24oc291cmNlLCBqb2IsIGxhc3RKb2IpCgogIGlmIFBob25lTnVtYmVyc1tsYXN0Sm9iLm5hbWVdIH49IG5pbCB0aGVuCiAgICBUcmlnZ2VyRXZlbnQoJ2VzeF9waG9uZTpyZW1vdmVTb3VyY2UnLCBsYXN0Sm9iLm5hbWUsIHNvdXJjZSkKICBlbmQKCiAgaWYgUGhvbmVOdW1iZXJzW2pvYi5uYW1lXSB+PSBuaWwgdGhlbgogICAgVHJpZ2dlckV2ZW50KCdlc3hfcGhvbmU6YWRkU291cmNlJywgam9iLm5hbWUsIHNvdXJjZSkKICBlbmQKCmVuZCkKClJlZ2lzdGVyU2VydmVyRXZlbnQoJ2VzeF9waG9uZTpyZWxvYWQnKQpBZGRFdmVudEhhbmRsZXIoJ2VzeF9waG9uZTpyZWxvYWQnLCBmdW5jdGlvbihwaG9uZU51bWJlcikKCiAgbG9jYWwgX3NvdXJjZSAgPSBzb3VyY2UKICBsb2NhbCB4UGxheWVyICA9IEVTWC5HZXRQbGF5ZXJGcm9tSWQoX3NvdXJjZSkKICBsb2NhbCBjb250YWN0cyA9IHhQbGF5ZXIuZ2V0KCdjb250YWN0cycpIG9yIHt9CgogIFRyaWdnZXJDbGllbnRFdmVudCgnZXN4X3Bob25lOmxvYWRlZCcsIF9zb3VyY2UsIHBob25lTnVtYmVyLCBjb250YWN0cykKCmVuZCkKClJlZ2lzdGVyU2VydmVyRXZlbnQoJ2VzeF9waG9uZTpzZW5kJykKQWRkRXZlbnRIYW5kbGVyKCdlc3hfcGhvbmU6c2VuZCcsIGZ1bmN0aW9uKHBob25lTnVtYmVyLCBtZXNzYWdlLCBhbm9uLCBwb3NpdGlvbikKCWxvY2FsIF9zb3VyY2UgPSBzb3VyY2UKCWxvY2FsIHhQbGF5ZXIgPSBFU1guR2V0UGxheWVyRnJvbUlkKF9zb3VyY2UpCgoJcHJpbnQoJ01FU1NBR0UgPT4gJyAuLiB4UGxheWVyLm5hbWUgLi4gJ0AnIC4uIHBob25lTnVtYmVyIC4uICcgOiAnIC4uIG1lc3NhZ2UpCgoJaWYgUGhvbmVOdW1iZXJzW3Bob25lTnVtYmVyXSB+PSBuaWwgdGhlbgoJCWZvciBrLHYgaW4gcGFpcnMoUGhvbmVOdW1iZXJzW3Bob25lTnVtYmVyXS5zb3VyY2VzKSBkbwoKCQkJbG9jYWwgbnVtVHlwZSAgICAgICAgICA9IFBob25lTnVtYmVyc1twaG9uZU51bWJlcl0udHlwZQoJCQlsb2NhbCBudW1IYXNEaXNwYXRjaCAgID0gUGhvbmVOdW1iZXJzW3Bob25lTnVtYmVyXS5oYXNEaXNwYXRjaAoJCQlsb2NhbCBudW1IaWRlICAgICAgICAgID0gUGhvbmVOdW1iZXJzW3Bob25lTnVtYmVyXS5oaWRlTnVtYmVyCgkJCWxvY2FsIG51bUhpZGVQb3NJZkFub24gPSBQaG9uZU51bWJlcnNbcGhvbmVOdW1iZXJdLmhpZGVQb3NJZkFub24KCQkJbG9jYWwgbnVtUG9zaXRpb24gICAgICA9IChQaG9uZU51bWJlcnNbcGhvbmVOdW1iZXJdLnNoYXJlUG9zIGFuZCBwb3NpdGlvbiBvciBmYWxzZSkKCQkJbG9jYWwgbnVtU291cmNlICAgICAgICA9IHRvbnVtYmVyKGspCgoJCQlpZiBudW1IaWRlUG9zSWZBbm9uIGFuZCBhbm9uIHRoZW4KCQkJCW51bVBvc2l0aW9uID0gZmFsc2UKCQkJZW5kCgkJCWlmIChwaG9uZU51bWJlciA9PSAicG9saWNlIikgb3IgKHBob25lTnVtYmVyID09ICJtZWNhbm8iKSBvciAocGhvbmVOdW1iZXIgPT0gImFtYnVsYW5jZSIpIG9yIChwaG9uZU51bWJlciA9PSAid2FsaWQiKSB0aGVuCgkJCQlUcmlnZ2VyRXZlbnQoJ0lua2xva2tlbjpHZXRLbG9rJywgZnVuY3Rpb24oS2xva2RpZW5zdCkgCgkJCQkJaWYgKEtsb2tkaWVuc3RbbnVtU291cmNlXSB+PSBuaWwpIGFuZCAoS2xva2RpZW5zdFtudW1Tb3VyY2VdKSB0aGVuCgkJCQkJCWlmIG51bUhhc0Rpc3BhdGNoIHRoZW4KCQkJCQkJCVRyaWdnZXJDbGllbnRFdmVudCgnZXN4X3Bob25lOm9uTWVzc2FnZScsIG51bVNvdXJjZSwgeFBsYXllci5nZXQoJ3Bob25lTnVtYmVyJyksIG1lc3NhZ2UsIG51bVBvc2l0aW9uLCAobnVtSGlkZSBhbmQgdHJ1ZSBvciBhbm9uKSwgbnVtVHlwZSwgR2V0RGlzdHBhdGNoUmVxdWVzdElkKCkpCgkJCQkJCWVsc2UKCQkJCQkJCVRyaWdnZXJDbGllbnRFdmVudCgnZXN4X3Bob25lOm9uTWVzc2FnZScsIG51bVNvdXJjZSwgeFBsYXllci5nZXQoJ3Bob25lTnVtYmVyJyksIG1lc3NhZ2UsIG51bVBvc2l0aW9uLCAobnVtSGlkZSBhbmQgdHJ1ZSBvciBhbm9uKSwgbnVtVHlwZSwgZmFsc2UpCgkJCQkJCWVuZAoJCQkJCWVuZAoJCQkJZW5kKQoJCQllbHNlCgkJCQlpZiBudW1IYXNEaXNwYXRjaCB0aGVuCgkJCQkJVHJpZ2dlckNsaWVudEV2ZW50KCdlc3hfcGhvbmU6b25NZXNzYWdlJywgbnVtU291cmNlLCB4UGxheWVyLmdldCgncGhvbmVOdW1iZXInKSwgbWVzc2FnZSwgbnVtUG9zaXRpb24sIChudW1IaWRlIGFuZCB0cnVlIG9yIGFub24pLCBudW1UeXBlLCBHZXREaXN0cGF0Y2hSZXF1ZXN0SWQoKSkKCQkJCWVsc2UKCQkJCQlUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTpvbk1lc3NhZ2UnLCBudW1Tb3VyY2UsIHhQbGF5ZXIuZ2V0KCdwaG9uZU51bWJlcicpLCBtZXNzYWdlLCBudW1Qb3NpdGlvbiwgKG51bUhpZGUgYW5kIHRydWUgb3IgYW5vbiksIG51bVR5cGUsIGZhbHNlKQoJCQkJZW5kCgkJCWVuZAoJCWVuZAoJZW5kCmVuZCkKCkFkZEV2ZW50SGFuZGxlcignZXN4X3Bob25lOnJlZ2lzdGVyTnVtYmVyJywgZnVuY3Rpb24obnVtYmVyLCB0eXBlLCBzaGFyZVBvcywgaGFzRGlzcGF0Y2gsIGhpZGVOdW1iZXIsIGhpZGVQb3NJZkFub24pCgogIGxvY2FsIGhpZGVOdW1iZXIgICAgPSBoaWRlTnVtYmVyICAgIG9yIGZhbHNlCiAgbG9jYWwgaGlkZVBvc0lmQW5vbiA9IGhpZGVQb3NJZkFub24gb3IgZmFsc2UKCiAgUGhvbmVOdW1iZXJzW251bWJlcl0gPSB7CiAgICB0eXBlICAgICAgICAgID0gdHlwZSwKICAgIHNoYXJlUG9zICAgICAgPSBzaGFyZVBvcywKICAgIGhhc0Rpc3BhdGNoICAgPSAoaGFzRGlzcGF0Y2ggb3IgZmFsc2UpLAogICAgaGlkZU51bWJlciAgICA9IGhpZGVOdW1iZXIsCiAgICBoaWRlUG9zSWZBbm9uID0gaGlkZVBvc0lmQW5vbiwKICAgIHNvdXJjZXMgICAgICAgPSB7fQogIH0KCmVuZCkKCkFkZEV2ZW50SGFuZGxlcignZXN4X3Bob25lOmFkZFNvdXJjZScsIGZ1bmN0aW9uKG51bWJlciwgc291cmNlKQogIFBob25lTnVtYmVyc1tudW1iZXJdLnNvdXJjZXNbdG9zdHJpbmcoc291cmNlKV0gPSB0cnVlCmVuZCkKCkFkZEV2ZW50SGFuZGxlcignZXN4X3Bob25lOnJlbW92ZVNvdXJjZScsIGZ1bmN0aW9uKG51bWJlciwgc291cmNlKQogIFBob25lTnVtYmVyc1tudW1iZXJdLnNvdXJjZXNbdG9zdHJpbmcoc291cmNlKV0gPSBuaWwKZW5kKQoKUmVnaXN0ZXJTZXJ2ZXJFdmVudCgnZXN4X3Bob25lOmFkZFBsYXllckNvbnRhY3QnKQpBZGRFdmVudEhhbmRsZXIoJ2VzeF9waG9uZTphZGRQbGF5ZXJDb250YWN0JywgZnVuY3Rpb24ocGhvbmVOdW1iZXIsIGNvbnRhY3ROYW1lKQoKICBsb2NhbCBfc291cmNlICAgICA9IHNvdXJjZQogIGxvY2FsIHhQbGF5ZXIgICAgID0gRVNYLkdldFBsYXllckZyb21JZChfc291cmNlKQogIGxvY2FsIGZvdW5kTnVtYmVyID0gZmFsc2UKICBsb2NhbCBmb3VuZFBsYXllciA9IG5pbAoKICBNeVNRTC5Bc3luYy5mZXRjaEFsbCgKICAgICdTRUxFQ1QgcGhvbmVfbnVtYmVyIEZST00gdXNlcnMgV0hFUkUgcGhvbmVfbnVtYmVyID0gQG51bWJlcicsCiAgICB7CiAgICAgIFsnQG51bWJlciddID0gcGhvbmVOdW1iZXIKICAgIH0sCiAgICBmdW5jdGlvbihyZXN1bHQpCgogICAgICBpZiByZXN1bHRbMV0gfj0gbmlsIHRoZW4KICAgICAgICBmb3VuZE51bWJlciA9IHRydWUKICAgICAgZW5kCgogICAgICBpZiBmb3VuZE51bWJlciB0aGVuCgogICAgICAgIGlmIHBob25lTnVtYmVyID09IHhQbGF5ZXIuZ2V0KCdwaG9uZU51bWJlcicpIHRoZW4KICAgICAgICAgIFRyaWdnZXJDbGllbnRFdmVudCgnZXN4OnNob3dOb3RpZmljYXRpb24nLCBfc291cmNlLCAnSmUga3VudCBqZXplbGYgbmlldCB0b2V2b2VnZW4nKQogICAgICAgIGVsc2UKCiAgICAgICAgICBsb2NhbCBoYXNBbHJlYWR5QWRkZWQgPSBmYWxzZQogICAgICAgICAgbG9jYWwgY29udGFjdHMgICAgICAgID0geFBsYXllci5nZXQoJ2NvbnRhY3RzJykKCiAgICAgICAgICBmb3IgaT0xLCAjY29udGFjdHMsIDEgZG8KICAgICAgICAgICAgaWYgY29udGFjdHNbaV0ubnVtYmVyID09IHBob25lTnVtYmVyIHRoZW4KICAgICAgICAgICAgICBoYXNBbHJlYWR5QWRkZWQgPSB0cnVlCiAgICAgICAgICAgIGVuZAogICAgICAgICAgZW5kCgogICAgICAgICAgaWYgaGFzQWxyZWFkeUFkZGVkIHRoZW4KICAgICAgICAgICAgVHJpZ2dlckNsaWVudEV2ZW50KCdlc3g6c2hvd05vdGlmaWNhdGlvbicsIF9zb3VyY2UsICdEaXQgY29udGFjdCBzdGFhdCBhbCBpbiBqZSBjb250YWN0ZW4nKQogICAgICAgICAgZWxzZQoKICAgICAgICAgICAgdGFibGUuaW5zZXJ0KGNvbnRhY3RzLCB7CiAgICAgICAgICAgICAgbmFtZSAgID0gY29udGFjdE5hbWUsCiAgICAgICAgICAgICAgbnVtYmVyID0gcGhvbmVOdW1iZXIsCiAgICAgICAgICAgIH0pCgogICAgICAgICAgICB4UGxheWVyLnNldCgnY29udGFjdHMnLCBjb250YWN0cykKCiAgICAgICAgICAgIE15U1FMLkFzeW5jLmV4ZWN1dGUoCiAgICAgICAgICAgICAgJ0lOU0VSVCBJTlRPIHVzZXJfY29udGFjdHMgKGlkZW50aWZpZXIsIG5hbWUsIG51bWJlcikgVkFMVUVTIChAaWRlbnRpZmllciwgQG5hbWUsIEBudW1iZXIpJywKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICBbJ0BpZGVudGlmaWVyJ10gPSB4UGxheWVyLmlkZW50aWZpZXIsCiAgICAgICAgICAgICAgICBbJ0BuYW1lJ10gICAgICAgPSBjb250YWN0TmFtZSwKICAgICAgICAgICAgICAgIFsnQG51bWJlciddICAgICA9IHBob25lTnVtYmVyCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICBmdW5jdGlvbihyb3dzQ2hhbmdlZCkKCiAgICAgICAgICAgICAgICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeDpzaG93Tm90aWZpY2F0aW9uJywgX3NvdXJjZSwgJ0NvbnRhY3QgdG9lZ2V2b2VnZCcpCgogICAgICAgICAgICAgICAgVHJpZ2dlckNsaWVudEV2ZW50KCdlc3hfcGhvbmU6YWRkQ29udGFjdCcsIF9zb3VyY2UsIGNvbnRhY3ROYW1lLCBwaG9uZU51bWJlcikKICAgICAgICAgICAgICBlbmQKICAgICAgICAgICAgKQoKICAgICAgICAgIGVuZAogICAgICAgIGVuZAoKICAgICAgZWxzZQogICAgICAgIFRyaWdnZXJDbGllbnRFdmVudCgnZXN4OnNob3dOb3RpZmljYXRpb24nLCBzb3VyY2UsICdEaXQgbnVtbWVyIGJlc3RhYXQgbmlldCcpCiAgICAgIGVuZAoKICAgIGVuZAogICkKCmVuZCkKUmVnaXN0ZXJTZXJ2ZXJFdmVudCgnZXN4X3Bob25lOnJlbW92ZVBsYXllckNvbnRhY3QnKQpBZGRFdmVudEhhbmRsZXIoJ2VzeF9waG9uZTpyZW1vdmVQbGF5ZXJDb250YWN0JywgZnVuY3Rpb24ocGhvbmVOdW1iZXIpCglsb2NhbCBfc291cmNlICAgICA9IHNvdXJjZQoJbG9jYWwgeFBsYXllciAgICAgPSBFU1guR2V0UGxheWVyRnJvbUlkKF9zb3VyY2UpCglsb2NhbCBmb3VuZE51bWJlciA9IGZhbHNlCglsb2NhbCBmb3VuZFBsYXllciA9IG5pbAoJTXlTUUwuQXN5bmMuZmV0Y2hBbGwoCgkJJ1NFTEVDVCBwaG9uZV9udW1iZXIgRlJPTSB1c2VycyBXSEVSRSBwaG9uZV9udW1iZXIgPSBAbnVtYmVyJywKCQl7CgkJWydAbnVtYmVyJ10gPSBwaG9uZU51bWJlcgoJCX0sCgkJZnVuY3Rpb24ocmVzdWx0KQoJCQlpZiByZXN1bHRbMV0gfj0gbmlsIHRoZW4KCQkJCWZvdW5kTnVtYmVyID0gdHJ1ZQoJCQllbmQKCQkJaWYgZm91bmROdW1iZXIgdGhlbgoJCQkJaWYgcGhvbmVOdW1iZXIgPT0geFBsYXllci5nZXQoJ3Bob25lTnVtYmVyJykgdGhlbgoJCQkJCVRyaWdnZXJDbGllbnRFdmVudCgnZXN4OnNob3dOb3RpZmljYXRpb24nLCBfc291cmNlLCAnSmUga3VudCBqZXplbGYgbmlldCB2ZXJ3aWpkZXJlbicpCgkJCQllbHNlCgkJCQkJbG9jYWwgaGFzQWxyZWFkeUFkZGVkID0gZmFsc2UKCQkJCQlsb2NhbCBjb250YWN0cyAgICAgICAgPSB4UGxheWVyLmdldCgnY29udGFjdHMnKQoJCQkJCWxvY2FsIGFkZGVkID0gbmlsCgkJCQkJZm9yIGk9MSwgI2NvbnRhY3RzLCAxIGRvCgkJCQkJCWlmIGNvbnRhY3RzW2ldLm51bWJlciA9PSBwaG9uZU51bWJlciB0aGVuCgkJCQkJCQloYXNBbHJlYWR5QWRkZWQgPSB0cnVlCgkJCQkJCQlhZGRlZCA9IGkKCQkJCQkJZW5kCgkJCQkJZW5kCgkJCQkJaWYgaGFzQWxyZWFkeUFkZGVkIHRoZW4KCQkJCQkJLS0gVHJpZ2dlckNsaWVudEV2ZW50KCdlc3g6c2hvd05vdGlmaWNhdGlvbicsIF9zb3VyY2UsICdEaXQgY29udGFjdCBzdGFhdCBhbCBpbiBqZSBjb250YWN0ZW4nKQoJCQkJCQl0YWJsZS5yZW1vdmUoY29udGFjdHMsIGFkZGVkKQoJCQkJCQl4UGxheWVyLnNldCgnY29udGFjdHMnLCBjb250YWN0cykKCQkJCQkJTXlTUUwsQXN5bmMuZXhlY3V0ZSgnREVMRVRFIEZST00gYHVzZXJfY29udGFjdHNgIFdIRVJFIGBpZGVudGlmaWVyYD0iJy4ueFBsYXllci5pZGVudGlmaWVyLi4nIiBBTkQgYG51bWJlcmA9IicuLnBob25lTnVtYmVyLi4nIiAnLCB7IH0sCgkJCQkJCQlmdW5jdGlvbihyb3dzQ2hhbmdlZCkKCQkJCQkJCQlUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeDpzaG93Tm90aWZpY2F0aW9uJywgX3NvdXJjZSwgJ0NvbnRhY3QgdmVydmVyd2lqZGVyZCcpCgkJCQkJCQkJVHJpZ2dlckNsaWVudEV2ZW50KCdlc3hfcGhvbmU6cmVtb3ZlZENvbnRhY3QnLCBfc291cmNlLCAgcGhvbmVOdW1iZXIpCgkJCQkJCQllbmQKCQkJCQkJKQoJCQkJCWVsc2UKCQkJCQkJVHJpZ2dlckNsaWVudEV2ZW50KCdlc3g6c2hvd05vdGlmaWNhdGlvbicsIF9zb3VyY2UsICdIaWogc3RvbmQgbmlldCBpbiBqZSBjb250YWN0ZW4nKQoJCQkJCWVuZAoJCQkJZW5kCgkJCWVsc2UKCQkJCVRyaWdnZXJDbGllbnRFdmVudCgnZXN4OnNob3dOb3RpZmljYXRpb24nLCBzb3VyY2UsICdEaXQgbnVtbWVyIGJlc3RhYXQgbmlldCcpCgkJCWVuZAoJCWVuZAoJKQplbmQpCgoKClJlZ2lzdGVyU2VydmVyRXZlbnQoJ2VzeF9waG9uZTpzdG9wRGlzcGF0Y2gnKQpBZGRFdmVudEhhbmRsZXIoJ2VzeF9waG9uZTpzdG9wRGlzcGF0Y2gnLCBmdW5jdGlvbihkaXNwYXRjaFJlcXVlc3RJZCkKICBUcmlnZ2VyQ2xpZW50RXZlbnQoJ2VzeF9waG9uZTpzdG9wRGlzcGF0Y2gnLCAtMSwgZGlzcGF0Y2hSZXF1ZXN0SWQsIEdldFBsYXllck5hbWUoc291cmNlKSkKZW5kKQoKUmVnaXN0ZXJTZXJ2ZXJFdmVudCgnZXN4X3Bob25lOmJpbGxDYWxsJykKQWRkRXZlbnRIYW5kbGVyKCdlc3hfcGhvbmU6YmlsbENhbGwnLCBmdW5jdGlvbihkdXJhdGlvbikKCiAgbG9jYWwgX3NvdXJjZSA9IHNvdXJjZQogIGxvY2FsIHhQbGF5ZXIgPSBFU1guR2V0UGxheWVyRnJvbUlkKF9zb3VyY2UpCiAgbG9jYWwgYW1vdW50ICA9IG1hdGguZmxvb3IoZHVyYXRpb24gKiAwLjM0KQoKICB4UGxheWVyLnJlbW92ZUFjY291bnRNb25leSgnYmFuaycsIGFtb3VudCkKCiAgVHJpZ2dlckNsaWVudEV2ZW50KCdlc3g6c2hvd05vdGlmaWNhdGlvbicsIF9zb3VyY2UsICdEZXplIG9wcm9lcCBoZWVmdCB+cn7igqwnIC4uIGFtb3VudCAuLiAnfnN+IGdla29zdCcpCgplbmQpCgpSZWdpc3RlclNlcnZlckV2ZW50KCdlc3hfcGhvbmU6YmFua1RyYW5zZmVyJykKQWRkRXZlbnRIYW5kbGVyKCdlc3hfcGhvbmU6YmFua1RyYW5zZmVyJywgZnVuY3Rpb24odGFyZ2V0LCBhbW91bnQpCgogIGxvY2FsIF9zb3VyY2UgICAgICAgPSBzb3VyY2UKICBsb2NhbCB4UGxheWVyICAgICAgID0gRVNYLkdldFBsYXllckZyb21JZChfc291cmNlKQogIGxvY2FsIHRhcmdldFhQbGF5ZXIgPSBFU1guR2V0UGxheWVyRnJvbUlkKHRhcmdldCkKCiAgaWYgYW1vdW50ID4gMCBhbmQgYW1vdW50IDw9IHhQbGF5ZXIuZ2V0QWNjb3VudCgnYmFuaycpLm1vbmV5IHRoZW4KICAgCiAgICB4UGxheWVyICAgICAgLnJlbW92ZUFjY291bnRNb25leSgnYmFuaycsIGFtb3VudCkKICAgIHRhcmdldFhQbGF5ZXIuYWRkQWNjb3VudE1vbmV5ICAgKCdiYW5rJywgYW1vdW50KQoKICAgIFRyaWdnZXJDbGllbnRFdmVudCgnZXN4OnNob3dOb3RpZmljYXRpb24nLCBfc291cmNlLCAnSmUgaGVidCB+Z37igqwnIC4uIGFtb3VudCAuLiAnfnN+IG92ZXJnZW1hYWt0IGFhbiAnIC4uIHRhcmdldFhQbGF5ZXIubmFtZSkKCVRyaWdnZXJFdmVudCgiTE9HR0VSOlNpbXBsZUVtYmVkSG9vayIsICJMZWdhbGVMb2dzIiwgIndpdCIsIHhQbGF5ZXIubmFtZSwgeFBsYXllci5uYW1lLi4nSGVlZnQgJyAuLiBhbW91bnQgLi4gJyBvdmVyZ2VtYWFrdCBhYW4gJyAuLiB0YXJnZXRYUGxheWVyLm5hbWUpCgogIGVsc2UKICAgIFRyaWdnZXJDbGllbnRFdmVudCgnZXN4OnNob3dOb3RpZmljYXRpb24nLCBfc291cmNlLCAnT25nZWxkaWdlIGhvZXZlZWxoZWlkJykKICBlbmQKCmVuZCk=