Guest User

Untitled

a guest
Jan 12th, 2019
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.80 KB | None | 0 0
  1. ---
  2. -- Implementation of the main LuaIRC module
  3.  
  4. -- initialization {{{
  5. local base = _G
  6. local constants = require 'irc.constants'
  7. local ctcp = require 'irc.ctcp'
  8. local c = ctcp._ctcp_quote
  9. local irc_debug = require 'irc.debug'
  10. local message = require 'irc.message'
  11. local misc = require 'irc.misc'
  12. local socket = require 'socket'
  13. local os = require 'os'
  14. local string = require 'string'
  15. local table = require 'table'
  16. -- }}}
  17.  
  18. ---
  19. -- LuaIRC - IRC framework written in Lua
  20. -- @release 0.3
  21. module 'irc'
  22.  
  23. -- constants {{{
  24. _VERSION = 'LuaIRC 0.3'
  25. -- }}}
  26.  
  27. -- defaults {{{
  28. TIMEOUT = 60 -- connection timeout
  29. NETWORK = "localhost" -- default network
  30. PORT = 6667 -- default port
  31. NICKLIST = {"luabot"} -- default nick
  32. USERNAME = "MartinchoBot" -- default username
  33. REALNAME = "MartinchoBot" -- default realname
  34. DEBUG = false -- whether we want extra debug information
  35. OUTFILE = nil -- file to send debug output to - nil is stdout
  36. ARGS = {} -- args passed in the irc.connect() function
  37. -- }}}
  38.  
  39. -- classes {{{
  40. local Channel = base.require 'irc.channel'
  41. -- }}}
  42.  
  43. -- local variables {{{
  44. local irc_sock = nil
  45. local rsockets = {}
  46. local wsockets = {}
  47. local rcallbacks = {}
  48. local wcallbacks = {}
  49. local icallbacks = {
  50. whois = {},
  51. serverversion = {},
  52. servertime = {},
  53. ctcp_ping = {},
  54. ctcp_time = {},
  55. ctcp_version = {},
  56. }
  57. local requestinfo = {whois = {}}
  58. local handlers = {}
  59. local ctcp_handlers = {}
  60. local user_handlers = {}
  61. local serverinfo = {}
  62. local ip = nil
  63. -- }}}
  64.  
  65.  
  66. -- private functions {{{
  67. -- main_loop_iter {{{
  68. local function main_loop_iter()
  69. if #rsockets == 0 and #wsockets == 0 then return false end
  70. local rready, wready, err = socket.select(rsockets, wsockets)
  71. if err then irc_debug._err(err); return false; end
  72.  
  73. for _, sock in base.ipairs(rready) do
  74. local cb = socket.protect(rcallbacks[sock])
  75. local ret, err = cb(sock)
  76. if not ret then
  77. irc_debug._warn("socket error: " .. err)
  78. _unregister_socket(sock, 'r')
  79. end
  80. end
  81.  
  82. for _, sock in base.ipairs(wready) do
  83. local cb = socket.protect(wcallbacks[sock])
  84. local ret, err = cb(sock)
  85. if not ret then
  86. irc_debug._warn("socket error: " .. err)
  87. _unregister_socket(sock, 'w')
  88. end
  89. end
  90.  
  91. return true
  92. end
  93. -- }}}
  94.  
  95. -- begin_main_loop {{{
  96. local function begin_main_loop()
  97. while true do
  98. ret = main_loop_iter()
  99. if not ret then --try to reconnect
  100. socket.sleep(5) -- wait 5 seconds
  101. base.print("reconnecting...")
  102. reconnect(ARGS)
  103. end
  104. end
  105. end
  106. -- }}}
  107.  
  108. -- incoming_message {{{
  109. local function incoming_message(sock)
  110. local raw_msg = socket.try(sock:receive())
  111. irc_debug._message("RECV", raw_msg)
  112. local msg = message._parse(raw_msg)
  113.  
  114. -- If nick is already in use, try to change it
  115. if msg.command=="ERR_NICKNAMEINUSE" then
  116. table.remove(NICKLIST,1)
  117. send("NICK", NICKLIST[1])
  118. end
  119. misc._try_call_warn("Unhandled server message: " .. msg.command,
  120. handlers["on_" .. msg.command:lower()],
  121. (misc._parse_user(msg.from)), base.unpack(msg.args))
  122. return true
  123. end
  124. -- }}}
  125.  
  126. -- callback {{{
  127. local function callback(name, ...)
  128. return misc._try_call(user_handlers[name], ...)
  129. end
  130. -- }}}
  131. -- }}}
  132.  
  133. -- internal message handlers {{{
  134. -- command handlers {{{
  135. -- on_nick {{{
  136. function handlers.on_nick(from, new_nick)
  137. for chan in channels() do
  138. chan:_change_nick(from, new_nick)
  139. end
  140. callback("nick_change", new_nick, from)
  141. end
  142. -- }}}
  143.  
  144. -- on_join {{{
  145. function handlers.on_join(from, chan)
  146. chan = string.lower(chan)
  147. base.assert(serverinfo.channels[chan],
  148. "Received join message for unknown channel: " .. chan)
  149. if serverinfo.channels[chan].join_complete then
  150. serverinfo.channels[chan]:_add_user(from)
  151. callback("join", serverinfo.channels[chan], from)
  152. end
  153. end
  154. -- }}}
  155.  
  156. -- on_part {{{
  157. function handlers.on_part(from, chan, part_msg)
  158. -- don't assert on chan here, since we get part messages for ourselves
  159. -- after we remove the channel from the channel list
  160. if not serverinfo.channels[chan] then return end
  161. if serverinfo.channels[chan].join_complete then
  162. serverinfo.channels[chan]:_remove_user(from)
  163. callback("part", serverinfo.channels[chan], from, part_msg)
  164. end
  165. end
  166. -- }}}
  167.  
  168. -- on_mode {{{
  169. function handlers.on_mode(from, to, mode_string, ...)
  170. to = string.lower(to)
  171. local dir = mode_string:sub(1, 1)
  172. mode_string = mode_string:sub(2)
  173. local args = {...}
  174.  
  175. if to:sub(1, 1) == "#" then
  176. -- handle channel mode requests {{{
  177. base.assert(serverinfo.channels[to],
  178. "Received mode change for unknown channel: " .. to)
  179. local chan = serverinfo.channels[to]
  180. local ind = 1
  181. for i = 1, mode_string:len() do
  182. local mode = mode_string:sub(i, i)
  183. local target = args[ind]
  184. -- channel modes other than op/voice will be implemented as
  185. -- information request commands
  186. if mode == "o" then -- channel op {{{
  187. chan:_change_status(target, dir == "+", "o")
  188. callback(({["+"] = "op", ["-"] = "deop"})[dir],
  189. chan, from, target)
  190. ind = ind + 1
  191. -- }}}
  192. elseif mode == "v" then -- voice {{{
  193. chan:_change_status(target, dir == "+", "v")
  194. callback(({["+"] = "voice", ["-"] = "devoice"})[dir],
  195. chan, from, target)
  196. ind = ind + 1
  197. -- }}}
  198. end
  199. end
  200. -- }}}
  201. elseif from == to then
  202. -- handle user mode requests {{{
  203. -- TODO: make users more easily accessible so this is actually
  204. -- reasonably possible
  205. for i = 1, mode_string:len() do
  206. local mode = mode_string:sub(i, i)
  207. if mode == "i" then -- invisible {{{
  208. -- }}}
  209. elseif mode == "s" then -- server messages {{{
  210. -- }}}
  211. elseif mode == "w" then -- wallops messages {{{
  212. -- }}}
  213. elseif mode == "o" then -- ircop {{{
  214. -- }}}
  215. end
  216. end
  217. -- }}}
  218. end
  219. end
  220. -- }}}
  221.  
  222. -- on_topic {{{
  223. function handlers.on_topic(from, chan, new_topic)
  224. chan = string.lower(chan)
  225. base.assert(serverinfo.channels[chan],
  226. "Received topic message for unknown channel: " .. chan)
  227. serverinfo.channels[chan]._topic.text = new_topic
  228. serverinfo.channels[chan]._topic.user = from
  229. serverinfo.channels[chan]._topic.time = os.time()
  230. if serverinfo.channels[chan].join_complete then
  231. callback("topic_change", serverinfo.channels[chan])
  232. end
  233. end
  234. -- }}}
  235.  
  236. -- on_invite {{{
  237. function handlers.on_invite(from, to, chan)
  238. callback("invite", from, chan)
  239. end
  240. -- }}}
  241.  
  242. -- on_kick {{{
  243. function handlers.on_kick(from, chan, to)
  244. chan = string.lower(chan)
  245. base.assert(serverinfo.channels[chan],
  246. "Received kick message for unknown channel: " .. chan)
  247. if serverinfo.channels[chan].join_complete then
  248. serverinfo.channels[chan]:_remove_user(to)
  249. callback("kick", serverinfo.channels[chan], to, from)
  250. end
  251. end
  252. -- }}}
  253.  
  254. -- on_privmsg {{{
  255. function handlers.on_privmsg(from, to, msg)
  256. local msgs = ctcp._ctcp_split(msg)
  257. for _, v in base.ipairs(msgs) do
  258. local msg = v.str
  259. if v.ctcp then
  260. -- ctcp message {{{
  261. local words = misc._split(msg)
  262. local received_command = words[1]
  263. if received_command then
  264. local cb = "on_" .. received_command:lower()
  265. table.remove(words, 1)
  266. -- not using try_call here because the ctcp specification requires
  267. -- an error response to nonexistant commands
  268. if base.type(ctcp_handlers[cb]) == "function" then
  269. ctcp_handlers[cb](from, to, table.concat(words, " "))
  270. else
  271. notice(from, c("ERRMSG", received_command, ":Unknown query"))
  272. end
  273. -- }}}
  274. end
  275. else
  276. -- normal message {{{
  277. if to:sub(1, 1) == "#" then
  278. to = string.lower(to)
  279. base.assert(serverinfo.channels[to],
  280. "Received channel msg from unknown channel: " .. to)
  281. callback("channel_msg", serverinfo.channels[to], from, msg)
  282. else
  283. callback("private_msg", from, msg)
  284. end
  285. -- }}}
  286. end
  287. end
  288. end
  289. -- }}}
  290.  
  291. -- on_notice {{{
  292. function handlers.on_notice(from, to, msg)
  293. local msgs = ctcp._ctcp_split(msg)
  294. for _, v in base.ipairs(msgs) do
  295. local msg = v.str
  296. if v.ctcp then
  297. -- ctcp message {{{
  298. local words = misc._split(msg)
  299. local command = words[1]:lower()
  300. table.remove(words, 1)
  301. misc._try_call_warn("Unknown CTCP message: " .. command,
  302. ctcp_handlers["on_rpl_"..command], from, to,
  303. table.concat(words, ' '))
  304. -- }}}
  305. else
  306. -- normal message {{{
  307. if to:sub(1, 1) == "#" then
  308. to = string.lower(to)
  309. base.assert(serverinfo.channels[to],
  310. "Received channel msg from unknown channel: " .. to)
  311. callback("channel_notice", serverinfo.channels[to], from, msg)
  312. else
  313. callback("private_notice", from, msg)
  314. end
  315. -- }}}
  316. end
  317. end
  318. end
  319. -- }}}
  320.  
  321. -- on_quit {{{
  322. function handlers.on_quit(from, quit_msg)
  323. for name, chan in base.pairs(serverinfo.channels) do
  324. chan:_remove_user(from)
  325. end
  326. callback("quit", from, quit_msg)
  327. end
  328. -- }}}
  329.  
  330. -- on_ping {{{
  331. -- respond to server pings to make sure it knows we are alive
  332. function handlers.on_ping(from, respond_to)
  333. send("PONG", respond_to)
  334. end
  335. -- }}}
  336. -- }}}
  337.  
  338. -- server replies {{{
  339. -- on_rpl_topic {{{
  340. -- catch topic changes
  341. function handlers.on_rpl_topic(from, chan, topic)
  342. chan = string.lower(chan)
  343. base.assert(serverinfo.channels[chan],
  344. "Received topic information about unknown channel: " .. chan)
  345. serverinfo.channels[chan]._topic.text = topic
  346. end
  347. -- }}}
  348.  
  349. -- on_rpl_notopic {{{
  350. function handlers.on_rpl_notopic(from, chan)
  351. chan = string.lower(chan)
  352. base.assert(serverinfo.channels[chan],
  353. "Received topic information about unknown channel: " .. chan)
  354. serverinfo.channels[chan]._topic.text = ""
  355. end
  356. -- }}}
  357.  
  358. -- on_rpl_topicdate {{{
  359. -- "topic was set by <user> at <time>"
  360. function handlers.on_rpl_topicdate(from, chan, user, time)
  361. chan = string.lower(chan)
  362. base.assert(serverinfo.channels[chan],
  363. "Received topic information about unknown channel: " .. chan)
  364. serverinfo.channels[chan]._topic.user = user
  365. serverinfo.channels[chan]._topic.time = base.tonumber(time)
  366. end
  367. -- }}}
  368.  
  369. -- on_rpl_namreply {{{
  370. -- handles a NAMES reply
  371. function handlers.on_rpl_namreply(from, chanmode, chan, userlist)
  372. chan = string.lower(chan)
  373. base.assert(serverinfo.channels[chan],
  374. "Received user information about unknown channel: " .. chan)
  375. serverinfo.channels[chan]._chanmode = constants.chanmodes[chanmode]
  376. local users = misc._split(userlist)
  377. for k,v in base.ipairs(users) do
  378. if v:sub(1, 1) == "@" or v:sub(1, 1) == "+" then
  379. local nick = v:sub(2)
  380. serverinfo.channels[chan]:_add_user(nick, v:sub(1, 1))
  381. else
  382. serverinfo.channels[chan]:_add_user(v)
  383. end
  384. end
  385. end
  386. -- }}}
  387.  
  388. -- on_rpl_endofnames {{{
  389. -- when we get this message, the channel join has completed, so call the
  390. -- external cb
  391. function handlers.on_rpl_endofnames(from, chan)
  392. chan = string.lower(chan)
  393. base.assert(serverinfo.channels[chan],
  394. "Received user information about unknown channel: " .. chan)
  395. if not serverinfo.channels[chan].join_complete then
  396. callback("me_join", serverinfo.channels[chan])
  397. serverinfo.channels[chan].join_complete = true
  398. end
  399. end
  400. -- }}}
  401.  
  402. -- on_rpl_welcome {{{
  403. function handlers.on_rpl_welcome(from)
  404. serverinfo = {
  405. connected = false,
  406. connecting = true,
  407. channels = {}
  408. }
  409. end
  410. -- }}}
  411.  
  412. -- on_rpl_yourhost {{{
  413. function handlers.on_rpl_yourhost(from, msg)
  414. serverinfo.host = from
  415. end
  416. -- }}}
  417.  
  418. -- on_rpl_motdstart {{{
  419. function handlers.on_rpl_motdstart(from)
  420. serverinfo.motd = ""
  421. end
  422. -- }}}
  423.  
  424. -- on_rpl_motd {{{
  425. function handlers.on_rpl_motd(from, motd)
  426. serverinfo.motd = (serverinfo.motd or "") .. motd .. "\n"
  427. end
  428. -- }}}
  429.  
  430. -- on_rpl_endofmotd {{{
  431. function handlers.on_rpl_endofmotd(from)
  432. if not serverinfo.connected then
  433. serverinfo.connected = true
  434. serverinfo.connecting = false
  435. callback("connect")
  436. end
  437. end
  438. -- }}}
  439.  
  440. -- on_rpl_whoisuser {{{
  441. function handlers.on_rpl_whoisuser(from, nick, user, host, star, realname)
  442. local lnick = nick:lower()
  443. requestinfo.whois[lnick].nick = nick
  444. requestinfo.whois[lnick].user = user
  445. requestinfo.whois[lnick].host = host
  446. requestinfo.whois[lnick].realname = realname
  447. end
  448. -- }}}
  449.  
  450. -- on_rpl_whoischannels {{{
  451. function handlers.on_rpl_whoischannels(from, nick, channel_list)
  452. nick = nick:lower()
  453. if not requestinfo.whois[nick].channels then
  454. requestinfo.whois[nick].channels = {}
  455. end
  456. for _, channel in base.ipairs(misc._split(channel_list)) do
  457. table.insert(requestinfo.whois[nick].channels, channel)
  458. end
  459. end
  460. -- }}}
  461.  
  462. -- on_rpl_whoisserver {{{
  463. function handlers.on_rpl_whoisserver(from, nick, server, serverinfo)
  464. nick = nick:lower()
  465. requestinfo.whois[nick].server = server
  466. requestinfo.whois[nick].serverinfo = serverinfo
  467. end
  468. -- }}}
  469.  
  470. -- on_rpl_away {{{
  471. function handlers.on_rpl_away(from, nick, away_msg)
  472. nick = nick:lower()
  473. if requestinfo.whois[nick] then
  474. requestinfo.whois[nick].away_msg = away_msg
  475. end
  476. end
  477. -- }}}
  478.  
  479. -- on_rpl_whoisoperator {{{
  480. function handlers.on_rpl_whoisoperator(from, nick)
  481. requestinfo.whois[nick:lower()].is_oper = true
  482. end
  483. -- }}}
  484.  
  485. -- on_rpl_whoisidle {{{
  486. function handlers.on_rpl_whoisidle(from, nick, idle_seconds)
  487. requestinfo.whois[nick:lower()].idle_time = idle_seconds
  488. end
  489. -- }}}
  490.  
  491. -- on_rpl_endofwhois {{{
  492. function handlers.on_rpl_endofwhois(from, nick)
  493. nick = nick:lower()
  494. local cb = table.remove(icallbacks.whois[nick], 1)
  495. cb(requestinfo.whois[nick])
  496. requestinfo.whois[nick] = nil
  497. if #icallbacks.whois[nick] > 0 then send("WHOIS", nick)
  498. else icallbacks.whois[nick] = nil
  499. end
  500. end
  501. -- }}}
  502.  
  503. -- on_rpl_version {{{
  504. function handlers.on_rpl_version(from, version, server, comments)
  505. local cb = table.remove(icallbacks.serverversion[server], 1)
  506. cb({version = version, server = server, comments = comments})
  507. if #icallbacks.serverversion[server] > 0 then send("VERSION", server)
  508. else icallbacks.serverversion[server] = nil
  509. end
  510. end
  511. -- }}}
  512.  
  513. -- on_rpl_time {{{
  514. function on_rpl_time(from, server, time)
  515. local cb = table.remove(icallbacks.servertime[server], 1)
  516. cb({time = time, server = server})
  517. if #icallbacks.servertime[server] > 0 then send("TIME", server)
  518. else icallbacks.servertime[server] = nil
  519. end
  520. end
  521. -- }}}
  522. -- }}}
  523.  
  524. -- ctcp handlers {{{
  525. -- requests {{{
  526. -- on_action {{{
  527. function ctcp_handlers.on_action(from, to, message)
  528. if to:sub(1, 1) == "#" then
  529. to = string.lower(to)
  530. base.assert(serverinfo.channels[to],
  531. "Received channel msg from unknown channel: " .. to)
  532. callback("channel_act", serverinfo.channels[to], from, message)
  533. else
  534. callback("private_act", from, message)
  535. end
  536. end
  537. -- }}}
  538.  
  539. -- on_dcc {{{
  540. -- TODO: can we not have this handler be registered unless the dcc module is
  541. -- loaded?
  542. function ctcp_handlers.on_dcc(from, to, message)
  543. local type, argument, address, port, size = base.unpack(misc._split(message, " ", nil, '"', '"'))
  544. address = misc._ip_int_to_str(address)
  545. if type == "SEND" then
  546. if callback("dcc_send", from, to, argument, address, port, size) then
  547. dcc._accept(argument, address, port)
  548. end
  549. elseif type == "CHAT" then
  550. -- TODO: implement this? do people ever use this?
  551. end
  552. end
  553. -- }}}
  554.  
  555. -- on_version {{{
  556. function ctcp_handlers.on_version(from, to)
  557. notice(from, c("VERSION", _VERSION .. " running under " .. base._VERSION .. " with " .. socket._VERSION))
  558. end
  559. -- }}}
  560.  
  561. -- on_errmsg {{{
  562. function ctcp_handlers.on_errmsg(from, to, message)
  563. notice(from, c("ERRMSG", message, ":No error has occurred"))
  564. end
  565. -- }}}
  566.  
  567. -- on_ping {{{
  568. function ctcp_handlers.on_ping(from, to, timestamp)
  569. notice(from, c("PING", timestamp))
  570. end
  571. -- }}}
  572.  
  573. -- on_time {{{
  574. function ctcp_handlers.on_time(from, to)
  575. notice(from, c("TIME", os.date()))
  576. end
  577. -- }}}
  578. -- }}}
  579.  
  580. -- responses {{{
  581. -- on_rpl_action {{{
  582. -- actions are handled the same, notice or not
  583. ctcp_handlers.on_rpl_action = ctcp_handlers.on_action
  584. -- }}}
  585.  
  586. -- on_rpl_version {{{
  587. function ctcp_handlers.on_rpl_version(from, to, version)
  588. local lfrom = from:lower()
  589. local cb = table.remove(icallbacks.ctcp_version[lfrom], 1)
  590. cb({version = version, nick = from})
  591. if #icallbacks.ctcp_version[lfrom] > 0 then say(from, c("VERSION"))
  592. else icallbacks.ctcp_version[lfrom] = nil
  593. end
  594. end
  595. -- }}}
  596.  
  597. -- on_rpl_errmsg {{{
  598. function ctcp_handlers.on_rpl_errmsg(from, to, message)
  599. callback("ctcp_error", from, to, message)
  600. end
  601. -- }}}
  602.  
  603. -- on_rpl_ping {{{
  604. function ctcp_handlers.on_rpl_ping(from, to, timestamp)
  605. local lfrom = from:lower()
  606. local cb = table.remove(icallbacks.ctcp_ping[lfrom], 1)
  607. cb({time = os.time() - timestamp, nick = from})
  608. if #icallbacks.ctcp_ping[lfrom] > 0 then say(from, c("PING", os.time()))
  609. else icallbacks.ctcp_ping[lfrom] = nil
  610. end
  611. end
  612. -- }}}
  613.  
  614. -- on_rpl_time {{{
  615. function ctcp_handlers.on_rpl_time(from, to, time)
  616. local lfrom = from:lower()
  617. local cb = table.remove(icallbacks.ctcp_time[lfrom], 1)
  618. cb({time = time, nick = from})
  619. if #icallbacks.ctcp_time[lfrom] > 0 then say(from, c("TIME"))
  620. else icallbacks.ctcp_time[lfrom] = nil
  621. end
  622. end
  623. -- }}}
  624. -- }}}
  625. -- }}}
  626. -- }}}
  627.  
  628. -- module functions {{{
  629. -- socket handling functions {{{
  630. -- _register_socket {{{
  631. --
  632. -- Register a socket to listen on.
  633. -- @param sock LuaSocket socket object
  634. -- @param mode 'r' if the socket is for reading, 'w' if for writing
  635. -- @param cb Callback to call when the socket is ready for reading/writing.
  636. -- It will be called with the socket as the single argument.
  637. function _register_socket(sock, mode, cb)
  638. local socks, cbs
  639. if mode == 'r' then
  640. socks = rsockets
  641. cbs = rcallbacks
  642. else
  643. socks = wsockets
  644. cbs = wcallbacks
  645. end
  646. base.assert(not cbs[sock], "socket already registered")
  647. table.insert(socks, sock)
  648. cbs[sock] = cb
  649. end
  650. -- }}}
  651.  
  652. -- _unregister_socket {{{
  653. --
  654. -- Remove a previously registered socket.
  655. -- @param sock Socket to unregister
  656. -- @param mode 'r' to unregister it for reading, 'w' for writing
  657. function _unregister_socket(sock, mode)
  658. local socks, cbs
  659. if mode == 'r' then
  660. socks = rsockets
  661. cbs = rcallbacks
  662. else
  663. socks = wsockets
  664. cbs = wcallbacks
  665. end
  666. for i, v in base.ipairs(socks) do
  667. if v == sock then table.remove(socks, i); break; end
  668. end
  669. cbs[sock] = nil
  670. end
  671. -- }}}
  672. -- }}}
  673. -- }}}
  674.  
  675. -- public functions {{{
  676. -- server commands {{{
  677. -- connect {{{
  678. ---
  679. -- Start a connection to the irc server.
  680. -- @param args Table of named arguments containing connection parameters.
  681. -- Defaults are the all-caps versions of these parameters given
  682. -- at the top of the file, and are overridable by setting them
  683. -- as well, i.e. <pre>irc.NETWORK = irc.freenode.net</pre>
  684. -- Possible options are:
  685. -- <ul>
  686. -- <li><i>network:</i> address of the irc network to connect to
  687. -- (default: 'localhost')</li>
  688. -- <li><i>port:</i> port to connect to
  689. -- (default: '6667')</li>
  690. -- <li><i>pass:</i> irc server password
  691. -- (default: don't send)</li>
  692. -- <li><i>nick:</i> nickname to connect as
  693. -- (default: 'luabot')</li>
  694. -- <li><i>username:</i> username to connect with
  695. -- (default: 'LuaIRC')</li>
  696. -- <li><i>realname:</i> realname to connect with
  697. -- (default: 'LuaIRC')</li>
  698. -- <li><i>timeout:</i> amount of time in seconds to wait before
  699. -- dropping an idle connection
  700. -- (default: '60')</li>
  701. -- </ul>
  702. function connect(args)
  703. ARGS = args
  704. local network = args.network or NETWORK
  705. local port = args.port or PORT
  706. local nickList = args.nickList or NICKLIST
  707. local username = args.username or USERNAME
  708. local realname = args.realname or REALNAME
  709. local timeout = args.timeout or TIMEOUT
  710. NICKLIST = {}
  711. for i,v in base.ipairs(nickList) do
  712. table.insert(NICKLIST,v)
  713. end
  714. serverinfo.connecting = true
  715. if OUTFILE then irc_debug.set_output(OUTFILE) end
  716. if DEBUG then irc_debug.enable() end
  717. irc_sock = base.assert(socket.connect(network, port))
  718. irc_sock:settimeout(timeout)
  719. _register_socket(irc_sock, 'r', incoming_message)
  720. if args.pass then send("PASS", args.pass) end
  721. send("NICK", NICKLIST[1])
  722. send("USER", username, get_ip(), network, realname)
  723. begin_main_loop()
  724. end
  725. -- }}}
  726.  
  727.  
  728. -- This will be called in the loop
  729. -- Try to reconnect to the same network
  730.  
  731. function reconnect(args)
  732. --clear all
  733. irc_sock = nil
  734. rsockets = {}
  735. wsockets = {}
  736. rcallbacks = {}
  737. wcallbacks = {}
  738. icallbacks = {
  739. whois = {},
  740. serverversion = {},
  741. servertime = {},
  742. ctcp_ping = {},
  743. ctcp_time = {},
  744. ctcp_version = {},
  745. }
  746.  
  747. local network = args.network or NETWORK
  748. local port = args.port or PORT
  749. local nickList = args.nickList or NICKLIST
  750. local username = args.username or USERNAME
  751. local realname = args.realname or REALNAME
  752. local timeout = args.timeout or TIMEOUT
  753. NICKLIST = {}
  754. for i,v in base.ipairs(nickList) do
  755. table.insert(NICKLIST,v)
  756. end
  757. serverinfo.connecting = true
  758. if OUTFILE then irc_debug.set_output(OUTFILE) end
  759. if DEBUG then irc_debug.enable() end
  760. --irc_sock = base.assert(socket.connect(network, port))
  761. irc_sock = socket.connect(network, port)
  762. if not irc_sock then return end
  763. irc_sock:settimeout(timeout)
  764. _register_socket(irc_sock, 'r', incoming_message)
  765. if args.pass then send("PASS", args.pass) end
  766. send("NICK", NICKLIST[1])
  767. send("USER", username, get_ip(), network, realname)
  768. end
  769.  
  770.  
  771. -- quit {{{
  772. ---
  773. -- Close the connection to the irc server.
  774. -- @param message Quit message (optional, defaults to 'Leaving')
  775. function quit(message)
  776. message = message or "Leaving"
  777. send("QUIT", message)
  778. serverinfo.connected = false
  779. end
  780. -- }}}
  781.  
  782. -- join {{{
  783. ---
  784. -- Join a channel.
  785. -- @param channel Channel to join
  786. function join(channel)
  787. if not channel then return end
  788. channel = string.lower(channel)
  789. serverinfo.channels[channel] = Channel.new(channel)
  790. send("JOIN", channel)
  791. end
  792. -- }}}
  793.  
  794. -- part {{{
  795. ---
  796. -- Leave a channel.
  797. -- @param channel Channel to leave
  798. function part(channel)
  799. if not channel then return end
  800. serverinfo.channels[channel] = nil
  801. send("PART", channel)
  802. end
  803. -- }}}
  804.  
  805. -- say {{{
  806. ---
  807. -- Send a message to a user or channel.
  808. -- @param name User or channel to send the message to
  809. -- @param message Message to send
  810. function say(name, message)
  811. if not name then return end
  812. message = message or ""
  813. send("PRIVMSG", name, message)
  814. end
  815. -- }}}
  816.  
  817. -- notice {{{
  818. ---
  819. -- Send a notice to a user or channel.
  820. -- @param name User or channel to send the notice to
  821. -- @param message Message to send
  822. function notice(name, message)
  823. if not name then return end
  824. message = message or ""
  825. send("NOTICE", name, message)
  826. end
  827. -- }}}
  828.  
  829. -- act {{{
  830. ---
  831. -- Perform a /me action.
  832. -- @param name User or channel to send the action to
  833. -- @param action Action to send
  834. function act(name, action)
  835. if not name then return end
  836. action = action or ""
  837. send("PRIVMSG", name, c("ACTION", action))
  838. end
  839. -- }}}
  840. -- }}}
  841.  
  842. -- information requests {{{
  843. -- server_version {{{
  844. ---
  845. -- Request the version of the IRC server you are currently connected to.
  846. -- @param cb Callback to call when the information is available. The single
  847. -- table parameter to this callback will contain the fields:
  848. -- <ul>
  849. -- <li><i>server:</i> the server which responded to the request</li>
  850. -- <li><i>version:</i> the server version</li>
  851. -- <li><i>comments:</i> other data provided by the server</li>
  852. -- </ul>
  853. function server_version(cb)
  854. -- apparently the optional server parameter isn't supported for servers
  855. -- which you are not directly connected to (freenode specific?)
  856. local server = serverinfo.host
  857. if not icallbacks.serverversion[server] then
  858. icallbacks.serverversion[server] = {cb}
  859. send("VERSION", server)
  860. else
  861. table.insert(icallbacks.serverversion[server], cb)
  862. end
  863. end
  864. -- }}}
  865.  
  866. -- whois {{{
  867. -- TODO: allow server parameter (to get user idle time)
  868. ---
  869. -- Request WHOIS information about a given user.
  870. -- @param cb Callback to call when the information is available. The single
  871. -- table parameter to this callback may contain any or all of the
  872. -- fields:
  873. -- <ul>
  874. -- <li><i>nick:</i> the nick that was passed to this function
  875. -- (this field will always be here)</li>
  876. -- <li><i>user:</i> the IRC username of the user</li>
  877. -- <li><i>host:</i> the user's hostname</li>
  878. -- <li><i>realname:</i> the IRC realname of the user</li>
  879. -- <li><i>server:</i> the IRC server the user is connected to</li>
  880. -- <li><i>serverinfo:</i> arbitrary information about the above
  881. -- server</li>
  882. -- <li><i>awaymsg:</i> set to the user's away message if they are
  883. -- away</li>
  884. -- <li><i>is_oper:</i> true if the user is an IRCop</li>
  885. -- <li><i>idle_time:</i> amount of time the user has been idle</li>
  886. -- <li><i>channels:</i> array containing the channels the user has
  887. -- joined</li>
  888. -- </ul>
  889. -- @param nick User to request WHOIS information about
  890. function whois(cb, nick)
  891. nick = nick:lower()
  892. requestinfo.whois[nick] = {}
  893. if not icallbacks.whois[nick] then
  894. icallbacks.whois[nick] = {cb}
  895. send("WHOIS", nick)
  896. else
  897. table.insert(icallbacks.whois[nick], cb)
  898. end
  899. end
  900. -- }}}
  901.  
  902. -- server_time {{{
  903. ---
  904. -- Request the current time of the server you are connected to.
  905. -- @param cb Callback to call when the information is available. The single
  906. -- table parameter to this callback will contain the fields:
  907. -- <ul>
  908. -- <li><i>server:</i> the server which responded to the request</li>
  909. -- <li><i>time:</i> the time reported by the server</li>
  910. -- </ul>
  911. function server_time(cb)
  912. -- apparently the optional server parameter isn't supported for servers
  913. -- which you are not directly connected to (freenode specific?)
  914. local server = serverinfo.host
  915. if not icallbacks.servertime[server] then
  916. icallbacks.servertime[server] = {cb}
  917. send("TIME", server)
  918. else
  919. table.insert(icallbacks.servertime[server], cb)
  920. end
  921. end
  922. -- }}}
  923. -- }}}
  924.  
  925. -- ctcp commands {{{
  926. -- ctcp_ping {{{
  927. ---
  928. -- Send a CTCP ping request.
  929. -- @param cb Callback to call when the information is available. The single
  930. -- table parameter to this callback will contain the fields:
  931. -- <ul>
  932. -- <li><i>nick:</i> the nick which responded to the request</li>
  933. -- <li><i>time:</i> the roundtrip ping time, in seconds</li>
  934. -- </ul>
  935. -- @param nick User to ping
  936. function ctcp_ping(cb, nick)
  937. nick = nick:lower()
  938. if not icallbacks.ctcp_ping[nick] then
  939. icallbacks.ctcp_ping[nick] = {cb}
  940. say(nick, c("PING", os.time()))
  941. else
  942. table.insert(icallbacks.ctcp_ping[nick], cb)
  943. end
  944. end
  945. -- }}}
  946.  
  947. -- ctcp_time {{{
  948. ---
  949. -- Send a localtime request.
  950. -- @param cb Callback to call when the information is available. The single
  951. -- table parameter to this callback will contain the fields:
  952. -- <ul>
  953. -- <li><i>nick:</i> the nick which responded to the request</li>
  954. -- <li><i>time:</i> the localtime reported by the remote client</li>
  955. -- </ul>
  956. -- @param nick User to request the localtime from
  957. function ctcp_time(cb, nick)
  958. nick = nick:lower()
  959. if not icallbacks.ctcp_time[nick] then
  960. icallbacks.ctcp_time[nick] = {cb}
  961. say(nick, c("TIME"))
  962. else
  963. table.insert(icallbacks.ctcp_time[nick], cb)
  964. end
  965. end
  966. -- }}}
  967.  
  968. -- ctcp_version {{{
  969. ---
  970. -- Send a client version request.
  971. -- @param cb Callback to call when the information is available. The single
  972. -- table parameter to this callback will contain the fields:
  973. -- <ul>
  974. -- <li><i>nick:</i> the nick which responded to the request</li>
  975. -- <li><i>version:</i> the version reported by the remote client</li>
  976. -- </ul>
  977. -- @param nick User to request the client version from
  978. function ctcp_version(cb, nick)
  979. nick = nick:lower()
  980. if not icallbacks.ctcp_version[nick] then
  981. icallbacks.ctcp_version[nick] = {cb}
  982. say(nick, c("VERSION"))
  983. else
  984. table.insert(icallbacks.ctcp_version[nick], cb)
  985. end
  986. end
  987. -- }}}
  988. -- }}}
  989.  
  990. -- callback functions {{{
  991. -- register_callback {{{
  992. ---
  993. -- Register a user function to be called when a specific event occurs.
  994. -- @param name Name of the event
  995. -- @param fn Function to call when the event occurs, or nil to clear the
  996. -- callback for this event
  997. -- @return Value of the original callback for this event (or nil if no previous
  998. -- callback had been set)
  999. function register_callback(name, fn)
  1000. local old_handler = user_handlers[name]
  1001. user_handlers[name] = fn
  1002. return old_handler
  1003. end
  1004. -- }}}
  1005. -- }}}
  1006.  
  1007. -- misc functions {{{
  1008. -- send {{{
  1009. -- TODO: CTCP quoting should be explicit, this table thing is quite ugly (if
  1010. -- convenient)
  1011. ---
  1012. -- Send a raw IRC command.
  1013. -- @param command String containing the raw IRC command
  1014. -- @param ... Arguments to the command. Each argument is either a string or
  1015. -- an array. Strings are sent literally, arrays are CTCP quoted
  1016. -- as a group. The last argument (if it exists) is preceded by
  1017. -- a : (so it may contain spaces).
  1018. function send(command, ...)
  1019. if not serverinfo.connected and not serverinfo.connecting then return end
  1020. local message = command
  1021. for i, v in base.ipairs({...}) do
  1022. if i == #{...} then
  1023. v = ":" .. v
  1024. end
  1025. message = message .. " " .. v
  1026. end
  1027. message = ctcp._low_quote(message)
  1028. -- we just truncate for now. -2 to account for the \r\n
  1029. message = message:sub(1, constants.IRC_MAX_MSG - 2)
  1030. irc_debug._message("SEND", message)
  1031. irc_sock:send(message .. "\r\n")
  1032. end
  1033. -- }}}
  1034.  
  1035. -- get_ip {{{
  1036. ---
  1037. -- Get the local IP address for the server connection.
  1038. -- @return A string representation of the local IP address that the IRC server
  1039. -- connection is communicating on
  1040. function get_ip()
  1041. return (ip or irc_sock:getsockname())
  1042. end
  1043. -- }}}
  1044.  
  1045. -- set_ip {{{
  1046. ---
  1047. -- Set the local IP manually (to allow for NAT workarounds)
  1048. -- @param new_ip IP address to set
  1049. function set_ip(new_ip)
  1050. ip = new_ip
  1051. end
  1052. -- }}}
  1053.  
  1054. -- channels {{{
  1055. -- TODO: @see doesn't currently work for files/modules
  1056. ---
  1057. -- Iterate over currently joined channels.
  1058. -- channels() is an iterator function for use in for loops.
  1059. -- For example, <pre>for chan in irc.channels() do print(chan:name) end</pre>
  1060. -- @see irc.channel
  1061. function channels()
  1062. return function(state, arg)
  1063. return misc._value_iter(state, arg,
  1064. function(v)
  1065. return v.join_complete
  1066. end)
  1067. end,
  1068. serverinfo.channels,
  1069. nil
  1070. end
  1071. -- }}}
  1072. -- }}}
  1073. -- }}}
Add Comment
Please, Sign In to add comment