Ewgeniy

radar

Jun 2nd, 2026 (edited)
46
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 20.50 KB | None | 0 0
  1. -- ============================================================
  2. -- RADAR SURVEILLANCE SYSTEM v2.4
  3. -- Computronics Radar Edition
  4. -- Retro Government Terminal Interface (1960s-70s Style)
  5. -- ============================================================
  6.  
  7. local component = require("component")
  8. local event     = require("event")
  9. local term      = require("term")
  10. local fs        = require("filesystem")
  11. local serial    = require("serialization")
  12. local computer  = require("computer")
  13. local gpu       = component.gpu
  14.  
  15. if not component.isAvailable("radar") then
  16.   io.stderr:write("SYSTEM ERROR: RADAR COMPONENT NOT FOUND\n")
  17.   os.exit()
  18. end
  19.  
  20. local radar = component.radar
  21.  
  22. local hasInternet = component.isAvailable("internet")
  23. local internet    = hasInternet and require("internet") or nil
  24. local hasDataCard = component.isAvailable("data")
  25. local dataCard    = hasDataCard and component.data or nil
  26.  
  27. -- ============================================================
  28. -- ЦВЕТА
  29. -- ============================================================
  30. local C = {
  31.   bg         = 0x000000,
  32.   border     = 0x00AA00,
  33.   header     = 0x00FF00,
  34.   text       = 0x00CC00,
  35.   dim        = 0x005500,
  36.   alert      = 0xFF3300,
  37.   warning    = 0xFFAA00,
  38.   ok         = 0x00FF00,
  39.   highlight  = 0x33FF33,
  40.   classified = 0xFF0000,
  41. }
  42.  
  43. local oldBg = gpu.getBackground()
  44. local oldFg = gpu.getForeground()
  45. local W, H  = gpu.getResolution()
  46.  
  47. -- ============================================================
  48. -- КОНФИГ
  49. -- ============================================================
  50. local CONFIG_FILE = "/home/radar_config.dat"
  51. local LOG_FILE    = "/home/radar_log.txt"
  52.  
  53. local config = {
  54.   scanRange    = 64,
  55.   scanInterval = 3,
  56.   timeZone     = 3,
  57.   mode         = "local",
  58.   botToken     = "",
  59.   chatID       = "",
  60.   whiteList    = {"cloud", "LeShyj", "entity.Cat.name", "reload", "karnel"},
  61. }
  62.  
  63. -- ============================================================
  64. -- ШИФРОВАНИЕ
  65. -- ============================================================
  66. local function xorCrypt(data, key)
  67.   local result, keyLen = {}, #key
  68.   for i = 1, #data do
  69.     local ki = ((i-1) % keyLen) + 1
  70.     local xb = (string.byte(data,i) + string.byte(key,ki)) % 256
  71.     result[i] = string.char(xb)
  72.   end
  73.   return table.concat(result)
  74. end
  75.  
  76. local function toHex(s)
  77.   return (s:gsub(".", function(c) return string.format("%02x", c:byte()) end))
  78. end
  79.  
  80. local function fromHex(h)
  81.   return (h:gsub("..", function(cc) return string.char(tonumber(cc,16)) end))
  82. end
  83.  
  84. local function encryptData(plain, pass)
  85.   if hasDataCard then
  86.     local key = dataCard.sha256(pass):sub(1,16)
  87.     local iv  = dataCard.random(16)
  88.     return "AES:" .. toHex(iv) .. ":" .. toHex(dataCard.encrypt(plain, key, iv))
  89.   end
  90.   return "XOR:" .. toHex(xorCrypt(plain, pass))
  91. end
  92.  
  93. local function decryptData(cipher, pass)
  94.   if cipher:sub(1,4) == "AES:" then
  95.     if not hasDataCard then return nil, "No Data Card" end
  96.     local p = {}; for s in cipher:gmatch("[^:]+") do p[#p+1]=s end
  97.     if #p < 3 then return nil, "Bad AES format" end
  98.     local ok, dec = pcall(dataCard.decrypt, fromHex(p[3]), dataCard.sha256(pass):sub(1,16), fromHex(p[2]))
  99.     return ok and dec or nil, "Decryption failed"
  100.   elseif cipher:sub(1,4) == "XOR:" then
  101.     return xorCrypt(fromHex(cipher:sub(5)), pass)
  102.   end
  103.   return nil, "Unknown format"
  104. end
  105.  
  106. local function saveConfig(pass)
  107.   local f, err = io.open(CONFIG_FILE, "w")
  108.   if not f then return false, err end
  109.   f:write(encryptData(serial.serialize(config), pass)); f:close()
  110.   return true
  111. end
  112.  
  113. local function loadConfig(pass)
  114.   local f = io.open(CONFIG_FILE, "r")
  115.   if not f then return false, "Not found" end
  116.   local data, err = decryptData(f:read("*a"), pass); f:close()
  117.   if not data then return false, err end
  118.   local ok, t = pcall(serial.unserialize, data)
  119.   if ok and type(t)=="table" then config=t; return true end
  120.   return false, "Bad data"
  121. end
  122.  
  123. -- ============================================================
  124. -- УТИЛИТЫ
  125. -- ============================================================
  126. local function sc(fg, bg)
  127.   if fg then gpu.setForeground(fg) end
  128.   if bg then gpu.setBackground(bg) end
  129. end
  130.  
  131. local function centerText(y, txt, col)
  132.   sc(col or C.text)
  133.   local x = math.max(1, math.floor((W - #txt)/2) + 1)
  134.   gpu.set(x, y, txt)
  135. end
  136.  
  137. local function drawBox(x, y, w, h, title)
  138.   sc(C.border)
  139.   gpu.set(x, y, "+" .. string.rep("-", w-2) .. "+")
  140.   gpu.set(x, y+h-1, "+" .. string.rep("-", w-2) .. "+")
  141.   for i = 1, h-2 do
  142.     gpu.set(x, y+i, "|"); gpu.set(x+w-1, y+i, "|")
  143.     gpu.fill(x+1, y+i, w-2, 1, " ")
  144.   end
  145.   if title then sc(C.header); gpu.set(x+2, y, "[ "..title.." ]") end
  146. end
  147.  
  148. local function getTime()
  149.   -- fs.lastModified возвращает реальное время хоста в миллисекундах (Unix ms)
  150.   -- os.time() возвращает игровое время — не подходит
  151.   local name = os.tmpname()
  152.   local f = io.open(name, "w")
  153.   if f then
  154.     f:close()
  155.     local ms = fs.lastModified(name)
  156.     fs.remove(name)
  157.     if ms and ms > 0 then
  158.       -- ms → секунды, прибавляем смещение GMT+3
  159.       local t = math.floor(ms / 1000) + config.timeZone * 3600
  160.       return os.date("%d.%m.%Y %H:%M:%S", t)
  161.     end
  162.   end
  163.   -- Fallback: если tmpname не сработал
  164.   return os.date("%d.%m.%Y %H:%M:%S", os.time())
  165. end
  166.  
  167. local function urlencode(s)
  168.   s = s:gsub("\n","\r\n")
  169.   s = s:gsub("([^%w ])", function(c) return string.format("%%%02X", c:byte()) end)
  170.   return s:gsub(" ", "+")
  171. end
  172.  
  173. local function saveToFile(txt)
  174.   local f = io.open(LOG_FILE, "a")
  175.   if f then f:write(txt.."\n"); f:close() end
  176. end
  177.  
  178. local function isWhitelisted(name)
  179.   for _, v in ipairs(config.whiteList) do if v==name then return true end end
  180.   return false
  181. end
  182.  
  183. -- ============================================================
  184. -- TELEGRAM
  185. -- ============================================================
  186. local tgQueue = {}
  187.  
  188. local function queueTelegram(txt) tgQueue[#tgQueue+1] = txt end
  189.  
  190. local function processTelegramQueue()
  191.   if not internet or #tgQueue == 0 then return end
  192.   local txt = table.remove(tgQueue, 1)
  193.   local url = "https://api.telegram.org/bot"..config.botToken..
  194.               "/sendMessage?chat_id="..config.chatID..
  195.               "&text="..urlencode(txt)
  196.   local ok, h = pcall(internet.request, url)
  197.   if ok and h then pcall(function() h() end) end
  198. end
  199.  
  200. local function sendTelegramBlocking(txt)
  201.   if not internet then return false end
  202.   local url = "https://api.telegram.org/bot"..config.botToken..
  203.               "/sendMessage?chat_id="..config.chatID..
  204.               "&text="..urlencode(txt)
  205.   local ok, h = pcall(internet.request, url)
  206.   if not ok then return false end
  207.   local res = ""; for chunk in h do res=res..chunk end
  208.   return true
  209. end
  210.  
  211. -- ============================================================
  212. -- РАЗБОР ОТВЕТА РАДАРА
  213. -- Формат: {{name="X", distance=Y}, {name="Z", distance=W}, ...}
  214. -- Возвращает: {["X"] = {name, distance, whitelisted}, ...}
  215. -- ============================================================
  216. local EXIT_DISTANCE = 30  -- дальше этой дистанции = считается вышедшим
  217.  
  218. local function parseRadarResult(raw)
  219.   local out = {}
  220.   if type(raw) ~= "table" then return out end
  221.  
  222.   for i = 1, #raw do
  223.     local entry = raw[i]
  224.     if type(entry) == "table" then
  225.       local name = entry.name
  226.       local dist = entry.distance or 0
  227.       -- Игрок виден радаром, но дальше порога выхода — не включаем в активные
  228.       if type(name) == "string" and name ~= "" and dist < EXIT_DISTANCE then
  229.         out[name] = {
  230.           name        = name,
  231.           distance    = dist,
  232.           whitelisted = isWhitelisted(name),
  233.         }
  234.       end
  235.     end
  236.   end
  237.  
  238.   return out
  239. end
  240.  
  241. -- ============================================================
  242. -- СОСТОЯНИЕ
  243. -- ============================================================
  244. -- currentTargets: {[name] = {name, distance, whitelisted}}
  245. -- Обновляется каждый скан целиком через = newScan
  246. local currentTargets = {}
  247. local eventLog       = {}   -- {time, kind="ENTER"/"EXIT", name, distance}
  248. local scanCount      = 0
  249. local lastScanError  = nil
  250.  
  251. -- ============================================================
  252. -- ЦИКЛ СКАНИРОВАНИЯ
  253. -- ============================================================
  254. local function runScanCycle()
  255.   scanCount = scanCount + 1
  256.   lastScanError = nil
  257.  
  258.   -- Небольшая пауза — некоторые версии Computronics радара кешируют результат
  259.   -- и возвращают старые данные если запрашивать слишком часто
  260.   os.sleep(0.05)
  261.  
  262.   local ok, raw = pcall(radar.getPlayers, config.scanRange)
  263.   if not ok then
  264.     lastScanError = tostring(raw):sub(1, 60)
  265.     return
  266.   end
  267.  
  268.   if type(raw) ~= "table" then
  269.     lastScanError = "bad type: " .. type(raw)
  270.     return
  271.   end
  272.  
  273.   local newScan = parseRadarResult(raw)
  274.   local timeStr = getTime()
  275.  
  276.   -- ENTER: есть в newScan, не было в currentTargets
  277.   for name, data in pairs(newScan) do
  278.     if not currentTargets[name] then
  279.       eventLog[#eventLog+1] = {time=timeStr, kind="ENTER", name=name, distance=data.distance}
  280.       if not data.whitelisted then
  281.         local msg = string.format("[ENTER] %s | %.1fm | %s", name, data.distance, timeStr)
  282.         if config.mode == "telegram" then queueTelegram(msg) end
  283.         saveToFile(msg)
  284.       end
  285.     end
  286.   end
  287.  
  288.   -- EXIT: было в currentTargets, нет в newScan
  289.   for name, data in pairs(currentTargets) do
  290.     if not newScan[name] then
  291.       eventLog[#eventLog+1] = {time=timeStr, kind="EXIT", name=name, distance=data.distance}
  292.       if not data.whitelisted then
  293.         local msg = string.format("[EXIT] %s | last %.1fm | %s", name, data.distance or 0, timeStr)
  294.         if config.mode == "telegram" then queueTelegram(msg) end
  295.         saveToFile(msg)
  296.       end
  297.     end
  298.   end
  299.  
  300.   -- Заменяем состояние целиком → дистанции всегда актуальны
  301.   currentTargets = newScan
  302. end
  303.  
  304. -- ============================================================
  305. -- ОТРИСОВКА ГЛАВНОГО ЭКРАНА
  306. -- ============================================================
  307. local function drawMainScreen()
  308.   gpu.setBackground(C.bg)
  309.   gpu.fill(1, 1, W, H, " ")
  310.  
  311.   sc(C.classified)
  312.   gpu.set(1, 1, string.rep("=", W))
  313.   centerText(1, " TOP SECRET // SURVEILLANCE ACTIVE ")
  314.   gpu.set(1, 2, string.rep("=", W))
  315.  
  316.   sc(C.header)
  317.   centerText(3, "+--------------------------------------------+")
  318.   centerText(4, "|   R A D A R   S U R V E I L L A N C E     |")
  319.   centerText(5, "|          MONITORING  ACTIVE                |")
  320.   centerText(6, "+--------------------------------------------+")
  321.  
  322.   -- Статусбар
  323.   sc(C.text);  gpu.set(2,  8, "STATUS: ")
  324.   sc(C.ok);    gpu.set(10, 8, "ONLINE")
  325.   sc(C.text);  gpu.set(20, 8, "| MODE: ")
  326.   sc(C.highlight)
  327.   gpu.set(28, 8, config.mode == "telegram" and "TG UPLINK" or "LOCAL    ")
  328.   sc(C.text);  gpu.set(40, 8, "| RNG: ")
  329.   sc(C.highlight); gpu.set(47, 8, config.scanRange.."m")
  330.   sc(C.text);  gpu.set(54, 8, "| SCAN#"..scanCount)
  331.  
  332.   sc(C.dim); gpu.set(1, 9, string.rep("-", W))
  333.  
  334.   -- Список живых целей (сортировка по дистанции)
  335.   local liveList = {}
  336.   for _, e in pairs(currentTargets) do liveList[#liveList+1] = e end
  337.   table.sort(liveList, function(a,b) return (a.distance or 0) < (b.distance or 0) end)
  338.  
  339.   sc(C.warning); gpu.set(2, 10, ">> LIVE RADAR ["..#liveList.." contacts]")
  340.   sc(C.dim);     gpu.set(W-22, 10, "TIME: "..getTime())
  341.  
  342.   if lastScanError then
  343.     sc(C.alert)
  344.     gpu.set(2, 11, "ERR: "..lastScanError:sub(1, W-7))
  345.   end
  346.  
  347.   sc(C.dim); gpu.set(1, 12, string.rep("-", W))
  348.  
  349.   local liveY   = 13
  350.   local maxLive = math.max(3, math.floor((H-18)/2))
  351.  
  352.   if #liveList == 0 then
  353.     sc(C.dim); gpu.set(4, liveY, "-- NO CONTACTS IN RANGE --")
  354.   else
  355.     for i = 1, math.min(#liveList, maxLive) do
  356.       local e = liveList[i]
  357.       local y = liveY + i - 1
  358.       -- Сначала очищаем всю строку чтобы старые символы не оставались
  359.       gpu.setBackground(C.bg); gpu.fill(2, y, W-2, 1, " ")
  360.       if e.whitelisted then
  361.         sc(C.ok);   gpu.set(2, y, "[OK] ")
  362.         sc(C.text)
  363.       else
  364.         sc(C.alert); gpu.set(2, y, "[!!] ")
  365.         sc(C.alert)
  366.       end
  367.       local info = string.format("%s | %.2fm", e.name, e.distance or 0)
  368.       if #info > W-8 then info = info:sub(1, W-8) end
  369.       gpu.set(7, y, info)
  370.     end
  371.   end
  372.  
  373.   -- Лог событий
  374.   local usedLive = math.max(1, math.min(#liveList, maxLive))
  375.   local logY     = liveY + usedLive + 1
  376.  
  377.   sc(C.dim);    gpu.set(1, logY, string.rep("-", W))
  378.   sc(C.header); gpu.set(2, logY+1, "EVENT LOG ["..#eventLog.."] (ENTER / EXIT):")
  379.  
  380.   local maxLines = H - logY - 4
  381.   local startIdx = math.max(1, #eventLog - maxLines + 1)
  382.   for i = startIdx, #eventLog do
  383.     local ent = eventLog[i]
  384.     local y   = logY + 2 + (i - startIdx)
  385.     if y >= H-2 then break end
  386.     if ent.kind == "ENTER" then
  387.       sc(C.alert);   gpu.set(2, y, "[IN ] ")
  388.     else
  389.       sc(C.warning); gpu.set(2, y, "[OUT] ")
  390.     end
  391.     sc(C.dim)
  392.     local distStr = ent.distance and string.format("%.0fm", ent.distance) or "?"
  393.     gpu.set(8, y, ent.time.." "..ent.name.." "..distStr)
  394.   end
  395.  
  396.   -- Подвал
  397.   sc(C.dim); gpu.set(1, H-2, string.rep("-", W))
  398.   sc(C.text)
  399.   gpu.set(2,  H-1, "CYCLE:"..config.scanInterval.."s")
  400.   gpu.set(14, H-1, "| EVENTS:"..#eventLog)
  401.   gpu.set(30, H-1, "| WL:"..#config.whiteList)
  402.   gpu.set(42, H-1, "| CRYPT:"..(hasDataCard and "AES" or "XOR"))
  403.   if #tgQueue > 0 then sc(C.warning); gpu.set(56, H-1, "| TQ:"..#tgQueue) end
  404.  
  405.   sc(C.dim);     gpu.set(1, H, string.rep("=", W))
  406.   sc(C.warning); centerText(H, " [Q] TERMINATE SESSION ")
  407. end
  408.  
  409. -- ============================================================
  410. -- ЭКРАНЫ ИНИЦИАЛИЗАЦИИ
  411. -- ============================================================
  412. local function clearScreen()
  413.   gpu.setBackground(C.bg); gpu.fill(1,1,W,H," ")
  414. end
  415.  
  416. local function inputAt(x, y, masked)
  417.   term.setCursor(x, y); sc(C.highlight)
  418.   if not masked then return io.read() end
  419.   local buf = {}
  420.   while true do
  421.     local _, _, char, code = event.pull("key_down")
  422.     if code == 28 then break
  423.     elseif code == 14 and #buf > 0 then
  424.       table.remove(buf)
  425.       local cx = term.getCursor()
  426.       gpu.set(cx-1, y, " "); term.setCursor(cx-1, y)
  427.     elseif char and char >= 32 and char < 127 then
  428.       buf[#buf+1] = string.char(char)
  429.       gpu.set(x + #buf - 1, y, "*")
  430.       term.setCursor(x + #buf, y)
  431.     end
  432.   end
  433.   return table.concat(buf)
  434. end
  435.  
  436. local function splash()
  437.   clearScreen(); drawBox(1,1,W,H)
  438.   centerText(2, "* * *  T O P   S E C R E T  * * *", C.classified)
  439.   centerText(3, "AUTHORIZED PERSONNEL ONLY", C.dim)
  440.   local art = {
  441.     "          .---.          ",
  442.     "        /       \\        ",
  443.     "       |  (( ))  |       ",
  444.     "        \\       /        ",
  445.     "     .---`-----'---.     ",
  446.     "    /    R A D A R    \\  ",
  447.     "   '----._________.----' ",
  448.     "         |       |       ",
  449.     "         |  |||  |       ",
  450.     "        _|___|___|_      ",
  451.   }
  452.   for i, ln in ipairs(art) do centerText(4+i, ln, C.header) end
  453.   local iy = 16
  454.   centerText(iy,   "=======================================", C.text)
  455.   centerText(iy+1, "  RADAR SURVEILLANCE TERMINAL  v2.4  ", C.text)
  456.   centerText(iy+2, "  COMPUTRONICS TRACKING DIVISION     ", C.text)
  457.   centerText(iy+3, "=======================================", C.text)
  458.   centerText(iy+5, "Press any key to initialize system...", C.dim)
  459. end
  460.  
  461. local function initialize()
  462.   clearScreen(); splash(); event.pull("key_down")
  463.  
  464.   local password
  465.  
  466.   if fs.exists(CONFIG_FILE) then
  467.     clearScreen(); drawBox(1,1,W,H)
  468.     centerText(2, "* * *  S E C U R E   A C C E S S  * * *", C.classified)
  469.     sc(C.text); gpu.set(4,10,"Encrypted config: "..CONFIG_FILE)
  470.     gpu.set(4,12,"ACCESS CODE > ")
  471.     password = inputAt(18, 12, true)
  472.     local ok, err = loadConfig(password)
  473.     if ok then
  474.       sc(C.ok); centerText(14,"CONFIG DECRYPTED  [OK]")
  475.       centerText(16,"Mode: "..config.mode:upper().." | Range: "..config.scanRange)
  476.       os.sleep(2); return true, password
  477.     end
  478.     sc(C.alert); centerText(14,"FAILED: "..(err or "?"))
  479.     centerText(16,"Starting fresh setup..."); os.sleep(2)
  480.   end
  481.  
  482.   -- Новый пароль
  483.   clearScreen(); drawBox(1,1,W,H)
  484.   centerText(2, "* * *  F I R S T   R U N  * * *", C.classified)
  485.   sc(C.text)
  486.   gpu.set(4,10,"Set a password to encrypt your config.")
  487.   gpu.set(4,13,"NEW CODE    > "); password = inputAt(18,13,true)
  488.   gpu.set(4,15,"CONFIRM     > ")
  489.   if password ~= inputAt(18,15,true) then
  490.     sc(C.alert); centerText(17,"MISMATCH - RETRY"); os.sleep(2)
  491.     return initialize()
  492.   end
  493.  
  494.   -- Выбор режима
  495.   while true do
  496.     clearScreen(); drawBox(1,1,W,H)
  497.     centerText(4, "+==========================================+", C.header)
  498.     centerText(5, "|    SELECT OUTPUT MODE                    |", C.header)
  499.     centerText(6, "+==========================================+", C.header)
  500.     centerText(9,  "[1]  LOCAL STORAGE ("..LOG_FILE..")", C.highlight)
  501.     centerText(11, "[2]  TELEGRAM UPLINK", C.highlight)
  502.     if not hasInternet then centerText(12,"     [!] NO INTERNET CARD", C.alert) end
  503.     centerText(14,"CHOICE [1/2]: _", C.text)
  504.     local _, _, char, code = event.pull("key_down")
  505.     if char == 49 then config.mode="local"; break
  506.     elseif char == 50 then
  507.       if hasInternet then config.mode="telegram"
  508.       else sc(C.alert); centerText(16,"No internet card! Using local."); os.sleep(2); config.mode="local"
  509.       end
  510.       break
  511.     elseif code == 14 then return false, nil
  512.     end
  513.   end
  514.  
  515.   -- Telegram-токены
  516.   if config.mode == "telegram" then
  517.     clearScreen(); drawBox(1,1,W,H)
  518.     centerText(4,"+==========================================+",C.header)
  519.     centerText(5,"|    TELEGRAM UPLINK CONFIG                |",C.header)
  520.     centerText(6,"+==========================================+",C.header)
  521.     sc(C.text)
  522.     gpu.set(4,9,"BOT TOKEN > "); term.setCursor(16,9); sc(C.highlight)
  523.     config.botToken = io.read()
  524.     sc(C.text)
  525.     gpu.set(4,11,"CHAT ID   > "); term.setCursor(16,11); sc(C.highlight)
  526.     config.chatID = io.read()
  527.     sc(C.warning); centerText(13,"TESTING CONNECTION..."); os.sleep(1)
  528.     if sendTelegramBlocking("[SYSTEM] Radar v2.4 ONLINE.") then
  529.       centerText(15,"CONNECTION OK", C.ok)
  530.     else
  531.       centerText(15,"FAILED - FALLING BACK TO LOCAL", C.alert)
  532.       config.mode = "local"
  533.     end
  534.     os.sleep(2)
  535.   end
  536.  
  537.   -- Сохранение
  538.   clearScreen(); drawBox(1,1,W,H)
  539.   centerText(H/2-1,"SAVING ENCRYPTED CONFIG...", C.warning)
  540.   local ok, err = saveConfig(password)
  541.   centerText(H/2+1, ok and "SAVED  [OK]" or "SAVE FAILED: "..(err or "?"),
  542.              ok and C.ok or C.alert)
  543.   os.sleep(2)
  544.   return true, password
  545. end
  546.  
  547. -- ============================================================
  548. -- ГЛАВНЫЙ ЦИКЛ
  549. -- ============================================================
  550. local function main()
  551.   gpu.setBackground(C.bg); gpu.setForeground(C.text)
  552.  
  553.   local ok = initialize()
  554.   if not ok then
  555.     gpu.setBackground(oldBg); gpu.setForeground(oldFg)
  556.     term.clear(); print("SHUTDOWN - USER ABORT"); return
  557.   end
  558.  
  559.   local running  = true
  560.   local lastScan = -9999
  561.  
  562.   drawMainScreen()
  563.  
  564.   while running do
  565.     local id, _, char = event.pull(0.5)
  566.     if id == "key_down" and (char == 113 or char == 81) then
  567.       running = false
  568.     end
  569.  
  570.     if running then
  571.       local now = computer.uptime()
  572.       if now - lastScan >= config.scanInterval then
  573.         runScanCycle()
  574.         drawMainScreen()
  575.         lastScan = now
  576.       end
  577.       if config.mode == "telegram" then processTelegramQueue() end
  578.     end
  579.   end
  580.  
  581.   if config.mode == "telegram" then
  582.     pcall(sendTelegramBlocking, "[SYSTEM] Radar v2.4 OFFLINE.")
  583.   end
  584.  
  585.   gpu.setBackground(oldBg); gpu.setForeground(oldFg); term.clear()
  586.   sc(C.ok)
  587.   print("+------------------------------------------+")
  588.   print("|  SHUTDOWN COMPLETE                       |")
  589.   print("|  LOG:    " .. LOG_FILE)
  590.   print("|  CONFIG: " .. CONFIG_FILE)
  591.   print("|  EVENTS: " .. #eventLog)
  592.   print("+------------------------------------------+")
  593.   gpu.setForeground(oldFg)
  594. end
  595.  
  596. main()
Advertisement
Add Comment
Please, Sign In to add comment