View difference between Paste ID: FckshTWR and gaSL8HZC
SHOW: | | - or go back to the newest paste.
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
452+
	print(s)
453-
		seen[win] = false
453+
454
455
-- helper
456-
		while #s > w do
456+
457-
			table.insert(win,s:sub(1,w))
457+
458-
			s = s:sub(w+1)
458+
459
	end
460-
		table.insert(win,s)
460+
461-
		while #win > BUFFERSIZE do
461+
462-
			table.remove(win,1)
462+
463
local drawWin = function(win)
464
	if legacy then
465
		local x,y = term.getCursorPos()
466
		for i = 2,h-2 do
467
			term.setCursorPos(1,i)
468
			term.clearLine()
469
		end
470
		if #win > 0 then
471
			local i = math.max(1,#win-h+4)
472
			iend = #win
473
			local row = 2
474
			while i <= iend do
475
				term.setCursorPos(1,row)
476
				local line = win[i]
477
				--[[
478
				local n = 0
479
				while #line > w do
480
					term.write(line:sub(1,w))
481
					row = row + 1
482
					n = n + 1
483
					term.setCursorPos(1,row)
484
					line = line:sub(w+1)
485
				end
486
				]]--
487
				term.write(line)
488
				row = row + 1
489
				i = i + 1
490
			end
491
		end
492
		term.setCursorPos(x,y)
493
	end
494
end
495
496
-- draw the separator with list of windows and indicators of activity
497
498
local drawSeparator = function()
499
	if legacy then
500
		local x,y = term.getCursorPos()
501
		local nwin = #winnumbers
502
		--local left = math.floor(0.5*(w-2*nwin+1))
503
		--local right = w-left-2*nwin-1
504
		term.setCursorPos(1,h-1)
505
		term.write(string.rep("-",w))
506
		local newcount = 0
507
		for i = 1,nwin do
508
			if not seen[windows[winnumbers[i]]] then
509
				newcount = newcount + 1
510
			end
511
		end
512
		local start = math.ceil(0.5*(w-(2*nwin+1+newcount)))
513
		term.setCursorPos(start,h-1)
514
		for i=1,nwin do
515
			term.write(" ")
516
			if winnumbers[i] == currentChannel then
517
				term.setBackgroundColor(colors.white)
518
				term.setTextColor(colors.black)
519
			end
520
			term.write(string.format("%d",i))
521
			if not seen[windows[winnumbers[i]]] then
522
				term.write("+")
523
			end
524
			term.setBackgroundColor(colors.black)
525
			term.setTextColor(colors.white)
526
		end
527
		term.write(" ")
528
		term.setCursorPos(x,y)
529
	end
530
end
531
532
-- top banner with channel name
533
local drawBanner = function()
534
	local x,y = term.getCursorPos()
535
	term.setCursorPos(1,1)
536
	term.clearLine()
537
	--local banner = "["..currentChannel.."]"
538
	local banner = currentChannel
539
	local start = math.ceil(0.5*(w-#banner))
540
	term.setCursorPos(start,1)
541
	term.write(banner)
542
	term.setCursorPos(x,y)
543
end
544
545
local drawInput = function()
546
	if legacy then
547
		term.setCursorPos(1,h)
548
		term.clearLine()
549
		term.write("> ")
550
	end
551
end
552
553
local handleResponse = function(data)
554
	for i = 1, #data do
555
		local id = data[i][2]
556
		if id == "PING" then
557
			pong(data[i][4][1])
558
		elseif id == "PRIVMSG" then
559
			local senderDetails = data[i][3]
560
			local sender = senderDetails:sub(1,senderDetails:find("!")-1)
561
			local channel = data[i][4][1]:lower()
562
      --print(textutils.serialize(data[i]))
563
      --print(channel)
564
			if channel == nick then
565
				channel = sender
566
				if not windows[sender] then
567
					if legacy then
568
						windows[channel] = {}
569
					end
570
					table.insert(winnumbers,channel)
571
					seen[windows[channel]] = false
572
				end
573
			end
574
			local msg = data[i][4][2]
575
			if legacy then
576
				writeToWin(windows[channel],"<"..sender.."> "..msg)
577
			end
578
			--print("<"..sender.."> "..msg)
579
		elseif id == "NICK" then
580
			if newNick ~= "" then
581
				local name = data[i][3]:sub(1,data[i][3]:find("!")-1)
582
				if name == nick then
583
					nick = newNick
584
					newNick = ""
585
				end
586
			end
587
		elseif id == "433" then
588
			writeToWin(windows["status"],"Nickname already in use!")
589
			newNick = ""
590
		elseif codes[id] then
591
			if legacy then
592
				writeToWin(windows["status"],writeLine(data[i][4],""))
593
				--print(data[i][4][2])
594
			end
595
			--print(data[i][4][2])
596
		else
597
			errormsg(writeLine(data[i],""))
598
		end
599
	end
600
end
601
602
commands = {}
603
commands["join"] = function(input)
604
  if not input[2] then
605
    errormsg("No channel specified!")
606
    return true
607
  end
608
	local channel = input[2]:lower()
609
	if channel:sub(1,1) ~= "#" then
610
		errormsg("Invalid channel name!")
611
		return true
612
	end
613
	currentChannel = channel
614
	if legacy then
615
		windows[channel] = {}
616
	end
617
	windows["current"] = windows[currentChannel]
618
	table.insert(winnumbers,channel)
619
	seen[windows[channel]] = true
620
	send("JOIN "..channel)
621
	return true
622
end
623
624
commands["who"] = function(input)
625
	local channel = input[2]
626
	send("WHO "..channel)
627
	return true
628
end
629
630
commands["whois"] = function(input)
631
	local user = input[2]
632
	send("WHOIS "..user)
633
	return true
634
end
635
636
commands["part"] = function(input)
637
	local channel = currentChannel
638
	if channel == "status" then
639
		errormsg("Can't part status window!")
640
	else
641
		local nwin
642
		for i,v in pairs(winnumbers) do
643
			if v == channel then
644
				nwin = i
645
				break
646
			end
647
		end
648
		table.remove(winnumbers,nwin)
649
		seen[windows[channel]] = nil
650
		windows[channel] = nil
651
		currentChannel = winnumbers[#winnumbers]
652
		print(currentChannel)
653
		windows["current"] = windows[currentChannel]
654
		seen[windows["current"]] = true
655
		if channel:sub(1,1) == "#" then
656
			send("PART "..channel)
657
		end
658
	end
659
	return true
660
end
661
662
commands["quit"] = function(input)
663
	quit(input[2])
664
	return false
665
end
666
667
commands["window"] = function(input)
668
	local nwin = tonumber(input[2])
669
	if not nwin then
670
		errormsg("Invalid window number!")
671
	elseif nwin > #winnumbers or nwin < 1 then
672
		errormsg("Invalid window number!")
673
	else
674
		windows["current"] = windows[winnumbers[nwin]]
675
		seen[windows["current"]] = true
676
		currentChannel = winnumbers[nwin]
677
	end
678
	return true
679
end
680
681
commands["nick"] = function(input)
682
	local nickname = input[2]
683
	if nickname then
684
		send("NICK "..nickname)
685
		newNick = nickname
686
		writeToWin(windows["status"],"Changed nick to: "..newNick)
687
	else
688
		errormsg("No nickname given.")
689
	end
690
	return true
691
end
692
693
commands["query"] = function(input)
694
	local user = input[2]
695
	if user then
696
		currentChannel = user
697
		if legacy then
698
			windows[user] = {}
699
		end
700
		windows["current"] = windows[currentChannel]
701
		table.insert(winnumbers,user)
702
		seen[windows[user]] = true
703
		--send("JOIN "..channel)
704
	else
705
		errormsg("No username given.")
706
	end
707
	return true
708
end
709
710
commands["help"] = function(input)
711
	writeToWin(windows["status"],"Available commands:")
712
	for k,v in pairs(commands) do
713
		writeToWin(windows["status"],"- "..k)
714
	end
715
	return true
716
end
717
718
local alias = {}
719
alias["w"] = "window"
720
alias["j"] = "join"
721
alias["p"] = "part"
722
alias["q"] = "quit"
723
alias["exit"] = "quit"
724
alias["e"] = "quit"
725
alias["n"] = "nick"
726
alias["qr"] = "query"
727
alias["h"] = "help"
728
729
730
local changeWindow = function(nwin)
731
	windows["current"] = windows[winnumbers[nwin]]
732
	seen[windows["current"]] = true
733
	currentChannel = winnumbers[nwin]
734
	return true
735
end
736
737
local handeInput = function(input)
738
	if not input then return true end
739
	if input:sub(1,1) == "/" then
740
		input = split(input," ")
741
		local cmd = input[1]:sub(2)
742
		if commands[cmd] then
743
			return commands[cmd](input)
744
		elseif alias[cmd] then
745
			return commands[alias[cmd]](input)
746
		else
747
			errormsg("Invalid command!")
748
		end
749
	elseif currentChannel ~= "status" then
750
			send("PRIVMSG "..currentChannel.." :"..input)
751
			writeToWin(windows["current"],"<"..nick.."> "..input)
752
	else
753
		writeToWin(windows["status"],input)
754
	end
755
	return true
756
end
757
758
local keys = {}
759
760
-- arrow keys cause problems with read() :/
761
762
--[[
763
keys[203] = function() -- LEFT
764
	local n = win2num(currentChannel)
765
	if n > 1 then
766
		changeWindow(n-1)
767
	end
768
	return true
769
end
770
771
keys[205] = function() -- RIGHT
772
	local n = win2num(currentChannel)
773
	if n < #winnumbers then
774
		changeWindow(n+1)
775
	end
776
	return true
777
end
778
]]--
779
780
-- TAB to cycle through windows
781
keys[15] = function()
782
	local n = win2num(currentChannel)
783
	if n < #winnumbers then
784
		changeWindow(n+1)
785
	else
786
		changeWindow(1)
787
	end
788
	return true
789
end
790
791
local receive = function()
792
	resp = recv()
793
	while resp do
794
		_data = resp.readAll()
795
		if #_data > 0 then
796
			data = decode(_data)
797
			handleResponse(data)
798
			while lock == "on" do
799
				sleep(0.1)
800
			end
801
			lock = "on"
802
			drawBanner()
803
			drawWin(windows["current"])
804
			drawSeparator()
805
			--drawInput()
806
			lock = "off"
807
		end
808
		resp = recv()
809
	end
810
	quitReason = "Disconnected!"
811
end
812
813
local interface = function()
814
	input = read()
815
	while handeInput(input) do
816
		while lock == "on" do
817
			sleep(0.1)
818
		end
819
		lock = "on"
820
		drawBanner()
821
		drawWin(windows["current"])
822
		drawSeparator()
823
		drawInput()
824
		lock = "off"
825
		input = read()
826
	end
827
	quitReason = "Bye!"
828
end
829
830
local specialKeys = function()
831
	while true do
832
		local ev, key = os.pullEvent("key")
833
		if keys[key] then
834
			if not keys[key]() then break end
835
			while lock == "on" do
836
				sleep(0.1)
837
			end
838
			lock = "on"
839
			drawBanner()
840
			drawWin(windows["current"])
841
			drawSeparator()
842
			--drawInput()
843
			lock = "off"
844
		end
845
	end
846
	quitReason = "Bye!"
847
end
848
849
local args = {...}
850
if #args > 0 then
851
	if args[1] == "help" then
852
		print("Usage: irc [nick] [password]")
853
		print("Edit the file to change servers.")
854
		return
855
	else
856
		nick = args[1]
857
    password = args[2]
858
	end
859
end
860
drawBanner()
861
drawWin(windows["current"])
862
drawSeparator()
863
drawInput()
864
errormsg("Connecting...")
865
resp = connect(password)
866
if not resp then exit("Unable to connect!") return end
867
_data = resp.readAll()
868
data = decode(_data)
869
sessionID = data[2]
870
errormsg("Got sessionID: "..sessionID)
871
--sleep(1)
872
parallel.waitForAny(receive,interface,specialKeys)
873
exit(quitReason)