-- Telem Installer by cyberbit -- MIT License -- Version 2025-04-04 local ui = (function () -- PrimeUI by JackMacWindows -- Public domain/CC0 -- Packaged from https://github.com/MCJack123/PrimeUI local a=require"cc.expect".expect;local b={} do local c={}local d; function b.addTask(e)a(1,e,"function")local f={coro=coroutine.create(e)}c[#c+1]=f;_,f.filter=coroutine.resume(f.coro)end; function b.resolve(...)coroutine.yield(c,...)end; function b.clear()term.setCursorPos(1,1)term.setCursorBlink(false)term.setBackgroundColor(colors.black)term.setTextColor(colors.white)term.clear()c={}d=nil end; function b.setCursorWindow(g)a(1,g,"table","nil")d=g and g.restoreCursor end; function b.getWindowPos(g,h,i)if g==term then return h,i end;while g~=term.native()and g~=term.current()do if not g.getPosition then return h,i end;local j,k=g.getPosition()h,i=h+j-1,i+k-1;_,g=debug.getupvalue(select(2,debug.getupvalue(g.isColor,1)),1)end;return h,i end; function b.run()while true do if d then d()end;local l=table.pack(os.pullEvent())for _,m in ipairs(c)do if m.filter==nil or m.filter==l[1]then local n=table.pack(coroutine.resume(m.coro,table.unpack(l,1,l.n)))if not n[1]then error(n[2],2)end;if n[2]==c then return table.unpack(n,3,n.n)end;m.filter=n[2]end end end end end; function b.borderBox(g,h,i,o,p,q,r)a(1,g,"table")a(2,h,"number")a(3,i,"number")a(4,o,"number")a(5,p,"number")q=a(6,q,"number","nil")or colors.white;r=a(7,r,"number","nil")or colors.black;g.setBackgroundColor(r)g.setTextColor(q)g.setCursorPos(h-1,i-1)g.write("\x9C"..("\x8C"):rep(o))g.setBackgroundColor(q)g.setTextColor(r)g.write("\x93")for s=1,p do g.setCursorPos(g.getCursorPos()-1,i+s-1)g.write("\x95")end;g.setBackgroundColor(r)g.setTextColor(q)for s=1,p do g.setCursorPos(h-1,i+s-1)g.write("\x95")end;g.setCursorPos(h-1,i+p)g.write("\x8D"..("\x8C"):rep(o).."\x8E")end; function b.button(g,h,i,t,u,q,r,v)a(1,g,"table")a(2,h,"number")a(3,i,"number")a(4,t,"string")a(5,u,"function","string")q=a(6,q,"number","nil")or colors.white;r=a(7,r,"number","nil")or colors.gray;v=a(8,v,"number","nil")or colors.lightGray;g.setCursorPos(h,i)g.setBackgroundColor(r)g.setTextColor(q)g.write(" "..t.." ")b.addTask(function()local w=false;while true do local x,y,z,A=os.pullEvent()local B,C=b.getWindowPos(g,h,i)if x=="mouse_click"and y==1 and z>=B and z=B and zp then F.setCursorPos(o,p)F.setBackgroundColor(r)F.setTextColor(q)F.write("\31")end;G.setCursorPos(2,J)G.setCursorBlink(true)b.setCursorWindow(G)local B,C=b.getWindowPos(g,h,i)b.addTask(function()local L=1;while true do local l=table.pack(os.pullEvent())local M;if l[1]=="key"then if l[2]==keys.up then M=-1 elseif l[2]==keys.down then M=1 elseif l[2]==keys.space and D[H[J][1]]~="R"then H[J][2]=not H[J][2]G.setCursorPos(2,J)G.write(H[J][2]and"\xD7"or" ")if type(u)=="string"then b.resolve("checkSelectionBox",u,H[J][1],H[J][2])elseif u then u(H[J][1],H[J][2])else D[H[J][1]]=H[J][2]end;for s,m in ipairs(H)do local N=D[m[1]]=="R"and"R"or m[2]G.setCursorPos(2,s)G.write(N and(N=="R"and"-"or"\xD7")or" ")end;G.setCursorPos(2,J)end elseif l[1]=="mouse_scroll"and l[3]>=B and l[3]=C and l[4]=1 and J+M<=E)then J=J+M;if J-L<0 or J-L>=p then L=L+M;G.reposition(1,2-L)end;G.setCursorPos(2,J)end;F.setCursorPos(o,1)F.write(L>1 and"\30"or" ")F.setCursorPos(o,p)F.write(L1 then error("bad argument #1 (value out of range)",2)end;g.setCursorPos(h,i)g.setBackgroundColor(r)g.setBackgroundColor(q)g.write((" "):rep(math.floor(aj*o)))g.setBackgroundColor(r)g.setTextColor(q)g.write((ah and"\x7F"or" "):rep(o-math.floor(aj*o)))end;ai(0)return ai end; -- function b.scrollBox(g,h,i,o,p,ak,al,am,q,r)a(1,g,"table")a(2,h,"number")a(3,i,"number")a(4,o,"number")a(5,p,"number")a(6,ak,"number")a(7,al,"boolean","nil")a(8,am,"boolean","nil")q=a(9,q,"number","nil")or colors.white;r=a(10,r,"number","nil")or colors.black;if al==nil then al=true end;local F=window.create(g==term and term.current()or g,h,i,o,p)F.setBackgroundColor(r)F.clear()local G=window.create(F,1,1,o-(am and 1 or 0),ak)G.setBackgroundColor(r)G.clear()if am then F.setBackgroundColor(r)F.setTextColor(q)F.setCursorPos(o,p)F.write(ak>p and"\31"or" ")end;h,i=b.getWindowPos(g,h,i)b.addTask(function()local L=1;while true do local l=table.pack(os.pullEvent())ak=select(2,G.getSize())local M;if l[1]=="key"and al then if l[2]==keys.up then M=-1 elseif l[2]==keys.down then M=1 end elseif l[1]=="mouse_scroll"and l[3]>=h and l[3]=i and l[4]=1 and L+M<=ak-p)then L=L+M;G.reposition(1,2-L)end;if am then F.setBackgroundColor(r)F.setTextColor(q)F.setCursorPos(o,1)F.write(L>1 and"\30"or" ")F.setCursorPos(o,p)F.write(Lo-1 and at:sub(1,o-4).."..."or at)end;ap.setCursorPos(o,1)ap.write(ar>1 and"\30"or" ")ap.setCursorPos(o,p)ap.write(ar<#an-p+1 and"\31"or" ")ap.setVisible(true)end;as()b.addTask(function()while true do local _,a8=os.pullEvent("key")if a8==keys.down and aq<#an then aq=aq+1;if aq>ar+p-1 then ar=ar+1 end;if type(ao)=="string"then b.resolve("selectionBox",ao,aq)elseif ao then ao(aq)end;as()elseif a8==keys.up and aq>1 then aq=aq-1;if aq= 300 and statusCode < 400 then local location = response.getResponseHeaders()['Location'] if not location then return nil, 'Redirect location not provided' end url = location redirects = redirects + 1 else return response end end return nil, 'Too many redirects' end local termW, termH = term.getSize() local boxSizing = { mainPadding = 2 } boxSizing.contentBox = termW - boxSizing.mainPadding * 2 + 1 boxSizing.borderBox = boxSizing.contentBox - 1 local curt = function () return term.current() end ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Select install') local installEntries = { "Release (minified)", "Release", "Source" } local installDescriptions = { 'Minified module + dependencies. Choose "Release" for a debug-friendly build.', "Packaged module + dependencies.", "Full module and dependency sources. Recommended for development." } function string:overlay(overlay) return overlay .. self:sub(#overlay + 1) end local youWouldntDownloadATree = function (tree, updateProgress, updateBlob) local sourceCount = 0 for _,_ in ipairs(tree.sources) do sourceCount = sourceCount + 1 end updateProgress(0) for i,v in ipairs(tree.sources) do local logicalPath = v.target or string.gsub(v.path, 'src/', '') local physicalPath = shell.resolve(logicalPath) updateBlob(string.gsub(logicalPath, 'telem/', '')) if v.type == 'tree' then fs.makeDir(physicalPath) elseif v.type == 'blob' then local bloburl = string.gsub(tree.url, '{sha}', tree.sha) bloburl = string.gsub(bloburl, '{path}', v.path) local blobreq = httpGetRedirect(bloburl) local fout = fs.open(physicalPath, 'w') fout.write(blobreq.readAll()) fout.flush() fout.close() end updateProgress(i / sourceCount) end end local parseAssets = function (assets) local parsed = { min = { size = 0 }, max = { size = 0 } } for i,v in ipairs(assets) do if v.name == 'telem.lua' then parsed.max.lib = v.name parsed.max.size = parsed.max.size + v.size elseif v.name == 'telem.min.lua' then parsed.min.lib = v.name parsed.min.size = parsed.min.size + v.size elseif v.name == 'vendor.lua' then parsed.max.vendor = v.name parsed.max.size = parsed.max.size + v.size elseif v.name == 'vendor.min.lua' then parsed.min.vendor = v.name parsed.min.size = parsed.min.size + v.size end end return parsed end local showReleaseSelector = function () ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Reading releases...') ui.borderBox(curt(), boxSizing.mainPadding + 1, 6, boxSizing.borderBox, 8) local releaseUrl = 'https://telem-get.cyberbit.dev/blob/releases' local req = httpGetRedirect(releaseUrl) local jres = textutils.unserialiseJSON(req.readAll()) local nonPreReleases = {} for i,v in ipairs(jres.releases) do if not v.prerelease then table.insert(nonPreReleases, v) end end local releaseLabels = {} local releaseNames = {} local releaseAssets = {} local releaseDescriptions = {} for i,v in ipairs(nonPreReleases) do local annotation = '' if v.latest then annotation = '[latest]' elseif v.prerelease then annotation = '[pre]' end annotation = string.format(('%%%is'):format(boxSizing.contentBox - 2), annotation) releaseLabels[i] = annotation:overlay(v.name) releaseNames[i] = v.name releaseAssets[i] = parseAssets(v.assets) releaseDescriptions[i] = ('%u bytes (%u bytes minified)'):format(releaseAssets[i].max.size, releaseAssets[i].min.size) end ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Select a release') ui.borderBox(curt(), boxSizing.mainPadding + 1, 6, boxSizing.borderBox, 8) local descriptionBox = ui.textBox(curt(), boxSizing.mainPadding, 15, boxSizing.contentBox, 5, releaseDescriptions[1]) ui.selectionBox(curt(), boxSizing.mainPadding + 1, 6, boxSizing.contentBox, 8, releaseLabels, 'done', function (opt) descriptionBox(releaseDescriptions[opt]) end) local _, _, selection = ui.run() local releaseName local releaseAssetsParsed for i,v in ipairs(releaseLabels) do if v == selection then releaseName = releaseNames[i] releaseAssetsParsed = releaseAssets[i] break end end return releaseName, releaseAssetsParsed end local showComplete = function (installName) ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Complete') ui.textBox(curt(), boxSizing.mainPadding, 6, boxSizing.contentBox, 8, ('%s has been installed. You may now close this installer.'):format(installName)) ui.button(curt(), boxSizing.mainPadding, 18, "Finish", "done") ui.keyAction(keys.enter, "done") ui.run() end local installActions = { -- Release (minified) function () local releaseName, releaseAssetsParsed = showReleaseSelector() ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Installing...') ui.textBox(curt(), boxSizing.mainPadding, 6, boxSizing.contentBox, 8, 'Downloading minified release ' .. releaseName) local progress = ui.progressBar(curt(), boxSizing.mainPadding, 8, boxSizing.contentBox, nil, nil, true) local currentBlob = ui.textBox(curt(), boxSizing.mainPadding, 10, boxSizing.contentBox, 2, '-----') local fakeTree = { sha = releaseName, url = 'https://telem-get.cyberbit.dev/blob/asset/{sha}/{path}', sources = { { path = 'telem', type = 'tree' } } } ui.addTask(function () if releaseAssetsParsed.min.lib then table.insert(fakeTree.sources, { path = 'telem.min.lua', target = 'telem/init.lua', type = 'blob' }) end if releaseAssetsParsed.min.vendor then table.insert(fakeTree.sources, { path = 'vendor.min.lua', target = 'telem/vendor.lua', type = 'blob' }) end youWouldntDownloadATree(fakeTree, progress, currentBlob) ui.resolve() end) ui.run() showComplete('Minified release') end, -- Release function () local releaseName, releaseAssetsParsed = showReleaseSelector() ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Installing...') ui.textBox(curt(), boxSizing.mainPadding, 6, boxSizing.contentBox, 8, 'Downloading release ' .. releaseName) local progress = ui.progressBar(curt(), boxSizing.mainPadding, 8, boxSizing.contentBox, nil, nil, true) local currentBlob = ui.textBox(curt(), boxSizing.mainPadding, 10, boxSizing.contentBox, 2, '-----') local fakeTree = { sha = releaseName, url = 'https://telem-get.cyberbit.dev/blob/asset/{sha}/{path}', sources = { { path = 'telem', type = 'tree' } } } ui.addTask(function () if releaseAssetsParsed.min.lib then table.insert(fakeTree.sources, { path = 'telem.lua', target = 'telem/init.lua', type = 'blob' }) end if releaseAssetsParsed.min.vendor then table.insert(fakeTree.sources, { path = 'vendor.lua', target = 'telem/vendor.lua', type = 'blob' }) end youWouldntDownloadATree(fakeTree, progress, currentBlob) ui.resolve() end) ui.run() showComplete('Release') end, -- Source function () ui.clear() ui.textBox(curt(), boxSizing.mainPadding, 2, boxSizing.contentBox, 5, 'Telem Installer - Installing...') local currentStep = ui.textBox(curt(), boxSizing.mainPadding, 6, boxSizing.contentBox, 8, '') local progress = ui.progressBar(term.current(), boxSizing.mainPadding, 8, boxSizing.contentBox, nil, nil, true) local currentBlob = ui.textBox(curt(), boxSizing.mainPadding, 10, boxSizing.contentBox, 2, 'reading source tree...') ui.addTask(function () currentStep('Step 1 of 2: Modules') local treeUrl = 'https://telem-get.cyberbit.dev/blob/lib/main' local req = httpGetRedirect(treeUrl) local res = textutils.unserialiseJSON(req.readAll()) youWouldntDownloadATree(res, progress, currentBlob) currentStep('Step 2 of 2: Vendors') treeUrl = 'https://telem-get.cyberbit.dev/blob/vendor/main' req = httpGetRedirect(treeUrl) res = textutils.unserialiseJSON(req.readAll()) youWouldntDownloadATree(res, progress, currentBlob) ui.resolve() end) ui.run() showComplete('Source') end } local runInstallAction = function (releaseEntry) for i,v in ipairs(installEntries) do if v == releaseEntry then return installActions[i]() end end error('undefined release, aborting') end local descriptionBox = ui.textBox(curt(), boxSizing.mainPadding, 15, boxSizing.contentBox, 5, installDescriptions[1]) ui.borderBox(curt(), boxSizing.mainPadding + 1, 6, boxSizing.borderBox, 8) ui.selectionBox(curt(), boxSizing.mainPadding + 1, 6, boxSizing.contentBox, 8, installEntries, 'done', function (opt) descriptionBox(installDescriptions[opt]) end) local _, _, selection = ui.run() runInstallAction(selection) ui.clear()