--[[ 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 . ]] --[[ 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