View difference between Paste ID: iExcMj7b and gaSL8HZC
SHOW: | | - or go back to the newest paste.
1-
--[[
1+
local baseUrl = "http:/86.11.11.101:9090/"
2-
IRCCC v 0.9.2 ComputerCraft IRC Client
2+
3-
Matti Vapa, 2014
3+
4-
https://github.com/mattijv/IRCCC/
4+
local nick = "rockettek"
5-
http://pastebin.com/gaSL8HZC
5+
local password = "oauth:c306b08cc06j7foa7uzxez63fyiff4"
6
7-
This is a ComputerCraft IRC client, or more specifically a client for the qwebirc backend server.
7+
8-
Unfortunately the HTTP API in ComputerCraft doesn't allow for a true IRC client (even though the
8+
9-
HTTP protocol can in theory be used to communicate with an IRC server[1]). This client provides
9+
10-
a work-around by interfacing with the qwebirc backend. qwebirc is a popular webchat interface and
10+
11-
is relatively straightforward to work with. In addition, several IRC servers already host a webchat
11+
12-
server, so there's no need to set up your own, although that remains a possibility if you can't
12+
13-
find a webchat for your favourite server to connect to.
13+
14
-- This module is released under the MIT License (MIT).
15-
How to use:
15+
16
-- edited for brevity
17-
	* Download with: pastebin get gaSL8HZC irc
17+
18-
	* Check that the baseUrl and dynamicUrl parameters are correct. Examples are provided for espernet
18+
19-
	  and quakenet. By default the client connects to espernet.
19+
20-
	* Start the client with:
20+
21-
		irc
21+
22
local decode_scanNumber
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.
23+
24-
Currently supported commands are:
24+
25
local decode_scanWhitespace
26-
/window N			- switch to window N
26+
27-
/join CHANNEL		- join CHANNEL
27+
28-
/part				- part the current channel or private chat
28+
29-
/quit 				- disconnect and quit
29+
30-
/nick NICK			- change nickname to NICK
30+
31-
/query USER 		- start a private chat with user USER
31+
32-
/help 				- list available commands
32+
33
    return "null"
34
  end
35-
1. https://www.youtube.com/watch?v=2ctRfWnisSk#t=343
35+
36
  local vtype = base.type(v)  
37-
TODO:
37+
38-
		- color support
38+
39-
		- better handling of the IRC protocol
39+
40-
		- use window API where available
40+
41
  end
42-
This program is released under the MIT license.
42+
43
  -- Handle booleans
44-
]]--
44+
45
    return base.tostring(v)
46
  end
47-
-- look at the source of the qwebirc webchat login page and take the values
47+
48-
-- for baseUrl and dynamicBaseUrl for use here
48+
49-
-- examples for espernet and quakenet
49+
50
    local rval = {}
51-
local baseUrl = "http://webchat.esper.net/"
51+
52
    local bArray, maxCount = isArray(v)
53-
--local baseUrl = "http://webchat.quakenet.org/"
53+
54-
--local dynamicUrl = "dynamic/leibniz/"
54+
55
        table.insert(rval, encode(v[i]))
56
      end
57
    else        -- An object, not an array
58
      for i,j in base.pairs(v) do
59
        if isEncodable(i) and isEncodable(j) then
60
          table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
61
        end
62
      end
63
    end
64
    if bArray then
65
      return '[' .. table.concat(rval,',') ..']'
66
    else
67
      return '{' .. table.concat(rval,',') .. '}'
68
    end
69
  end
70
 
71
  -- Handle null values
72
  if vtype=='function' and v==null then
73
    return 'null'
74
  end
75
 
76
  base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
77
end
78
 
79
local function decode(s, startPos)
80
  startPos = startPos and startPos or 1
81
  startPos = decode_scanWhitespace(s,startPos)
82
  base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
83
  local curChar = string.sub(s,startPos,startPos)
84
  -- Object
85
  if curChar=='{' then
86
    return decode_scanObject(s,startPos)
87
  end
88
  -- Array
89
  if curChar=='[' then
90
    return decode_scanArray(s,startPos)
91
  end
92
  -- Number
93
  if string.find("+-0123456789.e", curChar, 1, true) then
94
    return decode_scanNumber(s,startPos)
95
  end
96
  -- String
97
  if curChar==[["]] or curChar==[[']] then
98
    return decode_scanString(s,startPos)
99
  end
100
  if string.sub(s,startPos,startPos+1)=='/*' then
101
    return decode(s, decode_scanComment(s,startPos))
102
  end
103
  -- Otherwise, it must be a constant
104
  return decode_scanConstant(s,startPos)
105
end
106
 
107
local function null()
108
  return null -- so json.null() will also return null ;-)
109
end
110
 
111
 
112
function decode_scanArray(s,startPos)
113
  local array = {}      -- The return value
114
  local stringLen = string.len(s)
115
  base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
116
  startPos = startPos + 1
117
  -- Infinite loop for array elements
118
  repeat
119
    startPos = decode_scanWhitespace(s,startPos)
120
    base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
121
    local curChar = string.sub(s,startPos,startPos)
122
    if (curChar==']') then
123
      return array, startPos+1
124
    end
125
    if (curChar==',') then
126
      startPos = decode_scanWhitespace(s,startPos+1)
127
    end
128
    base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
129
    object, startPos = decode(s,startPos)
130
    table.insert(array,object)
131
  until false
132
end
133
 
134
function decode_scanComment(s, startPos)
135
  base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
136
  local endPos = string.find(s,'*/',startPos+2)
137
  base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
138
  return endPos+2  
139
end
140
 
141
function decode_scanConstant(s, startPos)
142
  local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
143
  local constNames = {"true","false","null"}
144
 
145
  for i,k in base.pairs(constNames) do
146
    --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
147
    if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
148
      return consts[k], startPos + string.len(k)
149
    end
150
  end
151
  base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
152
end
153
 
154
function decode_scanNumber(s,startPos)
155
  local endPos = startPos+1
156
  local stringLen = string.len(s)
157
  local acceptableChars = "+-0123456789.e"
158
  while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
159
        and endPos<=stringLen
160
        ) do
161
    endPos = endPos + 1
162
  end
163
  local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
164
  local stringEval = base.loadstring(stringValue)
165
  base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
166
  return stringEval(), endPos
167
end
168
 
169
function decode_scanObject(s,startPos)
170
  local object = {}
171
  local stringLen = string.len(s)
172
  local key, value
173
  base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
174
  startPos = startPos + 1
175
  repeat
176
    startPos = decode_scanWhitespace(s,startPos)
177
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
178
    local curChar = string.sub(s,startPos,startPos)
179
    if (curChar=='}') then
180
      return object,startPos+1
181
    end
182
    if (curChar==',') then
183
      startPos = decode_scanWhitespace(s,startPos+1)
184
    end
185
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
186
    -- Scan the key
187
    key, startPos = decode(s,startPos)
188
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
189
    startPos = decode_scanWhitespace(s,startPos)
190
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
191
    base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
192
    startPos = decode_scanWhitespace(s,startPos+1)
193
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
194
    value, startPos = decode(s,startPos)
195
    object[key]=value
196
  until false   -- infinite loop while key-value pairs are found
197
end
198
 
199
function decode_scanString(s,startPos)
200
  base.assert(startPos, 'decode_scanString(..) called without start position')
201
  local startChar = string.sub(s,startPos,startPos)
202
  base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
203
  local escaped = false
204
  local endPos = startPos + 1
205
  local bEnded = false
206
  local stringLen = string.len(s)
207
  repeat
208
    local curChar = string.sub(s,endPos,endPos)
209
    -- Character escaping is only used to escape the string delimiters
210
    if not escaped then
211
      if curChar==[[\]] then
212
        escaped = true
213
      else
214
        bEnded = curChar==startChar
215
      end
216
    else
217
      -- If we're escaped, we accept the current character come what may
218
      escaped = false
219
    end
220
    endPos = endPos + 1
221
    base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
222
  until bEnded
223
  local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
224
  local stringEval = base.loadstring(stringValue)
225
  base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
226
  return stringEval(), endPos  
227
end
228
 
229
function decode_scanWhitespace(s,startPos)
230
  local whitespace=" \n\r\t"
231
  local stringLen = string.len(s)
232
  while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
233
    startPos = startPos + 1
234
  end
235
  return startPos
236
end
237
 
238
function encodeString(s)
239
  s = string.gsub(s,'\\','\\\\')
240
  s = string.gsub(s,'"','\\"')
241
  s = string.gsub(s,'\n','\\n')
242
  s = string.gsub(s,'\t','\\t')
243
  return s
244
end
245
 
246
function isArray(t)
247
  -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
248
  -- (with the possible exception of 'n')
249
  local maxIndex = 0
250
  for k,v in base.pairs(t) do
251
    if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then      -- k,v is an indexed pair
252
      if (not isEncodable(v)) then return false end     -- All array elements must be encodable
253
      maxIndex = math.max(maxIndex,k)
254
    else
255
      if (k=='n') then
256
        if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
257
      else -- Else of (k=='n')
258
        if isEncodable(v) then return false end
259
      end  -- End of (k~='n')
260
    end -- End of k,v not an indexed pair
261
  end  -- End of loop across all pairs
262
  return true, maxIndex
263
end
264
 
265
function isEncodable(o)
266
  local t = base.type(o)
267
  return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
268
end
269
270
--------------------------------------------------------------------------------
271
--------------------------------------------------------------------------------
272
273
-- ComputerCraft IRC client code begins here
274
275
--------------------------------------------------------------------------------
276
--------------------------------------------------------------------------------
277
278
local defaultNick = "cc-irc-client"..tostring(math.random(1,1000))
279
local nick = defaultNick
280
local password = nil
281
local newNick = ""
282
283
local counter = 0 -- used by the qwebirc protocol for something
284
local sessionID = "" -- as is this, although a bit more self evident
285
286
local currentChannel = "status"
287
local quitReason = ""
288
289
local BUFFERSIZE = 100 -- keep this many lines in each windows history
290
local offset = 0 -- for scrolling, not implemented
291
292
local win2num = function(win)
293
	for k,v in pairs(winnumbers) do
294
		if v == win then return k end
295
	end
296
	return nil
297
end
298
299
--os.loadAPI("json")
300
301
local post = function(url,data)
302
	local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
303
	local resp = http.post(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
304
	counter = counter + 1
305
	return resp
306
end
307
308
-- better responsiveness with asynchronous methods as we usually let the recv couroutine handle the responses
309
local request = function(url,data)
310
	local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
311
	http.request(baseUrl..dynamicUrl.."e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
312
	counter = counter + 1
313
end
314
315
-- these special URLs are used by the webchat server for different methods
316
local send = function(data)
317
	return request("p","&s="..sessionID.."&c="..textutils.urlEncode(data))
318
end
319
320
local recv = function()
321
	return post("s","&s="..sessionID)
322
end
323
324
local connect = function(password)
325
  if password ~= nil then
326
  	return post("n","&nick="..nick.."&password="..password)
327
  else
328
    return post("n","&nick="..nick)
329
  end
330
end
331
332
-- some helper functions
333
local pong = function(data)
334
	return send("PONG :"..data)
335
end
336
337
local quit = function(_reason)
338
	local reason = _reason or ""
339
	return send("QUIT :"..reason)
340
end
341
342-
local w,h = term.getSize()
342+
343-
local legacy = true --(window == nil) CC v < 1.6 doesn't have the window API which would be useful, but maybe later
343+
344-
local windows = {}
344+
345-
local winnumbers = {}
345+
346-
local seen = {}
346+
347-
if not legacy then
347+
348-
	local origTerm = term.current()
348+
349-
	windows["input"] = window.create(origTerm,1,h-1,w,2)
349+
350-
	windows["status"] = window.create(origTerm,1,1,w,h-2)
350+
351-
	windows["current"] = windows["status"]
351+
352-
else
352+
353-
	windows["status"] = {}
353+
354-
	windows["current"] = windows["status"]
354+
355
end
356-
table.insert(winnumbers,"status")
356+
357-
seen[windows["status"]] = true
357+
358-
local lock = "off"
358+
359
writeLine = function(data,line)
360
	if not (type(data) == "table") then
361
		return tostring(data)
362
	end
363
	for i=1,#data do
364
		if type(data[i]) == "table" then
365
			line = writeLine(data[i],line)
366
		elseif data[i] ~= nick then
367
			line = line..data[i].." "
368
		end
369
	end
370
	return line
371
end
372
373
-- some IRC protocol codes we handle "properly"
374
local codes = {}
375
codes["371"] = "RPL_INFO"
376
codes["374"] = "RPL_ENDINFO"
377
codes["375"] = "RPL_MOTDSTART"
378
codes["372"] = "RPL_MOTD"
379
codes["376"] = "RPL_ENDOFMOTD"
380
codes["352"] = "RPL_WHOREPLY"
381
382
-- add lines to window
383
local writeToWin = function(win, s)
384
	if win ~= windows["current"] then
385
		seen[win] = false
386
	end
387
	if legacy then
388
		while #s > w do
389
			table.insert(win,s:sub(1,w))
390
			s = s:sub(w+1)
391
		end
392
		table.insert(win,s)
393
		while #win > BUFFERSIZE do
394
			table.remove(win,1)
395
		end
396
	end
397
end
398
399
-- helper
400
local errormsg = function(msg)
401
	if legacy then
402
		print(msg)
403
	end
404
end
405
406
local handleResponse = function(data)
407
	for i = 1, #data do
408
		local id = data[i][2]
409
		if id == "PING" then
410
			pong(data[i][4][1])
411
		elseif id == "PRIVMSG" then
412
			local senderDetails = data[i][3]
413
			local sender = senderDetails:sub(1,senderDetails:find("!")-1)
414
			local channel = data[i][4][1]:lower()
415
      --print(textutils.serialize(data[i]))
416
      --print(channel)
417
			if channel == nick then
418
				channel = sender
419
			end
420
			local msg = data[i][4][2]
421
			print("<"..sender.."> "..msg)
422
		elseif id == "NICK" then
423
			if newNick ~= "" then
424
				local name = data[i][3]:sub(1,data[i][3]:find("!")-1)
425
				if name == nick then
426
					nick = newNick
427
					newNick = ""
428
				end
429
			end
430
		elseif id == "433" then
431
			print("Nickname already in use!")
432
			newNick = ""
433
		elseif codes[id] then
434
			--[[if legacy then
435
				print(writeLine(data[i][4],""))
436
				--print(data[i][4][2])
437
			end]]
438
			print(data[i][4][2])
439
		else
440
			print(writeLine(data[i],""))
441
		end
442
	end
443
end
444
445
commands = {}
446
commands["join"] = function(input)
447
  if not input[2] then
448
    print("No channel specified!")
449
    return true
450
  end
451
	local channel = input[2]:lower()
452
	if channel:sub(1,1) ~= "#" then
453
		errormsg("Invalid channel name!")
454
		return true
455
	end
456
	currentChannel = channel
457
	send("JOIN "..channel)
458
	return true
459
end
460
461
local alias = {}
462
alias["j"] = "join"
463
464
local receive = function()
465
	resp = recv()
466
	while resp do
467
		_data = resp.readAll()
468
		if #_data > 0 then
469
			data = decode(_data)
470-
		writeToWin(windows["status"],msg)
470+
471
			while lock == "on" do
472
				sleep(0.1)
473
			end
474-
-- draw the current window to the screen
474+
475-
local drawWin = function(win)
475+
476
			drawWin(windows["current"])
477-
		local x,y = term.getCursorPos()
477+
478-
		for i = 2,h-2 do
478+
479-
			term.setCursorPos(1,i)
479+
480-
			term.clearLine()
480+
481
		resp = recv()
482-
		if #win > 0 then
482+
483-
			local i = math.max(1,#win-h+4)
483+
484-
			iend = #win
484+
485-
			local row = 2
485+
print("Connecting...")
486-
			while i <= iend do
486+
487-
				term.setCursorPos(1,row)
487+
488-
				local line = win[i]
488+
489-
				--[[
489+
490-
				local n = 0
490+
491-
				while #line > w do
491+
492-
					term.write(line:sub(1,w))
492+
return commands[alias["j"]]("#rockettek")
493-
					row = row + 1
493+
interface()