NeOzay

github.lua

Aug 18th, 2021 (edited)
491
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local component = require("component")
  2. local fs = require("filesystem")
  3. local serialization = require("serialization")
  4. local internet = require("internet")
  5.  
  6. local JSON = require("dkjson")
  7. local internetCard = component.internet
  8.  
  9. local _
  10.  
  11.  
  12. ---@param handle any
  13. local function readAll(handle)
  14.     ---@type string
  15.     local chunk
  16.     ---@type string[]
  17.     local data = {}
  18.     while true do
  19.         chunk = handle.read(math.huge)
  20.  
  21.         if chunk then
  22.             table.insert(data,chunk)
  23.         else
  24.             break
  25.         end
  26.     end
  27.     return table.concat(data)
  28. end
  29.  
  30. -- Build a github API url, with authorization headers.
  31. ---@param path string
  32. ---@param auth auth
  33. ---@return response,table
  34. local function getAPI(path, auth)
  35.     local url = ('https://api.github.com/%s'):format(path)
  36.     local headers
  37.     if auth and auth.type == 'oauth' then
  38.         headers = { ['Authorization'] = ('token %s'):format(auth.token) }
  39.     end
  40.  
  41.     local handle = internetCard.request(url, _,headers)
  42.  
  43.     ---@type table
  44.     local data = JSON.decode(readAll(handle))
  45.     handle.close()
  46.  
  47.     local rawReponce = { handle.response() }
  48.     ---@shape response
  49.     ---@field code number
  50.     ---@field message string
  51.     ---@field headers table
  52.     local reponse = { code = rawReponce[1],
  53.                       message = rawReponce[2],
  54.                       headers = rawReponce[3] }
  55.     return reponse, data
  56. end
  57.  
  58. ---@param s string
  59. local function encodeURI(s)
  60.     return s:gsub(' ', '%%20')
  61. end
  62.  
  63. ---@param blob blob
  64. ---@param path string
  65. local function downloadFile(blob, sha, path)
  66.     local auth = blob.repo.auth
  67.     local headers
  68.     if auth and auth.type == 'oauth' then
  69.         headers = { ['Authorization'] = ('token %s'):format(auth.token) }
  70.     end
  71.     local url = ('https://raw.githubusercontent.com/%s/%s/%s/%s'):format(blob.repo.user, blob.repo.name, sha, encodeURI(blob:fullPath()))
  72.     local handle = internetCard.request(url,_ , headers)
  73.  
  74.     local h = io.open(path, 'w')
  75.     h:write(readAll(handle))
  76.     h:close()
  77.     handle.close()
  78. end
  79.  
  80. -- A class for authorization
  81. local authFile = '/home/.github-auth'
  82. ---@param data table<string,userData>
  83. local function writeAuth(data)
  84.     local f = io.open(authFile, 'w')
  85.     f:write(serialization.serialize(data))
  86.     f:close()
  87. end
  88. local function getAuthTable()
  89.     ---@shape userData
  90.     ---@field type string
  91.     ---@field token string
  92.     ---@field user string
  93.  
  94.     ---@type table<string,userData>
  95.     local authTable = {}
  96.     if fs.exists(authFile) then
  97.         local f = io.open(authFile, 'r')
  98.         authTable = serialization.unserialize(f:read())
  99.         f:close()
  100.     end
  101.     return authTable
  102. end
  103.  
  104. ---@class auth
  105. local Auth = {}
  106. Auth.__index = Auth
  107.  
  108. ---@param type string
  109. ---@param user string
  110. ---@param token string
  111. ---@return auth
  112. function Auth.new(type, user, token)
  113.     ---@type
  114.     return setmetatable({ type = type, user = user, token = token }, Auth)
  115. end
  116.  
  117. ---@param user string
  118. function Auth.get(user)
  119.     local authTable = getAuthTable()
  120.     local auth = authTable[user]
  121.     if auth then
  122.         return Auth.new(auth.type, auth.user, auth.token)
  123.     end
  124.  
  125. end
  126.  
  127. function Auth:save()
  128.     local authTable = getAuthTable()
  129.     authTable[self.user] = self
  130.     writeAuth(authTable)
  131. end
  132.  
  133. ---@param user string
  134. function Auth.delete(user)
  135.     local authTable = getAuthTable()
  136.     authTable[user] = nil
  137.     writeAuth(authTable)
  138. end
  139.  
  140. function Auth:checkToken()
  141.     local status, _ = getAPI('user', self)
  142.     return status.code == 200
  143. end
  144.  
  145. -- A class for a blob (aka a file)
  146. ---@class blob
  147. ---@field path string
  148. ---@field repo repo
  149. ---@field sha string
  150. ---@field parent tree
  151. local Blob = {}
  152. Blob.__index = Blob
  153.  
  154. ---@param repo table
  155. ---@param sha string
  156. ---@param path string
  157. ---@return  blob
  158. function Blob.new(repo, sha, path)
  159.     ---@type
  160.     return setmetatable({ repo = repo, sha = sha, path = path }, Blob)
  161. end
  162.  
  163. ---@return string
  164. function Blob:fullPath()
  165.     ---@type string
  166.     local fullPath
  167.     if self.parent then
  168.         fullPath = fs.concat(self.parent:fullPath(), self.path)
  169.         function self:fullPath() return fullPath end
  170.         return fullPath
  171.     else
  172.         return self.path
  173.     end
  174. end
  175.  
  176. -- A class for a tree (aka a folder)
  177. ---@class tree
  178. ---@field repo repo
  179. ---@field sha string
  180. ---@field path string
  181. ---@field parent tree
  182. ---@field size number
  183. ---@field contents (tree|blob)[]
  184. local Tree = {}
  185. Tree.__index = Tree
  186.  
  187. ---@param repo repo
  188. ---@param sha string
  189. ---@param path string
  190. ---@return tree
  191. ---@overload fun(repo:repo,sha:string):tree
  192. function Tree.new(repo, sha, path)
  193.     local url = ('repos/%s/%s/git/trees/%s'):format(repo.user, repo.name, sha)
  194.     local status, data = getAPI(url, repo.auth)
  195.     if not status then
  196.         error('Could not get github API from ' .. url)
  197.     end
  198.  
  199.     if data.tree then
  200.  
  201.         ---@type
  202.         local t = setmetatable({
  203.             repo = repo, sha = data.sha,
  204.             path = path or '', size = 0,
  205.             contents = {}
  206.         }, Tree)
  207.         ---@type tree
  208.         local tree = t
  209.  
  210.         for _, childdata in ipairs(data.tree) do
  211.             childdata.fullPath = fs.concat(tree:fullPath(), childdata.path)
  212.             ---@type tree|blob
  213.             local child
  214.             if childdata.type == 'blob' then
  215.                 child = Blob.new(repo, childdata.sha, childdata.path)
  216.                 child.size = childdata.size
  217.             elseif childdata.type == 'tree' then
  218.                 child = Tree.new(repo, childdata.sha, childdata.path)
  219.             else
  220.                 error("uh oh", JSON.encode(childdata))
  221.                 --child = childdata
  222.             end
  223.  
  224.             tree.size = tree.size + child.size
  225.             child.parent = tree
  226.             table.insert(tree.contents, child)
  227.  
  228.         end
  229.         return tree
  230.     else
  231.         error("uh oh", JSON.encode(data))
  232.     end
  233. end
  234.  
  235. ---@param t tree
  236. ---@param level number
  237. local function walkTree(t, level)
  238.     for _, item in ipairs(t.contents) do
  239.         coroutine.yield(item, level)
  240.         if getmetatable(item) == Tree then
  241.             walkTree(--[[---@ not blob]]
  242.             item, level + 1)
  243.         end
  244.     end
  245. end
  246.  
  247. ---@return fun():tree|blob,number
  248. function Tree:iter()
  249.     ---@type
  250.     return coroutine.wrap(function()
  251.         walkTree(self, 0)
  252.     end)
  253. end
  254.  
  255. ---@param dest string
  256. ---@param onProgress fun(item:(blob|tree),number)
  257. ---@overload fun(dest:string)
  258. function Tree:cloneTo(dest, onProgress)
  259.     if not fs.exists(dest) then
  260.         fs.makeDirectory(dest)
  261.     elseif not fs.isDirectory(dest) then
  262.         return error("Destination is a file!")
  263.     end
  264.  
  265.     for item,level in self:iter() do
  266.         local gitpath = item:fullPath()
  267.         local path = fs.concat(dest, gitpath)
  268.         if getmetatable(item) == Tree then
  269.             fs.makeDirectory(path)
  270.         elseif getmetatable(item) == Blob then
  271.             downloadFile(--[[---@not tree]] item, self.sha, path)
  272.         end
  273.         if onProgress then onProgress(item,level) end
  274.     end
  275. end
  276. Tree.fullPath = Blob.fullPath
  277.  
  278. -- A class for a release
  279. ---@class release
  280. ---@field repo repo
  281. ---@field tag string
  282. local Release = {}
  283. Release.__index = Release
  284.  
  285. ---@param repo repo
  286. ---@param tag string
  287. ---@return release
  288. function Release.new(repo, tag)
  289.     ---@type
  290.     return setmetatable({ repo = repo, tag = tag }, Release)
  291. end
  292.  
  293. function Release:tree()
  294.     return self.repo:tree(self.tag)
  295. end
  296.  
  297. -- A class for a repo
  298. ---@type table<repo,table<"trees",table<string,tree>>>
  299. local __repoPriv = setmetatable({}, { mode = 'k' })
  300. ---@class repo
  301. ---@field user string
  302. ---@field name string
  303. ---@field auth auth
  304. local Repository = {}
  305.  
  306. Repository.__index = Repository
  307.  
  308. ---@param user string
  309. ---@param name string
  310. ---@param auth auth
  311. ---@return repo
  312. ---@overload fun(user:string,name:string):repo
  313. function Repository.new(user, name, auth)
  314.     if auth then
  315.         auth:checkToken()
  316.     end
  317.     ---@type
  318.     local r = setmetatable({ user = user, name = name, auth = auth }, Repository)
  319.     __repoPriv[r] = { trees = {} }
  320.     return r
  321. end
  322.  
  323. ---@param sha string
  324. ---@overload fun():tree
  325. function Repository:tree(sha)
  326.     sha = sha or "master"
  327.     if not __repoPriv[self].trees[sha] then
  328.         __repoPriv[self].trees[sha] = Tree.new(self, sha)
  329.     end
  330.     return __repoPriv[self].trees[sha]
  331. end
  332.  
  333. ---@param url string
  334. ---@param repo repo
  335. local function releaseFromURL(url, repo)
  336.     local status, data = getAPI(url, repo.auth)
  337.     if not status then
  338.         error('Could not get release github API from ' .. url)
  339.     end
  340.     -- format is described at https://developer.github.com/v3/repos/releases/
  341.     return Release.new(repo, data["tag_name"])
  342. end
  343.  
  344. function Repository:latestRelease()
  345.     return releaseFromURL(('repos/%s/%s/releases/latest'):format(self.user, self.name), self)
  346. end
  347.  
  348. ---@param tag string
  349. function Repository:releaseForTag(tag)
  350.     return releaseFromURL(('repos/%s/%s/releases/tags/%s'):format(self.user, self.name, tag), self)
  351. end
  352.  
  353. function Repository:__tostring()
  354.     return ("Repo@%s/%s"):format(self.user, self.name)
  355. end
  356.  
  357. -- Export members
  358. local github = {}
  359. github.Repository = Repository
  360. github.Blob = Blob
  361. github.Tree = Tree
  362. github.Auth = Auth
  363. github.Release = Release
  364. github.repo = Repository.new
  365. return github
RAW Paste Data