Guest User

Untitled

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