213 lines
6.3 KiB
Lua
213 lines
6.3 KiB
Lua
|
|
irc = {}
|
|
irc.Channel = {}
|
|
irc.Channel.__index = irc.Channel
|
|
|
|
function irc.Channel:Say(Message)
|
|
self._irc:Say(self.Name, Message)
|
|
end
|
|
|
|
function irc.Channel:Whois(Nickname)
|
|
self._irc:Whois(Nickname)
|
|
end
|
|
|
|
function irc:ReceiveData(Data)
|
|
hook.Call('debug', Data)
|
|
local Prefix, Command, Arguments
|
|
if Data:sub(1, 1) == ':' then
|
|
local Pattern = ':([^ ]+) +([^ ]+) *(.*)'
|
|
Prefix, Command, Arguments = string.match(Data, Pattern)
|
|
else
|
|
local Pattern = '([^ ]+) *(.*)'
|
|
Prefix = nil
|
|
Command, Arguments = string.match(Data, Pattern)
|
|
end
|
|
if Command == nil then
|
|
error('Invalid IRC line: ' .. Data)
|
|
end
|
|
|
|
self:HandleCommand(Prefix, Command, Arguments)
|
|
end
|
|
|
|
function irc:ParseArguments(Arguments)
|
|
local Parts = {}
|
|
local Current = ""
|
|
local GrabRest = false
|
|
for c in Arguments:gmatch('.') do
|
|
if GrabRest then
|
|
Current = Current .. c
|
|
elseif c == ' ' then
|
|
if Current ~= '' then
|
|
Parts[#Parts + 1] = Current
|
|
Current = ''
|
|
end
|
|
else
|
|
if Current == '' and c == ':' then
|
|
-- the rest is a single part, with spaces
|
|
GrabRest = true
|
|
else
|
|
Current = Current .. c
|
|
end
|
|
end
|
|
end
|
|
Parts[#Parts + 1] = Current
|
|
return Parts
|
|
end
|
|
|
|
function irc:HandleCommand(Prefix, Command, Arguments)
|
|
local Number = tonumber(Command)
|
|
local Arguments = self:ParseArguments(Arguments)
|
|
if Command == 'PING' then
|
|
self:_Send('PONG ' .. Arguments[1])
|
|
elseif Command == 'NOTICE' then
|
|
local Target, Message = unpack(Arguments)
|
|
hook.Call('irc.Notice', Target, Message)
|
|
elseif Command == 'MODE' then
|
|
local Target, Mode = unpack(Arguments)
|
|
hook.Call('irc.Mode', Target, Mode)
|
|
elseif Command == 'PRIVMSG' then
|
|
local Target, Message = unpack(Arguments)
|
|
local Username = Prefix:match('([^!]+)!.*')
|
|
if Target == self._nickname then
|
|
hook.Call('irc.PrivateMessage', Username, Message)
|
|
else
|
|
hook.Call('irc.Message', Username, self._channels[Target], Message)
|
|
end
|
|
elseif Number and (400 <= Number) and (Number < 500) then
|
|
if Number == 433 then
|
|
-- Nickname already in use
|
|
local New = hook.Call('irc.NicknameInUser', self._nickname)
|
|
if New and New ~= self._nickname then
|
|
self._nickname = New
|
|
else
|
|
self._nickname = self._nickname .. '_'
|
|
end
|
|
self:SetNick(self._nickname)
|
|
end
|
|
elseif Number and (Number >= 300) and (Number < 400) then
|
|
-- IRC server response. some parts of us might be looking for these
|
|
hook.Call('irc.GetResponse' .. tostring(Number), unpack(Arguments))
|
|
if self._response_hooks[Number] ~= nil then
|
|
for K, Callback in pairs(self._response_hooks[Number]) do
|
|
if Callback(unpack(Arguments)) ~= false then
|
|
self._response_hooks[Number][K] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function irc:OnResponse(Response, Callback)
|
|
if type(Response) ~= 'number' then
|
|
error("Watched response is not a number.")
|
|
end
|
|
if Response >= 300 and Response < 400 then
|
|
self._response_hooks[Response] = self._response_hooks[Response] or {}
|
|
local Count = #self._response_hooks[Response]
|
|
self._response_hooks[Response][Count + 1] = Callback
|
|
else
|
|
error("Watched response is not a 3xx response.")
|
|
end
|
|
end
|
|
|
|
function irc:_Send(message)
|
|
local Delay = (self._last_sent + self._send_delay) - os.time()
|
|
if Delay > 0 then
|
|
reactor:Sleep(Delay)
|
|
end
|
|
self._last_sent = os.time()
|
|
self.Socket:send(message..'\r\n')
|
|
end
|
|
|
|
function irc:Whois(nickname)
|
|
self:_Send('WHOIS ' .. nickname)
|
|
end
|
|
|
|
function irc:SetNick(nickname)
|
|
self:_Send('NICK '..nickname)
|
|
hook.Call('info', 'Changing nickname to ' .. self._nickname)
|
|
end
|
|
|
|
function irc:LoginUser(username, realname)
|
|
self:_Send('USER ' .. username .. ' 0 * :' .. realname)
|
|
hook.Call('info', 'Logging in as ' .. username .. ' ' .. realname)
|
|
end
|
|
|
|
function irc:Say(target, message)
|
|
for Line in message:gmatch("[^\r\n]+") do
|
|
self:_Send('PRIVMSG ' .. target .. ' :' .. Line)
|
|
end
|
|
end
|
|
|
|
function irc:Join(channel)
|
|
local Channel = setmetatable({}, irc.Channel)
|
|
Channel.Name = channel
|
|
Channel.Topic = ""
|
|
Channel.Members = {}
|
|
Channel._irc = self
|
|
self._channels[channel] = Channel
|
|
print("Sending...")
|
|
self:_Send('JOIN ' .. channel)
|
|
print("Sent!")
|
|
self:OnResponse(332, function(Nick, _channel, Topic)
|
|
-- Channel topic
|
|
if _channel ~= channel then
|
|
return false
|
|
end
|
|
Channel.Topic = Topic
|
|
hook.Call('irc.ChannelTopic', Channel)
|
|
return true
|
|
end)
|
|
|
|
local MoreNicks = true
|
|
self:OnResponse(353, function(Nick, Type, _channel, Members)
|
|
if not MoreNicks then
|
|
return true
|
|
end
|
|
if _channel ~= channel then
|
|
return false
|
|
end
|
|
for Member in Members:gmatch("%S+") do
|
|
local Flag, Name = Member:match('([~@%&]?)(.+)')
|
|
local Data = {}
|
|
Data.Name = Name
|
|
Data.Flags = Flags
|
|
Channel.Members[Name] = Data
|
|
end
|
|
return true
|
|
end)
|
|
self:OnResponse(366, function()
|
|
MoreNicks = false
|
|
hook.Call('irc.ChannelNames', Channel)
|
|
end)
|
|
end
|
|
|
|
function irc:Connect(server, port, nickname, username, realname)
|
|
self._nickname = nickname
|
|
self._username = username
|
|
self._realname = realname
|
|
|
|
self._channels = {}
|
|
self._response_hooks = {}
|
|
self._last_sent = os.time()
|
|
self._send_delay = 0.5
|
|
|
|
-- Connection procedure (callback hell!)
|
|
local FinishedInitialNotices = function()
|
|
hook.Remove('irc.Notice', 'irc.Connect')
|
|
self:OnResponse(376, function()
|
|
hook.Call('irc.Connected')
|
|
end)
|
|
self:SetNick(self._nickname)
|
|
self:LoginUser(username, realname)
|
|
end
|
|
hook.Add('irc.Notice', 'irc.Connect', function(Target, Message)
|
|
reactor:SetTimer('irc.Connect.NoticeTimeout', 2, FinishedInitialNotices)
|
|
end)
|
|
|
|
local Socket = reactor:TCPConnect(server, port, function(socket)
|
|
irc:ReceiveData(socket)
|
|
end)
|
|
self.Socket = Socket
|
|
end
|