PageRenderTime 1ms CodeModel.GetById 13ms app.highlight 10ms RepoModel.GetById 27ms app.codeStats 0ms

/init.lua

http://github.com/JakobOvrum/LuaIRC
Lua | 233 lines | 185 code | 48 blank | 0 comment | 24 complexity | bad7f6e435fe020479ef7be639a435ca MD5 | raw file
  1local socket = require "socket"
  2
  3local error = error
  4local setmetatable = setmetatable
  5local rawget = rawget
  6local unpack = unpack
  7local pairs = pairs
  8local assert = assert
  9local require = require
 10local tonumber = tonumber
 11local type = type
 12local pcall = pcall
 13
 14module "irc"
 15
 16local meta = {}
 17meta.__index = meta
 18_META = meta
 19
 20require "irc.util"
 21require "irc.asyncoperations"
 22require "irc.handlers"
 23
 24local meta_preconnect = {}
 25function meta_preconnect.__index(o, k)
 26	local v = rawget(meta_preconnect, k)
 27
 28	if not v and meta[k] then
 29		error(("field '%s' is not accessible before connecting"):format(k), 2)
 30	end
 31	return v
 32end
 33
 34function new(data)
 35	local o = {
 36		nick = assert(data.nick, "Field 'nick' is required");
 37		username = data.username or "lua";
 38		realname = data.realname or "Lua owns";
 39		nickGenerator = data.nickGenerator or defaultNickGenerator;
 40		hooks = {};
 41		track_users = true;
 42	}
 43	assert(checkNick(o.nick), "Erroneous nickname passed to irc.new")
 44	return setmetatable(o, meta_preconnect)
 45end
 46
 47function meta:hook(name, id, f)
 48	f = f or id
 49	self.hooks[name] = self.hooks[name] or {}
 50	self.hooks[name][id] = f
 51	return id or f
 52end
 53meta_preconnect.hook = meta.hook
 54
 55
 56function meta:unhook(name, id)
 57	local hooks = self.hooks[name]
 58
 59	assert(hooks, "no hooks exist for this event")
 60	assert(hooks[id], "hook ID not found")
 61
 62	hooks[id] = nil
 63end
 64meta_preconnect.unhook = meta.unhook
 65
 66function meta:invoke(name, ...)
 67	local hooks = self.hooks[name]
 68	if hooks then
 69		for id,f in pairs(hooks) do
 70			if f(...) then
 71				return true
 72			end
 73		end
 74	end
 75end
 76
 77function meta_preconnect:connect(_host, _port)
 78	local host, port, password, secure, timeout
 79
 80	if type(_host) == "table" then
 81		host = _host.host
 82		port = _host.port
 83		timeout = _host.timeout
 84		password = _host.password
 85		secure = _host.secure
 86	else
 87		host = _host
 88		port = _port
 89	end
 90
 91	host = host or error("host name required to connect", 2)
 92	port = port or 6667
 93
 94	local s = socket.tcp()
 95
 96	s:settimeout(timeout or 30)
 97	assert(s:connect(host, port))
 98
 99	if secure then
100		local work, ssl = pcall(require, "ssl")
101		if not work then
102			error("LuaSec required for secure connections", 2)
103		end
104
105		local params
106		if type(secure) == "table" then
107			params = secure
108		else
109			params = {mode = "client", protocol = "tlsv1"}
110		end
111
112		s = ssl.wrap(s, params)
113		success, errmsg = s:dohandshake()
114		if not success then
115			error(("could not make secure connection: %s"):format(errmsg), 2)
116		end
117	end
118
119	self.socket = s
120	setmetatable(self, meta)
121
122	self:send("CAP REQ multi-prefix")
123
124	self:invoke("PreRegister", self)
125	self:send("CAP END")
126
127	if password then
128		self:send("PASS %s", password)
129	end
130
131	self:send("NICK %s", self.nick)
132	self:send("USER %s 0 * :%s", self.username, self.realname)
133
134	self.channels = {}
135
136	s:settimeout(0)
137
138	repeat
139		self:think()
140		socket.select(nil, nil, 0.1) -- Sleep so that we don't eat CPU
141	until self.authed
142end
143
144function meta:disconnect(message)
145	message = message or "Bye!"
146
147	self:invoke("OnDisconnect", message, false)
148	self:send("QUIT :%s", message)
149
150	self:shutdown()
151end
152
153function meta:shutdown()
154	self.socket:close()
155	setmetatable(self, nil)
156end
157
158local function getline(self, errlevel)
159	local line, err = self.socket:receive("*l")
160
161	if not line and err ~= "timeout" and err ~= "wantread" then
162		self:invoke("OnDisconnect", err, true)
163		self:shutdown()
164		error(err, errlevel)
165	end
166
167	return line
168end
169
170function meta:think()
171	while true do
172		local line = getline(self, 3)
173		if line and #line > 0 then
174			if not self:invoke("OnRaw", line) then
175				self:handle(parse(line))
176			end
177		else
178			break
179		end
180	end
181end
182
183local handlers = handlers
184
185function meta:handle(prefix, cmd, params)
186	local handler = handlers[cmd]
187	if handler then
188		return handler(self, prefix, unpack(params))
189	end
190end
191
192local whoisHandlers = {
193	["311"] = "userinfo";
194	["312"] = "node";
195	["319"] = "channels";
196	["330"] = "account"; -- Freenode
197	["307"] = "registered"; -- Unreal
198}
199
200function meta:whois(nick)
201	self:send("WHOIS %s", nick)
202
203	local result = {}
204
205	while true do
206		local line = getline(self, 3)
207		if line then
208			local prefix, cmd, args = parse(line)
209
210			local handler = whoisHandlers[cmd]
211			if handler then
212				result[handler] = args
213			elseif cmd == "318" then
214				break
215			else
216				self:handle(prefix, cmd, args)
217			end
218		end
219	end
220
221	if result.account then
222		result.account = result.account[3]
223	elseif result.registered then
224		result.account = result.registered[2]
225	end
226
227	return result
228end
229
230function meta:topic(channel)
231	self:send("TOPIC %s", channel)
232end
233