2015-02-17 19:20:54 +13:00

273 lines
6.5 KiB
Lua

--[[
This file is part of Ice Lua Components.
Ice Lua Components is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ice Lua Components is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Ice Lua Components. If not, see <http://www.gnu.org/licenses/>.
]]
--[[
IMPORTANT NOTE FROM RFC 1459:
IRC messages are always lines of characters terminated with a CR-LF
(Carriage Return - Line Feed) pair, and these messages shall not
exceed 512 characters in length, counting all characters including
the trailing CR-LF. Thus, there are 510 characters maximum allowed
for the command and its parameters. There is no provision for
continuation message lines.
]]
irc = {}
irc.conns = {}
function ircbot_conn_new(settings)
local this = {}
this.addr = settings.addr or error("IRC bot address not set!")
this.port = settings.port or 6667
this.nick = settings.nick or "iceball"
this.user = settings.user or "iceball"
this.rname = settings.rname or "Iceball Server Bot"
this.channel = settings.channel or error("IRC bot channel not set!")
this.chat_prefix = settings.chat_prefix or "."
this.cmd_prefix = settings.cmd_prefix or "!"
this.chat_color = settings.chat_color or 0xFFFFAAFF
this.working = false
this.sockfd = nil
this.recvq = ""
this.sendq = ""
this.can_write = false
this.in_server = false
this.in_channel = false
function this.connect()
this.sockfd = common.tcp_connect(this.addr, this.port)
if this.sockfd == nil then
this.working = false
return false
end
this.working = true
this.sendq = this.sendq .. (
"USER"..
" "..this.user..
" ".."*"..
" ".."*"..
" :"..this.rname..
"\r\n")
this.sendq = this.sendq .. (
"NICK"..
" "..this.nick..
"\r\n")
return true
end
function this.write(msg)
if not this.working then return false end
if not this.can_write then return false end
-- TODO: SANITISE!
this.sendq = this.sendq .. (
"PRIVMSG"..
" "..this.channel..
" :"..msg..
"\r\n")
end
function this.handle_msg(sec_current, sec_delta, m)
if m.cmd == "PING" then
this.sendq = this.sendq .. (
"PONG"..
" :"..m.params[1]..
"\r\n")
elseif not this.in_server then
if m.cmd == "001" then
print("IRC bot entered server")
this.in_server = true
this.sendq = this.sendq .. (
"JOIN"..
" "..this.channel..
"\r\n")
end
elseif not this.in_channel then
if m.cmd == "JOIN" then
if m.params[1]:lower() == this.channel:lower() then
print("IRC bot entered channel")
this.in_channel = true
this.can_write = true
--this.write("channel join test")
else
print("IRC bot was force joined to a different channel")
this.sendq = this.sendq .. (
"PART"..
" "..m.params[1]..
" :".."not assigned to this channel"..
"\r\n")
end
end
elseif m.cmd == "353" then
-- RPL_NAMREPLY
-- TODO: parse oper status and whatnot
-- TODO: track nicks
elseif m.cmd == "PRIVMSG" then
print("privmsg: ("..m.params[1]..") <"..m.nick.."> "..m.params[2])
if m.params[1]:lower() == this.channel:lower() then
local msg = m.params[2]
if msg:sub(1,this.chat_prefix:len()) == this.chat_prefix then
local rmsg = msg:sub(this.chat_prefix:len()+1)
net_broadcast(nil, common.net_pack("BIz", PKT_CHAT_ADD_TEXT,
this.chat_color, "<"..m.nick.."> "..rmsg))
elseif msg:sub(1,this.cmd_prefix:len()) == this.cmd_prefix then
local msg = m.params[2]
local params = string.split(string.sub(msg,2), " ")
command_handle(nil, nil, m.nick, params, msg)
end
end
end
end
function this.update(sec_current, sec_delta, ...)
-- Cut off if connection broken
if not this.working then return false end
-- Send what we can
if this.sendq ~= "" then
local s = common.tcp_send(this.sockfd, this.sendq)
if s == false then
print("CANNOT SEND, ERROR OCCURRED")
this.working = false
common.tcp_close(this.sockfd)
this.sockfd = nil
return false
end
this.sendq = s
end
-- Receive what we can
local r = common.tcp_recv(this.sockfd)
if r == false then
print("CANNOT RECV, ERROR OCCURRED")
this.working = false
common.tcp_close(this.sockfd)
this.sockfd = nil
return false
end
this.recvq = this.recvq .. r
-- Parse recvq
while true do
local idx = this.recvq:find("\r\n")
if idx == nil then break end
local s = this.recvq:sub(1, idx-1)
this.recvq = this.recvq:sub(idx+2)
-- Hostmask
local mmask = nil
local mnick = nil
local muser = nil
local mhost = nil
if s:sub(1,1) == ":" then
-- Separate hostmask from message
idx = s:find(" ")
if not idx then
-- Shouldn't happen, but keep this robust anyway
mmask = s:sub(2)
s = ""
else
mmask = s:sub(2, idx-1)
s = s:sub(idx+1)
end
-- Split hostmask
local ts = mmask
idx = ts:find("@")
if idx then
mhost = ts:sub(idx+1)
ts = ts:sub(1, idx-1)
end
idx = ts:find("!")
if idx then
muser = ts:sub(idx+1)
ts = ts:sub(1, idx-1)
end
mnick = ts
end
-- Command
idx = s:find(" ")
local cmd = (idx and s:sub(1, idx-1)) or s
s = (idx and s:sub(idx+1))
local params = {}
while s do
if s:sub(1,1) == ":" then
s = s:sub(2)
params[1+#params] = s
s = nil
else
idx = s:find(" ")
params[1+#params] = (idx and s:sub(1, idx-1)) or s
s = (idx and s:sub(idx+1))
end
end
s = ""
local i
for i = 1,#params do
s = s .. " {" .. params[i] .. "}"
end
print("IRC:", mmask, mnick, muser, mhost, cmd, s)
this.handle_msg(sec_current, sec_delta, {
mask = mmask,
nick = mnick,
user = muser,
host = mhost,
cmd = cmd,
params = params,
})
end
return true
end
if this.connect() then
irc.conns[1+#(irc.conns)] = this
end
end
function irc.connect(settings)
ircbot_conn_new(settings)
end
function irc.update(sec_current, sec_delta, ...)
local k,v
for k,v in pairs(irc.conns) do
v.update(sec_current, sec_delta, ...)
end
end
function irc.write(msg)
local k,v
for k,v in pairs(irc.conns) do
v.write(msg)
end
end