SHOW:
|
|
- or go back to the newest paste.
| 1 | -- A (very (very!)) simple IRC client. Reference: | |
| 2 | -- http://tools.ietf.org/html/rfc2812 | |
| 3 | ||
| 4 | local component = require("component")
| |
| 5 | local computer = require("computer")
| |
| 6 | ||
| 7 | if not component.isAvailable("internet") then
| |
| 8 | io.stderr:write("OpenIRC requires an Internet Card to run!\n")
| |
| 9 | return | |
| 10 | end | |
| 11 | ||
| 12 | local event = require("event")
| |
| 13 | local internet = require("internet")
| |
| 14 | local shell = require("shell")
| |
| 15 | local term = require("term")
| |
| 16 | local text = require("text")
| |
| 17 | ||
| 18 | local args, options = shell.parse(...) | |
| 19 | if #args < 1 then | |
| 20 | - | print("Usage: irc <nickname> [server[:port]]")
|
| 20 | + | print("Usage: irc <nickname> <oauth_key>")
|
| 21 | return | |
| 22 | end | |
| 23 | ||
| 24 | local nick = args[1] | |
| 25 | local pass = args[2] | |
| 26 | - | local host = args[3] or "irc.chat.twitch.tv:6667" |
| 26 | + | local host = "irc.chat.twitch.tv:6667" |
| 27 | ||
| 28 | if not host:find(":") then
| |
| 29 | host = host .. ":6667" | |
| 30 | end | |
| 31 | ||
| 32 | -- try to connect to server. | |
| 33 | local sock, reason = internet.open(host) | |
| 34 | if not sock then | |
| 35 | io.stderr:write(reason .. "\n") | |
| 36 | return | |
| 37 | end | |
| 38 | ||
| 39 | -- custom print that uses all except the last line for printing. | |
| 40 | local function print(message, overwrite) | |
| 41 | local w, h = component.gpu.getResolution() | |
| 42 | local line | |
| 43 | repeat | |
| 44 | line, message = text.wrap(text.trim(message), w, w) | |
| 45 | if not overwrite then | |
| 46 | component.gpu.copy(1, 1, w, h - 1, 0, -1) | |
| 47 | end | |
| 48 | overwrite = false | |
| 49 | component.gpu.fill(1, h - 1, w, 1, " ") | |
| 50 | component.gpu.set(1, h - 1, line) | |
| 51 | until not message or message == "" | |
| 52 | end | |
| 53 | ||
| 54 | -- utility method for reply tracking tables. | |
| 55 | function autocreate(table, key) | |
| 56 | table[key] = {}
| |
| 57 | return table[key] | |
| 58 | end | |
| 59 | ||
| 60 | -- extract nickname from identity. | |
| 61 | local function name(identity) | |
| 62 | return identity and identity:match("^[^!]+") or identity or "Anonymous"
| |
| 63 | end | |
| 64 | ||
| 65 | -- user defined callback for messages (via `lua function(msg) ... end`) | |
| 66 | local callback = nil | |
| 67 | ||
| 68 | -- list of whois info per user (used to accumulate whois replies). | |
| 69 | local whois = setmetatable({}, {__index=autocreate})
| |
| 70 | ||
| 71 | -- list of users per channel (used to accumulate names replies). | |
| 72 | local names = setmetatable({}, {__index=autocreate})
| |
| 73 | ||
| 74 | -- timer used to drive socket reading. | |
| 75 | local timer | |
| 76 | ||
| 77 | -- ignored commands, reserved according to RFC. | |
| 78 | -- http://tools.ietf.org/html/rfc2812#section-5.3 | |
| 79 | local ignore = {
| |
| 80 | [213]=true, [214]=true, [215]=true, [216]=true, [217]=true, | |
| 81 | [218]=true, [231]=true, [232]=true, [233]=true, [240]=true, | |
| 82 | [241]=true, [244]=true, [244]=true, [246]=true, [247]=true, | |
| 83 | [250]=true, [300]=true, [316]=true, [361]=true, [362]=true, | |
| 84 | [363]=true, [373]=true, [384]=true, [492]=true, | |
| 85 | -- custom ignored responses. | |
| 86 | [265]=true, [266]=true, [330]=true | |
| 87 | } | |
| 88 | ||
| 89 | -- command numbers to names. | |
| 90 | local commands = {
| |
| 91 | --Replys | |
| 92 | RPL_WELCOME = "001", | |
| 93 | RPL_YOURHOST = "002", | |
| 94 | RPL_CREATED = "003", | |
| 95 | RPL_MYINFO = "004", | |
| 96 | RPL_BOUNCE = "005", | |
| 97 | RPL_LUSERCLIENT = "251", | |
| 98 | RPL_LUSEROP = "252", | |
| 99 | RPL_LUSERUNKNOWN = "253", | |
| 100 | RPL_LUSERCHANNELS = "254", | |
| 101 | RPL_LUSERME = "255", | |
| 102 | RPL_AWAY = "301", | |
| 103 | RPL_UNAWAY = "305", | |
| 104 | RPL_NOWAWAY = "306", | |
| 105 | RPL_WHOISUSER = "311", | |
| 106 | RPL_WHOISSERVER = "312", | |
| 107 | RPL_WHOISOPERATOR = "313", | |
| 108 | RPL_WHOISIDLE = "317", | |
| 109 | RPL_ENDOFWHOIS = "318", | |
| 110 | RPL_WHOISCHANNELS = "319", | |
| 111 | RPL_CHANNELMODEIS = "324", | |
| 112 | RPL_NOTOPIC = "331", | |
| 113 | RPL_TOPIC = "332", | |
| 114 | RPL_NAMREPLY = "353", | |
| 115 | RPL_ENDOFNAMES = "366", | |
| 116 | RPL_MOTDSTART = "375", | |
| 117 | RPL_MOTD = "372", | |
| 118 | RPL_ENDOFMOTD = "376", | |
| 119 | RPL_WHOISSECURE = "671", | |
| 120 | RPL_HELPSTART = "704", | |
| 121 | RPL_HELPTXT = "705", | |
| 122 | RPL_ENDOFHELP = "706", | |
| 123 | RPL_UMODEGMSG = "718", | |
| 124 | ||
| 125 | --Errors | |
| 126 | ERR_BANLISTFULL = "478", | |
| 127 | ERR_CHANNELISFULL = "471", | |
| 128 | ERR_UNKNOWNMODE = "472", | |
| 129 | ERR_INVITEONLYCHAN = "473", | |
| 130 | ERR_BANNEDFROMCHAN = "474", | |
| 131 | ERR_CHANOPRIVSNEEDED = "482", | |
| 132 | ERR_UNIQOPRIVSNEEDED = "485", | |
| 133 | ERR_USERNOTINCHANNEL = "441", | |
| 134 | ERR_NOTONCHANNEL = "442", | |
| 135 | ERR_NICKCOLLISION = "436", | |
| 136 | ERR_NICKNAMEINUSE = "433", | |
| 137 | ERR_ERRONEUSNICKNAME = "432", | |
| 138 | ERR_WASNOSUCHNICK = "406", | |
| 139 | ERR_TOOMANYCHANNELS = "405", | |
| 140 | ERR_CANNOTSENDTOCHAN = "404", | |
| 141 | ERR_NOSUCHCHANNEL = "403", | |
| 142 | ERR_NOSUCHNICK = "401", | |
| 143 | ERR_MODELOCK = "742" | |
| 144 | } | |
| 145 | ||
| 146 | -- main command handling callback. | |
| 147 | local function handleCommand(prefix, command, args, message) | |
| 148 | --------------------------------------------------- | |
| 149 | -- Keepalive | |
| 150 | ||
| 151 | if command == "PING" then | |
| 152 | sock:write(string.format("PONG :%s\r\n", message))
| |
| 153 | sock:flush() | |
| 154 | ||
| 155 | --------------------------------------------------- | |
| 156 | -- General commands | |
| 157 | elseif command == "NICK" then | |
| 158 | local oldNick, newNick = name(prefix), tostring(args[1] or message) | |
| 159 | if oldNick == nick then | |
| 160 | nick = newNick | |
| 161 | end | |
| 162 | print(oldNick .. " is now known as " .. newNick .. ".") | |
| 163 | elseif command == "MODE" then | |
| 164 | if #args == 2 then | |
| 165 | print("[" .. args[1] .. "] " .. name(prefix) .. " set mode".. ( #args[2] > 2 and "s" or "" ) .. " " .. tostring(args[2] or message) .. ".")
| |
| 166 | else | |
| 167 | local setmode = {}
| |
| 168 | local cumode = "+" | |
| 169 | args[2]:gsub(".", function(char)
| |
| 170 | if char == "-" or char == "+" then | |
| 171 | cumode = char | |
| 172 | else | |
| 173 | table.insert(setmode, {cumode, char})
| |
| 174 | end | |
| 175 | end) | |
| 176 | local d = {}
| |
| 177 | local users = {}
| |
| 178 | for i = 3, #args do | |
| 179 | users[i-2] = args[i] | |
| 180 | end | |
| 181 | users[#users+1] = message | |
| 182 | local last | |
| 183 | local ctxt = "" | |
| 184 | for c = 1, #users do | |
| 185 | if not setmode[c] then | |
| 186 | break | |
| 187 | end | |
| 188 | local mode = setmode[c][2] | |
| 189 | local pfx = setmode[c][1]=="+" | |
| 190 | local key = mode == "o" and (pfx and "opped" or "deoped") or | |
| 191 | mode == "v" and (pfx and "voiced" or "devoiced") or | |
| 192 | mode == "q" and (pfx and "quieted" or "unquieted") or | |
| 193 | mode == "b" and (pfx and "banned" or "unbanned") or | |
| 194 | "set " .. setmode[c][1] .. mode .. " on" | |
| 195 | if last ~= key then | |
| 196 | if last then | |
| 197 | print(ctxt) | |
| 198 | end | |
| 199 | ctxt = "[" .. args[1] .. "] " .. name(prefix) .. " " .. key | |
| 200 | last = key | |
| 201 | end | |
| 202 | ctxt = ctxt .. " " .. users[c] | |
| 203 | end | |
| 204 | if #ctxt > 0 then | |
| 205 | print(ctxt) | |
| 206 | end | |
| 207 | end | |
| 208 | elseif command == "QUIT" then | |
| 209 | print(name(prefix) .. " quit (" .. (message or "Quit") .. ").")
| |
| 210 | elseif command == "JOIN" then | |
| 211 | print("[" .. args[1] .. "] " .. name(prefix) .. " entered the room.")
| |
| 212 | elseif command == "PART" then | |
| 213 | print("[" .. args[1] .. "] " .. name(prefix) .. " has left the room (quit: " .. (message or "Quit") .. ").")
| |
| 214 | elseif command == "TOPIC" then | |
| 215 | print("[" .. args[1] .. "] " .. name(prefix) .. " has changed the topic to: " .. message)
| |
| 216 | elseif command == "KICK" then | |
| 217 | print("[" .. args[1] .. "] " .. name(prefix) .. " kicked " .. args[2])
| |
| 218 | elseif command == "PRIVMSG" then | |
| 219 | local ctcp = message:match("^\1(.-)\1$")
| |
| 220 | if ctcp then | |
| 221 | local orig_ctcp, param = ctcp:match("^(%S+) ?(.-)$")
| |
| 222 | ctcp = orig_ctcp:upper() | |
| 223 | if ctcp == "TIME" then | |
| 224 | sock:write("NOTICE " .. name(prefix) .. " :\001TIME " .. os.date() .. "\001\r\n")
| |
| 225 | sock:flush() | |
| 226 | elseif ctcp == "VERSION" then | |
| 227 | sock:write("NOTICE " .. name(prefix) .. " :\001VERSION Minecraft/OpenComputers Lua 5.2\001\r\n")
| |
| 228 | sock:flush() | |
| 229 | elseif ctcp == "PING" then | |
| 230 | sock:write("NOTICE " .. name(prefix) .. " :\001PING " .. param .. "\001\r\n")
| |
| 231 | sock:flush() | |
| 232 | elseif ctcp == "ACTION" then | |
| 233 | print("[" .. args[1] .. "] * " .. name(prefix) .. string.gsub(string.gsub(message, "\001ACTION", ""), "\001", ""))
| |
| 234 | else | |
| 235 | -- Here we print the CTCP message if it was unhandled... | |
| 236 | print("[" .. name(prefix) .. "] CTCP " .. orig_ctcp)
| |
| 237 | end | |
| 238 | else | |
| 239 | if string.find(message, nick) then | |
| 240 | computer.beep() | |
| 241 | end | |
| 242 | print("[" .. args[1] .. "] " .. name(prefix) .. ": " .. message)
| |
| 243 | end | |
| 244 | elseif command == "NOTICE" then | |
| 245 | print("[NOTICE] " .. message)
| |
| 246 | elseif command == "ERROR" then | |
| 247 | print("[ERROR] " .. message)
| |
| 248 | ||
| 249 | --------------------------------------------------- | |
| 250 | -- Ignored reserved numbers | |
| 251 | -- -- http://tools.ietf.org/html/rfc2812#section-5.3 | |
| 252 | ||
| 253 | elseif tonumber(command) and ignore[tonumber(command)] then | |
| 254 | -- ignore | |
| 255 | ||
| 256 | --------------------------------------------------- | |
| 257 | -- Command replies | |
| 258 | -- http://tools.ietf.org/html/rfc2812#section-5.1 | |
| 259 | ||
| 260 | elseif command == commands.RPL_WELCOME then | |
| 261 | print(message) | |
| 262 | elseif command == commands.RPL_YOURHOST then -- ignore | |
| 263 | elseif command == commands.RPL_CREATED then -- ignore | |
| 264 | elseif command == commands.RPL_MYINFO then -- ignore | |
| 265 | elseif command == commands.RPL_BOUNCE then -- ignore | |
| 266 | elseif command == commands.RPL_LUSERCLIENT then | |
| 267 | print(message) | |
| 268 | elseif command == commands.RPL_LUSEROP then -- ignore | |
| 269 | elseif command == commands.RPL_LUSERUNKNOWN then -- ignore | |
| 270 | elseif command == commands.RPL_LUSERCHANNELS then -- ignore | |
| 271 | elseif command == commands.RPL_LUSERME then | |
| 272 | print(message) | |
| 273 | elseif command == commands.RPL_AWAY then | |
| 274 | print(string.format("%s is away: %s", name(args[1]), message))
| |
| 275 | elseif command == commands.RPL_UNAWAY or command == commands.RPL_NOWAWAY then | |
| 276 | print(message) | |
| 277 | elseif command == commands.RPL_WHOISUSER then | |
| 278 | local nick = args[2]:lower() | |
| 279 | whois[nick].nick = args[2] | |
| 280 | whois[nick].user = args[3] | |
| 281 | whois[nick].host = args[4] | |
| 282 | whois[nick].realName = message | |
| 283 | elseif command == commands.RPL_WHOISSERVER then | |
| 284 | local nick = args[2]:lower() | |
| 285 | whois[nick].server = args[3] | |
| 286 | whois[nick].serverInfo = message | |
| 287 | elseif command == commands.RPL_WHOISOPERATOR then | |
| 288 | local nick = args[2]:lower() | |
| 289 | whois[nick].isOperator = true | |
| 290 | elseif command == commands.RPL_WHOISIDLE then | |
| 291 | local nick = args[2]:lower() | |
| 292 | whois[nick].idle = tonumber(args[3]) | |
| 293 | elseif command == commands.RPL_WHOISSECURE then | |
| 294 | local nick = args[2]:lower() | |
| 295 | whois[nick].secureconn = "Is using a secure connection" | |
| 296 | elseif command == commands.RPL_ENDOFWHOIS then | |
| 297 | local nick = args[2]:lower() | |
| 298 | local info = whois[nick] | |
| 299 | if info.nick then print("Nick: " .. info.nick) end
| |
| 300 | if info.user then print("User name: " .. info.user) end
| |
| 301 | if info.realName then print("Real name: " .. info.realName) end
| |
| 302 | if info.host then print("Host: " .. info.host) end
| |
| 303 | if info.server then print("Server: " .. info.server .. (info.serverInfo and (" (" .. info.serverInfo .. ")") or "")) end
| |
| 304 | if info.secureconn then print(info.secureconn) end | |
| 305 | if info.channels then print("Channels: " .. info.channels) end
| |
| 306 | if info.idle then print("Idle for: " .. info.idle) end
| |
| 307 | whois[nick] = nil | |
| 308 | elseif command == commands.RPL_WHOISCHANNELS then | |
| 309 | local nick = args[2]:lower() | |
| 310 | whois[nick].channels = message | |
| 311 | elseif command == commands.RPL_CHANNELMODEIS then | |
| 312 | print("Channel mode for " .. args[1] .. ": " .. args[2] .. " (" .. args[3] .. ")")
| |
| 313 | elseif command == commands.RPL_NOTOPIC then | |
| 314 | print("No topic is set for " .. args[1] .. ".")
| |
| 315 | elseif command == commands.RPL_TOPIC then | |
| 316 | print("Topic for " .. args[1] .. ": " .. message)
| |
| 317 | elseif command == commands.RPL_NAMREPLY then | |
| 318 | local channel = args[3] | |
| 319 | table.insert(names[channel], message) | |
| 320 | elseif command == commands.RPL_ENDOFNAMES then | |
| 321 | local channel = args[2] | |
| 322 | print("Users on " .. channel .. ": " .. (#names[channel] > 0 and table.concat(names[channel], " ") or "none"))
| |
| 323 | names[channel] = nil | |
| 324 | elseif command == commands.RPL_MOTDSTART then | |
| 325 | if options.motd then | |
| 326 | print(message .. args[1]) | |
| 327 | end | |
| 328 | elseif command == commands.RPL_MOTD then | |
| 329 | if options.motd then | |
| 330 | print(message) | |
| 331 | end | |
| 332 | elseif command == commands.RPL_ENDOFMOTD then -- ignore | |
| 333 | elseif command == commands.RPL_HELPSTART or | |
| 334 | command == commands.RPL_HELPTXT or | |
| 335 | command == commands.RPL_ENDOFHELP then | |
| 336 | print(message) | |
| 337 | elseif command == commands.ERR_BANLISTFULL or | |
| 338 | command == commands.ERR_BANNEDFROMCHAN or | |
| 339 | command == commands.ERR_CANNOTSENDTOCHAN or | |
| 340 | command == commands.ERR_CHANNELISFULL or | |
| 341 | command == commands.ERR_CHANOPRIVSNEEDED or | |
| 342 | command == commands.ERR_ERRONEUSNICKNAME or | |
| 343 | command == commands.ERR_INVITEONLYCHAN or | |
| 344 | command == commands.ERR_NICKCOLLISION or | |
| 345 | command == commands.ERR_NOSUCHNICK or | |
| 346 | command == commands.ERR_NOTONCHANNEL or | |
| 347 | command == commands.ERR_UNIQOPRIVSNEEDED or | |
| 348 | command == commands.ERR_UNKNOWNMODE or | |
| 349 | command == commands.ERR_USERNOTINCHANNEL or | |
| 350 | command == commands.ERR_WASNOSUCHNICK or | |
| 351 | command == commands.ERR_MODELOCK then | |
| 352 | print("[ERROR]: " .. message)
| |
| 353 | elseif tonumber(command) and (tonumber(command) >= 200 and tonumber(command) < 400) then | |
| 354 | print("[Response " .. command .. "] " .. table.concat(args, ", ") .. ": " .. message)
| |
| 355 | ||
| 356 | --------------------------------------------------- | |
| 357 | -- Error messages. No real point in handling those manually. | |
| 358 | -- http://tools.ietf.org/html/rfc2812#section-5.2 | |
| 359 | ||
| 360 | elseif tonumber(command) and (tonumber(command) >= 400 and tonumber(command) < 600) then | |
| 361 | print("[Error] " .. table.concat(args, ", ") .. ": " .. message)
| |
| 362 | ||
| 363 | --------------------------------------------------- | |
| 364 | -- Unhandled message. | |
| 365 | ||
| 366 | else | |
| 367 | print("Unhandled command: " .. command .. ": " .. message)
| |
| 368 | end | |
| 369 | end | |
| 370 | ||
| 371 | -- catch errors to allow manual closing of socket and removal of timer. | |
| 372 | local result, reason = pcall(function() | |
| 373 | -- say hello. | |
| 374 | term.clear() | |
| 375 | print("Welcome to OpenIRC!")
| |
| 376 | ||
| 377 | -- avoid sock:read locking up the computer. | |
| 378 | sock:setTimeout(0.05) | |
| 379 | ||
| 380 | -- http://tools.ietf.org/html/rfc2812#section-3.1 | |
| 381 | sock:write(string.format("PASS %s\r\n", pass))
| |
| 382 | sock:write(string.format("NICK %s\r\n", nick))
| |
| 383 | sock:write(string.format("USER %s 0 * :%s [OpenComputers]\r\n", nick:lower(), nick))
| |
| 384 | sock:flush() | |
| 385 | ||
| 386 | -- socket reading logic (receive messages) driven by a timer. | |
| 387 | timer = event.timer(0.5, function() | |
| 388 | if not sock then | |
| 389 | return false | |
| 390 | end | |
| 391 | repeat | |
| 392 | local ok, line = pcall(sock.read, sock) | |
| 393 | if ok then | |
| 394 | if not line then | |
| 395 | print("Connection lost.")
| |
| 396 | sock:close() | |
| 397 | sock = nil | |
| 398 | return false | |
| 399 | end | |
| 400 | line = text.trim(line) -- get rid of trailing \r | |
| 401 | local match, prefix = line:match("^(:(%S+) )")
| |
| 402 | if match then line = line:sub(#match + 1) end | |
| 403 | local match, command = line:match("^(([^:]%S*))")
| |
| 404 | if match then line = line:sub(#match + 1) end | |
| 405 | local args = {}
| |
| 406 | repeat | |
| 407 | local match, arg = line:match("^( ([^:]%S*))")
| |
| 408 | if match then | |
| 409 | line = line:sub(#match + 1) | |
| 410 | table.insert(args, arg) | |
| 411 | end | |
| 412 | until not match | |
| 413 | local message = line:match("^ :(.*)$")
| |
| 414 | ||
| 415 | if callback then | |
| 416 | local result, reason = pcall(callback, prefix, command, args, message) | |
| 417 | if not result then | |
| 418 | print("Error in callback: " .. tostring(reason))
| |
| 419 | end | |
| 420 | end | |
| 421 | handleCommand(prefix, command, args, message) | |
| 422 | end | |
| 423 | until not ok | |
| 424 | end, math.huge) | |
| 425 | ||
| 426 | -- default target for messages, so we don't have to type /msg all the time. | |
| 427 | local target = nil | |
| 428 | ||
| 429 | -- command history. | |
| 430 | local history = {}
| |
| 431 | ||
| 432 | repeat | |
| 433 | local w, h = component.gpu.getResolution() | |
| 434 | term.setCursor(1, h) | |
| 435 | term.write((target or "?") .. "> ") | |
| 436 | local line = term.read(history) | |
| 437 | if sock and line and line ~= "" then | |
| 438 | line = text.trim(line) | |
| 439 | if line:lower():sub(1,4) == "/me " then | |
| 440 | print("[" .. (target or "?") .. "] " .. nick .. " " .. line:sub(5), true)
| |
| 441 | elseif line~="" then | |
| 442 | print("[" .. (target or "?") .. "] " .. nick .. ": " .. line, true)
| |
| 443 | end | |
| 444 | if line:lower():sub(1, 5) == "/msg " then | |
| 445 | local user, message = line:sub(6):match("^(%S+) (.+)$")
| |
| 446 | if message then | |
| 447 | message = text.trim(message) | |
| 448 | end | |
| 449 | if not user or not message or message == "" then | |
| 450 | print("Invalid use of /msg. Usage: /msg nick|channel message.")
| |
| 451 | line = "" | |
| 452 | else | |
| 453 | target = user | |
| 454 | line = "PRIVMSG " .. target .. " :" .. message | |
| 455 | end | |
| 456 | elseif line:lower():sub(1, 6) == "/join " then | |
| 457 | local channel = text.trim(line:sub(7)) | |
| 458 | if not channel or channel == "" then | |
| 459 | print("Invalid use of /join. Usage: /join channel.")
| |
| 460 | line = "" | |
| 461 | else | |
| 462 | target = channel | |
| 463 | line = "JOIN " .. channel | |
| 464 | end | |
| 465 | elseif line:lower():sub(1, 5) == "/lua " then | |
| 466 | local script = text.trim(line:sub(6)) | |
| 467 | local result, reason = load(script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
| |
| 468 | if not result then | |
| 469 | result, reason = load("return " .. script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
| |
| 470 | end | |
| 471 | line = "" | |
| 472 | if not result then | |
| 473 | print("Error: " .. tostring(reason))
| |
| 474 | else | |
| 475 | result, reason = pcall(result) | |
| 476 | if not result then | |
| 477 | print("Error: " .. tostring(reason))
| |
| 478 | elseif type(reason) == "function" then | |
| 479 | callback = reason | |
| 480 | elseif reason then | |
| 481 | line = tostring(reason) | |
| 482 | end | |
| 483 | end | |
| 484 | elseif line:lower():sub(1,4) == "/me " then | |
| 485 | if not target then | |
| 486 | print("No default target set. Use /msg or /join to set one.")
| |
| 487 | line = "" | |
| 488 | else | |
| 489 | line = "PRIVMSG " .. target .. " :\001ACTION " .. line:sub(5) .. "\001" | |
| 490 | end | |
| 491 | elseif line:sub(1, 1) == "/" then | |
| 492 | line = line:sub(2) | |
| 493 | elseif line ~= "" then | |
| 494 | if not target then | |
| 495 | print("No default target set. Use /msg or /join to set one.")
| |
| 496 | line = "" | |
| 497 | else | |
| 498 | line = "PRIVMSG " .. target .. " :" .. line | |
| 499 | end | |
| 500 | end | |
| 501 | if line and line ~= "" then | |
| 502 | sock:write(line .. "\r\n") | |
| 503 | sock:flush() | |
| 504 | end | |
| 505 | end | |
| 506 | until not sock or not line | |
| 507 | end) | |
| 508 | ||
| 509 | if sock then | |
| 510 | sock:write("QUIT\r\n")
| |
| 511 | sock:close() | |
| 512 | end | |
| 513 | if timer then | |
| 514 | event.cancel(timer) | |
| 515 | end | |
| 516 | ||
| 517 | if not result then | |
| 518 | error(reason, 0) | |
| 519 | end | |
| 520 | return reason |