Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- A (very (very!)) simple IRC client. Reference:
- -- http://tools.ietf.org/html/rfc2812
- local component = component or require "component"
- local internet = component.proxy(component.list "internet"())
- local event = require("event")
- local nick = "apifier"
- local host = "irc.osmarks.tk:6667"
- -- try to connect to server.
- local sock = internet.connect(host)
- sock.finishConnect()
- local function trim(value)
- local from = string.match(value, "^%s*()")
- return from > #value and "" or string.match(value, ".*%S", from)
- end
- -- utility method for reply tracking tables.
- function autocreate(table, key)
- table[key] = {}
- return table[key]
- end
- -- extract nickname from identity.
- local function name(identity)
- return identity and identity:match("^[^!]+") or identity or "Anonymous"
- end
- -- user defined callback for messages (via `lua function(msg) ... end`)
- local callback = nil
- -- list of whois info per user (used to accumulate whois replies).
- local whois = setmetatable({}, {__index=autocreate})
- -- list of users per channel (used to accumulate names replies).
- local names = setmetatable({}, {__index=autocreate})
- -- timer used to drive socket reading.
- local timer
- -- ignored commands, reserved according to RFC.
- -- http://tools.ietf.org/html/rfc2812#section-5.3
- local ignore = {
- [213]=true, [214]=true, [215]=true, [216]=true, [217]=true,
- [218]=true, [231]=true, [232]=true, [233]=true, [240]=true,
- [241]=true, [244]=true, [244]=true, [246]=true, [247]=true,
- [250]=true, [300]=true, [316]=true, [361]=true, [362]=true,
- [363]=true, [373]=true, [384]=true, [492]=true,
- -- custom ignored responses.
- [265]=true, [266]=true, [330]=true
- }
- -- command numbers to names.
- local commands = {
- --Replys
- RPL_WELCOME = "001",
- RPL_YOURHOST = "002",
- RPL_CREATED = "003",
- RPL_MYINFO = "004",
- RPL_BOUNCE = "005",
- RPL_LUSERCLIENT = "251",
- RPL_LUSEROP = "252",
- RPL_LUSERUNKNOWN = "253",
- RPL_LUSERCHANNELS = "254",
- RPL_LUSERME = "255",
- RPL_AWAY = "301",
- RPL_UNAWAY = "305",
- RPL_NOWAWAY = "306",
- RPL_WHOISUSER = "311",
- RPL_WHOISSERVER = "312",
- RPL_WHOISOPERATOR = "313",
- RPL_WHOISIDLE = "317",
- RPL_ENDOFWHOIS = "318",
- RPL_WHOISCHANNELS = "319",
- RPL_CHANNELMODEIS = "324",
- RPL_NOTOPIC = "331",
- RPL_TOPIC = "332",
- RPL_NAMREPLY = "353",
- RPL_ENDOFNAMES = "366",
- RPL_MOTDSTART = "375",
- RPL_MOTD = "372",
- RPL_ENDOFMOTD = "376",
- RPL_WHOISSECURE = "671",
- RPL_HELPSTART = "704",
- RPL_HELPTXT = "705",
- RPL_ENDOFHELP = "706",
- RPL_UMODEGMSG = "718",
- --Errors
- ERR_BANLISTFULL = "478",
- ERR_CHANNELISFULL = "471",
- ERR_UNKNOWNMODE = "472",
- ERR_INVITEONLYCHAN = "473",
- ERR_BANNEDFROMCHAN = "474",
- ERR_CHANOPRIVSNEEDED = "482",
- ERR_UNIQOPRIVSNEEDED = "485",
- ERR_USERNOTINCHANNEL = "441",
- ERR_NOTONCHANNEL = "442",
- ERR_NICKCOLLISION = "436",
- ERR_NICKNAMEINUSE = "433",
- ERR_ERRONEUSNICKNAME = "432",
- ERR_WASNOSUCHNICK = "406",
- ERR_TOOMANYCHANNELS = "405",
- ERR_CANNOTSENDTOCHAN = "404",
- ERR_NOSUCHCHANNEL = "403",
- ERR_NOSUCHNICK = "401",
- ERR_MODELOCK = "742"
- }
- -- main command handling callback.
- local function handleCommand(prefix, command, args, message)
- ---------------------------------------------------
- -- Keepalive
- if command == "PING" then
- sock.write(string.format("PONG :%s\r\n", message))
- --sock.flush()
- ---------------------------------------------------
- -- General commands
- elseif command == "NICK" then
- local oldNick, newNick = name(prefix), tostring(args[1] or message)
- if oldNick == nick then
- nick = newNick
- end
- print(oldNick .. " is now known as " .. newNick .. ".")
- elseif command == "MODE" then
- if #args == 2 then
- print("[" .. args[1] .. "] " .. name(prefix) .. " set mode".. ( #args[2] > 2 and "s" or "" ) .. " " .. tostring(args[2] or message) .. ".")
- else
- local setmode = {}
- local cumode = "+"
- args[2]:gsub(".", function(char)
- if char == "-" or char == "+" then
- cumode = char
- else
- table.insert(setmode, {cumode, char})
- end
- end)
- local d = {}
- local users = {}
- for i = 3, #args do
- users[i-2] = args[i]
- end
- users[#users+1] = message
- local last
- local ctxt = ""
- for c = 1, #users do
- if not setmode[c] then
- break
- end
- local mode = setmode[c][2]
- local pfx = setmode[c][1]=="+"
- local key = mode == "o" and (pfx and "opped" or "deoped") or
- mode == "v" and (pfx and "voiced" or "devoiced") or
- mode == "q" and (pfx and "quieted" or "unquieted") or
- mode == "b" and (pfx and "banned" or "unbanned") or
- "set " .. setmode[c][1] .. mode .. " on"
- if last ~= key then
- if last then
- print(ctxt)
- end
- ctxt = "[" .. args[1] .. "] " .. name(prefix) .. " " .. key
- last = key
- end
- ctxt = ctxt .. " " .. users[c]
- end
- if #ctxt > 0 then
- print(ctxt)
- end
- end
- elseif command == "QUIT" then
- print(name(prefix) .. " quit (" .. (message or "Quit") .. ").")
- elseif command == "JOIN" then
- print("[" .. args[1] .. "] " .. name(prefix) .. " entered the room.")
- elseif command == "PART" then
- print("[" .. args[1] .. "] " .. name(prefix) .. " has left the room (quit: " .. (message or "Quit") .. ").")
- elseif command == "TOPIC" then
- print("[" .. args[1] .. "] " .. name(prefix) .. " has changed the topic to: " .. message)
- elseif command == "KICK" then
- print("[" .. args[1] .. "] " .. name(prefix) .. " kicked " .. args[2])
- elseif command == "PRIVMSG" then
- local ctcp = message:match("^\1(.-)\1$")
- if ctcp then
- local orig_ctcp, param = ctcp:match("^(%S+) ?(.-)$")
- ctcp = orig_ctcp:upper()
- if ctcp == "TIME" then
- sock.write("NOTICE " .. name(prefix) .. " :\001TIME " .. os.date() .. "\001\r\n")
- --sock.flush()
- elseif ctcp == "VERSION" then
- sock.write("NOTICE " .. name(prefix) .. " :\001VERSION Minecraft/OpenComputers Lua 5.2\001\r\n")
- --sock.flush()
- elseif ctcp == "PING" then
- sock.write("NOTICE " .. name(prefix) .. " :\001PING " .. param .. "\001\r\n")
- --sock.flush()
- elseif ctcp == "ACTION" then
- print("[" .. args[1] .. "] * " .. name(prefix) .. string.gsub(string.gsub(message, "\001ACTION", ""), "\001", ""))
- else
- -- Here we print the CTCP message if it was unhandled...
- print("[" .. name(prefix) .. "] CTCP " .. orig_ctcp)
- end
- else
- if string.find(message, nick) then
- computer.beep()
- end
- print("[" .. args[1] .. "] " .. name(prefix) .. ": " .. message)
- end
- elseif command == "NOTICE" then
- print("[NOTICE] " .. message)
- elseif command == "ERROR" then
- print("[ERROR] " .. message)
- ---------------------------------------------------
- -- Ignored reserved numbers
- -- -- http://tools.ietf.org/html/rfc2812#section-5.3
- elseif tonumber(command) and ignore[tonumber(command)] then
- -- ignore
- ---------------------------------------------------
- -- Command replies
- -- http://tools.ietf.org/html/rfc2812#section-5.1
- elseif command == commands.RPL_WELCOME then
- print(message)
- elseif command == commands.RPL_YOURHOST then -- ignore
- elseif command == commands.RPL_CREATED then -- ignore
- elseif command == commands.RPL_MYINFO then -- ignore
- elseif command == commands.RPL_BOUNCE then -- ignore
- elseif command == commands.RPL_LUSERCLIENT then
- print(message)
- elseif command == commands.RPL_LUSEROP then -- ignore
- elseif command == commands.RPL_LUSERUNKNOWN then -- ignore
- elseif command == commands.RPL_LUSERCHANNELS then -- ignore
- elseif command == commands.RPL_LUSERME then
- print(message)
- elseif command == commands.RPL_AWAY then
- print(string.format("%s is away: %s", name(args[1]), message))
- elseif command == commands.RPL_UNAWAY or command == commands.RPL_NOWAWAY then
- print(message)
- elseif command == commands.RPL_WHOISUSER then
- local nick = args[2]:lower()
- whois[nick].nick = args[2]
- whois[nick].user = args[3]
- whois[nick].host = args[4]
- whois[nick].realName = message
- elseif command == commands.RPL_WHOISSERVER then
- local nick = args[2]:lower()
- whois[nick].server = args[3]
- whois[nick].serverInfo = message
- elseif command == commands.RPL_WHOISOPERATOR then
- local nick = args[2]:lower()
- whois[nick].isOperator = true
- elseif command == commands.RPL_WHOISIDLE then
- local nick = args[2]:lower()
- whois[nick].idle = tonumber(args[3])
- elseif command == commands.RPL_WHOISSECURE then
- local nick = args[2]:lower()
- whois[nick].secureconn = "Is using a secure connection"
- elseif command == commands.RPL_ENDOFWHOIS then
- local nick = args[2]:lower()
- local info = whois[nick]
- if info.nick then print("Nick: " .. info.nick) end
- if info.user then print("User name: " .. info.user) end
- if info.realName then print("Real name: " .. info.realName) end
- if info.host then print("Host: " .. info.host) end
- if info.server then print("Server: " .. info.server .. (info.serverInfo and (" (" .. info.serverInfo .. ")") or "")) end
- if info.secureconn then print(info.secureconn) end
- if info.channels then print("Channels: " .. info.channels) end
- if info.idle then print("Idle for: " .. info.idle) end
- whois[nick] = nil
- elseif command == commands.RPL_WHOISCHANNELS then
- local nick = args[2]:lower()
- whois[nick].channels = message
- elseif command == commands.RPL_CHANNELMODEIS then
- print("Channel mode for " .. args[1] .. ": " .. args[2] .. " (" .. args[3] .. ")")
- elseif command == commands.RPL_NOTOPIC then
- print("No topic is set for " .. args[1] .. ".")
- elseif command == commands.RPL_TOPIC then
- print("Topic for " .. args[1] .. ": " .. message)
- elseif command == commands.RPL_NAMREPLY then
- local channel = args[3]
- table.insert(names[channel], message)
- elseif command == commands.RPL_ENDOFNAMES then
- local channel = args[2]
- print("Users on " .. channel .. ": " .. (#names[channel] > 0 and table.concat(names[channel], " ") or "none"))
- names[channel] = nil
- elseif command == commands.RPL_MOTDSTART then
- if options.motd then
- print(message .. args[1])
- end
- elseif command == commands.RPL_MOTD then
- if options.motd then
- print(message)
- end
- elseif command == commands.RPL_ENDOFMOTD then -- ignore
- elseif command == commands.RPL_HELPSTART or
- command == commands.RPL_HELPTXT or
- command == commands.RPL_ENDOFHELP then
- print(message)
- elseif command == commands.ERR_BANLISTFULL or
- command == commands.ERR_BANNEDFROMCHAN or
- command == commands.ERR_CANNOTSENDTOCHAN or
- command == commands.ERR_CHANNELISFULL or
- command == commands.ERR_CHANOPRIVSNEEDED or
- command == commands.ERR_ERRONEUSNICKNAME or
- command == commands.ERR_INVITEONLYCHAN or
- command == commands.ERR_NICKCOLLISION or
- command == commands.ERR_NOSUCHNICK or
- command == commands.ERR_NOTONCHANNEL or
- command == commands.ERR_UNIQOPRIVSNEEDED or
- command == commands.ERR_UNKNOWNMODE or
- command == commands.ERR_USERNOTINCHANNEL or
- command == commands.ERR_WASNOSUCHNICK or
- command == commands.ERR_MODELOCK then
- print("[ERROR]: " .. message)
- elseif tonumber(command) and (tonumber(command) >= 200 and tonumber(command) < 400) then
- print("[Response " .. command .. "] " .. table.concat(args, ", ") .. ": " .. message)
- ---------------------------------------------------
- -- Error messages. No real point in handling those manually.
- -- http://tools.ietf.org/html/rfc2812#section-5.2
- elseif tonumber(command) and (tonumber(command) >= 400 and tonumber(command) < 600) then
- print("[Error] " .. table.concat(args, ", ") .. ": " .. message)
- ---------------------------------------------------
- -- Unhandled message.
- else
- print("Unhandled command: " .. command .. ": " .. message)
- end
- end
- -- catch errors to allow manual closing of socket and removal of timer.
- local result, reason = pcall(function()
- -- say hello.
- print("Welcome to OpenIRC!")
- -- avoid sock:read locking up the computer.
- --sock:setTimeout(0.05)
- -- http://tools.ietf.org/html/rfc2812#section-3.1
- sock.write(string.format("NICK %s\r\n", nick))
- sock.write(string.format("USER %s 0 * :%s [OpenComputers]\r\n", nick:lower(), nick))
- --sock:flush()
- -- socket reading logic (receive messages) driven by a timer.
- timer = event.timer(0.5, function()
- if not sock then
- return false
- end
- repeat
- local ok, line = pcall(sock.read, sock)
- if ok then
- if not line then
- print("Connection lost.")
- sock.close()
- sock = nil
- return false
- end
- line = trim(line) -- get rid of trailing \r
- local match, prefix = line:match("^(:(%S+) )")
- if match then line = line:sub(#match + 1) end
- local match, command = line:match("^(([^:]%S*))")
- if match then line = line:sub(#match + 1) end
- local args = {}
- repeat
- local match, arg = line:match("^( ([^:]%S*))")
- if match then
- line = line:sub(#match + 1)
- table.insert(args, arg)
- end
- until not match
- local message = line:match("^ :(.*)$")
- if callback then
- local result, reason = pcall(callback, prefix, command, args, message)
- if not result then
- print("Error in callback: " .. tostring(reason))
- end
- end
- handleCommand(prefix, command, args, message)
- end
- until not ok
- end, math.huge)
- -- default target for messages, so we don't have to type /msg all the time.
- local target = nil
- -- command history.
- repeat
- local w, h = component.gpu.getResolution()
- print((target or "?") .. "> ")
- local line = (require "io").read()
- if sock and line and line ~= "" then
- line = trim(line)
- if line:lower():sub(1,4) == "/me " then
- print("[" .. (target or "?") .. "] " .. nick .. " " .. line:sub(5), true)
- elseif line~="" then
- print("[" .. (target or "?") .. "] " .. nick .. ": " .. line, true)
- end
- if line:lower():sub(1, 5) == "/msg " then
- local user, message = line:sub(6):match("^(%S+) (.+)$")
- if message then
- message = trim(message)
- end
- if not user or not message or message == "" then
- print("Invalid use of /msg. Usage: /msg nick|channel message.")
- line = ""
- else
- target = user
- line = "PRIVMSG " .. target .. " :" .. message
- end
- elseif line:lower():sub(1, 6) == "/join " then
- local channel = trim(line:sub(7))
- if not channel or channel == "" then
- print("Invalid use of /join. Usage: /join channel.")
- line = ""
- else
- target = channel
- line = "JOIN " .. channel
- end
- elseif line:lower():sub(1, 5) == "/lua " then
- local script = trim(line:sub(6))
- local result, reason = load(script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
- if not result then
- result, reason = load("return " .. script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
- end
- line = ""
- if not result then
- print("Error: " .. tostring(reason))
- else
- result, reason = pcall(result)
- if not result then
- print("Error: " .. tostring(reason))
- elseif type(reason) == "function" then
- callback = reason
- elseif reason then
- line = tostring(reason)
- end
- end
- elseif line:lower():sub(1,4) == "/me " then
- if not target then
- print("No default target set. Use /msg or /join to set one.")
- line = ""
- else
- line = "PRIVMSG " .. target .. " :\001ACTION " .. line:sub(5) .. "\001"
- end
- elseif line:sub(1, 1) == "/" then
- line = line:sub(2)
- elseif line ~= "" then
- if not target then
- print("No default target set. Use /msg or /join to set one.")
- line = ""
- else
- line = "PRIVMSG " .. target .. " :" .. line
- end
- end
- if line and line ~= "" then
- sock.write(line .. "\r\n")
- --sock.flush()
- end
- end
- until not sock or not line
- end)
- if sock then
- sock.write("QUIT\r\n")
- sock.close()
- end
- if timer then
- event.cancel(timer)
- end
- if not result then
- error(reason, 0)
- end
- return reason
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement