Advertisement
Guest User

qawserdtfygu

a guest
Dec 7th, 2019
127
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.39 KB | None | 0 0
  1. package.preload["argparse"] = function(...)
  2. local function errorf(msg, ...)
  3. error(msg:format(...), 0)
  4. end
  5. local function setter(arg, result, value)
  6. result[arg.name] = value or true
  7. end
  8. local parser = { __name = "ArgParser" }
  9. parser.__index = parser
  10. function parser:add(names, arg)
  11. if type(names) == "string" then names = { names } end
  12. arg.names = names
  13. for i = 1, #names do
  14. local name = names[i]
  15. if name:sub(1, 2) == "--" then self.options[name:sub(3)] = arg
  16. elseif name:sub(1, 1) == "-" then self.flags[name:sub(2)] = arg
  17. else self.arguments[#self.arguments + 1] = arg; arg.argument = true end
  18. end
  19. table.insert(self.list, #self.list, arg)
  20. if arg.action == nil then arg.action = setter end
  21. if arg.required == nil then arg.required = names[1]:sub(1, 1) ~= "-" end
  22. if arg.name == nil then arg.name = names[1]:gsub("^-+", "") end
  23. if arg.mvar == nil then arg.mvar = arg.name:upper() end
  24. end
  25. function parser:parse(...)
  26. local args = table.pack(...)
  27. local i, n = 1, #args
  28. local arg_idx = 1
  29. local result = {}
  30. while i <= n do
  31. local arg = args[i]
  32. i = i + 1
  33. if arg:find("^%-%-([^=]+)=(.+)$") then
  34. local name, value = arg:match("^%-%-([^=]+)=(.+)$")
  35. local arg = self.options[name]
  36. if not arg then errorf("Unknown argument %q", name) end
  37. if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
  38. if not arg.value then errorf("%s does not accept a value", name) end
  39. arg:action(result, value)
  40. elseif arg:find("^%-%-(.*)$") then
  41. local name = arg:match("^%-%-(.*)$")
  42. local arg = self.options[name]
  43. if not arg then errorf("Unknown argument %q", name) end
  44. if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
  45. if arg.value then
  46. local value = args[i]
  47. i = i + 1
  48. if not value then errorf("%s needs a value", name) end
  49. arg:action(result, value)
  50. else
  51. arg:action(result)
  52. end
  53. elseif arg:find("^%-(.+)$") then
  54. local flags = arg:match("^%-(.+)$")
  55. for j = 1, #flags do
  56. local name = flags:sub(j, j)
  57. local arg = self.flags[name]
  58. if not arg then errorf("Unknown argument %q", name) end
  59. if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
  60. if arg.value then
  61. local value
  62. if j == #flags then
  63. value = args[i]
  64. i = i + 1
  65. else
  66. value = flags:sub(j + 1)
  67. end
  68. if not value then errorf("%s expects a value", name) end
  69. arg:action(result, value)
  70. break
  71. else
  72. arg:action(result)
  73. end
  74. end
  75. else
  76. local argument = self.arguments[arg_idx]
  77. if argument then
  78. argument:action(result, arg)
  79. arg_idx = arg_idx + 1
  80. else
  81. errorf("Unexpected argument %q", arg)
  82. end
  83. end
  84. end
  85. for i = 1, #self.list do
  86. local arg = self.list[i]
  87. if arg and arg.required and result[arg.name] == nil then
  88. errorf("%s is required (use -h to see usage)", arg.name)
  89. end
  90. end
  91. return result
  92. end
  93. local function get_usage(arg)
  94. local name
  95. if arg.argument then name = arg.mvar
  96. elseif arg.value then name = arg.names[1] .. "=" .. arg.mvar
  97. else name = arg.names[1]
  98. end
  99. if #arg.names > 1 then name = name .. "," .. table.concat(arg.names, ",", 2) end
  100. return name
  101. end
  102. local function create(prefix)
  103. local parser = setmetatable({
  104. options = {},
  105. flags = {},
  106. arguments = {},
  107. list = {},
  108. }, parser)
  109. parser:add({ "-h", "--help", "-?" }, {
  110. value = false, required = false,
  111. doc = "Show this help message",
  112. action = function()
  113. if prefix then print(prefix) print() end
  114. print("USAGE")
  115. local max = 0
  116. for i = 1, #parser.list do max = math.max(max, #get_usage(parser.list[i])) end
  117. local format = " %-" .. max .. "s %s"
  118. for i = 1, #parser.list do
  119. local arg = parser.list[i]
  120. print(format:format(get_usage(arg), arg.doc or ""))
  121. end
  122. error("", 0)
  123. end,
  124. })
  125. return parser
  126. end
  127. local function is_help(cmd)
  128. return cmd == "help" or cmd == "--help" or cmd == "-h" or cmd == "-?"
  129. end
  130. return { create = create, is_help = is_help }
  131. end
  132. package.preload["framebuffer"] = function(...)
  133. local stringify = require("json").stringify
  134. local colour_lookup = {}
  135. for i = 0, 15 do
  136. colour_lookup[2 ^ i] = string.format("%x", i)
  137. end
  138. local void = function() end
  139. local function empty(colour, width, height)
  140. local function is_colour() return colour end
  141. return {
  142. write = void, blit = void, clear = void, clearLine = void,
  143. setCursorPos = void, setCursorBlink = void,
  144. setPaletteColour = void, setPaletteColor = void,
  145. setTextColour = void, setTextColor = void, setBackgroundColour = void, setBackgroundColor = void,
  146. getTextColour = void, getTextColor = void, getBackgroundColour = void, getBackgroundColor = void,
  147. scroll = void,
  148. isColour = is_colour, isColor = is_colour,
  149. getSize = function() return width, height end,
  150. getPaletteColour = term.native().getPaletteColour, getPaletteColor = term.native().getPaletteColor,
  151. }
  152. end
  153. local function buffer(original)
  154. local text = {}
  155. local text_colour = {}
  156. local back_colour = {}
  157. local palette = {}
  158. local palette_24 = {}
  159. local cursor_x, cursor_y = 1, 1
  160. local cursor_blink = false
  161. local cur_text_colour = "0"
  162. local cur_back_colour = "f"
  163. local sizeX, sizeY = original.getSize()
  164. local color = original.isColor()
  165. local dirty = false
  166. local redirect = {}
  167. if original.getPaletteColour then
  168. for i = 0, 15 do
  169. local c = 2 ^ i
  170. palette[c] = { original.getPaletteColour( c ) }
  171. palette_24[colour_lookup[c]] = colours.rgb8(original.getPaletteColour( c ))
  172. end
  173. end
  174. function redirect.write(writeText)
  175. writeText = tostring(writeText)
  176. original.write(writeText)
  177. dirty = true
  178. if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then
  179. cursor_x = cursor_x + #writeText
  180. return
  181. end
  182. if cursor_x < 1 then
  183. writeText = writeText:sub(-cursor_x + 2)
  184. cursor_x = 1
  185. elseif cursor_x + #writeText > sizeX then
  186. writeText = writeText:sub(1, sizeX - cursor_x + 1)
  187. end
  188. local lineText = text[cursor_y]
  189. local lineColor = text_colour[cursor_y]
  190. local lineBack = back_colour[cursor_y]
  191. local preStop = cursor_x - 1
  192. local preStart = math.min(1, preStop)
  193. local postStart = cursor_x + #writeText
  194. local postStop = sizeX
  195. local sub, rep = string.sub, string.rep
  196. text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop)
  197. text_colour[cursor_y] = sub(lineColor, preStart, preStop)..rep(cur_text_colour, #writeText)..sub(lineColor, postStart, postStop)
  198. back_colour[cursor_y] = sub(lineBack, preStart, preStop)..rep(cur_back_colour, #writeText)..sub(lineBack, postStart, postStop)
  199. cursor_x = cursor_x + #writeText
  200. end
  201. function redirect.blit(writeText, writeFore, writeBack)
  202. original.blit(writeText, writeFore, writeBack)
  203. dirty = true
  204. if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then
  205. cursor_x = cursor_x + #writeText
  206. return
  207. end
  208. if cursor_x < 1 then
  209. writeText = writeText:sub(-cursor_x + 2)
  210. writeFore = writeFore:sub(-cursor_x + 2)
  211. writeBack = writeBack:sub(-cursor_x + 2)
  212. cursor_x = 1
  213. elseif cursor_x + #writeText > sizeX then
  214. writeText = writeText:sub(1, sizeX - cursor_x + 1)
  215. writeFore = writeFore:sub(1, sizeX - cursor_x + 1)
  216. writeBack = writeBack:sub(1, sizeX - cursor_x + 1)
  217. end
  218. local lineText = text[cursor_y]
  219. local lineColor = text_colour[cursor_y]
  220. local lineBack = back_colour[cursor_y]
  221. local preStop = cursor_x - 1
  222. local preStart = math.min(1, preStop)
  223. local postStart = cursor_x + #writeText
  224. local postStop = sizeX
  225. local sub = string.sub
  226. text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop)
  227. text_colour[cursor_y] = sub(lineColor, preStart, preStop)..writeFore..sub(lineColor, postStart, postStop)
  228. back_colour[cursor_y] = sub(lineBack, preStart, preStop)..writeBack..sub(lineBack, postStart, postStop)
  229. cursor_x = cursor_x + #writeText
  230. end
  231. function redirect.clear()
  232. for i = 1, sizeY do
  233. text[i] = string.rep(" ", sizeX)
  234. text_colour[i] = string.rep(cur_text_colour, sizeX)
  235. back_colour[i] = string.rep(cur_back_colour, sizeX)
  236. end
  237. dirty = true
  238. return original.clear()
  239. end
  240. function redirect.clearLine()
  241. if cursor_y > sizeY or cursor_y < 1 then
  242. return
  243. end
  244. text[cursor_y] = string.rep(" ", sizeX)
  245. text_colour[cursor_y] = string.rep(cur_text_colour, sizeX)
  246. back_colour[cursor_y] = string.rep(cur_back_colour, sizeX)
  247. dirty = true
  248. return original.clearLine()
  249. end
  250. function redirect.getCursorPos()
  251. return cursor_x, cursor_y
  252. end
  253. function redirect.setCursorPos(x, y)
  254. if type(x) ~= "number" then error("bad argument #1 (expected number, got " .. type(x) .. ")", 2) end
  255. if type(y) ~= "number" then error("bad argument #2 (expected number, got " .. type(y) .. ")", 2) end
  256. if x ~= cursor_x or y ~= cursor_y then
  257. cursor_x = math.floor(x)
  258. cursor_y = math.floor(y)
  259. dirty = true
  260. end
  261. return original.setCursorPos(x, y)
  262. end
  263. function redirect.setCursorBlink(b)
  264. if type(b) ~= "boolean" then error("bad argument #1 (expected boolean, got " .. type(b) .. ")", 2) end
  265. if cursor_blink ~= b then
  266. cursor_blink = b
  267. dirty = true
  268. end
  269. return original.setCursorBlink(b)
  270. end
  271. function redirect.getSize()
  272. return sizeX, sizeY
  273. end
  274. function redirect.scroll(n)
  275. if type(n) ~= "number" then error("bad argument #1 (expected number, got " .. type(n) .. ")", 2) end
  276. local empty_text = string.rep(" ", sizeX)
  277. local empty_text_colour = string.rep(cur_text_colour, sizeX)
  278. local empty_back_colour = string.rep(cur_back_colour, sizeX)
  279. if n > 0 then
  280. for i = 1, sizeY do
  281. text[i] = text[i + n] or empty_text
  282. text_colour[i] = text_colour[i + n] or empty_text_colour
  283. back_colour[i] = back_colour[i + n] or empty_back_colour
  284. end
  285. elseif n < 0 then
  286. for i = sizeY, 1, -1 do
  287. text[i] = text[i + n] or empty_text
  288. text_colour[i] = text_colour[i + n] or empty_text_colour
  289. back_colour[i] = back_colour[i + n] or empty_back_colour
  290. end
  291. end
  292. dirty = true
  293. return original.scroll(n)
  294. end
  295. function redirect.setTextColour(clr)
  296. if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end
  297. local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2)
  298. if new_colour ~= cur_text_colour then
  299. dirty = true
  300. cur_text_colour = new_colour
  301. end
  302. return original.setTextColour(clr)
  303. end
  304. redirect.setTextColor = redirect.setTextColour
  305. function redirect.setBackgroundColour(clr)
  306. if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end
  307. local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2)
  308. if new_colour ~= cur_back_colour then
  309. dirty = true
  310. cur_back_colour = new_colour
  311. end
  312. return original.setBackgroundColour(clr)
  313. end
  314. redirect.setBackgroundColor = redirect.setBackgroundColour
  315. function redirect.isColour()
  316. return color == true
  317. end
  318. redirect.isColor = redirect.isColour
  319. function redirect.getTextColour()
  320. return 2 ^ tonumber(cur_text_colour, 16)
  321. end
  322. redirect.getTextColor = redirect.getTextColour
  323. function redirect.getBackgroundColour()
  324. return 2 ^ tonumber(cur_back_colour, 16)
  325. end
  326. redirect.getBackgroundColor = redirect.getBackgroundColour
  327. if original.getPaletteColour then
  328. function redirect.setPaletteColour(colour, r, g, b)
  329. local palcol = palette[colour]
  330. if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end
  331. if type(r) == "number" and g == nil and b == nil then
  332. palcol[1], palcol[2], palcol[3] = colours.rgb8(r)
  333. palette_24[colour] = r
  334. else
  335. if type(r) ~= "number" then error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) end
  336. if type(g) ~= "number" then error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) end
  337. if type(b) ~= "number" then error("bad argument #4 (expected number, got " .. type(b ) .. ")", 2 ) end
  338. palcol[1], palcol[2], palcol[3] = r, g, b
  339. palette_24[colour_lookup[colour]] = colours.rgb8(r, g, b)
  340. end
  341. dirty = true
  342. return original.setPaletteColour(colour, r, g, b)
  343. end
  344. redirect.setPaletteColor = redirect.setPaletteColour
  345. function redirect.getPaletteColour(colour)
  346. local palcol = palette[colour]
  347. if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end
  348. return palcol[1], palcol[2], palcol[3]
  349. end
  350. redirect.getPaletteColor = redirect.getPaletteColour
  351. end
  352. function redirect.is_dirty() return dirty end
  353. function redirect.clear_dirty() dirty = false end
  354. function redirect.serialise()
  355. return stringify {
  356. packet = 0x10,
  357. width = sizeX, height = sizeY,
  358. cursorX = cursor_x, cursorY = cursor_y, cursorBlink = cursor_blink,
  359. curFore = cur_text_colour, curBack = cur_back_colour,
  360. palette = palette_24,
  361. text = text, fore = text_colour, back = back_colour
  362. }
  363. end
  364. redirect.setCursorPos(1, 1)
  365. redirect.setBackgroundColor(colours.black)
  366. redirect.setTextColor(colours.white)
  367. redirect.clear()
  368. return redirect
  369. end
  370. return { buffer = buffer, empty = empty }
  371. end
  372. package.preload["encode"] = function(...)
  373. local function fletcher_32(str)
  374. local s1, s2, byte = 0, 0, string.byte
  375. if #str % 2 ~= 0 then str = str .. "\0" end
  376. for i = 1, #str, 2 do
  377. local c1, c2 = byte(str, i, i + 1)
  378. s1 = (s1 + c1 + (c2 * 0x100)) % 0xFFFF
  379. s2 = (s2 + s1) % 0xFFFF
  380. end
  381. return s2 * 0x10000 + s1
  382. end
  383. return {
  384. fletcher_32 = fletcher_32
  385. }
  386. end
  387. package.preload["json"] = function(...)
  388. local tonumber = tonumber
  389. local function skip_delim(str, pos, delim, err_if_missing)
  390. pos = pos + #str:match('^%s*', pos)
  391. if str:sub(pos, pos) ~= delim then
  392. if err_if_missing then error('Expected ' .. delim) end
  393. return pos, false
  394. end
  395. return pos + 1, true
  396. end
  397. local esc_map = { b = '\b', f = '\f', n = '\n', r = '\r', t = '\t' }
  398. local function parse_str_val(str, pos)
  399. local out, n = {}, 0
  400. if pos > #str then error("Malformed JSON (in string)") end
  401. while true do
  402. local c = str:sub(pos, pos)
  403. if c == '"' then return table.concat(out, "", 1, n), pos + 1 end
  404. n = n + 1
  405. if c == '\\' then
  406. local nextc = str:sub(pos + 1, pos + 1)
  407. if not nextc then error("Malformed JSON (in string)") end
  408. if nextc == "u" then
  409. local num = tonumber(str:sub(pos + 2, pos + 5), 16)
  410. if not num then error("Malformed JSON (in unicode string) ") end
  411. if num <= 255 then
  412. pos, out[n] = pos + 6, string.char(num)
  413. else
  414. pos, out[n] = pos + 6, "?"
  415. end
  416. else
  417. pos, out[n] = pos + 2, esc_map[nextc] or nextc
  418. end
  419. else
  420. pos, out[n] = pos + 1, c
  421. end
  422. end
  423. end
  424. local function parse_num_val(str, pos)
  425. local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
  426. local val = tonumber(num_str)
  427. if not val then error('Error parsing number at position ' .. pos .. '.') end
  428. return val, pos + #num_str
  429. end
  430. local null = {}
  431. local literals = {['true'] = true, ['false'] = false, ['null'] = null }
  432. local escapes = {}
  433. for i = 0, 255 do
  434. local c = string.char(i)
  435. if i >= 32 and i <= 126
  436. then escapes[c] = c
  437. else escapes[c] = ("\\u00%02x"):format(i)
  438. end
  439. end
  440. escapes["\t"], escapes["\n"], escapes["\r"], escapes["\""], escapes["\\"] = "\\t", "\\n", "\\r", "\\\"", "\\\\"
  441. local function parse(str, pos, end_delim)
  442. pos = pos or 1
  443. if pos > #str then error('Reached unexpected end of input.') end
  444. local pos = pos + #str:match('^%s*', pos)
  445. local first = str:sub(pos, pos)
  446. if first == '{' then
  447. local obj, key, delim_found = {}, true, true
  448. pos = pos + 1
  449. while true do
  450. key, pos = parse(str, pos, '}')
  451. if key == nil then return obj, pos end
  452. if not delim_found then error('Comma missing between object items.') end
  453. pos = skip_delim(str, pos, ':', true)
  454. obj[key], pos = parse(str, pos)
  455. pos, delim_found = skip_delim(str, pos, ',')
  456. end
  457. elseif first == '[' then
  458. local arr, val, delim_found = {}, true, true
  459. pos = pos + 1
  460. while true do
  461. val, pos = parse(str, pos, ']')
  462. if val == nil then return arr, pos end
  463. if not delim_found then error('Comma missing between array items.') end
  464. arr[#arr + 1] = val
  465. pos, delim_found = skip_delim(str, pos, ',')
  466. end
  467. elseif first == '"' then
  468. return parse_str_val(str, pos + 1)
  469. elseif first == '-' or first:match('%d') then
  470. return parse_num_val(str, pos)
  471. elseif first == end_delim then
  472. return nil, pos + 1
  473. else
  474. for lit_str, lit_val in pairs(literals) do
  475. local lit_end = pos + #lit_str - 1
  476. if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
  477. end
  478. local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
  479. error('Invalid json syntax starting at ' .. pos_info_str)
  480. end
  481. end
  482. local format, gsub, tostring, pairs, next, type, concat
  483. = string.format, string.gsub, tostring, pairs, next, type, table.concat
  484. local function stringify_impl(t, out, n)
  485. local ty = type(t)
  486. if ty == "table" then
  487. local first_ty = type(next(t))
  488. if first_ty == "nil" then
  489. out[n], n = "{}", n + 1
  490. return n
  491. elseif first_ty == "string" then
  492. out[n], n = "{", n + 1
  493. local first = true
  494. for k, v in pairs(t) do
  495. if first then first = false else out[n], n = ",", n + 1 end
  496. out[n] = format("\"%s\":", k)
  497. n = stringify_impl(v, out, n + 1)
  498. end
  499. out[n], n = "}", n + 1
  500. return n
  501. elseif first_ty == "number" then
  502. out[n], n = "[", n + 1
  503. for i = 1, #t do
  504. if i > 1 then out[n], n = ",", n + 1 end
  505. n = stringify_impl(t[i], out, n)
  506. end
  507. out[n], n = "]", n + 1
  508. return n
  509. else
  510. error("Cannot serialize key " .. first_ty)
  511. end
  512. elseif ty == "string" then
  513. if t:match("^[ -~]*$") then
  514. out[n], n = gsub(format("%q", t), "\n", "n"), n + 1
  515. else
  516. out[n], n = "\"" .. gsub(t, ".", escapes) .. "\"", n + 1
  517. end
  518. return n
  519. elseif ty == "number" or ty == "boolean" then
  520. out[n],n = tostring(t), n + 1
  521. return n
  522. else error("Cannot serialize type " .. ty)
  523. end
  524. end
  525. local function stringify(object)
  526. local buffer = {}
  527. local n = stringify_impl(object, buffer, 1)
  528. return concat(buffer, "", 1, n - 1)
  529. end
  530. local function try_parse(msg)
  531. local ok, res = pcall(parse, msg)
  532. if ok then return res else return nil, res end
  533. end
  534. return {
  535. stringify = stringify,
  536. try_parse = try_parse,
  537. parse = parse,
  538. null = null
  539. }
  540. end
  541. local tonumber = tonumber
  542. local argparse = require "argparse"
  543. local framebuffer = require "framebuffer"
  544. local encode = require "encode"
  545. local json = require "json"
  546. if _G.cloud_catcher then
  547. local usage = ([[
  548. cloud: <subcommand> [args]
  549. Communicate with the cloud-catcher session.
  550. Subcommands:
  551. edit <file> Open a file on the remote server.
  552. token Display the token for this
  553. connection.
  554. ]]):gsub("^%s+", ""):gsub("%s+$", ""):gsub("\n ", "\n")
  555. local subcommand = ...
  556. if subcommand == "edit" or subcommand == "e" then
  557. local arguments = argparse.create("cloud edit: Edit a file in the remote viewer")
  558. arguments:add({ "file" }, { doc = "The file to upload", required = true })
  559. local result = arguments:parse(select(2, ...))
  560. local file = result.file
  561. local resolved = shell.resolve(file)
  562. if not fs.exists(resolved) and not resolved:find("%.") then
  563. local extension = settings.get("edit.default_extension", "")
  564. if extension ~= "" and type(extension) == "string" then
  565. resolved = resolved .. "." .. extension
  566. end
  567. end
  568. if fs.isDir(resolved) then error(("%q is a directory"):format(file), 0) end
  569. if fs.isReadOnly(resolved) then
  570. if fs.exists(resolved) then
  571. print(("%q is read only, will not be able to modify"):format(file))
  572. else
  573. error(("%q does not exist"):format(file), 0)
  574. end
  575. end
  576. local ok, err = _G.cloud_catcher.edit(resolved)
  577. if not ok then error(err, 0) end
  578. elseif subcommand == "token" or subcommand == "t" then
  579. print(_G.cloud_catcher.token())
  580. elseif argparse.is_help(subcommand) then
  581. print(usage)
  582. elseif subcommand == nil then
  583. printError(usage)
  584. error()
  585. else
  586. error(("%q is not a cloud catcher subcommand, run with --h for more info"):format(subcommand), 0)
  587. end
  588. return
  589. end
  590. local current_path = shell.getRunningProgram()
  591. local current_name = fs.getName(current_path)
  592. local arguments = argparse.create(current_name .. ": Interact with this computer remotely")
  593. arguments:add({ "token" }, { doc = "The token to use when connecting" })
  594. arguments:add({ "--term", "-t" }, { value = true, doc = "Terminal dimensions or none to hide" })
  595. arguments:add({ "--dir", "-d" }, { value = true, doc = "The directory to sync to. Defaults to the current one." })
  596. arguments:add({ "--http", "-H" }, { value = false, doc = "Use HTTP instead of HTTPs" })
  597. local args = arguments:parse(...)
  598. local token = args.token
  599. if #token ~= 32 or token:find("[^%a%d]") then
  600. error("Invalid token (must be 32 alpha-numeric characters)", 0)
  601. end
  602. local capabilities = {}
  603. local term_opts = args.term
  604. local previous_term, parent_term = term.current()
  605. if term_opts == nil then
  606. parent_term = previous_term
  607. else if term_opts == "none" then
  608. parent_term = nil
  609. elseif term_opts == "hide" then
  610. parent_term = framebuffer.empty(true, term.getSize())
  611. elseif term_opts:find("^(%d+)x(%d+)$") then
  612. local w, h = term_opts:match("^(%d+)x(%d+)$")
  613. if w == 0 or h == 0 then error("Terminal cannot have 0 size", 0) end
  614. parent_term = framebuffer.empty(true, tonumber(w), tonumber(h))
  615. else
  616. error("Unknown format for term: expected \"none\", \"hide\" or \"wxh\"", 0)
  617. end
  618. end
  619. if parent_term then
  620. table.insert(capabilities, "terminal:host")
  621. local w, h = parent_term.getSize()
  622. if w * h > 5000 then error("Terminal is too large to handle", 0) end
  623. end
  624. local sync_dir = shell.resolve(args.dir or "./")
  625. if not fs.isDir(sync_dir) then error(("%q is not a directory"):format(sync_dir), 0) end
  626. table.insert(capabilities, "file:host")
  627. local url = ("%s://cloud-catcher.squiddev.cc/connect?id=%s&capabilities=%s"):format(
  628. args.http and "ws" or "wss", token, table.concat(capabilities, ","))
  629. local remote, err = http.websocket(url)
  630. if not remote then error("Cannot connect to cloud-catcher server: " .. err, 0) end
  631. local server_term, server_file_edit, server_file_host = false, false, false
  632. do
  633. local max_packet_size = 16384
  634. _G.cloud_catcher = {
  635. token = function() return token end,
  636. edit = function(file, force)
  637. if not server_file_edit then
  638. return false, "There are no editors connected"
  639. end
  640. local contents, exists
  641. local handle = fs.open(file, "rb")
  642. if handle then
  643. contents = handle.readAll()
  644. handle.close()
  645. exists = true
  646. else
  647. contents = ""
  648. exists = false
  649. end
  650. if #file + #contents + 5 > max_packet_size then
  651. return false, "This file is too large to be edited remotely"
  652. end
  653. local check = encode.fletcher_32(contents)
  654. local flag = 0x04
  655. if fs.isReadOnly(file) then flag = flag + 0x01 end
  656. if not exists then flag = flag + 0x08 end
  657. remote.send(json.stringify {
  658. packet = 0x22,
  659. id = 0,
  660. actions = {
  661. { file = file, checksum = check, flags = flag, action = 0, contents = contents }
  662. }
  663. })
  664. return true
  665. end
  666. }
  667. shell.setAlias("cloud", "/" .. current_path)
  668. local function complete_multi(text, options)
  669. local results = {}
  670. for i = 1, #options do
  671. local option, add_spaces = options[i][1], options[i][2]
  672. if #option + (add_spaces and 1 or 0) > #text and option:sub(1, #text) == text then
  673. local result = option:sub(#text + 1)
  674. if add_spaces then table.insert( results, result .. " " )
  675. else table.insert( results, result )
  676. end
  677. end
  678. end
  679. return results
  680. end
  681. local subcommands = { { "edit", true }, { "token", false } }
  682. shell.setCompletionFunction(current_path, function(shell, index, text, previous_text)
  683. if _G.cloud_catcher == nil then return end
  684. if index == 1 then
  685. return complete_multi(text, subcommands)
  686. elseif index == 2 and previous_text[2] == "edit" then
  687. return fs.complete(text, shell.dir(), true, false)
  688. end
  689. end)
  690. end
  691. local co, buffer
  692. if parent_term ~= nil then
  693. buffer = framebuffer.buffer(parent_term)
  694. co = coroutine.create(shell.run)
  695. term.redirect(buffer)
  696. end
  697. local info_dirty, last_label, get_label = true, nil, os.getComputerLabel
  698. local function send_info()
  699. last_label = get_label()
  700. info_dirty = false
  701. remote.send(json.stringify {
  702. packet = 0x12,
  703. id = os.getComputerID(),
  704. label = last_label,
  705. })
  706. end
  707. local ok, res = true
  708. if co then ok, res = coroutine.resume(co, "shell") end
  709. local last_change, last_timer = os.clock(), nil
  710. local pending_events, pending_n = {}, 0
  711. while ok and (not co or coroutine.status(co) ~= "dead") do
  712. if not info_dirty and last_label ~= get_label() then info_dirty = true end
  713. if server_term and last_timer == nil and (buffer.is_dirty() or info_dirty) then
  714. local now = os.clock()
  715. if now - last_change < 0.04 then
  716. last_timer = os.startTimer(0)
  717. else
  718. last_change = os.clock()
  719. if buffer.is_dirty() then
  720. remote.send(buffer.serialise())
  721. buffer.clear_dirty()
  722. end
  723. if info_dirty then send_info() end
  724. end
  725. end
  726. local event
  727. if pending_n >= 1 then
  728. event = table.remove(pending_events, 1)
  729. pending_n = pending_n - 1
  730. else
  731. event = table.pack(coroutine.yield())
  732. end
  733. if event[1] == "timer" and event[2] == last_timer then
  734. last_timer = nil
  735. if server_term then
  736. last_change = os.clock()
  737. if buffer.is_dirty() then remote.send(buffer.serialise()) buffer.clear_dirty() end
  738. if info_dirty then send_info() end
  739. end
  740. elseif event[1] == "websocket_closed" and event[2] == url then
  741. ok, res = false, "Connection lost"
  742. remote = nil
  743. elseif event[1] == "websocket_message" and event[2] == url then
  744. local packet = json.try_parse(event[3])
  745. local code = packet and packet.packet
  746. if type(code) ~= "number" then code = - 1 end
  747. if code >= 0x00 and code < 0x10 then
  748. if code == 0x00 then -- ConnectionUpdate
  749. server_term, server_file_edit, server_file_host = false, false, false
  750. for _, cap in ipairs(packet.capabilities) do
  751. if cap == "terminal:view" and buffer ~= nil then
  752. server_term = true
  753. remote.send(buffer.serialise()) buffer.clear_dirty()
  754. send_info()
  755. last_change = os.clock()
  756. elseif cap == "file:host" then
  757. server_file_host = true
  758. elseif cap == "file:edit" then
  759. server_file_edit = true
  760. end
  761. end
  762. elseif code == 0x02 then -- ConnectionPing
  763. remote.send([[{"packet":2}]])
  764. end
  765. elseif server_term and code >= 0x10 and code < 0x20 then
  766. if code == 0x11 then -- TerminalEvents
  767. for _, event in ipairs(packet.events) do
  768. pending_n = pending_n + 1
  769. pending_events[pending_n] = table.pack(event.name, table.unpack(event.args))
  770. end
  771. end
  772. elseif code >= 0x20 and code < 0x30 then
  773. if code == 0x22 then -- FileAction
  774. local result = {}
  775. for i, action in pairs(packet.actions) do
  776. local ok = bit32.band(action.flags, 0x1) == 1
  777. local expected_checksum = 0
  778. local handle = fs.open(action.file, "rb")
  779. if handle then
  780. local contents = handle.readAll()
  781. handle.close()
  782. expected_checksum = encode.fletcher_32(contents)
  783. end
  784. if not ok then
  785. ok = expected_checksum == 0 or action.checksum == expected_checksum
  786. end
  787. if not ok then
  788. result[i] = { file = action.file, checksum = expected_checksum, result = 2 }
  789. elseif action.action == 0x0 then -- Replace
  790. handle = fs.open(action.file, "wb")
  791. if handle then
  792. handle.write(action.contents)
  793. handle.close()
  794. result[i] = { file = action.file, checksum = encode.fletcher_32(action.contents), result = 1 }
  795. else
  796. result[i] = { file = action.file, checksum = expected_checksum, result = 3 }
  797. end
  798. elseif action.action == 0x1 then -- Patch
  799. handle = fs.open(action.file, "rb")
  800. if handle then
  801. local out, n = {}, 0
  802. for _, delta in pairs(action.delta) do
  803. if delta.kind == 0 then -- Same
  804. n = n + 1
  805. out[n] = handle.read(delta.length)
  806. elseif delta.kind == 1 then -- Added
  807. n = n + 1
  808. out[n] = delta.contents
  809. elseif delta.kind == 2 then -- Removed
  810. handle.read(delta.length)
  811. end
  812. end
  813. handle.close()
  814. handle = fs.open(action.file, "wb")
  815. if handle then
  816. local contents = table.concat(out)
  817. handle.write(contents)
  818. handle.close()
  819. result[i] = { file = action.file, checksum = encode.fletcher_32(contents), result = 1 }
  820. else
  821. result[i] = { file = action.file, checksum = expected_checksum, result = 3 }
  822. end
  823. else
  824. result[i] = { file = action.file, checksum = expected_checksum, result = 2 }
  825. end
  826. elseif action.action == 0x02 then -- Delete
  827. local ok = fs.delete(action.file)
  828. result[i] = { file = action.file, checksum = action.checksum, result = ok and 1 or 3 }
  829. end
  830. end
  831. remote.send(json.stringify {
  832. packet = 0x23,
  833. id = packet.id,
  834. files = result,
  835. })
  836. end
  837. end
  838. elseif res == nil or event[1] == res or event[1] == "terminate" then
  839. if co then
  840. ok, res = coroutine.resume(co, table.unpack(event, 1, event.n))
  841. elseif event[1] == "terminate" then
  842. ok, res = false, "Terminated"
  843. end
  844. end
  845. end
  846. term.redirect(previous_term)
  847. if previous_term == parent_term then
  848. term.clear()
  849. term.setCursorPos(1, 1)
  850. if previous_term.endPrivateMode then previous_term.endPrivateMode() end
  851. end
  852. _G.cloud_catcher = nil
  853. shell.clearAlias("cloud")
  854. shell.getCompletionInfo()[current_path] = nil
  855. if remote ~= nil then remote.close() end
  856. if not ok then error(res, 0) end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement