Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local tArgs, initialURL = {...}, "http://example.com"
- local history, currentPage, scroll, horizontalScroll, cache, bookmarks = {}, 0, 0, 0, {}, {}
- local width, height = term.getSize()
- local running = true
- local function urlEncode(url)
- return url:gsub("([^%w%-%.%_%~])", function(c) return string.format("%%%02X", string.byte(c)) end)
- end
- local function resolveURL(base, relative)
- if relative:match("^https?://") then return relative end
- if relative:sub(1,1) == "/" then return base:match("^(https?://[^/]+)") .. relative end
- return base:match("^(.*/)") .. relative
- end
- local function trim(s)
- return s:match("^%s*(.-)%s*$")
- end
- local function parseCSS(css)
- local styles = {}
- for selector, rules in css:gmatch("([^{]+){([^}]+)}") do
- selector = trim(selector)
- styles[selector] = {}
- for property, value in rules:gmatch("([^:]+):([^;]+);?") do
- styles[selector][trim(property)] = trim(value)
- end
- end
- return styles
- end
- local colorMap = {
- black = colors.black, red = colors.red, green = colors.green,
- yellow = colors.yellow, blue = colors.blue, purple = colors.purple,
- cyan = colors.cyan, white = colors.white, gray = colors.gray,
- lightGray = colors.lightGray, lime = colors.lime, orange = colors.orange,
- }
- local function getCCColor(cssColor)
- if colorMap[cssColor] then return colorMap[cssColor]
- elseif cssColor:match("^#%x%x%x$") then
- local r, g, b = cssColor:match("#(%x)(%x)(%x)")
- r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
- return 2^math.floor(r/8) + 2^(math.floor(g/8)+4) + 2^(math.floor(b/8)+8)
- end
- return colors.white
- end
- local function parseHTML(html)
- local content = {}
- local stack = {}
- local inStyle = false
- local cssContent = ""
- local function addText(text)
- text = text:gsub(" ", " ")
- :gsub("<", "<")
- :gsub(">", ">")
- :gsub("&", "&")
- :gsub('"', '"')
- :gsub("&#(%d+);", function(n) return string.char(tonumber(n)) end)
- text = text:gsub("^%s+", ""):gsub("%s+$", ""):gsub("%s+", " ")
- if text ~= "" then
- table.insert(content, {type = "text", text = text})
- end
- end
- for tag, text in html:gmatch("(<[^>]+>)([^<]*)") do
- local tagName = tag:match("</?(%w+)")
- if tagName then
- if inStyle then
- if tag:match("^%s*/%s*" .. tagName .. "%s*>") then
- inStyle = false
- table.insert(content, {type = "tag_end", name = tagName})
- table.remove(stack)
- end
- else
- local attrs = {}
- for k, v in tag:gmatch('(%w+)="([^"]*)"') do
- attrs[k] = v
- end
- if tag:match("^<style") then
- inStyle = true
- elseif tag:match("^</") then
- table.insert(content, {type = "tag_end", name = tagName})
- table.remove(stack)
- else
- table.insert(content, {type = "tag_start", name = tagName, attrs = attrs})
- if not tag:match("/>$") then
- table.insert(stack, tagName)
- end
- end
- end
- end
- if inStyle then
- cssContent = cssContent .. text
- else
- addText(text)
- end
- end
- return content, parseCSS(cssContent)
- end
- local function renderContent(content, styles, width)
- local output = {}
- local line = ""
- local links = {}
- local linkIndex = 1
- local listStack = {}
- local inPre = false
- local currentStyle = {color = colors.white, background = colors.black}
- local styleStack = {}
- local function applyStyle(tag)
- local style = styles[tag] or {}
- if style.color then currentStyle.color = getCCColor(style.color) end
- if style.background then currentStyle.background = getCCColor(style.background) end
- table.insert(styleStack, {color = currentStyle.color, background = currentStyle.background})
- end
- local function removeStyle()
- table.remove(styleStack)
- if #styleStack > 0 then
- currentStyle = styleStack[#styleStack]
- else
- currentStyle = {color = colors.white, background = colors.black}
- end
- end
- local function renderLine()
- if #line > 0 then
- table.insert(output, {text = string.rep(" ", #listStack) .. line, color = currentStyle.color, background = currentStyle.background})
- line = ""
- end
- end
- for _, item in ipairs(content) do
- if item.type == "text" then
- if inPre then
- line = line .. item.text
- if item.text:find("\n") then renderLine() end
- else
- for word in item.text:gmatch("%S+") do
- if #line + #word + 1 > width - 2 * #listStack then renderLine() end
- line = #line == 0 and word or line .. " " .. word
- end
- end
- elseif item.type == "tag_start" then
- applyStyle(item.name)
- if item.name == "br" then
- renderLine()
- elseif item.name:match("^h%d$") then
- renderLine()
- local level = tonumber(item.name:sub(2))
- line = string.rep("#", level) .. " "
- elseif item.name == "p" then
- renderLine()
- table.insert(output, {text = "", color = currentStyle.color, background = currentStyle.background})
- elseif item.name == "ul" or item.name == "ol" then
- renderLine()
- table.insert(listStack, item.name)
- elseif item.name == "li" then
- renderLine()
- line = (listStack[#listStack] == "ul" and "• " or #listStack .. ". ")
- elseif item.name == "a" and item.attrs and item.attrs.href then
- table.insert(links, {url = item.attrs.href, index = linkIndex})
- line = line .. "[" .. linkIndex .. "]"
- currentStyle.color = colors.blue
- elseif item.name == "pre" then
- inPre = true
- renderLine()
- end
- elseif item.type == "tag_end" then
- if item.name == "a" then
- linkIndex = linkIndex + 1
- removeStyle()
- elseif item.name == "p" or item.name:match("^h%d$") then
- renderLine()
- table.insert(output, {text = "", color = currentStyle.color, background = currentStyle.background})
- elseif item.name == "ul" or item.name == "ol" then
- renderLine()
- table.remove(listStack)
- elseif item.name == "pre" then
- inPre = false
- renderLine()
- end
- removeStyle()
- end
- end
- renderLine()
- return output, links
- end
- local function fetchPage(url)
- if cache[url] then return cache[url] end
- local response = http.get(url)
- if not response then return nil end
- local content = response.readAll()
- response.close()
- cache[url] = content
- return content
- end
- local buttons = {}
- local function createButton(name, x, y, width, height, text, onClick)
- buttons[name] = {x=x, y=y, width=width, height=height, text=text, onClick=onClick}
- end
- local function drawButton(name)
- local button = buttons[name]
- paintutils.drawFilledBox(button.x, button.y, button.x + button.width - 1, button.y + button.height - 1, colors.lightGray)
- term.setCursorPos(button.x + math.floor((button.width - #button.text) / 2), button.y + math.floor(button.height / 2))
- term.setTextColor(colors.black)
- term.write(button.text)
- end
- local function checkButtonClick(x, y)
- for name, button in pairs(buttons) do
- if x >= button.x and x < button.x + button.width and y >= button.y and y < button.y + button.height then
- paintutils.drawFilledBox(button.x, button.y, button.x + button.width - 1, button.y + button.height - 1, colors.gray)
- term.setCursorPos(button.x + math.floor((button.width - #button.text) / 2), button.y + math.floor(button.height / 2))
- term.setTextColor(colors.white)
- term.write(button.text)
- sleep(0.1)
- button.onClick()
- return true
- end
- end
- return false
- end
- local function loadBookmarks()
- if fs.exists("cc_web_bookmarks") then
- local file = fs.open("cc_web_bookmarks", "r")
- for line in file.readLine do
- local name, url = line:match("([^,]+),(.+)")
- if name and url then bookmarks[name] = url end
- end
- file.close()
- end
- end
- local function saveBookmarks()
- local file = fs.open("cc_web_bookmarks", "w")
- for name, url in pairs(bookmarks) do
- file.writeLine(name .. "," .. url)
- end
- file.close()
- end
- local function showLoadingScreen()
- term.clear()
- term.setCursorPos(1, 1)
- local logo = [[
- ( ( ( ( (
- )\ )\ )\))( '( ( )\
- (((_) (((_)___((_)()\ ) )\ )((_)
- )\___ )\__|___|(())\_)(|(_|(_)_
- ((/ __((/ __| \ \((_)/ / __| _ )
- | (__ | (__ \ \/\/ /| _|| _ \
- \___| \___| \_/\_/ |___|___/
- ]]
- local logoLines = {}
- for line in logo:gmatch("[^\r\n]+") do
- table.insert(logoLines, line)
- end
- local startY = math.floor((height - #logoLines) / 2) - 2
- for i, line in ipairs(logoLines) do
- term.setCursorPos(math.floor((width - #line) / 2), startY + i)
- term.write(line)
- end
- local barWidth = 40
- local barStartX = math.floor((width - barWidth) / 2)
- local barY = startY + #logoLines + 2
- term.setCursorPos(barStartX, barY)
- term.write("[" .. string.rep(" ", barWidth - 2) .. "]")
- local text = "A Web Browser For CC Made by nonogamer9"
- term.setCursorPos(math.floor((width - #text) / 2), barY + 2)
- term.write(text)
- for i = 1, barWidth - 2 do
- term.setCursorPos(barStartX + i, barY)
- term.write("=")
- sleep(5 / (barWidth - 2))
- end
- end
- local function displayPage(url)
- term.clear()
- term.setCursorPos(1,1)
- term.write("CC-WEB: Loading " .. url .. "...")
- if not url:match("^https?://") then url = "http://" .. url end
- local html = fetchPage(url)
- if not html and url:match("^http://") then
- url = url:gsub("^http://", "https://")
- html = fetchPage(url)
- end
- if not html then
- term.clear()
- term.setCursorPos(1,1)
- term.write("CC-WEB: Error loading page")
- os.sleep(2)
- return
- end
- local content, styles = parseHTML(html)
- local rendered, links = renderContent(content, styles, width - 1)
- local function drawScreen()
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clear()
- term.setCursorPos(1, 1)
- term.setTextColor(url:match("^https://") and colors.green or colors.red)
- term.write(url:match("^https://") and "[Secure] " or "[Not Secure] ")
- term.setTextColor(colors.white)
- for i = 2, height - 3 do
- local lineIndex = i + scroll - 1
- if lineIndex <= #rendered then
- local line = rendered[lineIndex]
- term.setCursorPos(1, i)
- term.setTextColor(line.color)
- term.setBackgroundColor(line.background)
- local displayText = line.text:sub(horizontalScroll + 1, horizontalScroll + width)
- term.write(displayText)
- end
- end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.setCursorPos(1, height - 2)
- term.write(string.rep("-", width))
- term.setCursorPos(1, height - 1)
- term.write("URL: " .. url:sub(1, width - 9))
- for _, button in pairs(buttons) do
- drawButton(_)
- end
- end
- createButton("back", width - 27, height, 3, 1, "<", function()
- if currentPage > 1 then
- currentPage = currentPage - 1
- displayPage(history[currentPage])
- end
- end)
- createButton("forward", width - 23, height, 3, 1, ">", function()
- if currentPage < #history then
- currentPage = currentPage + 1
- displayPage(history[currentPage])
- end
- end)
- createButton("reload", width - 19, height, 3, 1, "R", function()
- cache[url] = nil
- displayPage(url)
- end)
- createButton("bookmark", width - 15, height, 3, 1, "B", function()
- term.setCursorPos(1, height)
- term.clearLine()
- term.write("Enter bookmark name: ")
- local name = read()
- if name and #name > 0 then
- bookmarks[name] = url
- saveBookmarks()
- end
- end)
- createButton("toggle", width - 11, height, 3, 1, "T", function()
- local newUrl = url:gsub("^https?://", "")
- newUrl = (url:match("^https://") and "http://" or "https://") .. newUrl
- displayPage(newUrl)
- end)
- createButton("bookmarks", width - 7, height, 3, 1, "L", function()
- term.clear()
- term.setCursorPos(1, 1)
- term.write("Bookmarks:")
- local i = 1
- for name, bUrl in pairs(bookmarks) do
- term.setCursorPos(1, i + 1)
- term.write(i .. ". " .. name .. " - " .. bUrl)
- i = i + 1
- end
- term.setCursorPos(1, height)
- term.write("Enter number to load or 'q' to return: ")
- local input = read()
- if input ~= "q" then
- local num = tonumber(input)
- if num and num <= i - 1 then
- local j = 1
- for _, bUrl in pairs(bookmarks) do
- if j == num then
- displayPage(bUrl)
- return
- end
- j = j + 1
- end
- end
- end
- displayPage(url)
- end)
- createButton("quit", width - 3, height, 3, 1, "Q", function()
- running = false
- end)
- local function handleInput()
- while true do
- drawScreen()
- local event, param, x, y = os.pullEvent()
- if event == "key" then
- if param == keys.up and scroll > 0 then
- scroll = scroll - 1
- elseif param == keys.down and scroll < #rendered - height + 4 then
- scroll = scroll + 1
- elseif param == keys.left and horizontalScroll > 0 then
- horizontalScroll = horizontalScroll - 1
- elseif param == keys.right and horizontalScroll < width then
- horizontalScroll = horizontalScroll + 1
- elseif param == keys.q then
- return "quit"
- end
- elseif event == "mouse_click" and param == 1 then
- if checkButtonClick(x, y) then
- if not running then
- return "quit"
- end
- elseif y == height - 1 and x > 5 and x <= width - 1 then
- term.setCursorPos(6, height - 1)
- term.clearLine()
- term.write(url)
- term.setCursorPos(6, height - 1)
- local input = read(nil, nil, function(text)
- if text == "" then
- return {url}
- else
- return {}
- end
- end)
- if input then
- if not input:match("^https?://") then
- input = "http://" .. input
- end
- table.insert(history, input)
- currentPage = #history
- return displayPage(input)
- end
- elseif y <= height - 3 then
- local clickedLine = rendered[y + scroll - 1]
- if clickedLine then
- local clickedText = clickedLine.text
- local linkNumber = clickedText:match("%[(%d+)%]")
- if linkNumber then
- local clickedLink = links[tonumber(linkNumber)]
- if clickedLink then
- local newURL = resolveURL(url, clickedLink.url)
- table.insert(history, newURL)
- currentPage = #history
- return displayPage(newURL)
- end
- end
- end
- end
- end
- end
- end
- return handleInput()
- end
- loadBookmarks()
- showLoadingScreen()
- table.insert(history, initialURL)
- currentPage = 1
- while running do
- local result = displayPage(history[currentPage])
- if result == "quit" then
- break
- end
- end
- term.clear()
- term.setCursorPos(1,1)
- print("Thank you for using CC-WEB Browser!")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement