hooks.lua 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. -- This file is licensed under the terms of the BSD 2-clause license.
  2. -- See LICENSE.txt for details.
  3. local ie = ...
  4. -- MIME is part of LuaSocket
  5. local b64e = ie.require("mime").b64
  6. irc.hooks = {}
  7. irc.registered_hooks = {}
  8. local stripped_chars = "[\2\31]"
  9. local function normalize(text)
  10. -- Strip colors
  11. text = text:gsub("\3[0-9][0-9,]*", "")
  12. return text:gsub(stripped_chars, "")
  13. end
  14. function irc.doHook(conn)
  15. for name, hook in pairs(irc.registered_hooks) do
  16. for _, func in pairs(hook) do
  17. conn:hook(name, func)
  18. end
  19. end
  20. end
  21. function irc.register_hook(name, func)
  22. irc.registered_hooks[name] = irc.registered_hooks[name] or {}
  23. table.insert(irc.registered_hooks[name], func)
  24. end
  25. function irc.hooks.raw(line)
  26. if irc.config.debug then
  27. print("RECV: "..line)
  28. end
  29. end
  30. function irc.hooks.send(line)
  31. if irc.config.debug then
  32. print("SEND: "..line)
  33. end
  34. end
  35. function irc.hooks.chat(msg)
  36. local channel, text = msg.args[1], msg.args[2]
  37. if text:sub(1, 1) == string.char(1) then
  38. irc.conn:invoke("OnCTCP", msg)
  39. return
  40. end
  41. if channel == irc.conn.nick then
  42. irc.last_from = msg.user.nick
  43. irc.conn:invoke("PrivateMessage", msg)
  44. else
  45. irc.last_from = channel
  46. irc.conn:invoke("OnChannelChat", msg)
  47. end
  48. end
  49. local function get_core_version()
  50. local status = minetest.get_server_status()
  51. local start_pos = select(2, status:find("version=", 1, true))
  52. local end_pos = status:find(",", start_pos, true)
  53. return status:sub(start_pos + 1, end_pos - 1)
  54. end
  55. function irc.hooks.ctcp(msg)
  56. local text = msg.args[2]:sub(2, -2) -- Remove ^C
  57. local args = text:split(' ')
  58. local command = args[1]:upper()
  59. local function reply(s)
  60. irc.queue(irc.msgs.notice(msg.user.nick,
  61. ("\1%s %s\1"):format(command, s)))
  62. end
  63. if command == "ACTION" and msg.args[1] == irc.config.channel then
  64. local action = text:sub(8, -1)
  65. irc.sendLocal(("* %s@IRC %s"):format(msg.user.nick, action))
  66. elseif command == "VERSION" then
  67. reply(("Minetest version %s, IRC mod version %s.")
  68. :format(get_core_version(), irc.version))
  69. elseif command == "PING" then
  70. reply(args[2])
  71. elseif command == "TIME" then
  72. reply(os.date())
  73. end
  74. end
  75. function irc.hooks.channelChat(msg)
  76. local text = normalize(msg.args[2])
  77. irc.check_botcmd(msg)
  78. -- Don't let a user impersonate someone else by using the nick "IRC"
  79. local fake = msg.user.nick:lower():match("^[il|]rc$")
  80. if fake then
  81. irc.sendLocal("<"..msg.user.nick.."@IRC> "..text)
  82. return
  83. end
  84. -- Support multiple servers in a channel better by converting:
  85. -- "<server@IRC> <player> message" into "<player@server> message"
  86. -- "<server@IRC> *** player joined/left the game" into "*** player joined/left server"
  87. -- and "<server@IRC> * player orders a pizza" into "* player@server orders a pizza"
  88. local foundchat, _, chatnick, chatmessage =
  89. text:find("^<([^>]+)> (.*)$")
  90. local foundjoin, _, joinnick =
  91. text:find("^%*%*%* ([^%s]+) joined the game$")
  92. local foundleave, _, leavenick =
  93. text:find("^%*%*%* ([^%s]+) left the game$")
  94. local foundtimedout, _, timedoutnick =
  95. text:find("^%*%*%* ([^%s]+) left the game %(Timed out%)$")
  96. local foundaction, _, actionnick, actionmessage =
  97. text:find("^%* ([^%s]+) (.*)$")
  98. if text:sub(1, 5) == "[off]" then
  99. return
  100. elseif foundchat then
  101. irc.sendLocal(("<%s@%s> %s")
  102. :format(chatnick, msg.user.nick, chatmessage))
  103. elseif foundjoin then
  104. irc.sendLocal(("*** %s joined %s")
  105. :format(joinnick, msg.user.nick))
  106. elseif foundleave then
  107. irc.sendLocal(("*** %s left %s")
  108. :format(leavenick, msg.user.nick))
  109. elseif foundtimedout then
  110. irc.sendLocal(("*** %s left %s (Timed out)")
  111. :format(timedoutnick, msg.user.nick))
  112. elseif foundaction then
  113. irc.sendLocal(("* %s@%s %s")
  114. :format(actionnick, msg.user.nick, actionmessage))
  115. else
  116. irc.sendLocal(("<%s@IRC> %s"):format(msg.user.nick, text))
  117. end
  118. end
  119. function irc.hooks.pm(msg)
  120. -- Trim prefix if it is found
  121. local text = msg.args[2]
  122. local prefix = irc.config.command_prefix
  123. if prefix and text:sub(1, #prefix) == prefix then
  124. text = text:sub(#prefix + 1)
  125. end
  126. irc.bot_command(msg, text)
  127. end
  128. function irc.hooks.kick(channel, target, prefix, reason)
  129. if target == irc.conn.nick then
  130. minetest.chat_send_all("IRC: kicked from "..channel.." by "..prefix.nick..".")
  131. irc.disconnect("Kicked")
  132. else
  133. irc.sendLocal(("-!- %s was kicked from %s by %s [%s]")
  134. :format(target, channel, prefix.nick, reason))
  135. end
  136. end
  137. function irc.hooks.notice(user, target, message)
  138. if user.nick and target == irc.config.channel then
  139. irc.sendLocal("-"..user.nick.."@IRC- "..message)
  140. end
  141. end
  142. function irc.hooks.mode(user, target, modes, ...)
  143. local by = ""
  144. if user.nick then
  145. by = " by "..user.nick
  146. end
  147. local options = ""
  148. if select("#", ...) > 0 then
  149. options = " "
  150. end
  151. options = options .. table.concat({...}, " ")
  152. minetest.chat_send_all(("-!- mode/%s [%s%s]%s")
  153. :format(target, modes, options, by))
  154. end
  155. function irc.hooks.nick(user, newNick)
  156. irc.sendLocal(("-!- %s is now known as %s")
  157. :format(user.nick, newNick))
  158. end
  159. function irc.hooks.join(user, channel)
  160. irc.sendLocal(("-!- %s joined %s")
  161. :format(user.nick, channel))
  162. end
  163. function irc.hooks.part(user, channel, reason)
  164. reason = reason or ""
  165. irc.sendLocal(("-!- %s has left %s [%s]")
  166. :format(user.nick, channel, reason))
  167. end
  168. function irc.hooks.quit(user, reason)
  169. irc.sendLocal(("-!- %s has quit [%s]")
  170. :format(user.nick, reason))
  171. end
  172. function irc.hooks.disconnect(_, isError)
  173. irc.connected = false
  174. if isError then
  175. minetest.log("error", "IRC: Error: Disconnected, reconnecting in one minute.")
  176. minetest.chat_send_all("IRC: Error: Disconnected, reconnecting in one minute.")
  177. minetest.after(60, irc.connect, irc)
  178. else
  179. minetest.log("action", "IRC: Disconnected.")
  180. minetest.chat_send_all("IRC: Disconnected.")
  181. end
  182. end
  183. function irc.hooks.preregister(conn)
  184. if not (irc.config["sasl.user"] and irc.config["sasl.pass"]) then return end
  185. local authString = b64e(
  186. ("%s\x00%s\x00%s"):format(
  187. irc.config["sasl.user"],
  188. irc.config["sasl.user"],
  189. irc.config["sasl.pass"])
  190. )
  191. conn:send("CAP REQ sasl")
  192. conn:send("AUTHENTICATE PLAIN")
  193. conn:send("AUTHENTICATE "..authString)
  194. conn:send("CAP END")
  195. end
  196. irc.register_hook("PreRegister", irc.hooks.preregister)
  197. irc.register_hook("OnRaw", irc.hooks.raw)
  198. irc.register_hook("OnSend", irc.hooks.send)
  199. irc.register_hook("DoPrivmsg", irc.hooks.chat)
  200. irc.register_hook("OnPart", irc.hooks.part)
  201. irc.register_hook("OnKick", irc.hooks.kick)
  202. irc.register_hook("OnJoin", irc.hooks.join)
  203. irc.register_hook("OnQuit", irc.hooks.quit)
  204. irc.register_hook("NickChange", irc.hooks.nick)
  205. irc.register_hook("OnCTCP", irc.hooks.ctcp)
  206. irc.register_hook("PrivateMessage", irc.hooks.pm)
  207. irc.register_hook("OnNotice", irc.hooks.notice)
  208. irc.register_hook("OnChannelChat", irc.hooks.channelChat)
  209. irc.register_hook("OnModeChange", irc.hooks.mode)
  210. irc.register_hook("OnDisconnect", irc.hooks.disconnect)