Advertisement
nonogamer9

OC-WEB 2.0: A Web Browser For OpenComputers!

Feb 2nd, 2025 (edited)
78
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.14 KB | Software | 0 0
  1. local component = require("component")
  2. local event = require("event")
  3. local internet = require("internet")
  4. local term = require("term")
  5. local unicode = require("unicode")
  6. local computer = require("computer")
  7.  
  8. local gpu = component.gpu
  9. local hasColor = gpu.getDepth() > 1
  10.  
  11. local function trim(s)
  12.     return s:match("^%s*(.-)%s*$")
  13. end
  14.  
  15. local function serialize(tbl)
  16.     local result = {}
  17.     for k, v in pairs(tbl) do
  18.         table.insert(result, k .. "=" .. v)
  19.     end
  20.     return table.concat(result, ",")
  21. end
  22.  
  23. local function deserialize(str)
  24.     local result = {}
  25.     for pair in str:gmatch("([^,]+)") do
  26.         local k, v = pair:match("(.-)=(.*)")
  27.         result[trim(k)] = trim(v)
  28.     end
  29.     return result
  30. end
  31.  
  32. local function bootUpScreen()
  33.     term.clear()
  34.     local logo = [[
  35.  ::::::::   ::::::::               :::       ::: :::::::::: :::::::::
  36. :+:    :+: :+:    :+:              :+:       :+: :+:        :+:    :+:
  37. +:+    +:+ +:+                     +:+       +:+ +:+        +:+    +:+
  38. +#+    +:+ +#+       +#++:++#++:++ +#+  +:+  +#+ +#++:++#   +#++:++#+
  39. +#+    +#+ +#+                     +#+ +#+#+ +#+ +#+        +#+    +#+
  40. #+#    #+# #+#    #+#               #+#+# #+#+#  #+#        #+#    #+#
  41.  ########   ########                 ###   ###   ########## #########
  42.     ]]
  43.     term.setCursor(1, 1)
  44.     term.write(logo)
  45.     term.setCursor(1, select(2, term.getCursor()) + 1)
  46.     term.write("Loading...")
  47.     term.setCursor(1, select(2, term.getCursor()) + 2)
  48.     term.write("OC-WEB 2.0: A Web Browser For OpenComputers Coded by nonogamer9")
  49.     os.sleep(3)
  50. end
  51.  
  52. local function resolveURL(base, relative)
  53.     if not base or not relative then return nil end
  54.     if relative:match("^https?://") then return relative end
  55.     if relative:sub(1, 1) == "/" then
  56.         return base:match("^(https?://[^/]+)") .. relative
  57.     else
  58.         local baseMatch = base:match("^(https?://.*/)") or base:match("^(https?://[^/]+)")
  59.         if baseMatch then
  60.             if baseMatch:sub(-1) ~= "/" then
  61.                 baseMatch = baseMatch .. "/"
  62.             end
  63.             return baseMatch .. relative
  64.         else
  65.             return nil
  66.         end
  67.     end
  68. end
  69.  
  70. local function handleJavaScript(html, currentBaseURL)
  71.     local redirectUrl = html:match("window%.location%s*=%s*['\"](.-)['\"]")
  72.     if redirectUrl then
  73.         return resolveURL(currentBaseURL, redirectUrl)
  74.     end
  75.     return nil
  76. end
  77.  
  78. local function fetch(url, visited)
  79.     visited = visited or {}
  80.     if visited[url] then
  81.         return false, "Redirect loop detected"
  82.     end
  83.     visited[url] = true
  84.  
  85.     local headers = {
  86.         ["User-Agent"] = "OpenComputers/2.0 (Compatible; OCWeb/2.0)",
  87.         ["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  88.     }
  89.  
  90.     os.sleep(0)
  91.  
  92.     local handle, err = internet.request(url, nil, headers)
  93.     if not handle then
  94.         return false, "Failed to connect to " .. url .. ": " .. (err or "unknown error")
  95.     end
  96.  
  97.     local data = ""
  98.     local status, responseHeaders
  99.     local startTime = computer.uptime()
  100.     local chunkCount = 0
  101.     for chunk in handle do
  102.         chunkCount = chunkCount + 1
  103.         if computer.uptime() - startTime > 0.5 or chunkCount % 10 == 0 then
  104.             os.sleep(0)
  105.             startTime = computer.uptime()
  106.         end
  107.  
  108.         if not status then
  109.             status, responseHeaders = handle.response()
  110.         end
  111.         if chunk then
  112.             data = data .. chunk
  113.         end
  114.     end
  115.  
  116.     os.sleep(0)
  117.  
  118.     if status then
  119.         if status >= 400 then
  120.             return false, "HTTP Error: " .. status
  121.         elseif status >= 300 and status < 400 then
  122.             local redirectUrl = responseHeaders and responseHeaders["location"]
  123.             if redirectUrl then
  124.                 local resolvedRedirectUrl = resolveURL(url, redirectUrl)
  125.                 if resolvedRedirectUrl then
  126.                     print("Redirecting to: " .. resolvedRedirectUrl)
  127.                     os.sleep(0)
  128.                     return fetch(resolvedRedirectUrl, visited)
  129.                 else
  130.                     return false, "Invalid redirect URL: " .. redirectUrl
  131.                 end
  132.             end
  133.         end
  134.     end
  135.  
  136.     os.sleep(0)
  137.  
  138.     local metaRedirectUrl = data:match('<meta%s+http%-equiv="refresh"%s+content=".-;url=(.-)"')
  139.     if metaRedirectUrl then
  140.         local resolvedRedirectUrl = resolveURL(url, metaRedirectUrl)
  141.         if resolvedRedirectUrl then
  142.             print("Meta redirecting to: " .. resolvedRedirectUrl)
  143.             os.sleep(0)  -- Yield before recursive call
  144.             return fetch(resolvedRedirectUrl, visited)
  145.         else
  146.             return false, "Invalid meta redirect URL: " .. metaRedirectUrl
  147.         end
  148.     end
  149.  
  150.     os.sleep(0)
  151.  
  152.     local jsRedirectUrl = handleJavaScript(data, url)
  153.     if jsRedirectUrl then
  154.         print("JavaScript redirecting to: " .. jsRedirectUrl)
  155.         os.sleep(0)
  156.         return fetch(jsRedirectUrl, visited)
  157.     end
  158.  
  159.     return true, data
  160. end
  161.  
  162. local function parseCSS(cssText)
  163.     local styles = {}
  164.     for selector, properties in cssText:gmatch("([^{]+){([^}]+)}") do
  165.         selector = trim(selector)
  166.         styles[selector] = {}
  167.         for property, value in properties:gmatch("([^:]+):([^;]+)") do
  168.             styles[selector][trim(property)] = trim(value)
  169.         end
  170.         os.sleep(0)
  171.     end
  172.     return styles
  173. end
  174.  
  175. local function applyStyles(text, styles)
  176.     if styles["color"] then
  177.         local color = tonumber(styles["color"]:match("#(%x+)"), 16) or 0xFFFFFF
  178.         gpu.setForeground(color)
  179.     end
  180.     if styles["background-color"] then
  181.         local bgColor = tonumber(styles["background-color"]:match("#(%x+)"), 16) or 0x000000
  182.         gpu.setBackground(bgColor)
  183.     end
  184.     if styles["font-weight"] == "bold" then
  185.         text = "[B]" .. text .. "[/B]"
  186.     end
  187.     if styles["font-style"] == "italic" then
  188.         text = "[I]" .. text .. "[/I]"
  189.     end
  190.     return text
  191. end
  192.  
  193. local function parseHTML(html, baseStyles)
  194.     local links = {}
  195.     local styles = baseStyles or {}
  196.  
  197.     html = html:gsub('style="(.-)"', function(style)
  198.         local inlineStyles = {}
  199.         for property, value in style:gmatch("([^:]+):([^;]+)") do
  200.             inlineStyles[trim(property)] = trim(value)
  201.         end
  202.         return "data-inline-style='" .. serialize(inlineStyles) .. "'"
  203.     end)
  204.  
  205.     html = html:gsub("<script.->.-</script>", "")
  206.     html = html:gsub("<style.->.-</style>", "")
  207.     html = html:gsub("<b>(.-)</b>", function(content) return "[B]" .. content .. "[/B]" end)
  208.     html = html:gsub("<strong>(.-)</strong>", function(content) return "[B]" .. content .. "[/B]" end)
  209.     html = html:gsub("<i>(.-)</i>", function(content) return "[I]" .. content .. "[/I]" end)
  210.     html = html:gsub("<em>(.-)</em>", function(content) return "[I]" .. content .. "[/I]" end)
  211.     html = html:gsub("<u>(.-)</u>", function(content) return "[U]" .. content .. "[/U]" end)
  212.     html = html:gsub("<a href=\"(.-)\">(.-)</a>", function(href, content)
  213.         table.insert(links, {href = href, content = content})
  214.         return content .. " [" .. #links .. "]"
  215.     end)
  216.     html = html:gsub("<h([1-6])>(.-)</h%1>", function(level, content) return "[H" .. level .. "]" .. content .. "[/H" .. level .. "]" end)
  217.     html = html:gsub("<li>(.-)</li>", function(content) return " * " .. content end)
  218.     html = html:gsub("<br>", "\n")
  219.     html = html:gsub("<p>(.-)</p>", function(content) return "\n\n" .. content .. "\n\n" end)
  220.     html = html:gsub("<div>(.-)</div>", function(content) return content .. "\n" end)
  221.    
  222.     html = html:gsub("<table>(.-)</table>", function(tableContent)
  223.         local result = "\n"
  224.         for row in tableContent:gmatch("<tr>(.-)</tr>") do
  225.             for cell in row:gmatch("<t[dh]>(.-)</t[dh]>") do
  226.                 result = result .. cell .. "\t"
  227.             end
  228.             result = result .. "\n"
  229.         end
  230.         return result
  231.     end)
  232.  
  233.     html = html:gsub("data%-inline%-style='(.-)'(.-)", function(styleData, content)
  234.         local inlineStyles = deserialize(styleData)
  235.         return applyStyles(content, inlineStyles)
  236.     end)
  237.  
  238.     html = html:gsub("<.->", "")
  239.     html = html:gsub("&lt;", "<")
  240.     html = html:gsub("&gt;", ">")
  241.     html = html:gsub("&amp;", "&")
  242.     html = html:gsub("&quot;", "\"")
  243.     html = html:gsub("&apos;", "'")
  244.     html = html:gsub("&nbsp;", " ")
  245.     html = html:gsub("&mdash;", "—")
  246.  
  247.     os.sleep(0)
  248.     return html, links
  249. end
  250.  
  251. local function display(baseURL, data, links, styles)
  252.     local w, h = gpu.getResolution()
  253.     local bufferIndex = gpu.allocateBuffer(w, h)
  254.     gpu.setActiveBuffer(bufferIndex)
  255.  
  256.     local lines = {}
  257.     for line in data:gmatch("[^\r\n]+") do
  258.         table.insert(lines, line)
  259.         if #lines % 10 == 0 then
  260.             os.sleep(0)
  261.         end
  262.     end
  263.  
  264.     local offset = 0
  265.     local contentHeight = h - 1
  266.  
  267.     local function redraw()
  268.         gpu.setBackground(0x000000)
  269.         gpu.fill(1, 1, w, h, " ")
  270.         for i = 1, contentHeight do
  271.             local line = lines[i + offset] or ""
  272.             line = applyStyles(line, styles["body"] or {})
  273.             for tag, tagStyles in pairs(styles) do
  274.                 if tag ~= "body" then
  275.                     line = line:gsub("%[" .. tag .. "%](.-)[/%]", function(content)
  276.                         return applyStyles(content, tagStyles)
  277.                     end)
  278.                 end
  279.             end
  280.             gpu.set(1, i, unicode.sub(line, 1, w))
  281.             if i % 5 == 0 then
  282.                 os.sleep(0)
  283.             end
  284.         end
  285.  
  286.         gpu.setBackground(0x333333)
  287.         gpu.fill(1, h, w, 1, " ")
  288.         gpu.set(1, h, "URL: " .. (baseURL or "N/A") .. " | Use Arrow Keys to Scroll, Q to Quit, U to Change URL")
  289.         gpu.setBackground(0x000000)
  290.  
  291.         gpu.setActiveBuffer(0)
  292.         gpu.bitblt(0, 1, 1, w, h, bufferIndex)
  293.         gpu.setActiveBuffer(bufferIndex)
  294.     end
  295.  
  296.     redraw()
  297.  
  298.     while true do
  299.         local eventType, _, _, y, _, scrollDirection = event.pull()
  300.         local needsRedraw = false
  301.         if eventType == "key_down" then
  302.             if y == 200 then
  303.                 offset = math.max(0, offset - 1)
  304.                 needsRedraw = true
  305.             elseif y == 208 then
  306.                 offset = math.min(#lines - contentHeight, offset + 1)
  307.                 needsRedraw = true
  308.             elseif y == 16 then
  309.                 break
  310.             elseif y == 22 then
  311.                 gpu.freeBuffer(bufferIndex)
  312.                 return false
  313.             end
  314.         elseif eventType == "scroll" then
  315.             if scrollDirection == 1 then
  316.                 offset = math.max(0, offset - 1)
  317.                 needsRedraw = true
  318.             elseif scrollDirection == -1 then
  319.                 offset = math.min(#lines - contentHeight, offset + 1)
  320.                 needsRedraw = true
  321.             end
  322.         elseif eventType == "touch" then
  323.             local lineIndex = y + offset
  324.             if lineIndex <= #lines then
  325.                 local line = lines[lineIndex]
  326.                 local linkIndex = line:match("%[(%d+)%]$")
  327.                 if linkIndex then
  328.                     local link = links[tonumber(linkIndex)]
  329.                     if link and link.href then
  330.                         local resolvedURL = resolveURL(baseURL, link.href)
  331.                         if resolvedURL then
  332.                             print("Opening link: " .. resolvedURL)
  333.                             gpu.freeBuffer(bufferIndex)
  334.                             local success, newData = fetch(resolvedURL)
  335.                             if success then
  336.                                 local newStyles = parseCSS(newData:match("<style>(.-)</style>") or "")
  337.                                 local newParsedData, newLinks = parseHTML(newData, newStyles)
  338.                                 display(resolvedURL, newParsedData, newLinks, newStyles)
  339.                             else
  340.                                 print("Error fetching URL: " .. newData)
  341.                             end
  342.                             return
  343.                         else
  344.                             print("Invalid URL: " .. tostring(link.href))
  345.                         end
  346.                     else
  347.                         print("Invalid link at index: " .. linkIndex)
  348.                     end
  349.                 end
  350.             end
  351.         end
  352.         if needsRedraw then
  353.             redraw()
  354.         end
  355.         os.sleep(0)
  356.     end
  357.  
  358.     gpu.freeBuffer(bufferIndex)
  359. end
  360.  
  361. function main(showBootScreen)
  362.     if showBootScreen then
  363.         bootUpScreen()
  364.     end
  365.     term.clear()
  366.     print("Enter a URL:")
  367.     local url = io.read()
  368.     if not url:match("^https?://") then
  369.         print("Invalid URL. Please include http:// or https://")
  370.     else
  371.         local success, data = fetch(url)
  372.         if success then
  373.             local styles = parseCSS(data:match("<style>(.-)</style>") or "")
  374.             local parsedData, links = parseHTML(data, styles)
  375.             if display(url, parsedData, links, styles) == false then
  376.                 main(false)
  377.             end
  378.         else
  379.             print("Error fetching URL: " .. data)
  380.         end
  381.     end
  382. end
  383.  
  384. main(true)
  385.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement