View difference between Paste ID: cWSt5dDX and gaSL8HZC
SHOW: | | - or go back to the newest paste.
1
local nick = "rockettek"
2-
IRCCC v 0.9.2 ComputerCraft IRC Client
2+
local password = "oauth:c306b08cc06j7foa7uzxez63fyiff4"
3-
Matti Vapa, 2014
3+
4-
https://github.com/mattijv/IRCCC/
4+
5-
http://pastebin.com/gaSL8HZC
5+
6
local decode_scanComment
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
local isEncodable
15-
How to use:
15+
local counter = 0
16
local sessionID = ""
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
  end
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
 
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 base.tostring(v)
34
  end
35-
1. https://www.youtube.com/watch?v=2ctRfWnisSk#t=343
35+
36
  -- Handle tables
37-
TODO:
37+
38-
		- color support
38+
39-
		- better handling of the IRC protocol
39+
40-
		- use window API where available
40+
41
    if bArray then
42-
This program is released under the MIT license.
42+
43
        table.insert(rval, encode(v[i]))
44-
]]--
44+
45
    else        -- An object, not an array
46
      for i,j in base.pairs(v) do
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
      end
51-
local baseUrl = "http://webchat.esper.net/"
51+
52-
local dynamicUrl = ""
52+
53-
--local baseUrl = "http://webchat.quakenet.org/"
53+
54-
--local dynamicUrl = "dynamic/leibniz/"
54+
55
      return '{' .. table.concat(rval,',') .. '}'
56
    end
57-
--------------------------------------------------------------------------------
57+
58-
--------------------------------------------------------------------------------
58+
59-
-- JSON4Lua: JSON encoding / decoding support for the Lua language.
59+
60-
-- json Module.
60+
61-
-- Author: Craig Mason-Jones
61+
62-
-- Homepage: http://json.luaforge.net/
62+
63-
-- Version: 0.9.40
63+
64-
-- This module is released under the MIT License (MIT).
64+
65
end
66-
-- edited for brevity
66+
67
local function decode(s, startPos)
68
  startPos = startPos and startPos or 1
69
  startPos = decode_scanWhitespace(s,startPos)
70
  base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
71
  local curChar = string.sub(s,startPos,startPos)
72
  -- Object
73
  if curChar=='{' then
74
    return decode_scanObject(s,startPos)
75
  end
76
  -- Array
77
  if curChar=='[' then
78
    return decode_scanArray(s,startPos)
79
  end
80
  -- Number
81
  if string.find("+-0123456789.e", curChar, 1, true) then
82
    return decode_scanNumber(s,startPos)
83
  end
84
  -- String
85
  if curChar==[["]] or curChar==[[']] then
86
    return decode_scanString(s,startPos)
87
  end
88
  if string.sub(s,startPos,startPos+1)=='/*' then
89
    return decode(s, decode_scanComment(s,startPos))
90
  end
91
  -- Otherwise, it must be a constant
92
  return decode_scanConstant(s,startPos)
93
end
94
local function null()
95
  return null -- so json.null() will also return null ;-)
96
end
97
function decode_scanArray(s,startPos)
98
  local array = {}      -- The return value
99
  local stringLen = string.len(s)
100
  base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
101
  startPos = startPos + 1
102
  -- Infinite loop for array elements
103
  repeat
104
    startPos = decode_scanWhitespace(s,startPos)
105
    base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
106
    local curChar = string.sub(s,startPos,startPos)
107
    if (curChar==']') then
108
      return array, startPos+1
109
    end
110
    if (curChar==',') then
111
      startPos = decode_scanWhitespace(s,startPos+1)
112
    end
113
    base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
114
    object, startPos = decode(s,startPos)
115
    table.insert(array,object)
116
  until false
117
end
118
function decode_scanComment(s, startPos)
119
  base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
120
  local endPos = string.find(s,'*/',startPos+2)
121
  base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
122
  return endPos+2  
123
end
124
 
125
function decode_scanConstant(s, startPos)
126
  local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
127
  local constNames = {"true","false","null"}
128
 
129
  for i,k in base.pairs(constNames) do
130
    --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
131
    if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
132
      return consts[k], startPos + string.len(k)
133
    end
134
  end
135
  base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
136
end
137
function decode_scanNumber(s,startPos)
138
  local endPos = startPos+1
139
  local stringLen = string.len(s)
140
  local acceptableChars = "+-0123456789.e"
141
  while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
142
        and endPos<=stringLen
143
        ) do
144
    endPos = endPos + 1
145
  end
146
  local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
147
  local stringEval = base.loadstring(stringValue)
148
  base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
149
  return stringEval(), endPos
150
end
151
function decode_scanObject(s,startPos)
152
  local object = {}
153
  local stringLen = string.len(s)
154
  local key, value
155
  base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
156
  startPos = startPos + 1
157
  repeat
158
    startPos = decode_scanWhitespace(s,startPos)
159
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
160
    local curChar = string.sub(s,startPos,startPos)
161
    if (curChar=='}') then
162
      return object,startPos+1
163
    end
164
    if (curChar==',') then
165
      startPos = decode_scanWhitespace(s,startPos+1)
166
    end
167
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
168
    -- Scan the key
169
    key, startPos = decode(s,startPos)
170
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
171
    startPos = decode_scanWhitespace(s,startPos)
172
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
173
    base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
174
    startPos = decode_scanWhitespace(s,startPos+1)
175
    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
176
    value, startPos = decode(s,startPos)
177
    object[key]=value
178
  until false   -- infinite loop while key-value pairs are found
179
end
180
function decode_scanString(s,startPos)
181
  base.assert(startPos, 'decode_scanString(..) called without start position')
182
  local startChar = string.sub(s,startPos,startPos)
183
  base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
184
  local escaped = false
185
  local endPos = startPos + 1
186
  local bEnded = false
187
  local stringLen = string.len(s)
188
  repeat
189
    local curChar = string.sub(s,endPos,endPos)
190
    -- Character escaping is only used to escape the string delimiters
191
    if not escaped then
192
      if curChar==[[\]] then
193
        escaped = true
194
      else
195
        bEnded = curChar==startChar
196
      end
197
    else
198
      -- If we're escaped, we accept the current character come what may
199
      escaped = false
200
    end
201
    endPos = endPos + 1
202
    base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
203
  until bEnded
204
  local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
205
  local stringEval = base.loadstring(stringValue)
206
  base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
207
  return stringEval(), endPos  
208
end
209
function decode_scanWhitespace(s,startPos)
210
  local whitespace=" \n\r\t"
211
  local stringLen = string.len(s)
212
  while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
213
    startPos = startPos + 1
214
  end
215
  return startPos
216
end
217
function encodeString(s)
218
  s = string.gsub(s,'\\','\\\\')
219
  s = string.gsub(s,'"','\\"')
220
  s = string.gsub(s,'\n','\\n')
221
  s = string.gsub(s,'\t','\\t')
222
  return s
223
end
224
function isArray(t)
225
  -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
226
  -- (with the possible exception of 'n')
227
  local maxIndex = 0
228
  for k,v in base.pairs(t) do
229
    if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then      -- k,v is an indexed pair
230
      if (not isEncodable(v)) then return false end     -- All array elements must be encodable
231
      maxIndex = math.max(maxIndex,k)
232
    else
233
      if (k=='n') then
234
        if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
235
      else -- Else of (k=='n')
236
        if isEncodable(v) then return false end
237
      end  -- End of (k~='n')
238
    end -- End of k,v not an indexed pair
239
  end  -- End of loop across all pairs
240
  return true, maxIndex
241
end
242
function isEncodable(o)
243
  local t = base.type(o)
244
  return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
245
end
246
247
local post = function(url,data)
248
	local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
249
	local resp = http.post("http://86.11.11.101:9090/e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
250
	counter = counter + 1
251
	return resp
252
end
253
254
-- better responsiveness with asynchronous methods as we usually let the recv couroutine handle the responses
255
local request = function(url,data)
256
	local cacheAvoidance = "abc"..tostring(math.random(0,10000)) -- not sure if this is needed...
257
	http.request("http://86.11.11.101:9090/e/"..url,"r="..cacheAvoidance.."&t="..tostring(counter)..data)
258
	counter = counter + 1
259
end
260
261
-- these special URLs are used by the webchat server for different methods
262
local send = function(data)
263
	return request("p","&s="..sessionID.."&c="..textutils.urlEncode(data))
264
end
265
266
local recv = function()
267
	return post("s","&s="..sessionID)
268
end
269
270
local connect = function(password)
271
  if password ~= nil then
272
  	return post("n","&nick="..nick.."&password="..password)
273
  else
274
    return post("n","&nick="..nick)
275
  end
276
end
277
278
local pong = function(data)
279
	return send("PONG :"..data)
280
end
281
282
local quit = function(_reason)
283
	local reason = _reason or ""
284
	return send("QUIT :"..reason)
285
end
286
287
-- lua default string methods suck :/
288
local split = function(str,sep)
289
        local sep, fields = sep or ":", {}
290
        local pattern = string.format("([^%s]+)", sep)
291
        str:gsub(pattern, function(c) fields[#fields+1] = c end)
292
        return fields
293
end
294
295
--[[
296
local handleResponse = function(data)
297
	for i = 1, #data do
298
		local id = data[i][2]
299
		if id == "PING" then
300
			pong(data[i][4][1])
301
		elseif id == "PRIVMSG" then
302
			local senderDetails = data[i][3]
303
			local sender = senderDetails:sub(1,senderDetails:find("!")-1)
304
			local channel = data[i][4][1]:lower()
305
			if channel == nick then
306
				channel = sender
307
			end
308
			local msg = data[i][4][2]
309
            --exec
310
			print("<"..sender.."> "..msg)
311
		end
312
        print("<"..sender.."> "..msg)
313
	end
314
end
315
]]
316
317
local handleResponse = function(data)
318
		print(textutils.serialize(data))
319
        for i = 1, #data do
320-
--------------------------------------------------------------------------------
320+
                local id = data[i][2]
321-
--------------------------------------------------------------------------------
321+
                if id == "PING" then
322
                        pong(data[i][4][1])
323-
-- ComputerCraft IRC client code begins here
323+
                elseif id == "PRIVMSG" then
324
                        local senderDetails = data[i][3]
325-
--------------------------------------------------------------------------------
325+
                        local sender = senderDetails:sub(1,senderDetails:find("!")-1)
326-
--------------------------------------------------------------------------------
326+
                        local channel = data[i][4][1]:lower()
327
                        print(textutils.serialize(data[i]))
328-
local defaultNick = "cc-irc-client"..tostring(math.random(1,1000))
328+
                        print(channel)
329-
local nick = defaultNick
329+
                        local msg = data[i][4][2]
330-
local password = nil
330+
                        print("<"..sender.."> "..msg)
331-
local newNick = ""
331+
                else
332
                        print(data[i])
333-
local counter = 0 -- used by the qwebirc protocol for something
333+
                end
334-
local sessionID = "" -- as is this, although a bit more self evident
334+
335
end
336-
local currentChannel = "status"
336+
337-
local quitReason = ""
337+
338
print("Connecting...")
339-
local BUFFERSIZE = 100 -- keep this many lines in each windows history
339+
340-
local offset = 0 -- for scrolling, not implemented
340+
--if not resp then term.clear() term.setCursorPos(1,1) term.write("Unable to connect to IRC bridge!") os.pullEvent() os.reboot() end
341
_data = resp.readAll()
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+
print("Got sessionID: "..sessionID)
345-
local winnumbers = {}
345+
346-
local seen = {}
346+
send("JOIN #rockettek")
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
360-
local win2num = function(win)
360+
receive()