Advertisement
Xelostar

pinestore-cc

Sep 12th, 2023 (edited)
939
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.73 KB | Gaming | 0 0
  1.  
  2. local apiPath = "https://pinestore.cc/api/"
  3.  
  4. if not fs.exists("installed") then
  5.     fs.makeDir("installed")
  6. end
  7.  
  8. local width, height = term.getSize()
  9. local renderWindow = window.create(term.current(), 1, 1, width, height)
  10. local oldTerm = term.redirect(renderWindow)
  11. local maxWidth = 70
  12.  
  13. local function updateTermSize()
  14.     width, height = oldTerm.getSize()
  15.     local newW = math.min(width, maxWidth)
  16.     renderWindow.reposition(1 + math.floor((width - newW)*0.5 + 0.5), 1, newW, height)
  17.     oldTerm.setBackgroundColor(colors.black)
  18.     oldTerm.clear()
  19. end
  20. updateTermSize()
  21.  
  22. local online = true
  23.  
  24. local categories = {
  25.     "fun",
  26.     "tools",
  27.     "turtle",
  28.     "audio",
  29.     "other",
  30. }
  31. local selectedCategory = 1
  32. local selectedProject = 1
  33.  
  34. local projectActionsOpened = false
  35. local projectActionSelected = 1
  36. local searchOpened = false
  37. local searchQuery = ""
  38. local searchResultsOpened = false
  39.  
  40. local function getAPI(path)
  41.     local res = http.get(apiPath .. path)
  42.     if not res then
  43.         online = false
  44.         return
  45.     end
  46.     local data = res.readAll()
  47.     res.close()
  48.     return textutils.unserialiseJSON(data)
  49. end
  50.  
  51. function postAPI(path, body)
  52.     local res = http.post(apiPath .. path, textutils.serialiseJSON(body), {["Content-Type"] = "application/json"})
  53.     if not res then
  54.         return
  55.     end
  56.     local data = res.readAll()
  57.     res.close()
  58.     return textutils.unserialiseJSON(data)
  59. end
  60.  
  61. local installedInfo = {
  62.     projects = {},
  63. }
  64. if fs.exists("installed.json") then
  65.     local h = fs.open("installed.json", "r")
  66.     installedInfo = textutils.unserialiseJSON(h.readAll())
  67.     h.close()
  68. end
  69. local function saveInstalled()
  70.     local encoded = textutils.serialiseJSON(installedInfo)
  71.     local h = fs.open("installed.json", "w")
  72.     h.write(encoded)
  73.     h.close()
  74. end
  75.  
  76. local searchResults = {}
  77. local projectsData = getAPI("projects")
  78. local projects = projectsData.projects
  79. if not projects then
  80.     local ps = {}
  81.     for id, project in pairs(installedInfo.projects) do
  82.         ps[#ps+1] = project
  83.     end
  84.     projects = ps
  85. else
  86.     for i = 1, #projects do
  87.         local project = projects[i]
  88.         if installedInfo.projects[tostring(project.id)] then
  89.             installedInfo.projects[tostring(project.id)].downloads = project.downloads
  90.         end
  91.     end
  92.     saveInstalled()
  93. end
  94. for i = #projects, 1, -1 do
  95.     local project = projects[i]
  96.     if not project.install_command or not project.target_file then
  97.         table.remove(projects, i)
  98.     end
  99. end
  100.  
  101. local categoryProjects = {}
  102. for i = 1, #projects do
  103.     local project = projects[i]
  104.     if not project.category then project.category = "Other" end
  105.     if not categoryProjects[project.category] then
  106.         categoryProjects[project.category] = {}
  107.     end
  108.     categoryProjects[project.category][#categoryProjects[project.category]+1] = project
  109. end
  110. for category, projectList in pairs(categoryProjects) do
  111.     table.sort(projectList, function(a, b)
  112.         return b.downloads < a.downloads
  113.     end)
  114. end
  115.  
  116. local function installProject(project)
  117.     -- redirect term to old one
  118.     term.redirect(oldTerm)
  119.  
  120.     -- override fs methods
  121.     local projectPath = "installed/" .. project.id .. "/"
  122.     fs.makeDir(projectPath)
  123.     local oldFSOpen = fs.open
  124.     local oldFSMakeDir = fs.makeDir
  125.     local oldFSExists = fs.exists
  126.     function fs.open(path, mode)
  127.         -- print("open " .. path)
  128.         -- sleep(0.5)
  129.         if path:sub(1, 12) == "rom/programs" then
  130.             return oldFSOpen(path, mode)
  131.         end
  132.         return oldFSOpen(projectPath .. path, mode)
  133.     end
  134.     function fs.makeDir(path)
  135.         return oldFSMakeDir(projectPath .. path)
  136.     end
  137.     function fs.exists(path)
  138.         if path:sub(1, 12) == "rom/programs" then
  139.             return oldFSExists(path)
  140.         end
  141.         return oldFSExists(projectPath .. path)
  142.     end
  143.  
  144.     -- actually run the install command
  145.     local success, res = xpcall(shell.run, debug.traceback, project.install_command)
  146.  
  147.     -- return old fs methods
  148.     fs.open = oldFSOpen
  149.     fs.makeDir = oldFSMakeDir
  150.     fs.exists = oldFSExists
  151.  
  152.     -- use render window again
  153.     oldTerm = term.redirect(renderWindow)
  154.  
  155.     updateTermSize()
  156.  
  157.     if success then
  158.         -- set project info to installed
  159.         installedInfo.projects[tostring(project.id)] = project
  160.         saveInstalled()
  161.         postAPI("newdownload", {projectId = project.id})
  162.     else
  163.         error(res)
  164.     end
  165. end
  166.  
  167. local function startProject(project)
  168.     -- override fs methods
  169.     local projectPath = "installed/" .. project.id .. "/"
  170.     local oldFSOpen = fs.open
  171.     local oldFSMakeDir = fs.makeDir
  172.     local oldFSExists = fs.exists
  173.     local oldFSList = fs.list
  174.     function fs.open(path, mode)
  175.         if path:sub(1, 12) == "rom/programs" then
  176.             return oldFSOpen(path, mode)
  177.         end
  178.         return oldFSOpen(projectPath .. path, mode)
  179.     end
  180.     function fs.makeDir(path)
  181.         return oldFSMakeDir(projectPath .. path)
  182.     end
  183.     function fs.exists(path)
  184.         return oldFSExists(projectPath .. path)
  185.     end
  186.     function fs.list(path)
  187.         return oldFSList(projectPath .. path)
  188.     end
  189.  
  190.     term.redirect(oldTerm)
  191.     local success, res = xpcall(function()
  192.         local success = shell.run(project.target_file)
  193.  
  194.         if not success then
  195.             sleep(1)
  196.             term.setTextColor(colors.white)
  197.             print("\nPress any key to continue...")
  198.             os.pullEvent("key")
  199.         end
  200.     end, debug.traceback)
  201.  
  202.     -- return old fs methods
  203.     fs.open = oldFSOpen
  204.     fs.makeDir = oldFSMakeDir
  205.     fs.exists = oldFSExists
  206.     fs.list = oldFSList
  207.  
  208.     if not success then
  209.         if res:sub(1, 10) ~= "Terminated" then
  210.             term.setBackgroundColor(colors.black)
  211.             term.setTextColor(colors.red)
  212.             term.clear()
  213.             term.setCursorPos(1, 1)
  214.             print(res)
  215.             term.setTextColor(colors.white)
  216.             sleep(1)
  217.             print("\nPress any key to continue...")
  218.             os.pullEvent("key")
  219.         end
  220.     end
  221.  
  222.     term.redirect(renderWindow)
  223.  
  224.     updateTermSize()
  225. end
  226.  
  227. local function runSearch()
  228.     local searchData = getAPI("search?q=" .. textutils.urlEncode(searchQuery))
  229.     searchResults = searchData.projects
  230.     searchOpened = false
  231.     searchResultsOpened = true
  232.     selectedProject = 1
  233.     searchQuery = ""
  234. end
  235.  
  236. local function drawCategories()
  237.     term.setCursorPos(1, 2)
  238.     for nr = 1, #categories do
  239.         if nr == selectedCategory then
  240.             term.setTextColor(colors.lime)
  241.         else
  242.             term.setTextColor(colors.green)
  243.         end
  244.  
  245.         local str = "[" .. nr .. " " .. categories[nr] .. "] "
  246.         local x, y = term.getCursorPos()
  247.         if x + #str - 2 > width then
  248.             print("")
  249.         end
  250.         term.write(str)
  251.     end
  252. end
  253.  
  254. local function drawProjects(projects)
  255.     print("")
  256.     local startX, startY = term.getCursorPos()
  257.     local width, height = term.getSize()
  258.     local linesAvailable = height - startY - 1
  259.     local startI = math.max(1, selectedProject - linesAvailable + 1)
  260.     for i = startI, math.min(startI + linesAvailable, #projects) do
  261.         if i == selectedProject then
  262.             term.setTextColor(colors.yellow)
  263.             term.write("> ")
  264.         else
  265.             term.write("  ")
  266.         end
  267.  
  268.         local project = projects[i]
  269.         if installedInfo.projects[tostring(project.id)] then
  270.             term.setTextColor(colors.lightBlue)
  271.         else
  272.             term.setTextColor(colors.white)
  273.         end
  274.         term.write(project.name)
  275.  
  276.         term.setTextColor(colors.lightGray)
  277.         term.write(" by " .. project.owner_name)
  278.  
  279.         local showDownloads = true
  280.         local downText = " downloads"
  281.         if project.downloads == 1 then
  282.             downText = " download "
  283.         end
  284.         if width <= 25 then
  285.             showDownloads = false
  286.         elseif width <= 29 then
  287.             downText = ""
  288.         elseif width <= 42 then
  289.             downText = " dls"
  290.             if project.downloads == 1 then
  291.                 downText = " dl "
  292.             end
  293.         end
  294.         if showDownloads then
  295.             local downloadsText = project.downloads .. downText
  296.             local x, y = term.getCursorPos()
  297.             term.setCursorPos(width - #downloadsText + 1, y)
  298.             print(downloadsText)
  299.         else
  300.             print("")
  301.         end
  302.     end
  303. end
  304.  
  305. local function drawProjectOptions()
  306.     print("")
  307.  
  308.     local projects = categoryProjects[categories[selectedCategory]] or {}
  309.     if searchResultsOpened then
  310.         projects = searchResults
  311.     end
  312.     local project = projects[selectedProject]
  313.     local installed = installedInfo.projects[tostring(project.id)]
  314.  
  315.     for i = 1, installed and 3 or 2 do
  316.         if i == projectActionSelected then
  317.             term.setTextColor(colors.yellow)
  318.             term.write("> ")
  319.         else
  320.             term.write("  ")
  321.         end
  322.  
  323.         term.setTextColor(colors.white)
  324.         if installed then
  325.             if i == 1 then
  326.                 if project.version > installed.version then
  327.                     print("Update and run")
  328.                 else
  329.                     print("Run")
  330.                 end
  331.             elseif i == 2 then
  332.                 print("Uninstall")
  333.             elseif i == 3 then
  334.                 print("Back")
  335.             end
  336.         else
  337.             if i == 1 then
  338.                 print("Install")
  339.             elseif i == 2 then
  340.                 print("Back")
  341.             end
  342.         end
  343.     end
  344.  
  345.     print("")
  346.  
  347.     term.setTextColor(colors.white)
  348.     term.write(project.name)
  349.     term.setTextColor(colors.lightGray)
  350.     print(" by " .. project.owner_name)
  351.     term.setTextColor(colors.gray)
  352.     print("v" .. project.version .. " " .. os.date("%B %d, %Y", math.max(project.date_added, project.date_updated) / 1000))
  353.     print("")
  354.     term.setTextColor(colors.lime)
  355.  
  356.     local stoppedScroll = false
  357.     local scrollOld = term.scroll
  358.     function term.scroll()
  359.         stoppedScroll = true
  360.     end
  361.     print(project.description)
  362.     term.scroll = scrollOld
  363.     if stoppedScroll then
  364.         local width, height = term.getSize()
  365.         term.setCursorPos(1, height)
  366.         term.clearLine()
  367.     end
  368. end
  369.  
  370. local function selectProjectAction()
  371.     local projects = categoryProjects[categories[selectedCategory]] or {}
  372.     if searchResultsOpened then
  373.         projects = searchResults
  374.     end
  375.     local project = projects[selectedProject]
  376.     local installed = installedInfo.projects[tostring(project.id)]
  377.     local i = projectActionSelected
  378.  
  379.     if installed then
  380.         if i == 1 then
  381.             if project.version > installed.version then
  382.                 installProject(project) -- run install again to update
  383.             end
  384.  
  385.             startProject(project)
  386.             projectActionsOpened = false
  387.         elseif i == 2 then
  388.             fs.delete("installed/" .. project.id)
  389.             installedInfo.projects[tostring(project.id)] = nil
  390.             saveInstalled()
  391.             projectActionsOpened = false
  392.         elseif i == 3 then
  393.             projectActionsOpened = false
  394.         end
  395.     else
  396.         if i == 1 then
  397.             installProject(project)
  398.         elseif i == 2 then
  399.             projectActionsOpened = false
  400.         end
  401.     end
  402. end
  403.  
  404. local function drawSearch()
  405.     term.setTextColor(colors.lightGray)
  406.     print("\n\nSearch query:")
  407.     term.setTextColor(colors.white)
  408.     local lineCount = math.ceil(#searchQuery / width)
  409.     for i = 1, lineCount do
  410.         term.write(searchQuery:sub(1 + width*(i-1), width + width*(i-1)))
  411.         if i < lineCount then
  412.             local x, y = term.getCursorPos()
  413.             term.setCursorPos(1, y+1)
  414.         end
  415.     end
  416.     term.setCursorBlink(true)
  417. end
  418.  
  419. local function drawSearchResults()
  420.     term.setTextColor(colors.white)
  421.     print("\n\nSearch results:")
  422.     drawProjects(searchResults)
  423. end
  424.  
  425. local function drawMain()
  426.     term.setBackgroundColor(colors.black)
  427.     term.clear()
  428.  
  429.     term.setTextColor(colors.yellow)
  430.     term.setCursorPos(1, 1)
  431.     local width, height = term.getSize()
  432.     if width > 37 then
  433.         term.write("PineStore Console v1.0")
  434.     elseif width > 31 then 
  435.         term.write("PineStore Console")
  436.     elseif width > 25 then
  437.         term.write("PineStore C.")
  438.     else
  439.         term.write("PStore C.")
  440.     end
  441.     if not online then
  442.         term.setTextColor(colors.orange)
  443.         term.write(" offline mode")
  444.     end
  445.     term.setCursorBlink(false)
  446.  
  447.     if searchOpened then
  448.         local searchText = "[TAB close]"
  449.         term.setCursorPos(width - #searchText + 1, 1)
  450.         term.write(searchText)
  451.  
  452.         drawSearch()
  453.     elseif projectActionsOpened then
  454.         drawProjectOptions()
  455.     elseif searchResultsOpened then
  456.         drawSearchResults()
  457.     else
  458.         if online then
  459.             local searchText = "[TAB search]"
  460.             term.setCursorPos(width - #searchText + 1, 1)
  461.             term.write(searchText)
  462.         end
  463.  
  464.         drawCategories()
  465.         local projects = categoryProjects[categories[selectedCategory]] or {}
  466.         drawProjects(projects)
  467.     end
  468.  
  469.     renderWindow.setVisible(true)
  470.     renderWindow.setVisible(false)
  471. end
  472.  
  473. function runStore()
  474.     while true do
  475.         drawMain()
  476.  
  477.         -- if searchOpened then
  478.         --  local searchText = "[TAB close]"
  479.         --  term.setCursorPos(width - #searchText, 1)
  480.         --  term.write(searchText)
  481.    
  482.         --  drawSearch()
  483.         -- elseif searchResultsOpened then
  484.         --  drawSearchResults()
  485.         -- elseif projectActionsOpened then
  486.         --  drawProjectOptions()
  487.         -- else
  488.  
  489.         local event, key, x, y = os.pullEvent()
  490.         if event == "key" then
  491.             if searchOpened then
  492.                 if key == keys.tab then
  493.                     searchOpened = false
  494.                 elseif key == keys.enter then
  495.                     runSearch()
  496.                 elseif key == keys.backspace then
  497.                     searchQuery = searchQuery:sub(1, -2)
  498.                 end
  499.             elseif projectActionsOpened then
  500.                 if key == keys.up or key == keys.w then
  501.                     projectActionSelected = math.max(1, projectActionSelected - 1)
  502.                 elseif key == keys.down or key == keys.s then
  503.                     local projects = categoryProjects[categories[selectedCategory]] or {}
  504.                     if searchResultsOpened then
  505.                         projects = searchResults
  506.                     end
  507.                     local project = projects[selectedProject]
  508.                     local installed = installedInfo.projects[tostring(project.id)]
  509.                     projectActionSelected = math.max(1, math.min(installed and 3 or 2, projectActionSelected + 1))
  510.                 elseif key == keys.backspace or key == keys.grave then
  511.                     projectActionsOpened = false
  512.                 elseif key == keys.enter or key == keys.space then
  513.                     selectProjectAction()
  514.                 end
  515.             elseif searchResultsOpened then
  516.                 if key == keys.up or key == keys.w then
  517.                     selectedProject = math.max(1, selectedProject - 1)
  518.                 elseif key == keys.down or key == keys.s then
  519.                     selectedProject = math.max(1, math.min(#searchResults, selectedProject + 1))
  520.                 elseif key == keys.enter or key == keys.space then
  521.                     if #searchResults > 0 then
  522.                         projectActionsOpened = true
  523.                         projectActionSelected = 1
  524.                     end
  525.                 elseif key == keys.backspace or key == keys.grave or key == keys.tab then
  526.                     searchResultsOpened = false
  527.                 end
  528.             else
  529.                 if key >= keys.one and key <= keys.nine then
  530.                     selectedCategory = math.max(1, math.min(#categories, key - keys.one + 1))
  531.                 elseif key == keys.left or key == keys.a then
  532.                     selectedCategory = math.max(1, selectedCategory - 1)
  533.                 elseif key == keys.right or key == keys.d then
  534.                     selectedCategory = math.max(1, math.min(#categories, selectedCategory + 1))
  535.                 elseif key == keys.up or key == keys.w then
  536.                     selectedProject = math.max(1, selectedProject - 1)
  537.                 elseif key == keys.down or key == keys.s then
  538.                     local projects = categoryProjects[categories[selectedCategory]] or {}
  539.                     selectedProject = math.max(1, math.min(#projects, selectedProject + 1))
  540.                 elseif key == keys.enter or key == keys.space then
  541.                     projectActionsOpened = true
  542.                     projectActionSelected = 1
  543.                 elseif key == keys.tab and online then
  544.                     searchOpened = true
  545.                 end
  546.             end
  547.         elseif event == "char" then
  548.             if key ~= "`" then
  549.                 searchQuery = searchQuery .. key
  550.             end
  551.         elseif event == "term_resize" then
  552.             updateTermSize()
  553.         end
  554.     end
  555. end
  556.  
  557. local success, res = xpcall(runStore, debug.traceback)
  558. term.redirect(oldTerm)
  559. if not success then
  560.     if type(res) == "string" and res:sub(1, 10) == "Terminated" then
  561.         return
  562.     end
  563.     term.setBackgroundColor(colors.black)
  564.     term.setTextColor(colors.red)
  565.     term.clear()
  566.     term.setCursorPos(1, 1)
  567.     print(res)
  568.     term.setTextColor(colors.white)
  569. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement