PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/core.lua

http://github.com/sweetfish/lurch
Lua | 532 lines | 343 code | 102 blank | 87 comment | 123 complexity | 6d79fe13fdd1bcf9a1a4bb28c93ddb7e MD5 | raw file
  1. local b = {}
  2. -- load external triggers
  3. --triggers = {} -- { trigger_id = trigger_func(bot, chan, msg) }
  4. --dofile("triggers.lua")
  5. -- extra class vars
  6. b.trusted_users = {"sweetfish"}
  7. -- fancy print to console
  8. function b:log(str)
  9. print("[" .. os.date() .. "] " .. str)
  10. end
  11. -- send a raw message to the server
  12. function b:send(str, prio)
  13. --self.client:send(str .. "\n")
  14. --self:log("[<-] " .. str)
  15. if prio == nil then
  16. prio = 1
  17. end
  18. table.insert(self.outputqueue, {msg = str, prio = prio})
  19. end
  20. -- called when we have a working connection
  21. function b:on_connected()
  22. -- join all channels we know
  23. for k,v in pairs(self.config.channels) do
  24. self:join_channel(v)
  25. end
  26. end
  27. function b:is_trusted_user(nickname)
  28. for k,v in pairs(self.trusted_users) do
  29. if v == nickname then
  30. return true
  31. end
  32. end
  33. return false
  34. end
  35. -- parse incomming messages
  36. function b:parse_message(line, err)
  37. if not err then
  38. self:log("[->] " .. tostring(line))
  39. else
  40. self:log("Recieved an error: " .. tostring(err) .. " (line '" .. tostring(line) .. "')")
  41. end
  42. -- first incomming message?
  43. if not self.firstresponse then
  44. -- make sure we have the latest config
  45. self:load_config("settings")
  46. self.firstresponse = true
  47. -- send auth response
  48. self:send("NICK " .. self.config.nickname)
  49. self:send("USER " .. self.config.nickname .. " lua bot :mr lurch")
  50. return
  51. end
  52. -- response id ?
  53. local i,j,respid,respstr = string.find(line, ":.- (%d+) (.+)")
  54. if not (i == nil) then
  55. if respid == "433" then
  56. -- nick allready in use, try to change to alternative
  57. local new_nick = ""
  58. self.altnickid = self.altnickid + 1
  59. if self.altnickid >= #self.config.altnicks then
  60. -- run out of alternative nicks, make up a new one!
  61. new_nick = self.config.nickname .. tostring(math.random(1,9999))
  62. else
  63. new_nick = self.config.altnicks[self.altnickid]
  64. end
  65. self:change_nickname(new_nick)
  66. return
  67. end
  68. end
  69. -- we have a working connection!
  70. if string.sub(line, 1, 1) == ":" then
  71. if not self.connection_ok then
  72. self.connection_ok = true
  73. if not (self.on_connected == nil) then
  74. self:on_connected()
  75. end
  76. end
  77. end
  78. -- ping message?
  79. if string.sub(line, 1, 4) == "PING" then
  80. self:send("PONG " .. string.sub(line, 6))
  81. return
  82. end
  83. -- trigger ?
  84. -- (triggers are in the form of ':<triggername>')
  85. local i,j,s,c,k = string.find(line, ":(.-)!.- PRIVMSG (.-) :" .. self.config.triggerprefix .. "(.+)")
  86. if not (i == nil) then
  87. -- if 'sender' was not a channel, it must be the nickname
  88. if not (string.sub(c, 1, 1) == "#") then
  89. c = s
  90. end
  91. self:trigger(s, c, k)
  92. end
  93. -- forward to modules
  94. for k,v in pairs(self.modules) do
  95. if not (v.parse_message == nil) then
  96. if type(v.parse_message) == "function" then
  97. setfenv(v.parse_message, _G)(self, line)
  98. end
  99. end
  100. end
  101. end
  102. -- send a message to a specific channel or nickname
  103. function b:say(chan, msg)
  104. if not (self.client) then
  105. return
  106. end
  107. if not (msg == nil) then
  108. -- broadcast?
  109. if (chan == nil) then
  110. chan = ""
  111. local i = 1
  112. for k,v in pairs(self.config.channels) do
  113. if not (string.sub(v, 1, 1) == "#") then
  114. v = "#" .. v
  115. end
  116. if #chan == 0 then
  117. chan = v
  118. else
  119. chan = chan .. "," .. v
  120. end
  121. end
  122. end
  123. local pre = ""
  124. local suf = msg
  125. while (not (suf == "")) do
  126. pre = string.sub(suf, 1, self.maxstringlength)
  127. suf = string.sub(suf, self.maxstringlength)
  128. --table.insert(self.outputqueue, {msg = pre, prio = prio})
  129. self:send("PRIVMSG " .. chan .. " :" .. pre)
  130. end
  131. --self:send("PRIVMSG " .. chan .. " :" .. msg)
  132. end
  133. end
  134. -- join a channel
  135. function b:join_channel(chan)
  136. if not (string.sub(chan, 1, 1) == "#") then
  137. chan = "#" .. chan
  138. end
  139. self:send("JOIN " .. chan)
  140. end
  141. -- change nickname
  142. function b:change_nickname(new_nick)
  143. self.config.nickname = new_nick
  144. self:send("NICK " .. new_nick)
  145. end
  146. -- quit the irc network and exit the script
  147. function b:quit(msg)
  148. self:say(nil, msg)
  149. self:send("QUIT :" .. msg)
  150. os.exit()
  151. end
  152. -- reload core.lua script and rebind methods
  153. -- function also pulls the latest commit from the git repo
  154. function b:reload(chan)
  155. self:say(chan, "Pulling latest git...")
  156. os.execute("git pull origin master")
  157. self:say(chan, "Reloading core.lua...")
  158. local succ, err = pcall(dofile, "core.lua")
  159. if succ then
  160. local succ, err = pcall(bind_functions, self)
  161. if succ then
  162. self:say(chan, "Done.")
  163. else
  164. self:say(chan, "Method binding failed:")
  165. self:say(chan, tostring(err))
  166. end
  167. else
  168. self:say(chan, "Failed, error:")
  169. self:say(chan, tostring(err))
  170. end
  171. end
  172. function b:unload_module(chan, modulename)
  173. if self.config.modules[modulename] == nil then
  174. self:say(chan, "No module with that name.")
  175. else
  176. package.loaded["modules/" .. modulename .. "/" .. modulename] = nil
  177. self.config.modules[modulename] = nil
  178. self.modules[modulename] = nil
  179. end
  180. end
  181. function b:load_module(chan, modulename, moduleurl)
  182. if not (moduleurl == nil) then
  183. -- does module exist allready?
  184. local tryopen, errmsg = io.open("modules/" .. tostring(modulename) .. "/tmpfile", "w")
  185. if tryopen == nil then
  186. -- create dir
  187. os.execute("mkdir modules/" .. tostring(modulename))
  188. end
  189. self.config.modules[modulename] = moduleurl
  190. self.modules[modulename] = {}
  191. else
  192. if (self.config.modules[modulename] == nil) then
  193. --local tryopen, errmsg = io.open("modules/" .. tostring(modulename) .. "/tmpfile", "w")
  194. --if tryopen == nil then
  195. -- module does not exist
  196. self.say(chan,"Module does not exist! Use " .. tostring(self.config.triggerprefix) .. "loadmodule <name> <giturl> instead.")
  197. return
  198. end
  199. end
  200. -- remove old
  201. os.execute("rm -rf modules/" .. tostring(modulename))
  202. -- pull code
  203. print("tostring(self.config.modules[modulename]): " .. tostring(self.config.modules[modulename]))
  204. os.execute("git clone " .. tostring(self.config.modules[modulename]) .. " modules/" .. tostring(modulename))
  205. -- reload into modules
  206. package.loaded["modules/" .. modulename .. "/" .. modulename] = nil
  207. self.modules[modulename] = require("modules/" .. modulename .. "/" .. modulename)
  208. self:say(chan, "Module loaded.")
  209. end
  210. -- parse triggers
  211. function b:trigger(user, chan, msg)
  212. -----------------------------
  213. -- core triggers
  214. if (self:is_trusted_user(user)) then
  215. -- echo trigger?
  216. if string.sub(msg, 1, 4) == "echo" then
  217. self:say(chan, string.sub(msg, 6))
  218. end
  219. -- quit ?
  220. if string.sub(msg, 1) == "quit" then
  221. self:quit("good bye!")
  222. end
  223. -- reload ?
  224. if string.sub(msg, 1) == "reload" then
  225. self:save_config("settings")
  226. self:reload(chan)
  227. end
  228. -- saveconf ?
  229. if string.sub(msg, 1) == "saveconf" then
  230. self:save_config("settings")
  231. end
  232. -- loadconf ?
  233. if string.sub(msg, 1) == "loadconf" then
  234. self:load_config("settings")
  235. end
  236. -- clearqueue ?
  237. if string.sub(msg, 1) == "clearqueue" then
  238. self.outputqueue = {}
  239. end
  240. -- loadmod ?
  241. if string.sub(msg, 1, 7) == "loadmod" then
  242. --self:save_config("settings")
  243. local i,j,m,u = string.find(msg, "loadmod (.-) (.+)")
  244. if not (i == nil) then
  245. self:load_module(chan, m, u)
  246. else
  247. local i,j,m = string.find(msg, "loadmod (.+)")
  248. if not (i == nil) then
  249. self:load_module(chan, m, nil)
  250. end
  251. end
  252. end
  253. -- unloadmod ?
  254. if string.sub(msg, 1, 9) == "unloadmod" then
  255. --self:load_config("settings")
  256. local i,j,m = string.find(msg, "unloadmod (.+)")
  257. if not (i == nil) then
  258. self:unload_module(chan, m)
  259. end
  260. end
  261. --[[ rebase ?
  262. --- Depricated!
  263. if string.sub(msg, 1, 6) == "rebase" then
  264. local i,j,gitid = string.find(msg, "rebase (%d+)")
  265. if not (i == nil) then
  266. self:save_config("settings")
  267. self:rebase(chan, gitid)
  268. end
  269. end
  270. ]]
  271. -- join ?
  272. if string.sub(msg, 1, 4) == "join" then
  273. local i,j,c = string.find(msg, "join (.+)")
  274. if not (i == nil) then
  275. if not (string.sub(c, 1, 1) == "#") then
  276. c = "#" .. c
  277. end
  278. self:send("JOIN " .. c)
  279. -- add channel to internal channel list
  280. for k,v in pairs(self.config.channels) do
  281. if v == c then
  282. return
  283. end
  284. end
  285. table.insert(self.config.channels, c)
  286. end
  287. end
  288. -- nick ?
  289. if string.sub(msg, 1, 4) == "nick" then
  290. local i,j,n = string.find(msg, "nick (.+)")
  291. if not (i == nil) then
  292. self:change_nickname(n)
  293. end
  294. end
  295. -- exec ?
  296. if string.sub(msg, 1, 4) == "exec" then
  297. local i,j,s = string.find(msg, "exec (.+)")
  298. if not (i == nil) then
  299. local res = assert(loadstring("return (" .. s .. ")"))()
  300. self:say(chan, res)
  301. --self:send(tostring(res))
  302. end
  303. end
  304. end
  305. --------------------------------
  306. -- normal (public) triggers
  307. for k,v in pairs(self.triggers) do
  308. if string.sub(msg, 1, #tostring(k)) == tostring(k) then
  309. setfenv(v, _G)(self, chan, msg)
  310. break
  311. end
  312. end
  313. end
  314. -- save bot config to file
  315. function b:load_config(file)
  316. local filecheck, err = io.open(file .. ".lua")
  317. if not (filecheck == nil) then
  318. package.loaded[file] = nil
  319. local config = require(file)
  320. self.config = {}
  321. for k,v in pairs(config) do
  322. if not (string.sub(tostring(k), 1, 1) == "_") then
  323. self.config[k] = v
  324. end
  325. end
  326. end
  327. end
  328. -- load bot config from file
  329. function b:save_config(file)
  330. local new_data = 'module("' .. tostring(file) .. '")\n'
  331. local function configval_to_string(k,v,indent)
  332. local ret_str = ""
  333. if not (type(k) == "number") then
  334. ret_str = tostring(k) .. " = "
  335. end
  336. if type(v) == "number" then
  337. ret_str = ret_str .. tostring(v)
  338. elseif type(v) == "string" then
  339. ret_str = ret_str .. '"' .. tostring(v) .. '"'
  340. elseif type(v) == "table" then
  341. ret_str = ret_str .. "{"
  342. local ret_table = {}
  343. for i,j in pairs(v) do
  344. table.insert(ret_table, configval_to_string(i,j,indent + #tostring(k) + 3))
  345. end
  346. local sep = ""
  347. for b=1,indent do
  348. sep = sep .. " "
  349. end
  350. ret_str = ret_str .. table.concat(ret_table, ",\n" .. sep )
  351. ret_str = ret_str .. "\n" .. sep .. "}"
  352. else
  353. return "ERROR"
  354. end
  355. return ret_str
  356. end
  357. local new_config_table = {}
  358. if not (self.config == nil) then
  359. for k,v in pairs(self.config) do
  360. local new_value = ""
  361. table.insert(new_config_table, configval_to_string(k,v, #tostring(k) + 3))
  362. end
  363. end
  364. new_data = new_data .. table.concat(new_config_table, "\n")
  365. local new_file = io.open(tostring(file) .. ".lua", "w+")
  366. new_file:write(new_data .. "\n")
  367. new_file:close()
  368. end
  369. -- main bot loop
  370. -- pumps through all messages sent from the server
  371. -- retruns false if an error occurs
  372. function b:pump()
  373. -- handle outgoing messages
  374. local msgdelta = os.time() - self.lastsentstamp
  375. if (#self.outputqueue > 0) and (msgdelta >= self.msgwait) then
  376. if (#self.outputqueue > self.queuemax) then
  377. -- make sure we wait extra long for messages outside the queue
  378. if (msgdelta >= self.queuewait) then
  379. -- send first one in queue
  380. local elem = self.outputqueue[1]
  381. self.client:send(elem.msg .. "\n")
  382. self:log("[<-] " .. elem.msg)
  383. table.remove(self.outputqueue, 1)
  384. self.lastsentstamp = os.time()
  385. end
  386. else
  387. -- send all remaining
  388. local elem = self.outputqueue[1]
  389. self.client:send(elem.msg .. "\n")
  390. self:log("[<-] " .. elem.msg)
  391. table.remove(self.outputqueue, 1)
  392. self.lastsentstamp = os.time()
  393. end
  394. --[[if prio == nil then
  395. prio = 1
  396. end
  397. table.insert(self.outputqueue, {msg = str, prio = prio})]]
  398. end
  399. -- handle incomming messages
  400. local line, err = self.client:receive()
  401. if not (err == nil) then
  402. if not (err == "timeout") then
  403. self:log("Error from client:recieve(): " .. tostring(err))
  404. return false
  405. end
  406. end
  407. if line then
  408. -- got message
  409. self.activitystamp = os.time()
  410. local succ, err = pcall(self.parse_message, self, line, err)
  411. if not succ then
  412. self:log("Error when trying to parse message: " .. tostring(err))
  413. end
  414. return true
  415. end
  416. if (os.time() - self.activitystamp >= self.activitytimeout) then
  417. self:log("Connection timeout!")
  418. return false
  419. end
  420. return true
  421. end
  422. -- bind functions to specific bot instance
  423. function bind_functions( bot )
  424. for k,v in pairs(b) do
  425. bot[tostring(k)] = v
  426. end
  427. -- load triggers
  428. package.loaded["triggers"] = nil
  429. bot.triggers = require("triggers")
  430. end