Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[ TurtleAppstore.com client program by Al Sweigart
- Used to download/upload files from Minecraft to TurtleAppstore.com
- The appstore program uses a hidden file named .appstoreusername
- Usage:
- > appstore <permalink>
- > appstore get <appname> # uses set username
- > appstore get <username> <appname> # also sets the username
- > appstore run <appname>
- > appstore run <username> <appname>
- > appstore put <username> <filename> <otp> # uses set username
- > appstore put <filename> <otp> # uses set username
- > appstore update <filename> # doesn't cache the username
- > appstore updateAll # doesn't cache the username
- ]]
- -- TODO: what happens if we can't write to the turtle's harddrive due to lack of disk space?
- -- TODO: set up otp with a certain checksum so we can tell if the user might have typed their real turtleappstore.com password instead of an otp
- -- TODO:
- -- TODO: figure out where the error messages should go. in functions? after returning?
- -- Note that TAS_HOST does *not* have the "https://" or "http://" prefix.
- --local TAS_HOST = 'turtleappstore.com'
- local TAS_HOST = 'localhost:8000'
- local PERMALINK_LEN = 6
- local cliArgs = {...}
- -- START OF CLI COMPLETION FUNCTION
- -- Copied from http://www.computercraft.info/wiki/Shell.setCompletionFunction
- function tabCompletionFunction(shell, parNumber, curText, lastText)
- -- Check that the parameters entered so far are valid:
- local curParam = { ['get '] = {}, ['run '] = {}, ['put '] = {}, ['update '] = {}, ['updateall '] = {}, }
- for i = 2, #lastText do
- if curParam[lastText[i] .. " "] then
- curParam = curParam[lastText[i] .. " "]
- else
- return {}
- end
- end
- -- Check for suitable words for the current parameter:
- local results = {}
- for word, _ in pairs(curParam) do
- if word:sub(1, #curText) == curText then
- results[#results + 1] = word:sub(#curText + 1)
- end
- end
- return results
- end
- shell.setCompletionFunction("appstore", tabCompletionFunction)
- -- END OF CLI COMPLETION FUNCTION
- function main()
- local result, username, filename, otp, fo, sourceCode, appname
- -- handle command line arguments
- -- display usage
- if cliArgs[1] == nil then
- printUsage()
- return
- end
- if not canConnect() then
- print('Failed to connect to')
- print('turtleappstore.com.')
- error()
- end
- local cmd = cliArgs[1]
- if cmd == 'get' or cmd == 'run' then
- if cliArgs[3] ~= nil then
- -- Download an app via username & appname
- username = cliArgs[2]
- appname = cliArgs[3]
- username, appname = getAppByName(username, appname)
- if username == false then -- TODO: add failure
- print('Could not find an app with that name')
- print('from that user. Did you type the name')
- print('of the user and app correctly?')
- error()
- end
- cacheUsername(username)
- if cmd == 'run' then
- shell.run(appname) -- run the downloaded program
- end
- elseif cliArgs[2] ~= nil then
- -- Download an app via appname & cached username
- username = getCachedUsername()
- appname = cliArgs[2]
- if username == nil then
- print('Please specify a username & appname.')
- error()
- end
- print('Using username ' .. username) -- tell the user what cached user we're using
- username, appname = getAppByName(username, appname)
- if username == false then -- TODO add failure if not connected or site is down, etc.
- print('TODO failed msg2')
- error()
- end
- cacheUsername(username)
- if cmd == 'run' then
- shell.run(cliArgs[2]) -- run the downloaded program
- end
- end
- elseif cmd == 'put' then
- -- Upload an app via filename
- if cliArgs[2] == nil then
- -- user didn't supply anything after "put"
- print('Usage:')
- print(' appstore put <username> <filename> <otp>')
- error()
- elseif cliArgs[4] == nil then
- -- user supplied 3 things, we assume they are 'put', filename, and otp (and assume the username is cached)
- username = getCachedUsername()
- if username == nil then
- print('Usage:')
- print(' appstore put <username> <filename> <otp>')
- error()
- end
- print('Using username ' .. username) -- tell the user what cached user we're using
- filename = cliArgs[2]
- otp = cliArgs[3]
- else
- -- user supplied 'put', username, filename, and otp
- username = cliArgs[2]
- filename = cliArgs[3]
- otp = cliArgs[4]
- end
- print('Connecting to turtleappstore.com...')
- result = putAppByName(username, filename, otp)
- print(result)
- if result ~= 'Uploaded ' .. filename then
- error()
- end
- elseif cmd == 'update' then
- filename = cliArgs[2]
- local result, errMsg = updateApp(filename)
- if not result then
- print(errMsg)
- error()
- end
- elseif cmd == 'updateall' then
- updateAllApps('.')
- elseif string.len(cmd) == PERMALINK_LEN then
- -- Download an app via permalink
- local permalink = cliArgs[1]
- if getAppByPermalink(permalink) == false then
- error()
- end
- return true
- else
- print('Command not understood.')
- printUsage()
- error()
- end
- end
- function canConnect()
- return http.get('https://' .. TAS_HOST) ~= nil or http.get('http://' .. TAS_HOST) ~= nil
- end
- function getCachedUsername()
- if not fs.exists('.appstoreusername') then
- return nil
- end
- local fo = fs.open('.appstoreusername', 'r')
- local cachedUsername = fo.readLine()
- fo.close()
- if cachedUsername == '' then
- return nil
- end
- return cachedUsername
- end
- function cacheUsername(username)
- writeFile('.appstoreusername', username) -- TODO: make sure username is always in the proper case here
- end
- function printUsage()
- print('Usage:')
- -- TODO
- print(' appstore <permalink>')
- print(' appstore get <username> <appname>')
- print(' appstore run <username> <appname>')
- print(' appstore put <filename> <otp>')
- print(' appstore update <filename>')
- print(' appstore updateall')
- end
- function updateApp(filename)
- if not fs.exists(filename) then
- return false, 'File ' .. filename .. ' doesn\'t exist on this turtle.'
- end
- local username, appname, sourceCode = readApp(filename)
- if not username then
- return false, appstore .. ' couldn\'t be updated because it wasn\'t originally downloaded from turtleappstore.com.'
- end
- local username, appname = getAppByName(username, appname)
- if not username then
- return false, 'Couldn\'t download ' .. appname .. ' from user ' .. username .. '. Does this user and app still exist on turtleappstore.com?'
- end
- return username, appname
- end
- function updateAllApps(folder)
- -- autodetects appstore apps in the folder and updates them it there's a new version in the appstore
- -- TODO: I need an implementation of CRC32 or Adler32 that matches the output of Python's CRC32 and Adler32. For now, we update all apps regardless of changes or not.
- -- Note: CC uses only absolute paths, so `updateall` will only update apps in the root folder.
- local updatedAtLeastOneApp = false
- for i, v in pairs(fs.list(folder)) do
- if not fs.isDir(v) then
- local username, appname = updateApp(v)
- if username then
- updatedAtLeastOneApp = true
- end
- end
- end
- if not updatedAtLeastOneApp then
- print('No apps to update in this folder.')
- error()
- end
- return true
- end
- function downloadApp(username, appname)
- -- On success, returns the username, appname, source code of the app.
- -- On failure, returns false.
- -- `username` and `appname` will have the correct casing
- return _download(TAS_HOST .. '/u/' .. username .. '/a/' .. appname .. '/raw')
- end
- function downloadPermalink(permalink)
- -- On success, returns the username, appname, source code of the app.
- -- On failure, returns false.
- -- `username` and `appname` will have the correct casing
- return _download(TAS_HOST .. '/permalink/' .. permalink .. '/raw')
- end
- function _download(url)
- -- On success, returns the username, appname, source code of the app.
- -- On failure, returns false.
- -- `url` parameter will *not* have the "https://" or "http://" prefix, so
- -- that we can try both. (Dan suspects that some Java configs disallow SSL,
- -- so we try https first and fall back to http.)
- -- This is the helper function called by downloadApp() and downloadPermalink()
- local response = http.get('https://' .. url) -- attempt https
- if response == nil then
- response = http.get('http://' .. url) -- attempt http as fallback
- if response == nil then
- return false
- end
- end
- local sourceCode = response.readAll()
- response.close()
- local username, appname = readAppHeader(sourceCode)
- return username, appname, sourceCode
- end
- function getAppByPermalink(permalink)
- local username, appname, sourceCode = downloadPermalink(permalink)
- if username == false then
- print('Permalink "' .. permalink .. '" doesn\'t exist.')
- return false
- end
- writeFile(appname, sourceCode)
- print('Downloaded ' .. appname .. ' by ' .. username)
- cacheUsername(username)
- return true
- end
- function writeFile(filename, content)
- -- TODO: handle out of memory error
- -- TODO: what should the return value of this be?
- local fo = fs.open(filename, 'w')
- fo.write(content)
- fo.close()
- end
- function getAppByName(username, appname)
- local username, appname, sourceCode = downloadApp(username, appname)
- if username == false then
- return false
- end
- writeFile(appname, sourceCode)
- print('Downloaded ' .. appname .. ' by ' .. username)
- return username, appname -- returns correct case of username and appname
- end
- function putApp(filename, otp)
- end
- function readApp(filename)
- local fo = fs.open(filename, 'r')
- local sourceCode = fo.readAll()
- fo.close()
- local username, appname = readAppHeader(sourceCode)
- return username, appname, sourceCode
- end
- function readAppHeader(sourceCode)
- -- returns false, false if there is no header, otherwise returns the username, appname
- if string.sub(sourceCode, 1, 32) ~= '-- https://turtleappstore.com/u/' then
- return false
- end
- local usernameEnd = string.find(string.sub(sourceCode, 33), '/')
- local username = string.sub(sourceCode, 33, 33+usernameEnd-2)
- local appnameEnd = string.find(sourceCode, '\n')
- local appname = string.sub(sourceCode, 33+usernameEnd+2, appnameEnd - 1)
- return username, appname
- end
- function putAppByName(username, filename, otp)
- -- returns the result as a string
- if not fs.exists(filename) then
- return 'Cannot find file ' .. filename
- end
- local fo = fs.open(filename, 'r')
- local content = fo.readAll()
- fo.close()
- local response = http.post('https://' .. TAS_HOST .. '/put/', 'username=' .. textutils.urlEncode(username) ..
- '&filename=' .. textutils.urlEncode(filename) ..
- '&otp=' .. textutils.urlEncode(otp) ..
- '&content=' .. textutils.urlEncode(content))
- if response == nil then
- -- Fallback to http if https failed.
- response = http.post('http://' .. TAS_HOST .. '/put/', 'username=' .. textutils.urlEncode(username) ..
- '&filename=' .. textutils.urlEncode(filename) ..
- '&otp=' .. textutils.urlEncode(otp) ..
- '&content=' .. textutils.urlEncode(content))
- if response == nil then
- return 'Failed to connect.'
- end
- end
- local result = response.readAll()
- response.close()
- cacheUsername(username)
- addAppHeader(filename, username)
- return result
- end
- function addAppHeader(filename, username)
- local fo = fs.open(filename, 'r')
- local content = fo.readAll()
- fo.close()
- if string.sub(content, 1, 32) ~= '-- https://turtleappstore.com/u/' then
- local appHeader = '-- https://turtleappstore.com/u/' .. username .. '/a/' .. filename .. '\n'
- writeFile(filename, appHeader .. content)
- end
- end
- main()
Add Comment
Please, Sign In to add comment