Advertisement
hbar

irccc

Apr 18th, 2014
1,762
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 24.23 KB | None | 0 0
  1. --[[
  2. IRCCC v 0.9.2 ComputerCraft IRC Client
  3. Matti Vapa, 2014
  4. https://github.com/mattijv/IRCCC/
  5. http://pastebin.com/gaSL8HZC
  6.  
  7. This is a ComputerCraft IRC client, or more specifically a client for the qwebirc backend server.
  8. Unfortunately the HTTP API in ComputerCraft doesn't allow for a true IRC client (even though the
  9. HTTP protocol can in theory be used to communicate with an IRC server[1]). This client provides
  10. a work-around by interfacing with the qwebirc backend. qwebirc is a popular webchat interface and
  11. is relatively straightforward to work with. In addition, several IRC servers already host a webchat
  12. server, so there's no need to set up your own, although that remains a possibility if you can't
  13. find a webchat for your favourite server to connect to.
  14.  
  15. How to use:
  16.  
  17.     * Download with: pastebin get gaSL8HZC irc
  18.     * Check that the baseUrl and dynamicUrl parameters are correct. Examples are provided for espernet
  19.       and quakenet. By default the client connects to espernet.
  20.     * Start the client with:
  21.         irc
  22.  
  23. You can also supply an optional nick argument to set your nickname, or you can change your nick within the client with the standard /nick command.
  24. Currently supported commands are:
  25.  
  26. /window N           - switch to window N
  27. /join CHANNEL       - join CHANNEL
  28. /part               - part the current channel or private chat
  29. /quit               - disconnect and quit
  30. /nick NICK          - change nickname to NICK
  31. /query USER         - start a private chat with user USER
  32. /help               - list available commands
  33.  
  34.  
  35. 1. https://www.youtube.com/watch?v=2ctRfWnisSk#t=343
  36.  
  37. TODO:
  38.         - color support
  39.         - better handling of the IRC protocol
  40.         - use window API where available
  41.  
  42. This program is released under the MIT license.
  43.  
  44. ]]--
  45.  
  46.  
  47. -- look at the source of the qwebirc webchat login page and take the values
  48. -- for baseUrl and dynamicBaseUrl for use here
  49. -- examples for espernet and quakenet
  50.  
  51. local baseUrl = "http://webchat.esper.net/"
  52. local dynamicUrl = ""
  53. --local baseUrl = "http://webchat.quakenet.org/"
  54. --local dynamicUrl = "dynamic/leibniz/"
  55.  
  56.  
  57. --------------------------------------------------------------------------------
  58. --------------------------------------------------------------------------------
  59. -- JSON4Lua: JSON encoding / decoding support for the Lua language.
  60. -- json Module.
  61. -- Author: Craig Mason-Jones
  62. -- Homepage: http://json.luaforge.net/
  63. -- Version: 0.9.40
  64. -- This module is released under the MIT License (MIT).
  65.  
  66. -- edited for brevity
  67.  
  68. local base = _G
  69. local decode_scanArray
  70. local decode_scanComment
  71. local decode_scanConstant
  72. local decode_scanNumber
  73. local decode_scanObject
  74. local decode_scanString
  75. local decode_scanWhitespace
  76. local encodeString
  77. local isArray
  78. local isEncodable
  79.  
  80. local function encode (v)
  81.   -- Handle nil values
  82.   if v==nil then
  83.     return "null"
  84.   end
  85.  
  86.   local vtype = base.type(v)  
  87.  
  88.   -- Handle strings
  89.   if vtype=='string' then    
  90.     return '"' .. encodeString(v) .. '"'            -- Need to handle encoding in string
  91.   end
  92.  
  93.   -- Handle booleans
  94.   if vtype=='number' or vtype=='boolean' then
  95.     return base.tostring(v)
  96.   end
  97.  
  98.   -- Handle tables
  99.   if vtype=='table' then
  100.     local rval = {}
  101.     -- Consider arrays separately
  102.     local bArray, maxCount = isArray(v)
  103.     if bArray then
  104.       for i = 1,maxCount do
  105.         table.insert(rval, encode(v[i]))
  106.       end
  107.     else        -- An object, not an array
  108.       for i,j in base.pairs(v) do
  109.         if isEncodable(i) and isEncodable(j) then
  110.           table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
  111.         end
  112.       end
  113.     end
  114.     if bArray then
  115.       return '[' .. table.concat(rval,',') ..']'
  116.     else
  117.       return '{' .. table.concat(rval,',') .. '}'
  118.     end
  119.   end
  120.  
  121.   -- Handle null values
  122.   if vtype=='function' and v==null then
  123.     return 'null'
  124.   end
  125.  
  126.   base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
  127. end
  128.  
  129. local function decode(s, startPos)
  130.   startPos = startPos and startPos or 1
  131.   startPos = decode_scanWhitespace(s,startPos)
  132.   base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
  133.   local curChar = string.sub(s,startPos,startPos)
  134.   -- Object
  135.   if curChar=='{' then
  136.     return decode_scanObject(s,startPos)
  137.   end
  138.   -- Array
  139.   if curChar=='[' then
  140.     return decode_scanArray(s,startPos)
  141.   end
  142.   -- Number
  143.   if string.find("+-0123456789.e", curChar, 1, true) then
  144.     return decode_scanNumber(s,startPos)
  145.   end
  146.   -- String
  147.   if curChar==[["]] or curChar==[[']] then
  148.    return decode_scanString(s,startPos)
  149.  end
  150.  if string.sub(s,startPos,startPos+1)=='/*' then
  151.    return decode(s, decode_scanComment(s,startPos))
  152.  end
  153.  -- Otherwise, it must be a constant
  154.  return decode_scanConstant(s,startPos)
  155. end
  156.  
  157. local function null()
  158.  return null -- so json.null() will also return null ;-)
  159. end
  160.  
  161.  
  162. function decode_scanArray(s,startPos)
  163.  local array = {}      -- The return value
  164.  local stringLen = string.len(s)
  165.  base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
  166.  startPos = startPos + 1
  167.  -- Infinite loop for array elements
  168.  repeat
  169.    startPos = decode_scanWhitespace(s,startPos)
  170.    base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
  171.    local curChar = string.sub(s,startPos,startPos)
  172.    if (curChar==']') then
  173.      return array, startPos+1
  174.    end
  175.    if (curChar==',') then
  176.      startPos = decode_scanWhitespace(s,startPos+1)
  177.    end
  178.    base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
  179.    object, startPos = decode(s,startPos)
  180.    table.insert(array,object)
  181.  until false
  182. end
  183.  
  184. function decode_scanComment(s, startPos)
  185.  base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
  186.  local endPos = string.find(s,'*/',startPos+2)
  187.  base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
  188.  return endPos+2  
  189. end
  190.  
  191. function decode_scanConstant(s, startPos)
  192.  local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
  193.  local constNames = {"true","false","null"}
  194.  
  195.  for i,k in base.pairs(constNames) do
  196.    --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
  197.    if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
  198.      return consts[k], startPos + string.len(k)
  199.    end
  200.  end
  201.  base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
  202. end
  203.  
  204. function decode_scanNumber(s,startPos)
  205.  local endPos = startPos+1
  206.  local stringLen = string.len(s)
  207.  local acceptableChars = "+-0123456789.e"
  208.  while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
  209.        and endPos<=stringLen
  210.        ) do
  211.    endPos = endPos + 1
  212.  end
  213.  local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
  214.  local stringEval = base.loadstring(stringValue)
  215.  base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
  216.  return stringEval(), endPos
  217. end
  218.  
  219. function decode_scanObject(s,startPos)
  220.  local object = {}
  221.  local stringLen = string.len(s)
  222.  local key, value
  223.  base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
  224.  startPos = startPos + 1
  225.  repeat
  226.    startPos = decode_scanWhitespace(s,startPos)
  227.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
  228.    local curChar = string.sub(s,startPos,startPos)
  229.    if (curChar=='}') then
  230.      return object,startPos+1
  231.    end
  232.    if (curChar==',') then
  233.      startPos = decode_scanWhitespace(s,startPos+1)
  234.    end
  235.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
  236.    -- Scan the key
  237.    key, startPos = decode(s,startPos)
  238.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  239.    startPos = decode_scanWhitespace(s,startPos)
  240.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  241.    base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
  242.    startPos = decode_scanWhitespace(s,startPos+1)
  243.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  244.    value, startPos = decode(s,startPos)
  245.    object[key]=value
  246.  until false   -- infinite loop while key-value pairs are found
  247. end
  248.  
  249. function decode_scanString(s,startPos)
  250.  base.assert(startPos, 'decode_scanString(..) called without start position')
  251.  local startChar = string.sub(s,startPos,startPos)
  252.  base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
  253.   local escaped = false
  254.   local endPos = startPos + 1
  255.   local bEnded = false
  256.   local stringLen = string.len(s)
  257.   repeat
  258.     local curChar = string.sub(s,endPos,endPos)
  259.     -- Character escaping is only used to escape the string delimiters
  260.     if not escaped then
  261.       if curChar==[[\]] then
  262.         escaped = true
  263.       else
  264.         bEnded = curChar==startChar
  265.       end
  266.     else
  267.       -- If we're escaped, we accept the current character come what may
  268.       escaped = false
  269.     end
  270.     endPos = endPos + 1
  271.     base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
  272.   until bEnded
  273.   local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
  274.   local stringEval = base.loadstring(stringValue)
  275.   base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
  276.   return stringEval(), endPos  
  277. end
  278.  
  279. function decode_scanWhitespace(s,startPos)
  280.   local whitespace=" \n\r\t"
  281.   local stringLen = string.len(s)
  282.   while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
  283.     startPos = startPos + 1
  284.   end
  285.   return startPos
  286. end
  287.  
  288. function encodeString(s)
  289.   s = string.gsub(s,'\\','\\\\')
  290.   s = string.gsub(s,'"','\\"')
  291.   s = string.gsub(s,'\n','\\n')
  292.   s = string.gsub(s,'\t','\\t')
  293.   return s
  294. end
  295.  
  296. function isArray(t)
  297.   -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
  298.   -- (with the possible exception of 'n')
  299.   local maxIndex = 0
  300.   for k,v in base.pairs(t) do
  301.     if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then      -- k,v is an indexed pair
  302.       if (not isEncodable(v)) then return false end     -- All array elements must be encodable
  303.       maxIndex = math.max(maxIndex,k)
  304.     else
  305.       if (k=='n') then
  306.         if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
  307.       else -- Else of (k=='n')
  308.         if isEncodable(v) then return false end
  309.       end  -- End of (k~='n')
  310.     end -- End of k,v not an indexed pair
  311.   end  -- End of loop across all pairs
  312.   return true, maxIndex
  313. end
  314.  
  315. function isEncodable(o)
  316.   local t = base.type(o)
  317.   return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
  318. end
  319.  
  320. --------------------------------------------------------------------------------
  321. --------------------------------------------------------------------------------
  322.  
  323. -- ComputerCraft IRC client code begins here
  324.  
  325. --------------------------------------------------------------------------------
  326. --------------------------------------------------------------------------------
  327.  
  328. local defaultNick = "cc-irc-client"..tostring(math.random(1,1000))
  329. local nick = defaultNick
  330. local password = nil
  331. local newNick = ""
  332.  
  333. local counter = 0 -- used by the qwebirc protocol for something
  334. local sessionID = "" -- as is this, although a bit more self evident
  335.  
  336. local currentChannel = "status"
  337. local quitReason = ""
  338.  
  339. local BUFFERSIZE = 100 -- keep this many lines in each windows history
  340. local offset = 0 -- for scrolling, not implemented
  341.  
  342. local w,h = term.getSize()
  343. local legacy = true --(window == nil) CC v < 1.6 doesn't have the window API which would be useful, but maybe later
  344. local windows = {}
  345. local winnumbers = {}
  346. local seen = {}
  347. if not legacy then
  348.     local origTerm = term.current()
  349.     windows["input"] = window.create(origTerm,1,h-1,w,2)
  350.     windows["status"] = window.create(origTerm,1,1,w,h-2)
  351.     windows["current"] = windows["status"]
  352. else
  353.     windows["status"] = {}
  354.     windows["current"] = windows["status"]
  355. end
  356. table.insert(winnumbers,"status")
  357. seen[windows["status"]] = true
  358. local lock = "off"
  359.  
  360. local win2num = function(win)
  361.     for k,v in pairs(winnumbers) do
  362.         if v == win then return k end
  363.     end
  364.     return nil
  365. end
  366.  
  367. --os.loadAPI("json")
  368.  
  369. local post = function(url,data)
  370.     local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
  371.     local resp = http.post(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
  372.     counter = counter + 1
  373.     return resp
  374. end
  375.  
  376. -- better responsiveness with asynchronous methods as we usually let the recv couroutine handle the responses
  377. local request = function(url,data)
  378.     local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
  379.     http.request(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
  380.     counter = counter + 1
  381. end
  382.  
  383. -- these special URLs are used by the webchat server for different methods
  384. local send = function(data)
  385.     return request("p","&s="..sessionID.."&c="..textutils.urlEncode(data))
  386. end
  387.  
  388. local recv = function()
  389.     return post("s","&s="..sessionID)
  390. end
  391.  
  392. local connect = function(password)
  393.   if password ~= nil then
  394.     return post("n","&nick="..nick.."&password="..password)
  395.   else
  396.     return post("n","&nick="..nick)
  397.   end
  398. end
  399.  
  400. -- some helper functions
  401. local pong = function(data)
  402.     return send("PONG :"..data)
  403. end
  404.  
  405. local quit = function(_reason)
  406.     local reason = _reason or ""
  407.     return send("QUIT :"..reason)
  408. end
  409.  
  410. -- lua default string methods suck :/
  411. local split = function(str,sep)
  412.         local sep, fields = sep or ":", {}
  413.         local pattern = string.format("([^%s]+)", sep)
  414.         str:gsub(pattern, function(c) fields[#fields+1] = c end)
  415.         return fields
  416. end
  417.  
  418. local exit = function(reason)
  419.     local r = reason or ""
  420.     term.clear()
  421.     term.setCursorPos(1,1)
  422.     print(r)
  423. end
  424.  
  425. -- for debug
  426. local writeLine
  427. writeLine = function(data,line)
  428.     if not (type(data) == "table") then
  429.         return tostring(data)
  430.     end
  431.     for i=1,#data do
  432.         if type(data[i]) == "table" then
  433.             line = writeLine(data[i],line)
  434.         elseif data[i] ~= nick then
  435.             line = line..data[i].." "
  436.         end
  437.     end
  438.     return line
  439. end
  440.  
  441. -- some IRC protocol codes we handle "properly"
  442. local codes = {}
  443. codes["371"] = "RPL_INFO"
  444. codes["374"] = "RPL_ENDINFO"
  445. codes["375"] = "RPL_MOTDSTART"
  446. codes["372"] = "RPL_MOTD"
  447. codes["376"] = "RPL_ENDOFMOTD"
  448. codes["352"] = "RPL_WHOREPLY"
  449.  
  450. -- add lines to window
  451. local writeToWin = function(win, s)
  452.     if win ~= windows["current"] then
  453.         seen[win] = false
  454.     end
  455.     if legacy then
  456.         while #s > w do
  457.             table.insert(win,s:sub(1,w))
  458.             s = s:sub(w+1)
  459.         end
  460.         table.insert(win,s)
  461.         while #win > BUFFERSIZE do
  462.             table.remove(win,1)
  463.         end
  464.     end
  465. end
  466.  
  467. -- helper
  468. local errormsg = function(msg)
  469.     if legacy then
  470.         writeToWin(windows["status"],msg)
  471.     end
  472. end
  473.  
  474. -- draw the current window to the screen
  475. local drawWin = function(win)
  476.     if legacy then
  477.         local x,y = term.getCursorPos()
  478.         for i = 2,h-2 do
  479.             term.setCursorPos(1,i)
  480.             term.clearLine()
  481.         end
  482.         if #win > 0 then
  483.             local i = math.max(1,#win-h+4)
  484.             iend = #win
  485.             local row = 2
  486.             while i <= iend do
  487.                 term.setCursorPos(1,row)
  488.                 local line = win[i]
  489.                 --[[
  490.                 local n = 0
  491.                 while #line > w do
  492.                     term.write(line:sub(1,w))
  493.                     row = row + 1
  494.                     n = n + 1
  495.                     term.setCursorPos(1,row)
  496.                     line = line:sub(w+1)
  497.                 end
  498.                 ]]--
  499.                 term.write(line)
  500.                 row = row + 1
  501.                 i = i + 1
  502.             end
  503.         end
  504.         term.setCursorPos(x,y)
  505.     end
  506. end
  507.  
  508. -- draw the separator with list of windows and indicators of activity
  509.  
  510. local drawSeparator = function()
  511.     if legacy then
  512.         local x,y = term.getCursorPos()
  513.         local nwin = #winnumbers
  514.         --local left = math.floor(0.5*(w-2*nwin+1))
  515.         --local right = w-left-2*nwin-1
  516.         term.setCursorPos(1,h-1)
  517.         term.write(string.rep("-",w))
  518.         local newcount = 0
  519.         for i = 1,nwin do
  520.             if not seen[windows[winnumbers[i]]] then
  521.                 newcount = newcount + 1
  522.             end
  523.         end
  524.         local start = math.ceil(0.5*(w-(2*nwin+1+newcount)))
  525.         term.setCursorPos(start,h-1)
  526.         for i=1,nwin do
  527.             term.write(" ")
  528.             if winnumbers[i] == currentChannel then
  529.                 term.setBackgroundColor(colors.white)
  530.                 term.setTextColor(colors.black)
  531.             end
  532.             term.write(string.format("%d",i))
  533.             if not seen[windows[winnumbers[i]]] then
  534.                 term.write("+")
  535.             end
  536.             term.setBackgroundColor(colors.black)
  537.             term.setTextColor(colors.white)
  538.         end
  539.         term.write(" ")
  540.         term.setCursorPos(x,y)
  541.     end
  542. end
  543.  
  544. -- top banner with channel name
  545. local drawBanner = function()
  546.     local x,y = term.getCursorPos()
  547.     term.setCursorPos(1,1)
  548.     term.clearLine()
  549.     --local banner = "["..currentChannel.."]"
  550.     local banner = currentChannel
  551.     local start = math.ceil(0.5*(w-#banner))
  552.     term.setCursorPos(start,1)
  553.     term.write(banner)
  554.     term.setCursorPos(x,y)
  555. end
  556.  
  557. local drawInput = function()
  558.     if legacy then
  559.         term.setCursorPos(1,h)
  560.         term.clearLine()
  561.         term.write("> ")
  562.     end
  563. end
  564.  
  565. local handleResponse = function(data)
  566.     for i = 1, #data do
  567.         local id = data[i][2]
  568.         if id == "PING" then
  569.             pong(data[i][4][1])
  570.         elseif id == "PRIVMSG" then
  571.             local senderDetails = data[i][3]
  572.             local sender = senderDetails:sub(1,senderDetails:find("!")-1)
  573.             local channel = data[i][4][1]:lower()
  574.       --print(textutils.serialize(data[i]))
  575.       --print(channel)
  576.             if channel == nick then
  577.                 channel = sender
  578.                 if not windows[sender] then
  579.                     if legacy then
  580.                         windows[channel] = {}
  581.                     end
  582.                     table.insert(winnumbers,channel)
  583.                     seen[windows[channel]] = false
  584.                 end
  585.             end
  586.             local msg = data[i][4][2]
  587.             if legacy then
  588.                 writeToWin(windows[channel],"<"..sender.."> "..msg)
  589.             end
  590.             --print("<"..sender.."> "..msg)
  591.         elseif id == "NICK" then
  592.             if newNick ~= "" then
  593.                 local name = data[i][3]:sub(1,data[i][3]:find("!")-1)
  594.                 if name == nick then
  595.                     nick = newNick
  596.                     newNick = ""
  597.                 end
  598.             end
  599.         elseif id == "433" then
  600.             writeToWin(windows["status"],"Nickname already in use!")
  601.             newNick = ""
  602.         elseif codes[id] then
  603.             if legacy then
  604.                 writeToWin(windows["status"],writeLine(data[i][4],""))
  605.                 --print(data[i][4][2])
  606.             end
  607.             --print(data[i][4][2])
  608.         else
  609.             errormsg(writeLine(data[i],""))
  610.         end
  611.     end
  612. end
  613.  
  614. commands = {}
  615. commands["join"] = function(input)
  616.   if not input[2] then
  617.     errormsg("No channel specified!")
  618.     return true
  619.   end
  620.     local channel = input[2]:lower()
  621.     if channel:sub(1,1) ~= "#" then
  622.         errormsg("Invalid channel name!")
  623.         return true
  624.     end
  625.     currentChannel = channel
  626.     if legacy then
  627.         windows[channel] = {}
  628.     end
  629.     windows["current"] = windows[currentChannel]
  630.     table.insert(winnumbers,channel)
  631.     seen[windows[channel]] = true
  632.     send("JOIN "..channel)
  633.     return true
  634. end
  635.  
  636. commands["who"] = function(input)
  637.     local channel = input[2]
  638.     send("WHO "..channel)
  639.     return true
  640. end
  641.  
  642. commands["whois"] = function(input)
  643.     local user = input[2]
  644.     send("WHOIS "..user)
  645.     return true
  646. end
  647.  
  648. commands["part"] = function(input)
  649.     local channel = currentChannel
  650.     if channel == "status" then
  651.         errormsg("Can't part status window!")
  652.     else
  653.         local nwin
  654.         for i,v in pairs(winnumbers) do
  655.             if v == channel then
  656.                 nwin = i
  657.                 break
  658.             end
  659.         end
  660.         table.remove(winnumbers,nwin)
  661.         seen[windows[channel]] = nil
  662.         windows[channel] = nil
  663.         currentChannel = winnumbers[#winnumbers]
  664.         print(currentChannel)
  665.         windows["current"] = windows[currentChannel]
  666.         seen[windows["current"]] = true
  667.         if channel:sub(1,1) == "#" then
  668.             send("PART "..channel)
  669.         end
  670.     end
  671.     return true
  672. end
  673.  
  674. commands["quit"] = function(input)
  675.     quit(input[2])
  676.     return false
  677. end
  678.  
  679. commands["window"] = function(input)
  680.     local nwin = tonumber(input[2])
  681.     if not nwin then
  682.         errormsg("Invalid window number!")
  683.     elseif nwin > #winnumbers or nwin < 1 then
  684.         errormsg("Invalid window number!")
  685.     else
  686.         windows["current"] = windows[winnumbers[nwin]]
  687.         seen[windows["current"]] = true
  688.         currentChannel = winnumbers[nwin]
  689.     end
  690.     return true
  691. end
  692.  
  693. commands["nick"] = function(input)
  694.     local nickname = input[2]
  695.     if nickname then
  696.         send("NICK "..nickname)
  697.         newNick = nickname
  698.         writeToWin(windows["status"],"Changed nick to: "..newNick)
  699.     else
  700.         errormsg("No nickname given.")
  701.     end
  702.     return true
  703. end
  704.  
  705. commands["query"] = function(input)
  706.     local user = input[2]
  707.     if user then
  708.         currentChannel = user
  709.         if legacy then
  710.             windows[user] = {}
  711.         end
  712.         windows["current"] = windows[currentChannel]
  713.         table.insert(winnumbers,user)
  714.         seen[windows[user]] = true
  715.         --send("JOIN "..channel)
  716.     else
  717.         errormsg("No username given.")
  718.     end
  719.     return true
  720. end
  721.  
  722. commands["help"] = function(input)
  723.     writeToWin(windows["status"],"Available commands:")
  724.     for k,v in pairs(commands) do
  725.         writeToWin(windows["status"],"- "..k)
  726.     end
  727.     return true
  728. end
  729.  
  730. local alias = {}
  731. alias["w"] = "window"
  732. alias["j"] = "join"
  733. alias["p"] = "part"
  734. alias["q"] = "quit"
  735. alias["exit"] = "quit"
  736. alias["e"] = "quit"
  737. alias["n"] = "nick"
  738. alias["qr"] = "query"
  739. alias["h"] = "help"
  740.  
  741.  
  742. local changeWindow = function(nwin)
  743.     windows["current"] = windows[winnumbers[nwin]]
  744.     seen[windows["current"]] = true
  745.     currentChannel = winnumbers[nwin]
  746.     return true
  747. end
  748.  
  749. local handeInput = function(input)
  750.     if not input then return true end
  751.     if input:sub(1,1) == "/" then
  752.         input = split(input," ")
  753.         local cmd = input[1]:sub(2)
  754.         if commands[cmd] then
  755.             return commands[cmd](input)
  756.         elseif alias[cmd] then
  757.             return commands[alias[cmd]](input)
  758.         else
  759.             errormsg("Invalid command!")
  760.         end
  761.     elseif currentChannel ~= "status" then
  762.             send("PRIVMSG "..currentChannel.." :"..input)
  763.             writeToWin(windows["current"],"<"..nick.."> "..input)
  764.     else
  765.         writeToWin(windows["status"],input)
  766.     end
  767.     return true
  768. end
  769.  
  770. local keys = {}
  771.  
  772. -- arrow keys cause problems with read() :/
  773.  
  774. --[[
  775. keys[203] = function() -- LEFT
  776.     local n = win2num(currentChannel)
  777.     if n > 1 then
  778.         changeWindow(n-1)
  779.     end
  780.     return true
  781. end
  782.  
  783. keys[205] = function() -- RIGHT
  784.     local n = win2num(currentChannel)
  785.     if n < #winnumbers then
  786.         changeWindow(n+1)
  787.     end
  788.     return true
  789. end
  790. ]]--
  791.  
  792. -- TAB to cycle through windows
  793. keys[15] = function()
  794.     local n = win2num(currentChannel)
  795.     if n < #winnumbers then
  796.         changeWindow(n+1)
  797.     else
  798.         changeWindow(1)
  799.     end
  800.     return true
  801. end
  802.  
  803. local receive = function()
  804.     resp = recv()
  805.     while resp do
  806.         _data = resp.readAll()
  807.         if #_data > 0 then
  808.             data = decode(_data)
  809.             handleResponse(data)
  810.             while lock == "on" do
  811.                 sleep(0.1)
  812.             end
  813.             lock = "on"
  814.             drawBanner()
  815.             drawWin(windows["current"])
  816.             drawSeparator()
  817.             --drawInput()
  818.             lock = "off"
  819.         end
  820.         resp = recv()
  821.     end
  822.     quitReason = "Disconnected!"
  823. end
  824.  
  825. local interface = function()
  826.     input = read()
  827.     while handeInput(input) do
  828.         while lock == "on" do
  829.             sleep(0.1)
  830.         end
  831.         lock = "on"
  832.         drawBanner()
  833.         drawWin(windows["current"])
  834.         drawSeparator()
  835.         drawInput()
  836.         lock = "off"
  837.         input = read()
  838.     end
  839.     quitReason = "Bye!"
  840. end
  841.  
  842. local specialKeys = function()
  843.     while true do
  844.         local ev, key = os.pullEvent("key")
  845.         if keys[key] then
  846.             if not keys[key]() then break end
  847.             while lock == "on" do
  848.                 sleep(0.1)
  849.             end
  850.             lock = "on"
  851.             drawBanner()
  852.             drawWin(windows["current"])
  853.             drawSeparator()
  854.             --drawInput()
  855.             lock = "off"
  856.         end
  857.     end
  858.     quitReason = "Bye!"
  859. end
  860.  
  861. local args = {...}
  862. if #args > 0 then
  863.     if args[1] == "help" then
  864.         print("Usage: irc [nick] [password]")
  865.         print("Edit the file to change servers.")
  866.         return
  867.     else
  868.         nick = args[1]
  869.     password = args[2]
  870.     end
  871. end
  872. drawBanner()
  873. drawWin(windows["current"])
  874. drawSeparator()
  875. drawInput()
  876. errormsg("Connecting...")
  877. resp = connect(password)
  878. if not resp then exit("Unable to connect!") return end
  879. _data = resp.readAll()
  880. data = decode(_data)
  881. sessionID = data[2]
  882. errormsg("Got sessionID: "..sessionID)
  883. --sleep(1)
  884. parallel.waitForAny(receive,interface,specialKeys)
  885. exit(quitReason)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement