-- Path of Building -- -- Module: Import Tab -- Import/Export tab for the current build. -- local launch, main = ... local ipairs = ipairs local t_insert = table.insert local ImportTabClass = common.NewClass("ImportTab", "ControlHost", "Control", function(self, build) self.ControlHost() self.Control() self.build = build self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Idle" self.controls.sectionCharImport = common.New("SectionControl", {"TOPLEFT",self,"TOPLEFT"}, 10, 18, 600, 210, "Character Import") self.controls.charImportStatusLabel = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 14, 200, 16, function() return "^7Character import status: "..self.charImportStatus end) -- Stage: input account name self.controls.accountNameHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 16, "^7To start importing a character, enter the character's account name:") self.controls.accountNameHeader.shown = function() return self.charImportMode == "GETACCOUNTNAME" end self.controls.accountName = common.New("EditControl", {"TOPLEFT",self.controls.accountNameHeader,"BOTTOMLEFT"}, 0, 4, 200, 20, main.lastAccountName or "", nil, "[%C]", 50) self.controls.accountNameGo = common.New("ButtonControl", {"LEFT",self.controls.accountName,"RIGHT"}, 8, 0, 60, 20, "Start", function() self.controls.sessionInput.buf = "" self:DownloadCharacterList() end) self.controls.accountNameGo.enabled = function() return self.controls.accountName.buf:match("%S") end -- Stage: input POESESSID self.controls.sessionHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 14) self.controls.sessionHeader.label = function() return [[ ^7The list of characters on ']]..self.controls.accountName.buf..[[' couldn't be retrieved. This may be because: 1. The account name is wrong, or 2. The account's privacy settings hide the characters tab (this is the default setting). If this is your account, you can either: 1. Change your privacy settings to show you characters tab and then retry, or 2. Enter a valid POESESSID below. You can get this from your web browser's cookies while logged into the Path of Exile website. ]] end self.controls.sessionHeader.shown = function() return self.charImportMode == "GETSESSIONID" end self.controls.sessionRetry = common.New("ButtonControl", {"TOPLEFT",self.controls.sessionHeader,"TOPLEFT"}, 0, 108, 60, 20, "Retry", function() self.controls.sessionInput.buf = "" self:DownloadCharacterList() end) self.controls.sessionCancel = common.New("ButtonControl", {"LEFT",self.controls.sessionRetry,"RIGHT"}, 8, 0, 60, 20, "Cancel", function() self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Idle" end) self.controls.sessionInput = common.New("EditControl", {"TOPLEFT",self.controls.sessionRetry,"BOTTOMLEFT"}, 0, 8, 350, 20, "", "POESESSID", "[%x]", 32) self.controls.sessionGo = common.New("ButtonControl", {"LEFT",self.controls.sessionInput,"RIGHT"}, 8, 0, 60, 20, "Go", function() self:DownloadCharacterList() end) self.controls.sessionGo.enabled = function() return #self.controls.sessionInput.buf == 32 end -- Stage: select character and import data self.controls.charSelectHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 16, "^7Choose character to import data from:") self.controls.charSelectHeader.shown = function() return self.charImportMode == "SELECTCHAR" or self.charImportMode == "IMPORTING" end self.controls.charSelect = common.New("DropDownControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, 0, 4, 400, 18) self.controls.charSelect.enabled = function() return self.charImportMode == "SELECTCHAR" end self.controls.charImportHeader = common.New("LabelControl", {"TOPLEFT",self.controls.charSelect,"BOTTOMLEFT"}, 0, 16, 200, 16, "Import:") self.controls.charImport = common.New("ButtonControl", {"LEFT",self.controls.charImportHeader, "RIGHT"}, 8, 0, 100, 20, "Passive Tree", function() if self.build.spec:CountAllocNodes() > 0 then main:OpenConfirmPopup("Character Import", "Importing the passive tree will overwrite your current tree.", "Import", function() self:DownloadPassiveTree() end) else self:DownloadPassiveTree() end end) self.controls.charImport.enabled = function() return self.charImportMode == "SELECTCHAR" end self.controls.charDone = common.New("ButtonControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, 0, 30, 60, 20, "Done", function() self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Idle" end) -- Build import/export self.controls.sectionBuild = common.New("SectionControl", {"TOPLEFT",self.controls.sectionCharImport,"BOTTOMLEFT"}, 0, 18, 600, 200, "Build Sharing") self.controls.generateCodeLabel = common.New("LabelControl", {"TOPLEFT",self.controls.sectionBuild,"TOPLEFT"}, 6, 14, 0, 16, "^7Generate a code to share this build with other Path of Building users:") self.controls.generateCode = common.New("ButtonControl", {"LEFT",self.controls.generateCodeLabel,"RIGHT"}, 4, 0, 80, 20, "Generate", function() self.controls.generateCodeOut:SetText(common.base64.encode(Deflate(self.build:SaveDB("code"))):gsub("+","-"):gsub("/","_")) end) self.controls.generateCodeOut = common.New("EditControl", {"TOPLEFT",self.controls.generateCodeLabel,"BOTTOMLEFT"}, 0, 8, 250, 20, "", "Code", "[%z]") self.controls.generateCodeOut.enabled = function() return #self.controls.generateCodeOut.buf > 0 end self.controls.generateCodeCopy = common.New("ButtonControl", {"LEFT",self.controls.generateCodeOut,"RIGHT"}, 8, 0, 60, 20, "Copy", function() Copy(self.controls.generateCodeOut.buf) self.controls.generateCodeOut:SetText("") end) self.controls.generateCodeCopy.enabled = function() return #self.controls.generateCodeOut.buf > 0 end self.controls.generateCodeNote = common.New("LabelControl", {"TOPLEFT",self.controls.generateCodeOut,"BOTTOMLEFT"}, 0, 4, 0, 14, "^7Note: this code can be very long; it may be easiest to share it using Pastebin or similar.") self.controls.importCodeHeader = common.New("LabelControl", {"TOPLEFT",self.controls.generateCodeNote,"BOTTOMLEFT"}, 0, 26, 0, 16, "^7To import a build, enter the code here:") self.controls.importCodeIn = common.New("EditControl", {"TOPLEFT",self.controls.importCodeHeader,"BOTTOMLEFT"}, 0, 4, 250, 20, "", nil, "[%w_%-=]", nil, function(buf) if #buf == 0 then self.importCodeState = nil return end self.importCodeState = "INVALID" local xmlText = Inflate(common.base64.decode(buf:gsub("-","+"):gsub("_","/"))) if not xmlText then return end self.importCodeState = "VALID" self.importCodeXML = xmlText end) self.controls.importCodeState = common.New("LabelControl", {"LEFT",self.controls.importCodeIn,"RIGHT"}, 4, 0, 0, 16) self.controls.importCodeState.label = function() return (self.importCodeState == "VALID" and data.colorCodes.POSITIVE.."Code is valid") or (self.importCodeState == "INVALID" and data.colorCodes.NEGATIVE.."Invalid code") or "" end self.controls.importCodeMode = common.New("DropDownControl", {"TOPLEFT",self.controls.importCodeIn,"BOTTOMLEFT"}, 0, 4, 160, 20, { "Import to this build", "Import to a new build:" }) self.controls.importCodeMode.enabled = function() return self.importCodeState == "VALID" end self.controls.importCodeBuildName = common.New("EditControl", {"LEFT",self.controls.importCodeMode,"RIGHT"}, 4, 0, 400, 20, "", "New build name", "[%w _+-.()]", 50) self.controls.importCodeBuildName.enabled = function() return self.importCodeState == "VALID" and self.controls.importCodeMode.sel == 2 end self.controls.importCodeGo = common.New("ButtonControl", {"TOPLEFT",self.controls.importCodeMode,"BOTTOMLEFT"}, 0, 8, 60, 20, "Import", function() if self.controls.importCodeMode.sel == 1 then main:OpenConfirmPopup("Build Import", "^xFF9922Warning:^7 Importing to the current build will erase ALL existing data for this build.\nThis cannot be undone.", "Import", function() self:ImportToBuild(self.build.dbFileName, self.build.buildName) end) else local newBuildName = self.controls.importCodeBuildName.buf local newFileName = main.buildPath .. newBuildName .. ".xml" local file = io.open(newFileName, "r") if file then file:close() main:OpenMessagePopup("Build Import", "A build with that name already exists.") return end self:ImportToBuild(newFileName, newBuildName) end end) self.controls.importCodeGo.enabled = function() return self.importCodeState == "VALID" and (self.controls.importCodeMode.sel == 1 or self.controls.importCodeBuildName.buf:match("%S")) end end) function ImportTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y self.width = viewPort.width self.height = viewPort.height self:ProcessControlsInput(inputEvents, viewPort) main:DrawBackground(viewPort) self:DrawControls(viewPort) end function ImportTabClass:DownloadCharacterList() self.charImportMode = "DOWNLOADCHARLIST" self.charImportStatus = "Retrieving character list..." local accountName = self.controls.accountName.buf local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or main.accountSessionIDs[accountName] launch:DownloadPage("https://www.pathofexile.com/character-window/get-characters?accountName="..accountName, function(page, errMsg) if errMsg then self.charImportStatus = data.colorCodes.NEGATIVE.."Unknown error retrieving character list, try again ("..errMsg")" self.charImportMode = "GETACCOUNTNAME" return elseif page == "false" then self.charImportStatus = data.colorCodes.NEGATIVE.."Failed to retrieve character list." self.charImportMode = "GETSESSIONID" return end local charList, errMsg = self:ProcessJSON(page) if errMsg then self.charImportStatus = data.colorCodes.NEGATIVE.."Error processing character list, try again later" self.charImportMode = "GETACCOUNTNAME" return end self.charImportStatus = "Character list successfully retrieved." self.charImportMode = "SELECTCHAR" main.lastAccountName = accountName if sessionID then main.accountSessionIDs[accountName] = sessionID end wipeTable(self.controls.charSelect.list) for i, char in ipairs(charList) do t_insert(self.controls.charSelect.list, { val = char, label = string.format("%s: Level %d %s in %s", char.name or "?", char.level or 0, char.class or "?", char.league or "?") }) end table.sort(self.controls.charSelect.list, function(a,b) return a.val.name:lower() < b.val.name:lower() end) end, sessionID and "POESESSID="..sessionID) end function ImportTabClass:DownloadPassiveTree() self.charImportMode = "IMPORTING" self.charImportStatus = "Retrieving character passive tree..." local accountName = self.controls.accountName.buf local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or main.accountSessionIDs[accountName] local charSelect = self.controls.charSelect local charData = charSelect.list[charSelect.sel].val launch:DownloadPage("https://www.pathofexile.com/character-window/get-passive-skills?accountName="..accountName.."&character="..charData.name, function(page, errMsg) self.charImportMode = "SELECTCHAR" if errMsg then self.charImportStatus = data.colorCodes.NEGATIVE.."Unknown error importing character data, try again ("..errMsg")" return elseif page == "false" then self.charImportStatus = data.colorCodes.NEGATIVE.."Failed to retrieve character data, try again." return end local passiveData, errMsg = self:ProcessJSON(page) if errMsg then self.charImportStatus = data.colorCodes.NEGATIVE.."Error processing character data, try again later." return end self.charImportStatus = data.colorCodes.POSITIVE.."Passive tree successfully imported." self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, passiveData.hashes) self.build.spec:AddUndoState() self.build.characterLevel = charData.level self.build.buildFlag = true ConPrintTable(passiveData) end, sessionID and "POESESSID="..sessionID) end function ImportTabClass:ProcessJSON(json) local func, errMsg = loadstring("return "..jsonToLua(json)) if errMsg then return nil, errMsg end setfenv(func, { }) -- Sandbox the function just in case local data = func() if type(data) ~= "table" then return nil, "Return type is not a table" end return data end function ImportTabClass:ImportToBuild(buildFileName, buildName) self.build:Shutdown() local file = io.open(buildFileName, "w+") if not file then main:ShowMessagePopup("Build Import", "^xFF2222Error:^7 Couldn't create build file (invalid name?)") return end file:write(self.importCodeXML) file:close() self.build:Init(buildFileName, buildName) self.build.viewMode = "TREE" end