Advertisement
Z1maV1

capsule.lua

Aug 18th, 2024 (edited)
250
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 21.64 KB | None | 0 0
  1. -- code here is just in case capsule downloaded and ran on non diamondos machine
  2. -- this part will check if libs exists, if not, download them
  3.  
  4. if not fs.exists("/lib/stringbuilder.lua") then
  5.     shell.run("pastebin get KUjYeHfm /lib/stringbuilder.lua")
  6. end
  7.  
  8. if not fs.exists("/lib/print-utils.lua") then
  9.     shell.run("pastebin get TQxHVC6D /lib/print-utils.lua")
  10. end
  11.  
  12. if not fs.exists("/lib/crypto/sha2.lua") then
  13.     shell.run("pastebin get bmE3aTzv /lib/crypto/sha2.lua")
  14. end
  15.  
  16. if not fs.exists("/lib/stringbuilder.lua") then
  17.     shell.run("pastebin get KUjYeHfm /lib/stringbuilder.lua")
  18. end
  19.  
  20. if not fs.exists("/lib/config.lua") then
  21.     shell.run("pastebin get v5XWn99E /lib/config.lua")
  22. end
  23.  
  24. if not fs.exists("/lib/args.lua") then
  25.     shell.run("pastebin get swWJdc7X /lib/args.lua")
  26. end
  27.  
  28. if not fs.exists("/lib/logger.lua") then
  29.     shell.run("pastebin get abFf6TPB /lib/logger.lua")
  30. end
  31.  
  32. -- program itself
  33.  
  34. local stringBuilder = require("/lib/stringbuilder")
  35. local lib_print = require("/lib/print-utils")
  36. local lib_sha = require("/lib/crypto/sha2")
  37. local lib_config = require("/lib/config")
  38. local lib_args = require("/lib/args")
  39. local logs = require("/lib/logger")
  40.  
  41. -- here you can check repo structure if you want to repo yourself
  42. local DEFAULT_REPOSITORY = "https://github.com/zimavi/cc_pacman_api/raw/main/repo.json"
  43.  
  44. local repo_path = "/etc/capsule/repository.json"
  45. local packages_path = "/etc/capsule/package.list"
  46. local install_path = "/usr/bin/"
  47.  
  48. -- define version struct
  49. local version = {
  50.     major = 1,
  51.     minor = 0,
  52.     patch = 0,
  53. }
  54.  
  55. -- version to string function
  56. local tmp_toString = function (t)
  57.     return string.format("v%s.%s.%s", t.major, t.minor, t.patch)
  58. end
  59.  
  60. local function isRepoVerNewer(repo_ver, installed_ver)
  61.     if repo_ver.major > installed_ver.major then
  62.         return true
  63.     elseif repo_ver.minor > installed_ver.minor then
  64.         return true
  65.     elseif repo_ver.patch > installed_ver.patch then
  66.         return true
  67.     end
  68.  
  69.     return false
  70. end
  71.  
  72. local function showAvailableList()
  73.     if not fs.exists(repo_path) then
  74.         logs.critical("Cannot load repository. (Perhaps time to 'update'?)")
  75.         error("", 0)
  76.     end
  77.  
  78.     local handle = fs.open(repo_path, "r")
  79.     if not handle then
  80.         logs.critical("Cannot open repository file.")
  81.         error("", 0)
  82.     end
  83.  
  84.     local text = handle.readAll()
  85.  
  86.     local repo_table = textutils.unserializeJSON(text, {parse_empty_array = true})
  87.     handle.close()
  88.  
  89.     if not repo_table then
  90.         logs.critical("Cannot read JSON from repository.")
  91.         error("", 0)
  92.     end
  93.  
  94.     local sb = stringBuilder()
  95.  
  96.     for _, v in ipairs(repo_table) do
  97.         local sb2 = stringBuilder()
  98.         local version = v.version.major .. "." .. v.version.minor .. "." .. v.version.patch
  99.         sb:append("- Name: " .. v.name)
  100.         sb:append("  Installed Version: None")
  101.         sb:append("  Repository Version: " .. version)
  102.         sb:append("  Status: Available")
  103.         sb:append(sb2:toString("\n"))
  104.     end
  105.  
  106.     return sb:toString("\n")
  107. end
  108.  
  109. local function showInstalledList()
  110.     if not fs.exists(repo_path) then
  111.         logs.critical("Cannot load repository. (Perhaps time to 'update'?)")
  112.         error("", 0)
  113.     end
  114.  
  115.     local handle = fs.open(repo_path, "r")
  116.     if not handle then
  117.         logs.critical("Cannot open repository file.")
  118.         error("", 0)
  119.     end
  120.  
  121.     local text = handle.readAll()
  122.  
  123.     local repo_table = textutils.unserializeJSON(text, {parse_empty_array = true})
  124.     handle.close()
  125.  
  126.     if not repo_table then
  127.         logs.critical("Cannot read JSON from repository.")
  128.         error("", 0)
  129.     end
  130.  
  131.     handle = fs.open(packages_path, "r")
  132.     if not handle then
  133.         logs.critical("No packages installed.")
  134.         error("", 0)
  135.     end
  136.  
  137.     local packages = textutils.unserialize(handle.readAll())
  138.  
  139.     if not packages then
  140.         logs.critical("No packages installed.")
  141.         error("", 0)
  142.     end
  143.  
  144.     local sb = stringBuilder()
  145.  
  146.     for key, package in pairs(packages) do
  147.         for _, v in ipairs(repo_table) do
  148.             if key == v.name then
  149.                 local sb2 = stringBuilder()
  150.                 local repo_version_str = v.version.major .. "." .. v.version.minor .. "." .. v.version.patch
  151.                 local local_version_str = package.version.major .. "." .. package.version.minor .. "." .. package.version.patch
  152.                 sb:append("- Name: " .. v.name)
  153.                 sb:append("  Installed Version: " .. local_version_str)
  154.                 sb:append("  Repository Version: " .. repo_version_str)
  155.                 sb:append("  Status: " .. (isRepoVerNewer(v.version, package.version) and "Outdated" or "Up-to-date"))
  156.                 sb:append(sb2:toString("\n"))
  157.                 break
  158.             end
  159.         end
  160.     end
  161.  
  162.     return sb:toString("\n")
  163. end
  164.  
  165. local function showAllPackages()
  166.     if not fs.exists(repo_path) then
  167.         logs.critical("Cannot load repository. (Perhaps time to 'update'?)")
  168.         error("", 0)
  169.     end
  170.  
  171.     local handle = fs.open(repo_path, "r")
  172.     if not handle then
  173.         logs.critical("Cannot open repository file.")
  174.         error("", 0)
  175.     end
  176.  
  177.     local text = handle.readAll()
  178.  
  179.     local repo_table = textutils.unserializeJSON(text, {parse_empty_array = true})
  180.     handle.close()
  181.  
  182.     if not repo_table then
  183.         logs.critical("Cannot read JSON from repository.")
  184.         error("", 0)
  185.     end
  186.  
  187.     handle = fs.open(packages_path, "r")
  188.     if not handle then
  189.         return showAvailableList()
  190.     end
  191.     local packages = textutils.unserialize(handle.readAll())
  192.     handle.close()
  193.  
  194.     if not packages then            -- return just available if no installed found
  195.         return showAvailableList()
  196.     end
  197.  
  198.     local sb = stringBuilder()
  199.     local found
  200.  
  201.     for _, v in ipairs(repo_table) do
  202.         found = false
  203.         for key, package in pairs(packages) do
  204.             if key == v.name then
  205.                 found = true
  206.                 local sb2 = stringBuilder()
  207.                 local repo_version_str = v.version.major .. "." .. v.version.minor .. "." .. v.version.patch
  208.                 local local_version_str = package.version.major .. "." .. package.version.minor .. "." .. package.version.patch
  209.                 sb:append("- Name: " .. v.name)
  210.                 sb:append("  Installed Version: " .. local_version_str)
  211.                 sb:append("  Repository Version: " .. repo_version_str)
  212.                 sb:append("  Status: " .. (isRepoVerNewer(v.version, package.version) and "Outdated" or "Up-to-date"))
  213.                 sb:append(sb2:toString("\n"))
  214.                 break
  215.             end
  216.         end
  217.  
  218.         if not found then
  219.             local sb2 = stringBuilder()
  220.             local repo_version_str = v.version.major .. "." .. v.version.minor .. "." .. v.version.patch
  221.             sb:append("- Name: " .. v.name)
  222.             sb:append("  Installed Version: None")
  223.             sb:append("  Repository Version: " .. repo_version_str)
  224.             sb:append("  Status: Available")
  225.             sb:append(sb2:toString("\n"))
  226.         end
  227.     end
  228.  
  229.     return sb:toString("\n")
  230. end
  231.  
  232. local function getRepositoryList(params)
  233.     local installedOnly = params[1]
  234.     local availableOnly = params[2]
  235.     local all = params[3]
  236.  
  237.     if not installedOnly and not availableOnly and not all then
  238.         availableOnly = true
  239.     end
  240.  
  241.  
  242.     if all then
  243.         return showAllPackages()
  244.     elseif availableOnly then
  245.         return showAvailableList()
  246.     elseif installedOnly then
  247.         return showInstalledList()
  248.     end
  249. end
  250.  
  251. local function displayRepositoryList(repoList)
  252.     lib_print.scrollText(repoList, 15)
  253. end
  254.  
  255. local function displayHelpMessage()
  256.     local sb = stringBuilder()
  257.     sb:append("Capsule - CC:Tweaked package manager used by DiamondOS")
  258.     sb:append("Usage: capsule [flags] [options]")
  259.     sb:append("Flags:")
  260.     sb:append("  -h, --help         Display current message")
  261.     sb:append("  -v, --verbose      Show debug messages")
  262.     sb:append("  version            Display current version")
  263.     sb:append("  update             Fetches remote repository")
  264.     sb:append("  list               List through packages available")
  265.     sb:append("  --installed        List only installed packages")
  266.     sb:append("  --available        List only packages from repo (default)")
  267.     sb:append("  --all              List both installed and available")
  268.     sb:append("Options:")
  269.     sb:append("  -r, --repository   Specify repository (used with update)")
  270.     sb:append("  install            Install package from current repository")
  271.     sb:append("  checksum           Write file's checksum to checksum.txt")
  272.     sb:append("  validate           Validates installed package")
  273.     sb:append("  remove             Removes package from system")
  274.     sb:append("  upgrade            Upgrades all out-of-date packages")
  275.  
  276.     local str = sb:toString("\n")
  277.  
  278.     lib_print.scrollText(str, 11)
  279. end
  280.  
  281. local function updateRepository(repository)
  282.     -- check if no provided repository
  283.     if repository == nil then
  284.         logs.debug("No repository passed, using default.")
  285.         repository = DEFAULT_REPOSITORY
  286.     end
  287.  
  288.     -- send GET http request
  289.     logs.info("Fetching with " .. repository)
  290.     local request = http.get(repository)
  291.    
  292.     -- if not valid request arrived
  293.     if not request then
  294.         logs.critical("Cannot get response from repository server.")
  295.         return
  296.     end
  297.  
  298.     -- save repository to memory
  299.     local handle = fs.open(repo_path, "w")
  300.  
  301.     if not handle then
  302.         logs.error("Cannot save repository.")
  303.     end
  304.  
  305.     handle.write(request.readAll())
  306.  
  307.     logs.info("Fetched repository successfully.")
  308. end
  309.  
  310. local function installPackage(package_name)
  311.  
  312.     local handle = fs.open(repo_path, "r")
  313.     if not handle then
  314.         logs.critical("Cannot open repository file.")
  315.         error("", 0)
  316.     end
  317.    
  318.     local repo = textutils.unserializeJSON(handle.readAll())
  319.     if not repo then
  320.         logs.critical("Cannot read repository file.")
  321.         error("", 0)
  322.     end
  323.  
  324.     logs.info("Searching for " .. package_name:lower() .. "...")
  325.  
  326.     local package = {}
  327.  
  328.     for i, v in ipairs(repo) do
  329.         if v.name:lower() == package_name:lower() then
  330.             package = v
  331.             logs.info("Found package, downloading...")
  332.             break
  333.         end
  334.     end
  335.  
  336.     local attempts = 1
  337.  
  338.     ::redownload::
  339.  
  340.     if attempts > 4 then
  341.         logs.critical("Unable to download valid file. Try again later.")
  342.         error("", 0)
  343.     end
  344.  
  345.     local request, error = http.get(package.dwn_lnk)
  346.     if not request then
  347.         logs.critical("Cannot download package.")
  348.         logs.critical(error)
  349.         error("", 0)
  350.     end
  351.  
  352.     logs.info("Downloaded data, saving...")
  353.  
  354.     local data = request.readAll()
  355.  
  356.     local name = package_name:sub(-4) == ".lua" and package_name or package_name .. ".lua"
  357.  
  358.     logs.info("Validation file integrity...")
  359.  
  360.     local checksum = lib_sha.sha256(data)
  361.  
  362.     if checksum ~= package.checksum then
  363.         logs.error("File checksum validation failed: Missmatch")
  364.         logs.info("Going to attempt " .. attempts .. "/3")
  365.         attempts = attempts + 1
  366.         goto redownload
  367.     end
  368.  
  369.     logs.info("File validated, saving...")
  370.  
  371.     if lib_config.createIfNotExists(packages_path) then
  372.         local list = {
  373.             [package.name] = {
  374.                 version = package.version,
  375.                 path = fs.combine(install_path, name),
  376.                 author = package.author,
  377.                 desc = package.desc,
  378.                 dwn_lnk = package.dwn_lnk,
  379.                 checksum = package.checksum
  380.             }
  381.         }
  382.  
  383.         lib_config.save(list, packages_path)
  384.     else
  385.         local list = lib_config.load(packages_path)
  386.         list[package.name] = {
  387.             version = package.version,
  388.             path = fs.combine(install_path, name),
  389.             author = package.author,
  390.             desc = package.desc,
  391.             dwn_lnk = package.dwn_lnk,
  392.             checksum = package.checksum
  393.         }
  394.         lib_config.save(list, packages_path)
  395.     end
  396.  
  397.     logs.debug("package.list updated")
  398.  
  399.     local handle = fs.open(fs.combine(install_path, name), "w")
  400.     handle.write(data)
  401.     handle.close()
  402.  
  403.     logs.info("Package " .. package_name .. " installed successfully")
  404.  
  405. end
  406.  
  407. local function calculateChecksum(path)
  408.     if not fs.exists(path) then
  409.         logs.critical("File not found.")
  410.         error("", 0)
  411.     end
  412.  
  413.     logs.debug("Opening " .. path .. "...")
  414.  
  415.     local handle = fs.open(path, "r")
  416.     if not handle then
  417.         logs.critical("Cannot open the file.")
  418.         error("", 0)
  419.     end
  420.  
  421.     local data = handle.readAll()
  422.     handle.close()
  423.  
  424.     logs.debug("Computing checksum...")
  425.  
  426.     local checksum = lib_sha.sha256(data)
  427.  
  428.     logs.info("Done.")
  429.     logs.info("Checksum: " .. checksum)
  430.  
  431.     logs.debug("Saving...")
  432.  
  433.     handle = fs.open("checksum.txt", "w")
  434.     handle.write(checksum)
  435.     handle.close()
  436.    
  437. end
  438.  
  439. local function redownloadFile(package_url, package_checksum)
  440.  
  441.     local attempts = 1
  442.  
  443.     ::redownload1::
  444.  
  445.     if attempts > 4 then
  446.         logs.critical("Unable to download valid file. Try again later.")
  447.         error("", 0)
  448.     end
  449.  
  450.     local request, error = http.get(package_url)
  451.     if not request then
  452.         logs.critical("Cannot download package.")
  453.         logs.critical(error)
  454.         error("", 0)
  455.     end
  456.  
  457.     logs.info("Downloaded data, saving...")
  458.  
  459.     local data = request.readAll()
  460.  
  461.     logs.info("Validation file integrity...")
  462.  
  463.     local checksum = lib_sha.sha256(data)
  464.  
  465.     if checksum ~= package_checksum then
  466.         logs.error("File checksum validation failed: Missmatch")
  467.         logs.info("Going to attempt " .. attempts .. "/3")
  468.         attempts = attempts + 1
  469.         goto redownload1
  470.     end
  471.  
  472.     logs.info("File validated, saving...")
  473.  
  474.     return data
  475. end
  476.  
  477. local function validatePackage(package_name)
  478.     if not fs.exists(packages_path) then
  479.         logs.critical("No packages installed.")
  480.         error("", 0)
  481.     end
  482.  
  483.     local package_list = lib_config.load(packages_path)
  484.  
  485.     if not package_list then
  486.         logs.critical("No packages installed.")
  487.         error("", 0)
  488.     end
  489.  
  490.     local package = package_list[package_name]
  491.  
  492.     if not package then
  493.         logs.critical("Package is not installed.")
  494.         error("", 0)
  495.     end
  496.  
  497.     local path = package.path
  498.     local handle = fs.open(path, "r")
  499.     if not handle then
  500.         logs.error("Cannot open file, redownloading it...")
  501.         local data = redownloadFile(package.lnk, package.checksum)
  502.         handle = fs.open(path, "w")
  503.         handle.write(data)
  504.         handle.close()
  505.         logs.info("Done.")
  506.         return
  507.     end
  508.  
  509.     local data = handle.readAll()
  510.     handle.close()
  511.  
  512.     local checksum = lib_sha.sha256(data)
  513.  
  514.     if checksum ~= package.checksum then
  515.         logs.error("File checksum validation failed: Missmatch")
  516.         logs.info("File will be redownload")
  517.         data = redownloadFile(package.lnk, package.checksum)
  518.         handle = fs.open(package.path, "w")
  519.         handle.write(data)
  520.         handle.close()
  521.     else
  522.         logs.info("File checksum validation passed.")
  523.         logs.info("Package is validated.")
  524.     end
  525. end
  526.  
  527. local function removePackage(package_name)
  528.     if not fs.exists(packages_path) then
  529.         logs.critical("No packages installed.")
  530.         error("", 0)
  531.     end
  532.  
  533.     local package_list = lib_config.load(packages_path)
  534.  
  535.     if not package_list then
  536.         logs.critical("No packages installed.")
  537.         error("", 0)
  538.     end
  539.  
  540.     local package = package_list[package_name]
  541.  
  542.     if not package then
  543.         logs.critical("Package is not installed.")
  544.         error("", 0)
  545.     end
  546.  
  547.     logs.info("Deleting file...")
  548.     fs.delete(package.path)
  549.     logs.info("Done. Cleaning up...")
  550.     package_list[package_name] = nil
  551.     lib_config.save(package_list, packages_path)
  552.     logs.info("Done.")
  553. end
  554.  
  555. local function upgradePackages()
  556.     if not fs.exists(packages_path) then
  557.         logs.critical("No packages installed.")
  558.         error("", 0)
  559.     end
  560.  
  561.     local package_list = lib_config.load(packages_path)
  562.  
  563.     if not package_list then
  564.         logs.critical("No packages installed.")
  565.         error("", 0)
  566.     end
  567.  
  568.     if not fs.exists(repo_path) then
  569.         logs.critical("No repository found. (Perhaps 'update'?)")
  570.         error("", 0)
  571.     end
  572.  
  573.     local handle = fs.open(repo_path, "r")
  574.     if not handle then
  575.         logs.critical("Unable to read repository.")
  576.         error("", 0)
  577.     end
  578.  
  579.     local repo = textutils.unserializeJSON(handle.readAll())
  580.     if not repo then
  581.         logs.critical("Unable to read repository")
  582.         error("", 0)
  583.     end
  584.  
  585.     logs.info("Going through installed packages...")
  586.  
  587.     local looked_for = 0
  588.     local upgraded = 0
  589.  
  590.     for key, v in pairs(package_list) do
  591.         logs.info("Looking for " .. key)
  592.         looked_for = looked_for + 1
  593.         for _, p in ipairs(repo) do
  594.             if p.name == key then
  595.                 logs.info("Found corresponding package")
  596.                 if isRepoVerNewer(p.version, v.version) then
  597.                     logs.info("Current version is outdated")
  598.                     logs.info("Newer version will be downloaded...")
  599.  
  600.                     local attempts = 1
  601.  
  602.                     ::redownload1::
  603.  
  604.                     if attempts > 4 then
  605.                         logs.critical("Unable to download valid file. Try again later.")
  606.                         error("", 0)
  607.                     end
  608.  
  609.                     local request, error = http.get(p.dwn_lnk)
  610.                     if not request then
  611.                         logs.critical("Cannot download package.")
  612.                         logs.critical(error)
  613.                         error("", 0)
  614.                     end
  615.  
  616.                     logs.info("Downloaded data, saving...")
  617.  
  618.                     local data = request.readAll()
  619.  
  620.                     logs.info("Validation file integrity...")
  621.  
  622.                     local checksum = lib_sha.sha256(data)
  623.  
  624.                     if checksum ~= p.checksum then
  625.                         logs.error("File checksum validation failed: Missmatch")
  626.                         logs.info("Going to attempt " .. attempts .. "/3")
  627.                         attempts = attempts + 1
  628.                         goto redownload1
  629.                     end
  630.  
  631.                     logs.info("File validated, saving...")
  632.  
  633.                     local handle = fs.open(v.path, "w")
  634.                     handle.write(data)
  635.                     handle.close()
  636.  
  637.                     package_list[key] = {
  638.                         version = p.version,
  639.                         path = v.path,
  640.                         author = p.author,
  641.                         desc = p.desc,
  642.                         dwn_lnk = p.dwn_lnk,
  643.                         checksum = p.checksum
  644.                     }
  645.  
  646.                     lib_config.save(package_list, packages_path)
  647.                    
  648.                     logs.info("Package " .. key .. " has been upgraded.")
  649.  
  650.                     upgraded = upgraded + 1
  651.                 else
  652.                     logs.info("Package " .. key .. " is up-to-date.")
  653.                 end
  654.             end
  655.         end
  656.     end
  657.     logs.info("Done. (" .. upgraded .. "/" .. looked_for .. " upgraded)")
  658. end
  659.  
  660. -- Init code
  661. setmetatable(version, {__tostring = tmp_toString})
  662.  
  663. local args_parser = lib_args:new()
  664.  
  665. args_parser:addFlag("version")
  666. args_parser:addFlag("--help", "-h")
  667. args_parser:addFlag("--verbose", "-v")
  668. args_parser:addFlag("update")
  669. args_parser:addFlag("list")
  670. args_parser:addFlag("--installed")
  671. args_parser:addFlag("--available")
  672. args_parser:addFlag("--all")
  673. args_parser:addFlag("upgrade")
  674.  
  675. args_parser:addOption("--repository", "-r")
  676. args_parser:addOption("install")
  677. args_parser:addOption("checksum")
  678. args_parser:addOption("validate")
  679. args_parser:addOption("remove")
  680.  
  681. logs.configure({
  682.     logToFile = false,              -- whether to write logs to file
  683.     stampScreen = false,            -- whether to show timestamps on screen
  684.     filePath = "/tmp/capsule.log",  -- path where to store logs if first parameter is true
  685.     logLevel = logs.levels.INFO     -- level threshold (overriden by verbose flag)
  686. })
  687.  
  688. -- End of init code
  689.  
  690. args_parser:parse({ ... })
  691.  
  692. local flags = args_parser:getFlags()
  693. local options = args_parser:getOptions()
  694.  
  695. if flags["version"] then
  696.     print("Capsule "..tostring(version))
  697.     return
  698. end
  699.  
  700. if flags["--help"] then
  701.     displayHelpMessage()
  702.     return
  703. end
  704.  
  705. if flags["--verbose"] then
  706.     logs.configure({logLevel = logs.levels.DEBUG})
  707.     logs.debug("Starting in verbose.")
  708. end
  709.  
  710. if flags["list"] then
  711.     logs.debug("Listing repository")
  712.     displayRepositoryList(getRepositoryList({flags["--installed"], flags["--available"], flags["--all"]}))
  713.     return
  714. end
  715.  
  716. if flags["update"] then
  717.     logs.debug("update command executed.")
  718.     updateRepository(options["--repository"])
  719.     return
  720. end
  721.  
  722. if flags["upgrade"] then
  723.     logs.info("Upgrading packages, this may take a while")
  724.     upgradePackages()
  725.     return
  726. end
  727.  
  728. if options["checksum"] then
  729.     calculateChecksum(options["checksum"])
  730.     return
  731. end
  732.  
  733. if options["validate"] then
  734.     validatePackage(options["validate"]:lower())
  735.     return
  736. end
  737.  
  738. if options["install"] then
  739.     logs.info("Installing " .. options["install"])
  740.     installPackage(options["install"]:lower())
  741.     return
  742. end
  743.  
  744. if options["remove"] then
  745.     logs.info("Removing " .. options["remove"]:lower())
  746.     removePackage(options["remove"]:lower())
  747.     return
  748. end
  749.  
  750. displayHelpMessage()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement