SHARE
TWEET

Untitled

OstojaTheGamer Nov 14th, 2019 22 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[
  2. WocChat - the Wonderful OpenComputers Chat client
  3. Written by gamax92
  4. Some code borrowed from OpenIRC
  5. Some code borrowed from wget
  6. --]]
  7. local function errprint(msg)io.stderr:write(msg.."\n")end
  8. local component = require("component")
  9. local gpu = component.gpu
  10.  
  11. if gpu.maxDepth() < 4 then
  12.     errprint("WocChat requires atleast a T2 GPU and Screen!")
  13.     return
  14. elseif not component.isAvailable("internet") then
  15.     errprint("WocChat requires an Internet Card to run!")
  16.     return
  17. end
  18.  
  19. local version = "v0.0.3"
  20. local newterm = _OSVERSION ~= "OpenOS 1.5"
  21.  
  22. local bit32 = require("bit32")
  23. local computer = require("computer")
  24. local event = require("event")
  25. local fs = require("filesystem")
  26. local internet = require("internet")
  27. local shell = require("shell")
  28. local term = require("term")
  29. local text = require("text")
  30. local unicode = require("unicode")
  31. local process = require("process")
  32.  
  33. local unicodeBlacklist = unicode.char(0x200B,0xFEFF)
  34.  
  35. local args, options = shell.parse(...)
  36.  
  37. local config,blocks,persist = {},{{type="main",name="WocChat",text={{"*","Welcome to WocChat!"}}},active=1},{}
  38. local screen,theme,lastbg,lastbgp,lastfg,lastfgp
  39.  
  40. local function setBackground(bg, pal)
  41.     if bg ~= lastbg or pal ~= lastbgp then
  42.         gpu.setBackground(bg,not pal)
  43.         lastbg = bg
  44.         lastbgp = pal
  45.     end
  46. end
  47.  
  48. local function setForeground(fg, pal)
  49.     if fg ~= lastfg or pal ~= lastfgp then
  50.         gpu.setForeground(fg,not pal)
  51.         lastfg = fg
  52.         lastfgp = pal
  53.     end
  54. end
  55.  
  56. local function saveScreen()
  57.     local width,height = gpu.getResolution()
  58.     screen = {palette={},width=width,height=height,depth=gpu.getDepth()}
  59.     for i = 0,15 do
  60.         screen.palette[i] = gpu.getPaletteColor(i)
  61.     end
  62.     screen.bg = table.pack(gpu.getBackground())
  63.     screen.fg = table.pack(gpu.getForeground())
  64.     if newterm then
  65.         screen.precise = component.proxy(term.screen()).isPrecise()
  66.     else
  67.         screen.precise = component.screen.isPrecise()
  68.     end
  69. end
  70.  
  71. local function restoreScreen()
  72.     print("Restoring screen ...")
  73.     gpu.setDepth(screen.depth)
  74.     for i = 0,15 do
  75.         gpu.setPaletteColor(i,screen.palette[i])
  76.     end
  77.     term.setCursor(1,screen.height)
  78.     gpu.setBackground(table.unpack(screen.bg))
  79.     gpu.setForeground(table.unpack(screen.fg))
  80.     if newterm then
  81.         component.proxy(term.screen()).setPrecise(screen.precise)
  82.     else
  83.         component.screen.setPrecise(screen.precise)
  84.     end
  85.     term.clear()
  86. end
  87.  
  88. local function loadConfig()
  89.     local section
  90.     config = {}
  91.     for line in io.lines("/etc/wocchat.cfg") do
  92.         if line:sub(1,1) == "[" then
  93.             section = line:sub(2,-2)
  94.             config[section] = {}
  95.         elseif line ~= "" then
  96.             local key,value = line:match("(.-)=(.*)")
  97.             key,value = text.trim(key),text.trim(value)
  98.             key = section .. "\0" .. key:gsub("%.","\0")
  99.             path,name=key:match("(.+)%z(.+)")
  100.             local v = config
  101.             for part in (path .. "\0"):gmatch("(.-)%z") do
  102.                 if tonumber(part) ~= nil then part = tonumber(part) end
  103.                 if v[part] == nil then
  104.                     v[part] = {}
  105.                 end
  106.                 v = v[part]
  107.             end
  108.             if tonumber(name) ~= nil then name = tonumber(name) end
  109.             if value == "true" then
  110.                 v[name]=true
  111.             elseif value == "false" then
  112.                 v[name]=false
  113.             elseif value:sub(1,1) == "\"" then
  114.                 v[name]=value:sub(2,-2)
  115.             else
  116.                 v[name]=tonumber(value)
  117.             end
  118.         end
  119.     end
  120.     theme=setmetatable({},{__index=function(_,k)
  121.         local theme = config.theme.theme .. ".theme"
  122.         if config[theme][k] then
  123.             return config[theme][k]
  124.         elseif config[theme].parent ~= nil then
  125.             return config[config[theme].parent .. ".theme"][k]
  126.         end
  127.     end})
  128. end
  129.  
  130. local function configSort(a,b)
  131.     local ta,tb = type(a),type(b)
  132.     if ta == tb then
  133.         return a < b
  134.     end
  135.     return ta == "number"
  136. end
  137. local saveSection
  138. function saveSection(file,tbl,name)
  139.     name = name or ""
  140.     local keys = {}
  141.     for k,v in pairs(tbl) do
  142.         keys[#keys+1] = k
  143.     end
  144.     table.sort(keys,configSort)
  145.     for i = 1,#keys do
  146.         local k,v = keys[i],tbl[keys[i]]
  147.         local ndk = (name == "" and k or name .. "." .. k)
  148.         if type(v) == "table" then
  149.             saveSection(file,v,ndk)
  150.         elseif type(k) == "number" and type(v) == "number" then
  151.             file:write(ndk .. "=" .. string.format("0x%06X\n",v))
  152.         elseif type(v) == "string" then
  153.             file:write(ndk .. "=\"" .. v .. "\"\n")
  154.         else
  155.             file:write(ndk .. "=" .. tostring(v) .. "\n")
  156.         end
  157.     end
  158. end
  159. local sections = {wocchat=1,default=2,server=3,theme=4}
  160. local function sectionSort(a,b)
  161.     if sections[a] and sections[b] then
  162.         return sections[a] < sections[b]
  163.     elseif not sections[a] and not sections[b] then
  164.         return a < b
  165.     end
  166.     return sections[a] ~= nil
  167. end
  168. local function saveConfig()
  169.     local file, err = io.open("/etc/wocchat.cfg","wb")
  170.     if not file then
  171.         return false, err
  172.     end
  173.     config.wocchat.version = version
  174.     local keys = {}
  175.     for k,v in pairs(config) do
  176.         keys[#keys+1] = k
  177.     end
  178.     table.sort(keys,sectionSort)
  179.     for i = 1,#keys do
  180.         file:write("[" .. keys[i] .. "]\n")
  181.         saveSection(file, config[keys[i]])
  182.         file:write("\n")
  183.     end
  184.     file:close()
  185.     return true
  186. end
  187.  
  188. local function simpleHash(str)
  189.     local v = 0
  190.     for char in str:gmatch(".") do
  191.         v = bit32.bxor(bit32.lrotate(v,5),char:byte())
  192.     end
  193.     return v
  194. end
  195.  
  196. local dirty = {
  197.     blocks = true,
  198.     window = true,
  199.     nicks = true,
  200.     title = true,
  201. }
  202.  
  203. local default_support = {
  204.     PREFIX="(ov)@+",
  205.     CHANTYPES="#&",
  206. }
  207.  
  208. local function drawTree(width)
  209.     setBackground(theme.tree.color)
  210.     gpu.fill(1,1,width,screen.height," ")
  211.     local y = 1
  212.     for i = 1,#blocks do
  213.         local block = blocks[i]
  214.         if blocks.active == i then
  215.             setBackground(theme.tree.active.color)
  216.             gpu.fill(1,i,width,1," ")
  217.         end
  218.         setForeground(block.new and theme.tree.new.color or block.type:sub(1,5) == "dead_" and theme.tree.dead.color or block.type == "channel" and theme.tree.entry.color or theme.tree.group.color)
  219.         if block.type == "main" then
  220.             gpu.set(unicode.wlen(theme.tree.group.prefix.str)+1,y,block.name)
  221.         elseif block.type == "server" or block.type == "dead_server" then
  222.             gpu.set(unicode.wlen(theme.tree.group.prefix.str)+1,y,block.support.NETWORK or block.name)
  223.             setForeground(theme.tree.group.prefix.color)
  224.             gpu.set(1,y,theme.tree.group.prefix.str)
  225.         elseif block.type == "channel" or block.type == "dead_channel" then
  226.             gpu.set(unicode.wlen(theme.tree.entry.prefix.str)+1,y,block.name)
  227.             setForeground(theme.tree.entry.prefix.color)
  228.             local siblings = block.parent.children
  229.             if block == siblings[#siblings] then
  230.                 gpu.set(1,y,theme.tree.entry.prefix.last)
  231.             else
  232.                 gpu.set(1,y,theme.tree.entry.prefix.str)
  233.             end
  234.         end
  235.         if blocks.active == i then
  236.             setBackground(theme.tree.color)
  237.         end
  238.         y=y+1
  239.     end
  240. end
  241.  
  242. local function drawTabs()
  243.     setBackground(theme.tree.color)
  244.     setForeground(theme.window.color)
  245.     gpu.fill(2,screen.height,screen.width-1,1," ")
  246.     gpu.set(1,screen.height,"┃")
  247.     local x = 1
  248.     for i = 1,#blocks do
  249.         local block = blocks[i]
  250.         local name = block.name
  251.         if block.type == "server" or block.type == "dead_server" then
  252.             name = block.support.NETWORK or block.name
  253.         end
  254.         x=x+unicode.wlen(name)+1
  255.         gpu.set(x,screen.height,"┃")
  256.     end
  257.     x = 2
  258.     for i = 1,#blocks do
  259.         local block = blocks[i]
  260.         local name = block.name
  261.         if block.type == "server" or block.type == "dead_server" then
  262.             name = block.support.NETWORK or block.name
  263.         end
  264.         if blocks.active == i then
  265.             setBackground(theme.tree.active.color)
  266.         end
  267.         setForeground(block.type:sub(1,5) == "dead_" and theme.tree.dead.color or theme.tree.group.color)
  268.         gpu.set(x,screen.height,name)
  269.         if blocks.active == i then
  270.             setBackground(theme.tree.color)
  271.         end
  272.         x=x+unicode.wlen(name)+1
  273.     end
  274. end
  275.  
  276. local scroll_chars = {"█","▇","▆","▅","▄","▃","▂","▁"}
  277.  
  278. local function drawList(width,height)
  279.     local block = blocks[blocks.active]
  280.     local names = block.names
  281.     setBackground(theme.window.color)
  282.     gpu.fill(screen.width,1,1,height," ")
  283.     setBackground(theme.tree.color)
  284.     local x = screen.width-width+1
  285.     gpu.fill(x,1,width-1,height," ")
  286.     setForeground(theme.tree.group.color)
  287.     local prefix = blocks[blocks.active].parent.support.PREFIX or default_support.PREFIX
  288.     prefix = prefix:match("%)(.*)")
  289.     block.scroll = math.max(math.min(block.scroll,#names-height+1),1)
  290.     for y = block.scroll,math.min(#names,height+block.scroll-1) do
  291.         if names[y]:find("^[" .. prefix .. "]") then
  292.             gpu.set(x,y-block.scroll+1,names[y])
  293.         else
  294.             gpu.set(x+1,y-block.scroll+1,names[y])
  295.         end
  296.     end
  297.     local pos = (block.scroll-1)/(#names-height)*(height-1)+1
  298.     local ipos = math.floor((pos % 1)*8)+1
  299.     setForeground(theme.tree.active.color)
  300.     setBackground(theme.window.color)
  301.     gpu.set(screen.width,pos,scroll_chars[ipos])
  302.     setBackground(theme.tree.active.color)
  303.     setForeground(theme.window.color)
  304.     gpu.set(screen.width,pos+1,scroll_chars[ipos])
  305. end
  306.  
  307. local function colorChunker(ostr,kill)
  308.     local chunks = {}
  309.     while ostr:find("[\3\15]") do
  310.         local part,str
  311.         part,str,ostr = ostr:match("(.-)([\3\15])(.*)")
  312.         if part ~= "" then
  313.             chunks[#chunks+1] = part
  314.         end
  315.         if str == "\3" then
  316.             for char in ostr:gmatch(".") do
  317.                 if char:find("[^%d,]") or (str:find(",",nil,true) and char == ",") or #str >= 6 or (#str == 3 and not str:find(",",nil,true) and char ~= ",") or (#str == 1 and char == ",") then
  318.                     if not kill then
  319.                         chunks[#chunks+1] = str
  320.                     end
  321.                     ostr = ostr:sub(#str)
  322.                     break
  323.                 else
  324.                     str = str .. char
  325.                 end
  326.             end
  327.         else
  328.             if not kill then
  329.                 chunks[#chunks+1] = str
  330.             end
  331.         end
  332.     end
  333.     chunks[#chunks+1] = ostr
  334.     return chunks
  335. end
  336.  
  337. local function basicWrap(line,width)
  338.     local broken = ""
  339.     local clean = table.concat(colorChunker(line,true),"")
  340.     for part in text.wrappedLines(clean,width,width) do
  341.         broken = broken .. part .. "\n"
  342.     end
  343.     local new = ""
  344.     local bpos = 1
  345.     for i = 1,#broken do
  346.         if broken:sub(i,i) == "\n" then
  347.             new = new .. "\n"
  348.         elseif broken:sub(i,i) == line:sub(bpos,bpos) then
  349.             new = new .. broken:sub(i,i)
  350.             bpos=bpos+1
  351.         else
  352.             while broken:sub(i,i) ~= line:sub(bpos,bpos) do
  353.                 new = new .. line:sub(bpos,bpos)
  354.                 bpos=bpos+1
  355.             end
  356.             bpos=bpos+1
  357.             new = new .. broken:sub(i,i)
  358.         end
  359.     end
  360.     return new:gmatch("(.-)\n")
  361. end
  362.  
  363. local function drawWindow(x,width,height,irctext)
  364.     setBackground(theme.window.color)
  365.     gpu.fill(x,2,width,height," ")
  366.     local nickwidth = 0
  367.     for i = 1,#irctext do
  368.         nickwidth=math.max(nickwidth,#irctext[i][1])
  369.     end
  370.     local textwidth = width-nickwidth-1
  371.     local buffer = {}
  372.     for i = 1,#irctext do
  373.         local first = true
  374.         local line = irctext[i][2]:gsub("[\2\29\31" .. unicodeBlacklist .. "]",""):gsub("\15+","\15")
  375.         if not config.wocchat.showcolors or gpu.getDepth() < 8 then
  376.             line = table.concat(colorChunker(line,true),"")
  377.         end
  378.         for part in basicWrap(line,textwidth) do
  379.             if first then
  380.                 buffer[#buffer+1] = {irctext[i][1],part,irctext[i][3],true}
  381.                 first = false
  382.             else
  383.                 buffer[#buffer+1] = {"",part,irctext[i][3]}
  384.             end
  385.         end
  386.         while #buffer > height do
  387.             table.remove(buffer,1)
  388.         end
  389.     end
  390.     setForeground(theme.window.divider.color)
  391.     gpu.fill(x+nickwidth,2,1,height,theme.window.divider.str)
  392.     local nickcolors
  393.     if type(theme.window.nick.color) == "number" then
  394.         nickcolors = theme.window.nick.color
  395.     else
  396.         nickcolors = {}
  397.         for part in (theme.window.nick.color .. ","):gmatch("(.-),") do
  398.             nickcolors[#nickcolors+1] = tonumber(part)
  399.         end
  400.     end
  401.     for i = 1,#buffer do
  402.         if buffer[i][1] ~= "" then
  403.             setBackground(theme.window.color)
  404.             if type(nickcolors) == "number" then
  405.                 setForeground(nickcolors)
  406.             else
  407.                 setForeground(nickcolors[simpleHash(buffer[i][1])%#nickcolors+1])
  408.             end
  409.             gpu.set(x+nickwidth-unicode.wlen(buffer[i][1]),i+1,buffer[i][1])
  410.         end
  411.         if config.wocchat.showcolors and gpu.getDepth() >= 8 then
  412.             local chunk = colorChunker(buffer[i][2])
  413.             local xpos = x+nickwidth+1
  414.             if buffer[i][4] then
  415.                 setForeground(buffer[i][3] or theme.window.text.color)
  416.                 setBackground(theme.window.color)
  417.             end
  418.             for j = 1,#chunk do
  419.                 if chunk[j]:sub(1,1) == "\3" then
  420.                     local fg,bg
  421.                     if chunk[j]:find(",",nil,true) then
  422.                         fg,bg = chunk[j]:match("\3(.-),(.*)")
  423.                         fg,bg = tonumber(fg),tonumber(bg)
  424.                     else
  425.                         fg = tonumber(chunk[j]:sub(2))
  426.                     end
  427.                     if fg then setForeground(config.wocchat.mirc.colors[fg%16],true) end
  428.                     if bg then setBackground(config.wocchat.mirc.colors[bg%16],true) end
  429.                 elseif chunk[j] == "\15" then
  430.                     setForeground(buffer[i][3] or theme.window.text.color)
  431.                     setBackground(theme.window.color)
  432.                 else
  433.                     gpu.set(xpos,i+1,chunk[j])
  434.                     xpos = xpos + unicode.wlen(chunk[j])
  435.                 end
  436.             end
  437.         else
  438.             setForeground(buffer[i][3] or theme.window.text.color)
  439.             gpu.set(x+nickwidth+1,i+1,buffer[i][2])
  440.         end
  441.     end
  442. end
  443.  
  444. local function drawTextbar(x,y,width,text)
  445.     setBackground(theme.textbar.color)
  446.     gpu.fill(x,y,width,1," ")
  447.     -- TODO: Support drawing colors
  448.     text = table.concat(colorChunker(text,true),"")
  449.     if text ~= "" then
  450.         setForeground(theme.textbar.text.color)
  451.         gpu.set(x,y,unicode.sub(text,1,width))
  452.     end
  453. end
  454.  
  455. local customGPU={}
  456. if not newterm then
  457.     customGPU.gpu = setmetatable({
  458.         set = function(x,y,s,v) return gpu.set(x+customGPU.x-1,y+customGPU.y-1,s,v ~= nil and v) end,
  459.         get = function(x,y) return gpu.get(x+customGPU.x-1,y+customGPU.y-1) end,
  460.         getResolution = function() return customGPU.width, customGPU.height end,
  461.         copy = function(x,y,w,h,tx,ty) if ty ~= -1 then return gpu.copy(x+customGPU.x-1,y+customGPU.y-1,w,h,tx,ty) end end,
  462.         fill = function(x,y,w,h,c) return gpu.fill(x+customGPU.x-1,y+customGPU.y-1,w,h,c) end,
  463.     },{__index = gpu})
  464. end
  465. function customGPU:gpuSetup(x,y,width,height)
  466.     if self.window then
  467.         self.window.dx=x-1
  468.         self.window.dy=y-1
  469.         self.window.w=width
  470.         self.window.h=height
  471.     else
  472.         self.x=x
  473.         self.y=y
  474.         self.width=width
  475.         self.height=height
  476.     end
  477. end
  478. if not newterm then
  479.     customGPU:gpuSetup(1,1,gpu.getResolution())
  480. end
  481.  
  482. local function _redraw(first)
  483.     local yoffset
  484.     local treewidth = persist.treewidth
  485.     if config.wocchat.usetree then
  486.         yoffset = 0
  487.         if dirty.blocks then
  488.             treewidth = 8
  489.             for i = 1,#blocks do
  490.                 local block = blocks[i]
  491.                 if block.type == "server" or block.type == "dead_server" then
  492.                     treewidth=math.max(treewidth,unicode.len(theme.tree.group.prefix.str .. (block.support.NETWORK or block.name)))
  493.                 else
  494.                     treewidth=math.max(treewidth,unicode.len(theme.tree.entry.prefix.str .. block.name))
  495.                 end
  496.             end
  497.             if treewidth ~= persist.treewidth and not first then
  498.                 local old = screen.width-persist.treewidth-persist.listwidth-2
  499.                 local new = screen.width-treewidth-persist.listwidth-2
  500.                 gpu.copy(persist.treewidth+2,screen.height,math.min(new,old),1,old - new,0)
  501.             end
  502.             drawTree(treewidth)
  503.             dirty.blocks = false
  504.         end
  505.     else
  506.         treewidth, yoffset = -1, -1
  507.         if dirty.blocks then
  508.             drawTabs()
  509.             dirty.blocks = false
  510.         end
  511.     end
  512.     if treewidth ~= persist.treewidth then
  513.         dirty.window = true
  514.         dirty.title = true
  515.         if treewidth ~= -1 then
  516.             setBackground(theme.window.color)
  517.             setForeground(theme.tree.color)
  518.             gpu.fill(treewidth+1,2,1,screen.height-2,"▌")
  519.             setBackground(theme.textbar.color)
  520.             gpu.set(treewidth+1,screen.height,"▌")
  521.             gpu.set(treewidth+1,1,"▌")
  522.         end
  523.     end
  524.     local listwidth = persist.listwidth
  525.     if dirty.nicks then
  526.         listwidth = -1
  527.         if blocks[blocks.active].names ~= nil then
  528.             local names = blocks[blocks.active].names
  529.             local prefix = blocks[blocks.active].parent.support.PREFIX or default_support.PREFIX
  530.             prefix = prefix:match("%)(.*)")
  531.             for i = 1,#names do
  532.                 listwidth=math.max(listwidth,unicode.len(names[i])+(names[i]:find("^[" .. prefix .. "]") and 0 or 1))
  533.             end
  534.             listwidth=listwidth+1
  535.             drawList(listwidth,screen.height+yoffset)
  536.         end
  537.         dirty.nicks = false
  538.     end
  539.     if listwidth ~= persist.listwidth then
  540.         dirty.window = true
  541.         dirty.title = true
  542.         setBackground(theme.tree.color)
  543.         setForeground(theme.window.color)
  544.         gpu.fill(screen.width-listwidth,2,1,screen.height-2+yoffset,"▌")
  545.         setForeground(theme.textbar.color)
  546.         gpu.set(screen.width-listwidth,screen.height+yoffset,"▌")
  547.         gpu.set(screen.width-listwidth,1,"▌")
  548.     end
  549.     if dirty.title then
  550.         local block = blocks[blocks.active]
  551.         local title
  552.         if block.title ~= nil then
  553.             title = block.title
  554.         elseif block.support and block.support.NETWORK then
  555.             title = block.support.NETWORK .. " Main Window"
  556.         else
  557.             title = block.name .. " Main Window"
  558.         end
  559.         drawTextbar(treewidth+2,1,screen.width-treewidth-listwidth-2,title)
  560.         dirty.title = false
  561.     end
  562.     if dirty.window then
  563.         drawWindow(treewidth+2,screen.width-treewidth-listwidth-2,screen.height-2+yoffset,blocks[blocks.active].text)
  564.         dirty.window = false
  565.     end
  566.     if first then
  567.         drawTextbar(treewidth+2,screen.height+yoffset,screen.width-treewidth-listwidth-2,"")
  568.     elseif listwidth < persist.listwidth then
  569.         setBackground(theme.textbar.color)
  570.         gpu.fill(screen.width-persist.listwidth,screen.height+yoffset,persist.listwidth-listwidth,1," ")
  571.     end
  572.     customGPU:gpuSetup(treewidth+2,screen.height+yoffset,screen.width-treewidth-listwidth-2,1)
  573.  
  574.     persist.window_width = screen.width-treewidth-listwidth-2
  575.     persist.window_x = treewidth+2
  576.     persist.listwidth = listwidth
  577.     persist.treewidth = treewidth
  578.  
  579.     setBackground(theme.textbar.color)
  580.     setForeground(theme.textbar.text.color)
  581. end
  582. local redrawHang = false
  583. local function redraw(first)
  584.     if not redrawHang then
  585.         local ok, err = xpcall(_redraw, debug.traceback, first)
  586.         if not ok then
  587.             redrawHang = true
  588.             gpu.setPaletteColor(theme.textbar.color,0)
  589.             gpu.setPaletteColor(theme.textbar.text.color,0xFF0000)
  590.             setForeground(theme.textbar.text.color)
  591.             setBackground(theme.textbar.color)
  592.             if config.wocchat.usetree then
  593.                 gpu.fill(1,1,screen.width,screen.height-1," ")
  594.             else
  595.             end
  596.             gpu.set(1,1,"Rendering error!")
  597.             local y = 3
  598.             err = text.detab(err) .. "\n"
  599.             for line in err:gmatch("(.-)\n") do
  600.                 gpu.set(1,y,line)
  601.                 y=y+1
  602.             end
  603.         end
  604.     end
  605. end
  606.  
  607. local helper = {}
  608. function helper.write(sock,msg)
  609.     sock:write(msg .. "\r\n")
  610.     sock:flush()
  611. end
  612. function helper.addTextToBlock(block,user,msg,color)
  613.     block.text[#block.text+1] = {user,msg,color}
  614.     if block == blocks[blocks.active] then
  615.         dirty.window = true
  616.     else
  617.         block.new = true
  618.         dirty.blocks = true
  619.     end
  620. end
  621. function helper.addText(user,msg,color)
  622.     return helper.addTextToBlock(blocks[blocks.active],user,msg,color)
  623. end
  624. function helper.markDirty(...)
  625.     for k, v in pairs({...}) do dirty[v] = true end
  626. end
  627. function helper.getSocket()
  628.     local block = blocks[blocks.active]
  629.     if block.sock then
  630.         return block.sock
  631.     elseif block.parent then
  632.         return block.parent.sock
  633.     end
  634. end
  635. function helper.joinServer(server)
  636.     local server,id = config.server[server],server
  637.     local nick = config.default.nick
  638.     local block = {type="server",name=server.name,id=id,text={},nick=nick,children={},support={}}
  639.     local err
  640.     block.sock, err = internet.open(server.server)
  641.     if not block.sock then
  642.         helper.addText("","Failed connecting to " .. server.server .. ": " .. err,theme.actions.error.color)
  643.         return
  644.     end
  645.     block.sock:setTimeout(0.05)
  646.     block.sock:write(string.format("NICK %s\r\n", config.default.nick))
  647.     block.sock:write(string.format("USER %s 0 * :%s\r\n", config.default.user, config.default.realname))
  648.     block.sock:flush()
  649.     blocks[#blocks + 1] = block
  650.     blocks.active = #blocks
  651.     helper.markDirty("blocks","window","nicks","title")
  652. end
  653. function helper.joinChannel(block,channel,switch)
  654.     local cblock = {type="channel",name=channel,text={},title="",names={},parent=block,scroll=1}
  655.     local look = block
  656.     if #block.children > 0 then
  657.         look = block.children[#block.children]
  658.     end
  659.     for i = 1,#blocks do
  660.         if blocks[i] == look then
  661.             table.insert(blocks,i+1,cblock)
  662.             look = i+1
  663.             break
  664.         end
  665.     end
  666.     block.children[#block.children+1] = cblock
  667.     if not switch then blocks.active = look end
  668.     helper.markDirty("blocks","window","nicks","title")
  669.     return cblock
  670. end
  671. function helper.findChannel(block,channel)
  672.     local children = block.children
  673.     for i = 1,#children do
  674.         if children[i].name:lower() == channel:lower() then
  675.             return children[i]
  676.         end
  677.     end
  678. end
  679. function helper.findOrJoinChannel(block,channel)
  680.     local cblock = helper.findChannel(block,channel)
  681.     if cblock then return cblock end
  682.     cblock = helper.joinChannel(block,channel,true)
  683.     cblock.title = "Dialog with " .. channel
  684.     return cblock
  685. end
  686. function helper.closeServer(block)
  687.     local ablock = blocks[blocks.active]
  688.     local relocate = false
  689.     if ablock.parent then
  690.         if ablock.parent == block then
  691.             relocate = true -- Active block is a child of the server block
  692.         end
  693.     elseif ablock == block then
  694.         relocate = true -- Active block is the server block
  695.     end
  696.     if relocate then
  697.         -- Find the block before the server block
  698.         for i = 1,#blocks do
  699.             if blocks[i] == block then
  700.                 ablock = blocks[i-1]
  701.                 break
  702.             end
  703.         end
  704.     end
  705.     -- Remove all children blocks
  706.     for i = 1,#block.children do
  707.         local cblock = block.children[i]
  708.         for i = 1,#blocks do
  709.             if blocks[i] == cblock then
  710.                 table.remove(blocks,i)
  711.                 break
  712.             end
  713.         end
  714.     end
  715.     -- Remove server block
  716.     for i = 1,#blocks do
  717.         if blocks[i] == block then
  718.             table.remove(blocks,i)
  719.             break
  720.         end
  721.     end
  722.     -- Relocate active block
  723.     for i = 1,#blocks do
  724.         if blocks[i] == ablock then
  725.             blocks.active = i
  726.             break
  727.         end
  728.     end
  729.     -- Mark window sections dirty
  730.     if relocate then
  731.         helper.markDirty("blocks","window","nicks","title")
  732.     else
  733.         dirty.blocks = true
  734.     end
  735. end
  736. function helper.closeChannel(cblock)
  737.     local block = cblock.parent
  738.     for i = 1,#block.children do
  739.         if block.children[i] == cblock then
  740.             table.remove(block.children,i)
  741.             break
  742.         end
  743.     end
  744.     local decrement = true
  745.     for i = 1,#blocks do
  746.         if blocks[i] == cblock then
  747.             table.remove(blocks,i)
  748.             break
  749.         elseif blocks[i] == blocks[blocks.active] then
  750.             decrement = false
  751.         end
  752.     end
  753.     if decrement then
  754.         blocks.active = blocks.active - 1
  755.         helper.markDirty("blocks","window","nicks","title")
  756.     else
  757.         dirty.blocks = true
  758.     end
  759. end
  760. function helper.sortList(block)
  761.     local list = block.names
  762.     local prefix = block.parent.support.PREFIX or default_support.PREFIX
  763.     prefix = prefix:match("%)(.*)")
  764.     table.sort(list,function(a,b)
  765.         local as = prefix:find(a:sub(1,1),nil,true)
  766.         local bs = prefix:find(b:sub(1,1),nil,true)
  767.         if as and bs then
  768.             if as == bs then
  769.                 return a:lower() < b:lower()
  770.             else
  771.                 return as < bs
  772.             end
  773.         elseif as then
  774.             return true
  775.         elseif bs then
  776.             return false
  777.         else
  778.             return a:lower() < b:lower()
  779.         end
  780.     end)
  781. end
  782.  
  783. local function autocreate(table, key)
  784.     table[key] = {}
  785.     return table[key]
  786. end
  787.  
  788. local function name(identity)
  789.     return identity and identity:match("^[^!]+") or identity or "Anonymous"
  790. end
  791. local whois = setmetatable({}, {__index=autocreate})
  792. local names = setmetatable({}, {__index=autocreate})
  793.  
  794. local ignore = {
  795.     [213]=true, [214]=true, [215]=true, [216]=true, [217]=true,
  796.     [218]=true, [231]=true, [232]=true, [233]=true, [240]=true,
  797.     [241]=true, [244]=true, [244]=true, [246]=true, [247]=true,
  798.     [250]=true, [300]=true, [316]=true, [361]=true, [362]=true,
  799.     [363]=true, [373]=true, [384]=true, [492]=true,
  800.     -- custom ignored responses.
  801.     [265]=true, [266]=true, [330]=true
  802. }
  803.  
  804. local replies = {
  805.     RPL_WELCOME = "001",
  806.     RPL_YOURHOST = "002",
  807.     RPL_CREATED = "003",
  808.     RPL_MYINFO = "004",
  809.     RPL_ISUPPORT = "005",
  810.     RPL_LUSERCLIENT = "251",
  811.     RPL_LUSEROP = "252",
  812.     RPL_LUSERUNKNOWN = "253",
  813.     RPL_LUSERCHANNELS = "254",
  814.     RPL_LUSERME = "255",
  815.     RPL_AWAY = "301",
  816.     RPL_UNAWAY = "305",
  817.     RPL_NOWAWAY = "306",
  818.     RPL_WHOISUSER = "311",
  819.     RPL_WHOISSERVER = "312",
  820.     RPL_WHOISOPERATOR = "313",
  821.     RPL_WHOISIDLE = "317",
  822.     RPL_ENDOFWHOIS = "318",
  823.     RPL_WHOISCHANNELS = "319",
  824.     RPL_CHANNELMODEIS = "324",
  825.     RPL_NOTOPIC = "331",
  826.     RPL_TOPIC = "332",
  827.     RPL_TOPICWHOTIME = "333",
  828.     RPL_NAMREPLY = "353",
  829.     RPL_ENDOFNAMES = "366",
  830.     RPL_MOTDSTART = "375",
  831.     RPL_MOTD = "372",
  832.     RPL_ENDOFMOTD = "376",
  833.     RPL_WHOISSECURE = "671",
  834.     RPL_HELPSTART = "704",
  835.     RPL_HELPTXT = "705",
  836.     RPL_ENDOFHELP = "706",
  837.     RPL_UMODEGMSG = "718",
  838.  
  839.     ERR_BANLISTFULL = "478",
  840.     ERR_CHANNELISFULL = "471",
  841.     ERR_UNKNOWNMODE = "472",
  842.     ERR_INVITEONLYCHAN = "473",
  843.     ERR_BANNEDFROMCHAN = "474",
  844.     ERR_CHANOPRIVSNEEDED = "482",
  845.     ERR_UNIQOPRIVSNEEDED = "485",
  846.     ERR_USERNOTINCHANNEL = "441",
  847.     ERR_NOTONCHANNEL = "442",
  848.     ERR_NICKCOLLISION = "436",
  849.     ERR_NICKNAMEINUSE = "433",
  850.     ERR_ERRONEUSNICKNAME = "432",
  851.     ERR_WASNOSUCHNICK = "406",
  852.     ERR_TOOMANYCHANNELS = "405",
  853.     ERR_CANNOTSENDTOCHAN = "404",
  854.     ERR_NOSUCHCHANNEL = "403",
  855.     ERR_NOSUCHNICK = "401",
  856.     ERR_MODELOCK = "742"
  857. }
  858.  
  859. local function handleCommand(block, prefix, command, args, message)
  860.     local nprefix = block.support.PREFIX or default_support.PREFIX
  861.     nprefix = nprefix:match("%)(.*)")
  862.     local sock = block.sock
  863.     local nick = block.nick
  864.     if command == "PING" then
  865.         helper.write(sock, string.format("PONG :%s", message))
  866.     elseif command == "NICK" then
  867.         local oldNick, newNick = name(prefix), tostring(args[1] or message)
  868.         if oldNick == nick then
  869.             block.nick = newNick
  870.         end
  871.         for i = 1,#block.children do
  872.             local cblock = block.children[i]
  873.             for i = 1,#cblock.names do
  874.                 if cblock.names[i]:gsub("^[" .. nprefix .. "]+","") == oldNick then
  875.                     helper.addTextToBlock(cblock,"*",oldNick .. " is now known as " .. newNick .. ".")
  876.                     cblock.names[i] = newNick
  877.                     helper.sortList(cblock)
  878.                     break
  879.                 end
  880.             end
  881.             if cblock == blocks[blocks.active] then
  882.                 dirty.nicks = true
  883.             end
  884.         end
  885.     elseif command == "MODE" then
  886.         local cblock = helper.findChannel(block,args[1])
  887.         if #args == 2 then
  888.             helper.addTextToBlock(cblock or block,"*",(cblock and "" or "[" .. args[1] .. "] ") .. name(prefix) .. " set mode".. ( #args[2] > 2 and "s" or "" ) .. " " .. tostring(args[2] or message) .. ".")
  889.         else
  890.             local setmode = {}
  891.             local cumode = "+"
  892.             args[2]:gsub(".", function(char)
  893.                 if char == "-" or char == "+" then
  894.                     cumode = char
  895.                 else
  896.                     table.insert(setmode, {cumode, char})
  897.                 end
  898.             end)
  899.             local d = {}
  900.             local users = {}
  901.             for i = 3, #args do
  902.                 users[i-2] = args[i]
  903.             end
  904.             users[#users+1] = message
  905.             local last
  906.             local ctxt = ""
  907.             for c = 1, #users do
  908.                 if not setmode[c] then
  909.                     break
  910.                 end
  911.                 local mode = setmode[c][2]
  912.                 local pfx = setmode[c][1]=="+"
  913.                 local key = mode == "o" and (pfx and "opped" or "deoped") or
  914.                     mode == "v" and (pfx and "voiced" or "devoiced") or
  915.                     mode == "q" and (pfx and "quieted" or "unquieted") or
  916.                     mode == "b" and (pfx and "banned" or "unbanned") or
  917.                     "set " .. setmode[c][1] .. mode .. " on"
  918.                 if last ~= key then
  919.                     if last then
  920.                         helper.addTextToBlock(cblock or block,"*",ctxt)
  921.                     end
  922.                     ctxt = (cblock and "" or "[" .. args[1] .. "] ") .. name(prefix) .. " " .. key
  923.                     last = key
  924.                 end
  925.                 ctxt = ctxt .. " " .. users[c]
  926.             end
  927.             if #ctxt > 0 then
  928.                 helper.addTextToBlock(cblock or block,"*",ctxt)
  929.             end
  930.         end
  931.     elseif command == "QUIT" then
  932.         local name = name(prefix)
  933.         for i = 1,#block.children do
  934.             local cblock = block.children[i]
  935.             for i = 1,#cblock.names do
  936.                 if cblock.names[i]:gsub("^[" .. nprefix .. "]+","") == name then
  937.                     helper.addTextToBlock(cblock,"*", name .. " quit (" .. (message or "Quit") .. ").",theme.actions.part.color)
  938.                     table.remove(cblock.names,i)
  939.                     break
  940.                 end
  941.             end
  942.             if cblock == blocks[blocks.active] then
  943.                 dirty.nicks = true
  944.             end
  945.         end
  946.     elseif command == "JOIN" then
  947.         local name = name(prefix)
  948.         if name == nick then
  949.             helper.joinChannel(block,args[1])
  950.         else
  951.             local cblock = helper.findChannel(block,args[1])
  952.             helper.addTextToBlock(cblock,"*",name .. " entered the room.",theme.actions.join.color)
  953.             table.insert(cblock.names,name)
  954.             helper.sortList(cblock)
  955.             if cblock == blocks[blocks.active] then
  956.                 dirty.nicks = true
  957.             end
  958.         end
  959.     elseif command == "PART" then
  960.         local cblock = helper.findChannel(block,args[1])
  961.         local name = name(prefix)
  962.         if name == nick then
  963.             helper.closeChannel(cblock)
  964.         else
  965.             helper.addTextToBlock(cblock,"*",name .. " has left the room (quit: " .. (message or "Quit") .. ").",theme.actions.part.color)
  966.             for i = 1,#cblock.names do
  967.                 if cblock.names[i]:gsub("^[" .. nprefix .. "]+","") == name then
  968.                     table.remove(cblock.names,i)
  969.                     break
  970.                 end
  971.             end
  972.             if cblock == blocks[blocks.active] then
  973.                 dirty.nicks = true
  974.             end
  975.         end
  976.     elseif command == "TOPIC" then
  977.         local cblock = helper.findChannel(block,args[1])
  978.         helper.addTextToBlock(cblock,"*",name(prefix) .. " has changed the topic to: " .. message)
  979.     elseif command == "KICK" then
  980.         local cblock = helper.findChannel(block,args[1])
  981.         helper.addTextToBlock(cblock,"*",name(prefix) .. " kicked " .. args[2],theme.actions.part.color)
  982.         for i = 1,#cblock.names do
  983.             if cblock.names[i]:gsub("^[" .. nprefix .. "]+","") == args[2] then
  984.                 table.remove(cblock.names,i)
  985.                 break
  986.             end
  987.         end
  988.         if cblock == blocks[blocks.active] then
  989.             dirty.nicks = true
  990.         end
  991.     elseif command == "PRIVMSG" then
  992.         local channel = args[1]
  993.         if not (block.support.CHANTYPES or default_support.CHANTYPES):find(args[1]:sub(1,1),nil,true) then
  994.             channel = name(prefix)
  995.         end
  996.         local ctcp = message:match("^\1(.-)\1$")
  997.         if ctcp then
  998.             local ctcp, param = ctcp:match("^(%S+) ?(.-)$")
  999.             ctcp = ctcp:upper()
  1000.             if ctcp ~= "ACTION" then
  1001.                 helper.addTextToBlock(block,"*","[" .. name(prefix) .. "] CTCP " .. ctcp .. " " .. param)
  1002.             end
  1003.             if ctcp == "ACTION" then
  1004.                 local cblock = helper.findOrJoinChannel(block,channel)
  1005.                 helper.addTextToBlock(cblock,"*", name(prefix) .. " " .. param)
  1006.             elseif ctcp == "TIME" then
  1007.                 helper.write(sock, "NOTICE " .. name(prefix) .. " :\001TIME " .. os.date() .. "\001")
  1008.             elseif ctcp == "VERSION" then
  1009.                 helper.write(sock, "NOTICE " .. name(prefix) .. " :\001VERSION WocChat " .. version .. " [OpenComputers]\001")
  1010.             elseif ctcp == "PING" then
  1011.                 helper.write(sock, "NOTICE " .. name(prefix) .. " :\001PING " .. param .. "\001")
  1012.             end
  1013.         else
  1014.             local cblock = helper.findOrJoinChannel(block,channel)
  1015.             if message:find(nick, nil, true) then
  1016.                 if config.wocchat.notifysound then
  1017.                     computer.beep()
  1018.                 end
  1019.                 helper.addTextToBlock(cblock, name(prefix), message, theme.actions.highlight.color)
  1020.             else
  1021.                 helper.addTextToBlock(cblock, name(prefix), message)
  1022.             end
  1023.         end
  1024.     elseif command == "NOTICE" then
  1025.         local channel = args[1]
  1026.         if not (block.support.CHANTYPES or default_support.CHANTYPES):find(args[1]:sub(1,1),nil,true) then
  1027.             channel = name(prefix)
  1028.         end
  1029.         if message:find(nick, nil, true) then
  1030.             computer.beep()
  1031.         end
  1032.         if channel == prefix then
  1033.             helper.addTextToBlock(block, "*", "[NOTICE] " .. message)
  1034.         else
  1035.             local cblock = helper.findOrJoinChannel(block,channel)
  1036.             helper.addTextToBlock(cblock, "-" .. name(prefix) .. "-", message)
  1037.         end
  1038.     elseif command == "ERROR" then
  1039.         helper.addTextToBlock(block,"*","[ERROR] " .. message)
  1040.     elseif tonumber(command) and ignore[tonumber(command)] then
  1041.     elseif command == replies.RPL_WELCOME then
  1042.         helper.addTextToBlock(block,"*",message)
  1043.         if config.server[block.id] ~= nil and config.server[block.id].channels ~= nil then
  1044.             for channel in (config.server[block.id].channels .. ","):gmatch("(.-),") do
  1045.                 sock:write(string.format("JOIN %s\r\n", channel))
  1046.             end
  1047.             sock:flush()
  1048.         end
  1049.     elseif command == replies.RPL_YOURHOST then
  1050.         helper.addTextToBlock(block,"*",message)
  1051.     elseif command == replies.RPL_CREATED then
  1052.         helper.addTextToBlock(block,"*",message)
  1053.     elseif command == replies.RPL_MYINFO then
  1054.     elseif command == replies.RPL_ISUPPORT then
  1055.         for i = 2,#args do
  1056.             if args[i]:sub(1,1) == "-" then
  1057.                 block.support[args[i]:sub(2)] = nil
  1058.             elseif args[i]:find("=",nil,true) then
  1059.                 local parameter,value = args[i]:match("(.-)=(.+)")
  1060.                 if value == "" then
  1061.                     value = true
  1062.                 end
  1063.                 block.support[parameter] = value
  1064.             else
  1065.                 block.support[args[i]] = true
  1066.             end
  1067.             if args[i] == "NETWORK" or args[i] == "-NETWORK" or args[i]:sub(1,8) == "NETWORK=" then
  1068.                 helper.markDirty("blocks","title")
  1069.             end
  1070.         end
  1071.     elseif command == replies.RPL_LUSERCLIENT then
  1072.         helper.addTextToBlock(block,"*",message)
  1073.     elseif command == replies.RPL_LUSEROP then
  1074.     elseif command == replies.RPL_LUSERUNKNOWN then
  1075.     elseif command == replies.RPL_LUSERCHANNELS then
  1076.     elseif command == replies.RPL_LUSERME then
  1077.         helper.addTextToBlock(block,"*",message)
  1078.     elseif command == replies.RPL_AWAY then
  1079.         helper.addTextToBlock(block,"*",string.format("%s is away: %s", name(args[1]), message))
  1080.     elseif command == replies.RPL_UNAWAY or command == replies.RPL_NOWAWAY then
  1081.         helper.addTextToBlock(block,"*",message)
  1082.     elseif command == replies.RPL_WHOISUSER then
  1083.         local nick = args[2]:lower()
  1084.         whois[nick].nick = args[2]
  1085.         whois[nick].user = args[3]
  1086.         whois[nick].host = args[4]
  1087.         whois[nick].realName = message
  1088.     elseif command == replies.RPL_WHOISSERVER then
  1089.         local nick = args[2]:lower()
  1090.         whois[nick].server = args[3]
  1091.         whois[nick].serverInfo = message
  1092.     elseif command == replies.RPL_WHOISOPERATOR then
  1093.         local nick = args[2]:lower()
  1094.         whois[nick].isOperator = true
  1095.     elseif command == replies.RPL_WHOISIDLE then
  1096.         local nick = args[2]:lower()
  1097.         whois[nick].idle = tonumber(args[3])
  1098.     elseif command == replies.RPL_WHOISSECURE then
  1099.         local nick = args[2]:lower()
  1100.         whois[nick].secureconn = "Is using a secure connection"
  1101.     elseif command == replies.RPL_ENDOFWHOIS then
  1102.         local nick = args[2]:lower()
  1103.         local info = whois[nick]
  1104.         if info.nick then helper.addTextToBlock(block,"*","Nick: " .. info.nick) end
  1105.         if info.user then helper.addTextToBlock(block,"*","User name: " .. info.user) end
  1106.         if info.realName then helper.addTextToBlock(block,"*","Real name: " .. info.realName) end
  1107.         if info.host then helper.addTextToBlock(block,"*","Host: " .. info.host) end
  1108.         if info.server then helper.addTextToBlock(block,"*","Server: " .. info.server .. (info.serverInfo and (" (" .. info.serverInfo .. ")") or "")) end
  1109.         if info.secureconn then helper.addTextToBlock(block,"*",info.secureconn) end
  1110.         if info.channels then helper.addTextToBlock(block,"*","Channels: " .. info.channels) end
  1111.         if info.idle then helper.addTextToBlock(block,"*","Idle for: " .. info.idle) end
  1112.         whois[nick] = nil
  1113.     elseif command == replies.RPL_WHOISCHANNELS then
  1114.         local nick = args[2]:lower()
  1115.         whois[nick].channels = message
  1116.     elseif command == replies.RPL_CHANNELMODEIS then
  1117.         helper.addTextToBlock(block,"*","Channel mode for " .. args[1] .. ": " .. args[2] .. " (" .. args[3] .. ")")
  1118.     elseif command == replies.RPL_NOTOPIC then
  1119.         local cblock = helper.findChannel(block,args[2])
  1120.         helper.addTextToBlock(cblock,"*","No topic is set for " .. args[2] .. ".",theme.actions.title.color)
  1121.     elseif command == replies.RPL_TOPIC then
  1122.         local cblock = helper.findChannel(block,args[2])
  1123.         cblock.title = message
  1124.         helper.addTextToBlock(cblock,"*","Topic for " .. args[2] .. ": " .. message,theme.actions.title.color)
  1125.         if blocks[blocks.active] == cblock then
  1126.             dirty.title = true
  1127.         end
  1128.     elseif command == replies.RPL_TOPICWHOTIME then
  1129.         local cblock = helper.findChannel(block,args[2])
  1130.         helper.addTextToBlock(cblock,"*","Topic set by " .. args[3] .. " at " .. os.date("%a %b %d %H:%M:%S %Y",tonumber(args[4])),theme.actions.title.color)
  1131.         if blocks[blocks.active] == cblock then
  1132.             dirty.title = true
  1133.         end
  1134.     elseif command == replies.RPL_NAMREPLY then
  1135.         local channel = args[3]
  1136.         for name in (message .. " "):gmatch("(.-) ") do
  1137.             table.insert(names[channel], name)
  1138.         end
  1139.     elseif command == replies.RPL_ENDOFNAMES then
  1140.         local channel = args[2]
  1141.         local cblock = helper.findChannel(block,channel)
  1142.         cblock.names = names[channel]
  1143.         helper.sortList(cblock)
  1144.         if blocks[blocks.active] == cblock then
  1145.             dirty.nicks = true
  1146.         end
  1147.         names[channel] = nil
  1148.     elseif command == replies.RPL_MOTDSTART then
  1149.         if config.wocchat.showmotd then
  1150.             helper.addTextToBlock(block,"*",message .. args[1])
  1151.         end
  1152.     elseif command == replies.RPL_MOTD then
  1153.         if config.wocchat.showmotd then
  1154.             helper.addTextToBlock(block,"*",message)
  1155.         end
  1156.     elseif command == replies.RPL_ENDOFMOTD then
  1157.     elseif command == replies.RPL_HELPSTART or
  1158.     command == replies.RPL_HELPTXT or
  1159.     command == replies.RPL_ENDOFHELP then
  1160.         helper.addTextToBlock(block,"*",message)
  1161.     elseif command == replies.ERR_BANLISTFULL or
  1162.     command == replies.ERR_BANNEDFROMCHAN or
  1163.     command == replies.ERR_CANNOTSENDTOCHAN or
  1164.     command == replies.ERR_CHANNELISFULL or
  1165.     command == replies.ERR_CHANOPRIVSNEEDED or
  1166.     command == replies.ERR_ERRONEUSNICKNAME or
  1167.     command == replies.ERR_INVITEONLYCHAN or
  1168.     command == replies.ERR_NICKCOLLISION or
  1169.     command == replies.ERR_NOSUCHNICK or
  1170.     command == replies.ERR_NOTONCHANNEL or
  1171.     command == replies.ERR_UNIQOPRIVSNEEDED or
  1172.     command == replies.ERR_UNKNOWNMODE or
  1173.     command == replies.ERR_USERNOTINCHANNEL or
  1174.     command == replies.ERR_WASNOSUCHNICK or
  1175.     command == replies.ERR_MODELOCK then
  1176.         helper.addTextToBlock(block,"*","[ERROR]: " .. message)
  1177.     elseif tonumber(command) and (tonumber(command) >= 200 and tonumber(command) < 400) then
  1178.         helper.addTextToBlock(block,"*","[Response " .. command .. "] " .. table.concat(args, ", ") .. ": " .. (message or ""))
  1179.     elseif tonumber(command) and (tonumber(command) >= 400 and tonumber(command) < 600) then
  1180.         helper.addTextToBlock(block,"*","[Error] " .. table.concat(args, ", ") .. ": " .. (message or ""))
  1181.     else
  1182.         helper.addTextToBlock(block,"*","Unhandled command: " .. command .. ": " .. (message or ""))
  1183.     end
  1184. end
  1185.  
  1186. local commands = {}
  1187. function commands.help(...)
  1188.     local names = {}
  1189.     for k,v in pairs(commands) do
  1190.         names[#names+1] = k
  1191.     end
  1192.     table.sort(names)
  1193.     helper.addText("","Commands Available: " .. table.concat(names,", "))
  1194. end
  1195. function commands.server(...)
  1196.     local args,opts = shell.parse(...)
  1197.     if #args == 0 then
  1198.         helper.addText("","Usage: /server address[:port]")
  1199.     else
  1200.         if not args[1]:find(":",nil,true) then
  1201.             args[1] = args[1] .. ":6667"
  1202.         end
  1203.         config.server["_tmpserver"] = {name=args[1]:match("(.*):"),server=args[1]}
  1204.         helper.joinServer("_tmpserver")
  1205.         config.server["_tmpserver"] = nil
  1206.         redraw()
  1207.     end
  1208. end
  1209. function commands.connect(...)
  1210.     local args,opts = shell.parse(...)
  1211.     if #args == 0 then
  1212.         helper.addText("","Usage: /connect serverid")
  1213.     elseif config.server[args[1]] == nil then
  1214.         helper.addText("","No server named '" .. args[1] .. "'",theme.actions.error.color)
  1215.     else
  1216.         helper.joinServer(args[1])
  1217.         redraw()
  1218.     end
  1219. end
  1220. function commands.join(...)
  1221.     local args = {...}
  1222.     if #args == 0 then
  1223.         helper.addText("","Usage: /join channel1 channel2 channel3 ...")
  1224.     else
  1225.         local sock = helper.getSocket()
  1226.         if sock == nil then
  1227.             helper.addText("","/join cannot be performed on this block",theme.actions.error.color)
  1228.         else
  1229.             for i = 1,#args do
  1230.                 sock:write(string.format("JOIN %s\r\n", args[i]))
  1231.             end
  1232.             sock:flush()
  1233.         end
  1234.     end
  1235. end
  1236. function commands.part(...)
  1237.     local args = {...}
  1238.     local sock = helper.getSocket()
  1239.     if blocks[blocks.active].type == "dead_channel" then
  1240.         helper.closeChannel(blocks[blocks.active])
  1241.     elseif sock == nil then
  1242.         helper.addText("","/part cannot be performed on this block",theme.actions.error.color)
  1243.     elseif #args == 0 then
  1244.         if blocks[blocks.active].type == "channel" and (blocks[blocks.active].parent.support.CHANTYPES or default_support.CHANTYPES):find(blocks[blocks.active].name:sub(1,1),nil,true) then
  1245.             helper.write(sock, string.format("PART %s", blocks[blocks.active].name))
  1246.         else
  1247.             helper.addText("","/part cannot be performed on this block",theme.actions.error.color)
  1248.         end
  1249.     else
  1250.         for i = 1,#args do
  1251.             sock:write(string.format("PART %s\r\n", args[i]))
  1252.         end
  1253.         sock:flush()
  1254.     end
  1255. end
  1256. function commands.quit(...)
  1257.     local args = {...}
  1258.     local sock = helper.getSocket()
  1259.     if sock == nil then
  1260.         helper.addText("","/quit cannot be performed on this block",theme.actions.error.color)
  1261.     else
  1262.         helper.write(sock, "QUIT :" .. (#args > 0 and table.concat(args," ") or config.default.quit))
  1263.     end
  1264. end
  1265. function commands.raw(...)
  1266.     local args = {...}
  1267.     if #args ~= 0 then
  1268.         local sock = helper.getSocket()
  1269.         if sock == nil then
  1270.             helper.addText("","/raw cannot be performed on this block",theme.actions.error.color)
  1271.         else
  1272.             helper.write(sock, string.format("%s", table.concat(args," ")))
  1273.         end
  1274.     end
  1275. end
  1276. function commands.me(...)
  1277.     local args = {...}
  1278.     if #args == 0 then
  1279.         helper.addText("","Usage: /me <action>")
  1280.     else
  1281.         local sock = helper.getSocket()
  1282.         if blocks[blocks.active].type ~= "channel" or sock == nil then
  1283.             helper.addText("","/me cannot be performed on this block",theme.actions.error.color)
  1284.         else
  1285.             helper.write(sock, string.format("PRIVMSG %s :\1ACTION %s\1", blocks[blocks.active].name, table.concat(args," ")))
  1286.             helper.addText("*",blocks[blocks.active].parent.nick .. " " .. table.concat(args," "))
  1287.         end
  1288.     end
  1289. end
  1290. function commands.redraw(...)
  1291.     local args = {...}
  1292.     if #args == 0 then
  1293.         helper.markDirty("blocks","window","nicks","title")
  1294.     else
  1295.         local good = true
  1296.         for i = 1,#args do
  1297.             if args[i] ~= "blocks" and args[i] ~= "window" and args[i] ~= "nicks" and args[i] ~= "title" then
  1298.                 helper.addText("","Invalid type '" .. args[i] .. "'",theme.actions.error.color)
  1299.                 good = false
  1300.             end
  1301.         end
  1302.         if not good then return end
  1303.         for i = 1,#args do
  1304.             dirty[args[i]] = true
  1305.         end
  1306.     end
  1307.     redraw()
  1308. end
  1309. function commands.msg(...)
  1310.     local args = {...}
  1311.     if #args < 2 then
  1312.         helper.addText("","Usage: /msg <nickname> <message>")
  1313.     else
  1314.         local sock = helper.getSocket()
  1315.         if sock == nil then
  1316.             helper.addText("","/msg cannot be performed on this block",theme.actions.error.color)
  1317.         else
  1318.             helper.write(sock, string.format("PRIVMSG %s :%s", args[1], table.concat(args," ",2)))
  1319.             local block = blocks[blocks.active]
  1320.             if block.parent ~= nil then
  1321.                 block = block.parent
  1322.             end
  1323.             if (block.support.CHANTYPES or default_support.CHANTYPES):find(args[1]:sub(1,1),nil,true) then
  1324.                 local cblock = helper.findChannel(block,args[1])
  1325.                 if cblock then
  1326.                     helper.addTextToBlock(cblock,block.nick,table.concat(args," ",2))
  1327.                 end
  1328.             else
  1329.                 local cblock = helper.findOrJoinChannel(block,args[1])
  1330.                 helper.addTextToBlock(cblock,block.nick,table.concat(args," ",2))
  1331.             end
  1332.         end
  1333.     end
  1334. end
  1335. function commands.clear(...)
  1336.     blocks[blocks.active].text = {}
  1337.     dirty.window = true
  1338. end
  1339. function commands.close(...)
  1340.     local args = {...}
  1341.     local ablock = blocks[blocks.active]
  1342.     if ablock.type == "dead_server" then
  1343.         helper.closeServer(ablock)
  1344.     elseif ablock.type == "dead_channel" then
  1345.         helper.closeChannel(ablock)
  1346.     elseif ablock.type == "channel" and not (ablock.parent.support.CHANTYPES or default_support.CHANTYPES):find(ablock.name:sub(1,1),nil,true) then
  1347.         helper.closeChannel(ablock)
  1348.     else
  1349.         helper.addText("","/close cannot be performed on this block",theme.actions.error.color)
  1350.     end
  1351. end
  1352. function commands.nick(...)
  1353.     local args = {...}
  1354.     if #args == 0 then
  1355.         helper.addText("","Usage: /nick nickname")
  1356.     else
  1357.         local sock = helper.getSocket()
  1358.         if sock == nil then
  1359.             helper.addText("","/nick cannot be performed on this block",theme.actions.error.color)
  1360.         else
  1361.             helper.write(sock, string.format("NICK %s", args[1]))
  1362.         end
  1363.     end
  1364. end
  1365.  
  1366. local loadedConfig=false
  1367. local function main()
  1368.     if not fs.exists("/etc/wocchat.cfg") or options["dl-config"] then
  1369.         print("Downloading config ...")
  1370.         local f, reason = io.open("/etc/wocchat.cfg", "wb")
  1371.         if not f then
  1372.             errprint("Failed to open file for writing: " .. reason)
  1373.             return
  1374.         end
  1375.         local result, response = pcall(internet.request, "https://raw.githubusercontent.com/OpenPrograms/gamax92-Programs/master/wocchat/wocchat.cfg")
  1376.         if result then
  1377.             local result, reason = pcall(function()
  1378.                 for chunk in response do
  1379.                     f:write(chunk)
  1380.                 end
  1381.             end)
  1382.             f:close()
  1383.             if not result then
  1384.                 fs.remove("/etc/wocchat.cfg")
  1385.                 errprint("HTTP request failed: " .. reason)
  1386.                 return
  1387.             end
  1388.         else
  1389.             f:close()
  1390.             fs.remove("/etc/wocchat.cfg")
  1391.             errprint("HTTP request failed: " .. response)
  1392.             return
  1393.         end    
  1394.     end
  1395.     print("Loading config ...")
  1396.     loadConfig()
  1397.     if config.default.nick == nil then
  1398.         while true do
  1399.             term.write("Enter your default nickname: ")
  1400.             local nick = term.read()
  1401.             if nick == nil or nick == "" then
  1402.                 return
  1403.             end
  1404.             nick = text.trim(nick)
  1405.             if nick ~= "" then
  1406.                 config.default.nick = text.trim(nick)
  1407.                 break
  1408.             else
  1409.                 print("Invalid nickname.")
  1410.             end
  1411.         end
  1412.     end
  1413.     if config.default.user == nil then
  1414.         config.default.user = config.default.nick:lower()
  1415.     end
  1416.     if config.default.realname == nil then
  1417.         config.default.realname = config.default.nick .. " [WocChat]"
  1418.     end
  1419.     -- Version upgrade assistant
  1420.     if config.wocchat.version == nil then
  1421.         config.wocchat.version = "v0.0.2"
  1422.         config.default.realname = config.default.realname:gsub("%[OpenComputers%]$", "[WocChat]")
  1423.     end
  1424.     if config.wocchat.version == "v0.0.2" then
  1425.         config.wocchat.version = "v0.0.3"
  1426.  
  1427.         config.wocchat.notifysound = true
  1428.         config["base.theme"].actions.highlight = {color = 15}
  1429.         config["base.theme"].tree.entry.prefix.last = "└─"
  1430.         config["base.theme"].tree.entry.prefix.str = "├─"
  1431.     end
  1432.     loadedConfig=true
  1433.     print("Saving screen ...")
  1434.     saveScreen()
  1435.     if config.wocchat.usetree == nil then
  1436.         config.wocchat.usetree = (screen.width > 80 and screen.height > 25)
  1437.     end
  1438.     gpu.setDepth(gpu.maxDepth())
  1439.     gpu.setForeground(0xFFFFFF)
  1440.     gpu.setBackground(0)
  1441.     gpu.fill(1,1,screen.width,screen.height," ")
  1442.     gpu.set(screen.width/2-15,screen.height/2,"Loading theme, please wait ...")
  1443.     local i=0
  1444.     while theme[i] ~= nil do
  1445.         gpu.setPaletteColor(i,theme[i])
  1446.         gpu.fill(screen.width/2-16,screen.height/2+1,i*2+2,1,"█")
  1447.         i=i+1
  1448.     end
  1449.     if newterm then
  1450.         component.proxy(term.screen()).setPrecise(false)
  1451.     else
  1452.         component.screen.setPrecise(false)
  1453.     end
  1454.     term.setCursor(1,1)
  1455.     local scrolldrag=false
  1456.     function persist.mouse(event, addr, x, y, button)
  1457.         local ok,err = pcall(function()
  1458.         if event == "touch" then
  1459.             if config.wocchat.usetree then
  1460.                 if y <= #blocks and x <= persist.window_x-2 and y ~= blocks.active then
  1461.                     blocks.active = y
  1462.                     blocks[blocks.active].new = nil
  1463.                     helper.markDirty("blocks","window","nicks","title")
  1464.                     redraw()
  1465.                 end
  1466.             else
  1467.                 if y >= screen.height and x > 1 then
  1468.                     local bx = 2
  1469.                     for i = 1,#blocks do
  1470.                         local block = blocks[i]
  1471.                         local name = block.name
  1472.                         if block.type == "server" or block.type == "dead_server" then
  1473.                             name = block.support.NETWORK or block.name
  1474.                         end
  1475.                         local bnx = bx+unicode.wlen(name)
  1476.                         if x >= bx and x <= bnx then
  1477.                             blocks.active = i
  1478.                             helper.markDirty("blocks","window","nicks","title")
  1479.                             redraw()
  1480.                             break
  1481.                         end
  1482.                         bx=bnx+1
  1483.                     end
  1484.                 end
  1485.             end
  1486.         end
  1487.         if ((event == "touch" and x >= screen.width) or (event == "drag" and scrolldrag)) and y <= (config.wocchat.usetree and screen.height or screen.height - 1) and blocks[blocks.active].names ~= nil then
  1488.             local height = (config.wocchat.usetree and screen.height or screen.height - 1)
  1489.             blocks[blocks.active].scroll = math.floor((y-1)/(height-1)*(#blocks[blocks.active].names-height)+1)
  1490.             dirty.nicks = true
  1491.             scrolldrag = true
  1492.         end
  1493.         if event == "scroll" then
  1494.             if x > screen.width-persist.listwidth and y <= screen.height+(config.wocchat.usetree and 0 or 1) then
  1495.                 blocks[blocks.active].scroll = blocks[blocks.active].scroll - button
  1496.                 dirty.nicks = true
  1497.             end
  1498.         end
  1499.         if event == "drop" then
  1500.             scrolldrag = false
  1501.         end
  1502.         end)
  1503.         if not ok then
  1504.             helper.addText("EventErr",err,theme.actions.error.color)
  1505.         end
  1506.     end
  1507.     event.listen("touch",persist.mouse)
  1508.     event.listen("drag",persist.mouse)
  1509.     event.listen("drop",persist.mouse)
  1510.     event.listen("scroll",persist.mouse)
  1511.     persist.timer = event.timer(0.5, function()
  1512.         for i = 1,#blocks do
  1513.             local block = blocks[i]
  1514.             if block.sock ~= nil then
  1515.                 local sock = block.sock
  1516.                 repeat
  1517.                     local ok, line = pcall(sock.read, sock)
  1518.                     if ok then
  1519.                         if not line then
  1520.                             helper.addTextToBlock(block,"*","Connection lost.")
  1521.                             pcall(sock.close, sock)
  1522.                             block.sock = nil
  1523.                             block.type = "dead_" .. block.type
  1524.                             for j=1,#block.children do
  1525.                                 block.children[j].type = "dead_" .. block.children[j].type
  1526.                             end
  1527.                             dirty.blocks = true
  1528.                             if blocks[blocks.active] == block then
  1529.                                 dirty.window = true
  1530.                             end
  1531.                             break
  1532.                         end
  1533.                         line = text.trim(line) -- get rid of trailing \r
  1534.                         local origline = line
  1535.                         local match, prefix = line:match("^(:(%S+) )")
  1536.                         if match then line = line:sub(#match + 1) end
  1537.                         local match, command = line:match("^(([^:]%S*))")
  1538.                         if match then line = line:sub(#match + 1) end
  1539.                         local args = {}
  1540.                         local message
  1541.                         repeat
  1542.                             local match, arg = line:match("^( ([^:]%S*))")
  1543.                             -- HACK: sometimes arguments are in the message
  1544.                             if not match and #args == 0 then
  1545.                                 message = line:match("^ :(.*)$")
  1546.                                 if message then
  1547.                                     line = " " .. message
  1548.                                     match, arg = line:match("^( ([^:]%S*))")
  1549.                                 end
  1550.                             end
  1551.                             if match then
  1552.                                 line = line:sub(#match + 1)
  1553.                                 table.insert(args, arg)
  1554.                             end
  1555.                         until not match
  1556.                         message = message or line:match("^ :(.*)$")
  1557.                         local hco, hcerr = pcall(handleCommand, block, prefix, command, args, message)
  1558.                         if not hco then
  1559.                             helper.addTextToBlock(block,"LuaError",hcerr,theme.actions.error.color)
  1560.                         end
  1561.                         if config.wocchat.showraw then
  1562.                             helper.addTextToBlock(blocks[1],"RAW",origline)
  1563.                         end
  1564.                     end
  1565.                 until not ok
  1566.             end
  1567.         end
  1568.     end, math.huge)
  1569.     persist.timer = event.timer(0.05, function()
  1570.         if dirty.blocks or dirty.title or dirty.window or dirty.nicks then
  1571.             redraw()
  1572.         end
  1573.     end, math.huge)
  1574.     redraw(true)
  1575.     for k,v in pairs(config.server) do
  1576.         if v.autojoin then
  1577.             helper.joinServer(k)
  1578.         end
  1579.     end
  1580.     local history = {nowrap=true}
  1581.     while true do
  1582.         if dirty.blocks or dirty.title or dirty.window or dirty.nicks then
  1583.             redraw()
  1584.         end
  1585.         setBackground(theme.textbar.color)
  1586.         setForeground(theme.textbar.text.color)
  1587.         local line = term.read(history,nil,function(line,pos)
  1588.             local block = blocks[blocks.active]
  1589.             if block.names == nil then
  1590.                 return {}
  1591.             end
  1592.             local nprefix = (block.parent.support.PREFIX or default_support.PREFIX):match("%)(.*)")
  1593.             local line, extra = line:sub(1,pos-1), line:sub(pos)
  1594.             local base, word = (" " .. line):match("(.*%s)(.*)")
  1595.             base,word = base:sub(2),word:lower()
  1596.             local list = {}
  1597.             for i = 1,#block.names do
  1598.                 local name = block.names[i]:gsub("^[" .. nprefix .. "]+","")
  1599.                 if name:sub(1,#word):lower() == word then
  1600.                     list[#list+1] = base .. name .. (base == "" and ": " or "") .. extra
  1601.                 end
  1602.             end
  1603.             table.sort(list,function(a,b) return a:lower() < b:lower() end)
  1604.             if #list == 1 then
  1605.                 list[2] = list[1] -- Prevent term.read stupidity
  1606.             end
  1607.             return list
  1608.         end)
  1609.         if line ~= nil then line = line:gsub("[\r\n]","") end
  1610.         if line == "/exit" then break end
  1611.         if line == "" then
  1612.         elseif line:sub(1,1) == "/" and line:sub(1,2) ~= "//" then
  1613.             local parse = {}
  1614.             for part in (line:sub(2) .. " "):gmatch("(.-) ") do
  1615.                 parse[#parse+1] = part
  1616.             end
  1617.             if commands[parse[1]] ~= nil then
  1618.                 local ok, err = pcall(commands[parse[1]], table.unpack(parse,2))
  1619.                 if not ok then
  1620.                     helper.addText("CmdError",err,theme.actions.error.color)
  1621.                 end
  1622.             else
  1623.                 local sock = helper.getSocket()
  1624.                 if sock == nil then
  1625.                     helper.addText("","/" .. parse[1] .. " cannot be performed on this block",theme.actions.error.color)
  1626.                 else
  1627.                     helper.write(sock, table.concat(parse, " "))
  1628.                 end
  1629.             end
  1630.         elseif blocks[blocks.active].type == "channel" then
  1631.             if line:sub(1,2) == "//" then
  1632.                 line = line:sub(2)
  1633.             end
  1634.             helper.write(blocks[blocks.active].parent.sock, string.format("PRIVMSG %s :%s",blocks[blocks.active].name,line))
  1635.             helper.addText(blocks[blocks.active].parent.nick,line)
  1636.         else
  1637.             helper.addText("","Cannot type on this window")
  1638.         end
  1639.     end
  1640. end
  1641.  
  1642. -- Hijack needed for term.read
  1643. local old_getPrimary
  1644. if newterm then
  1645.     local w,h,dx,dy,x,y = term.getViewport()
  1646.     local oldwindow = process.info().data.window
  1647.     local window = term.internal.open()
  1648.     window.x=x
  1649.     window.y=y
  1650.     term.bind(gpu, window)
  1651.     process.info().data.window = window
  1652.     customGPU.window = window
  1653. else
  1654.     old_getPrimary = component.getPrimary
  1655.     function component.getPrimary(componentType)
  1656.         checkArg(1, componentType, "string")
  1657.         assert(component.isAvailable(componentType), "no primary '" .. componentType .. "' available")
  1658.         if componentType == "gpu" then
  1659.             return customGPU.gpu or old_getPrimary(componentType)
  1660.         end
  1661.         return old_getPrimary(componentType)
  1662.     end
  1663. end
  1664. local stat, err = xpcall(main,debug.traceback)
  1665. if newterm then
  1666.     process.info().data.window = nil
  1667. else
  1668.     component.getPrimary = old_getPrimary
  1669. end
  1670. for i = 1,#blocks do
  1671.     if blocks[i].sock then
  1672.         pcall(blocks[i].sock.write, blocks[i].sock, "QUIT :" .. config.default.quit .. "\r\n")
  1673.         pcall(blocks[i].sock.close, blocks[i].sock)
  1674.     end
  1675. end
  1676. if persist.mouse then
  1677.     event.ignore("touch",persist.mouse)
  1678.     event.ignore("drag",persist.mouse)
  1679.     event.ignore("drop",persist.mouse)
  1680.     event.ignore("scroll",persist.mouse)
  1681. end
  1682. if persist.timer then
  1683.     event.cancel(persist.timer)
  1684. end
  1685. if screen then
  1686.     restoreScreen()
  1687. end
  1688. if loadedConfig then
  1689.     print("Saving config ...")
  1690.     local sok,srr = saveConfig()
  1691.     if not sok then
  1692.         errprint("Failed to save config: " .. srr)
  1693.     end
  1694. else
  1695.     print("Failed to load configuration, skipping save.")
  1696. end
  1697. if not stat then
  1698.     errprint(err)
  1699. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Not a member of Pastebin yet?
Sign Up, it unlocks many cool features!
 
Top