rbot 0.9.10 → 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (706) hide show
  1. data/AUTHORS +22 -4
  2. data/COPYING +5 -18
  3. data/ChangeLog +396 -0
  4. data/README +23 -25
  5. data/REQUIREMENTS +58 -12
  6. data/Rakefile +215 -0
  7. data/bin/rbot +47 -25
  8. data/bin/rbot-remote +76 -0
  9. data/data/rbot/languages/french.lang +5 -5
  10. data/data/rbot/languages/german.lang +4 -4
  11. data/data/rbot/languages/italian.lang +53 -0
  12. data/data/rbot/languages/japanese.lang +43 -0
  13. data/data/rbot/languages/russian.lang +60 -60
  14. data/data/rbot/languages/traditional_chinese.lang +75 -0
  15. data/data/rbot/plugins/alias.rb +192 -0
  16. data/data/rbot/plugins/autoop.rb +74 -37
  17. data/data/rbot/plugins/autorejoin.rb +15 -5
  18. data/data/rbot/plugins/bans.rb +454 -150
  19. data/data/rbot/plugins/bash.rb +173 -31
  20. data/data/rbot/plugins/botsnack.rb +37 -0
  21. data/data/rbot/plugins/cal.rb +11 -2
  22. data/data/rbot/plugins/chanserv.rb +132 -0
  23. data/data/rbot/plugins/chucknorris.rb +2 -2
  24. data/data/rbot/plugins/chucknorris.yml.gz +0 -0
  25. data/data/rbot/plugins/debugger.rb +136 -0
  26. data/data/rbot/plugins/deepthoughts.rb +89 -95
  27. data/data/rbot/plugins/delicious.rb +109 -0
  28. data/data/rbot/plugins/dice.rb +77 -25
  29. data/data/rbot/plugins/dict.rb +243 -0
  30. data/data/rbot/plugins/dictclient.rb +211 -0
  31. data/data/rbot/plugins/digg.rb +2 -2
  32. data/data/rbot/plugins/excuse.rb +4 -4
  33. data/data/rbot/plugins/factoids.rb +506 -0
  34. data/data/rbot/plugins/figlet.rb +127 -18
  35. data/data/rbot/plugins/fish.rb +93 -33
  36. data/data/rbot/plugins/forecast.rb +40 -27
  37. data/data/rbot/plugins/fortune.rb +86 -11
  38. data/data/rbot/plugins/freshmeat.rb +46 -12
  39. data/data/rbot/plugins/games/azgame.rb +565 -0
  40. data/data/rbot/plugins/games/quiz.rb +961 -0
  41. data/data/rbot/plugins/games/roshambo.rb +62 -0
  42. data/data/rbot/plugins/{roulette.rb → games/roulette.rb} +58 -8
  43. data/data/rbot/plugins/games/shiritori.rb +485 -0
  44. data/data/rbot/plugins/games/uno.rb +1205 -0
  45. data/data/rbot/plugins/games/wheelfortune.rb +615 -0
  46. data/data/rbot/plugins/grouphug.rb +76 -22
  47. data/data/rbot/plugins/hl2.rb +97 -0
  48. data/data/rbot/plugins/host.rb +10 -1
  49. data/data/rbot/plugins/httpd.rb.disabled +1 -0
  50. data/data/rbot/plugins/imdb.rb +511 -47
  51. data/data/rbot/plugins/insult.rb +32 -37
  52. data/data/rbot/plugins/iplookup.rb +224 -224
  53. data/data/rbot/plugins/karma.rb +45 -21
  54. data/data/rbot/plugins/keywords.rb +258 -160
  55. data/data/rbot/plugins/lart.rb +126 -95
  56. data/data/rbot/plugins/lastfm.rb +421 -12
  57. data/data/rbot/plugins/linkbot.rb +75 -0
  58. data/data/rbot/plugins/markov.rb +103 -46
  59. data/data/rbot/plugins/math.rb +6 -5
  60. data/data/rbot/plugins/modes.rb +114 -0
  61. data/data/rbot/plugins/nickrecover.rb +97 -0
  62. data/data/rbot/plugins/nickserv.rb +111 -48
  63. data/data/rbot/plugins/nslookup.rb +15 -19
  64. data/data/rbot/plugins/plugin.header +11 -0
  65. data/data/rbot/plugins/quakeauth.rb +74 -11
  66. data/data/rbot/plugins/quotes.rb +189 -233
  67. data/data/rbot/plugins/reaction.rb +415 -0
  68. data/data/rbot/plugins/remind.rb +11 -15
  69. data/data/rbot/plugins/remotectl.rb +29 -0
  70. data/data/rbot/plugins/ri.rb +74 -0
  71. data/data/rbot/plugins/rot13.rb +16 -1
  72. data/data/rbot/plugins/rss.rb +1219 -585
  73. data/data/rbot/plugins/salut.rb +226 -0
  74. data/data/rbot/plugins/script.rb +184 -0
  75. data/data/rbot/plugins/search.rb +233 -0
  76. data/data/rbot/plugins/seen.rb +44 -40
  77. data/data/rbot/plugins/shortenurls.rb +93 -0
  78. data/data/rbot/plugins/slashdot.rb +49 -11
  79. data/data/rbot/plugins/spell.rb +37 -27
  80. data/data/rbot/plugins/theyfightcrime.rb +36 -39
  81. data/data/rbot/plugins/threat.rb +25 -22
  82. data/data/rbot/plugins/time.rb +135 -0
  83. data/data/rbot/plugins/topic.rb +107 -50
  84. data/data/rbot/plugins/translator.rb +360 -0
  85. data/data/rbot/plugins/tube.rb +28 -40
  86. data/data/rbot/plugins/twitter.rb +187 -0
  87. data/data/rbot/plugins/urban.rb +62 -53
  88. data/data/rbot/plugins/url.rb +193 -409
  89. data/data/rbot/plugins/usermodes.rb +26 -0
  90. data/data/rbot/plugins/wall.rb +60 -0
  91. data/data/rbot/plugins/weather.rb +254 -598
  92. data/data/rbot/plugins/wow.rb +41 -37
  93. data/data/rbot/plugins/wserver.rb +17 -25
  94. data/data/rbot/plugins/youtube.rb +237 -0
  95. data/data/rbot/templates/lart/{larts → larts-english} +0 -0
  96. data/data/rbot/templates/lart/larts-italian +25 -0
  97. data/data/rbot/templates/lart/larts-japanese +10 -0
  98. data/data/rbot/templates/lart/{praises → praises-english} +0 -0
  99. data/data/rbot/templates/lart/praises-italian +4 -0
  100. data/data/rbot/templates/lart/praises-japanese +3 -0
  101. data/data/rbot/templates/quiz/win_messages +11 -0
  102. data/data/rbot/templates/salut/salut-english +40 -0
  103. data/data/rbot/templates/salut/salut-french +29 -0
  104. data/data/rbot/templates/salut/salut-italian +51 -0
  105. data/data/rbot/templates/salut/salut-japanese +31 -0
  106. data/lib/rbot/botuser.rb +940 -0
  107. data/lib/rbot/config-compat.rb +23 -0
  108. data/lib/rbot/config.rb +211 -276
  109. data/lib/rbot/core/auth.rb +1063 -0
  110. data/lib/rbot/core/basics.rb +214 -0
  111. data/lib/rbot/core/config.rb +330 -0
  112. data/lib/rbot/core/filters_ui.rb +64 -0
  113. data/lib/rbot/core/irclog.rb +292 -0
  114. data/lib/rbot/core/remote.rb +401 -0
  115. data/lib/rbot/core/unicode.rb +91 -0
  116. data/lib/rbot/core/userdata.rb +200 -0
  117. data/lib/rbot/core/utils/extends.rb +444 -0
  118. data/lib/rbot/core/utils/filters.rb +154 -0
  119. data/lib/rbot/core/utils/httputil.rb +689 -0
  120. data/lib/rbot/core/utils/utils.rb +717 -0
  121. data/lib/rbot/dbhash.rb +30 -9
  122. data/lib/rbot/irc.rb +1966 -0
  123. data/lib/rbot/ircbot.rb +816 -675
  124. data/lib/rbot/ircsocket.rb +246 -232
  125. data/lib/rbot/language.rb +86 -8
  126. data/lib/rbot/load-gettext.rb +154 -0
  127. data/lib/rbot/maskdb.rb +162 -0
  128. data/lib/rbot/message.rb +392 -65
  129. data/lib/rbot/messagemapper.rb +438 -94
  130. data/lib/rbot/pkgconfig.rb +6 -0
  131. data/lib/rbot/plugins.rb +713 -172
  132. data/lib/rbot/post-config.rb +1 -0
  133. data/lib/rbot/rbotconfig.rb +31 -12
  134. data/lib/rbot/registry.rb +79 -48
  135. data/lib/rbot/rfc2812.rb +927 -519
  136. data/lib/rbot/timer.rb +232 -167
  137. data/po/en_US/rbot-alias.po +82 -0
  138. data/po/en_US/rbot-autoop.po +0 -0
  139. data/po/en_US/rbot-autorejoin.po +0 -0
  140. data/po/en_US/rbot-azgame.po +200 -0
  141. data/po/en_US/rbot-bans.po +0 -0
  142. data/po/en_US/rbot-bash.po +0 -0
  143. data/po/en_US/rbot-botsnack.po +0 -0
  144. data/po/en_US/rbot-cal.po +3 -0
  145. data/po/en_US/rbot-chanserv.po +0 -0
  146. data/po/en_US/rbot-chucknorris.po +0 -0
  147. data/po/en_US/rbot-debugger.po +0 -0
  148. data/po/en_US/rbot-deepthoughts.po +0 -0
  149. data/po/en_US/rbot-delicious.po +0 -0
  150. data/po/en_US/rbot-dice.po +0 -0
  151. data/po/en_US/rbot-dict.po +0 -0
  152. data/po/en_US/rbot-dictclient.po +124 -0
  153. data/po/en_US/rbot-digg.po +0 -0
  154. data/po/en_US/rbot-eightball.po +0 -0
  155. data/po/en_US/rbot-excuse.po +0 -0
  156. data/po/en_US/rbot-factoids.po +107 -0
  157. data/po/en_US/rbot-figlet.po +36 -0
  158. data/po/en_US/rbot-fish.po +0 -0
  159. data/po/en_US/rbot-forecast.po +0 -0
  160. data/po/en_US/rbot-fortune.po +0 -0
  161. data/po/en_US/rbot-freshmeat.po +0 -0
  162. data/po/en_US/rbot-grouphug.po +18 -0
  163. data/po/en_US/rbot-hl2.po +0 -0
  164. data/po/en_US/rbot-host.po +3 -0
  165. data/po/en_US/rbot-imdb.po +0 -0
  166. data/po/en_US/rbot-insult.po +0 -0
  167. data/po/en_US/rbot-iplookup.po +0 -0
  168. data/po/en_US/rbot-karma.po +0 -0
  169. data/po/en_US/rbot-keywords.po +24 -0
  170. data/po/en_US/rbot-lart.po +0 -0
  171. data/po/en_US/rbot-lastfm.po +172 -0
  172. data/po/en_US/rbot-linkbot.po +0 -0
  173. data/po/en_US/rbot-markov.po +20 -0
  174. data/po/en_US/rbot-math.po +0 -0
  175. data/po/en_US/rbot-modes.po +0 -0
  176. data/po/en_US/rbot-nickrecover.po +40 -0
  177. data/po/en_US/rbot-nickserv.po +104 -0
  178. data/po/en_US/rbot-nslookup.po +0 -0
  179. data/po/en_US/rbot-quakeauth.po +0 -0
  180. data/po/en_US/rbot-quiz.po +0 -0
  181. data/po/en_US/rbot-quotes.po +108 -0
  182. data/po/en_US/rbot-reaction.po +0 -0
  183. data/po/en_US/rbot-remind.po +0 -0
  184. data/po/en_US/rbot-remotectl.po +0 -0
  185. data/po/en_US/rbot-ri.po +0 -0
  186. data/po/en_US/rbot-roshambo.po +0 -0
  187. data/po/en_US/rbot-rot13.po +0 -0
  188. data/po/en_US/rbot-roulette.po +0 -0
  189. data/po/en_US/rbot-rss.po +20 -0
  190. data/po/en_US/rbot-salut.po +0 -0
  191. data/po/en_US/rbot-script.po +0 -0
  192. data/po/en_US/rbot-search.po +0 -0
  193. data/po/en_US/rbot-seen.po +0 -0
  194. data/po/en_US/rbot-shiritori.po +113 -0
  195. data/po/en_US/rbot-shortenurls.po +0 -0
  196. data/po/en_US/rbot-slashdot.po +0 -0
  197. data/po/en_US/rbot-spell.po +54 -0
  198. data/po/en_US/rbot-theyfightcrime.po +0 -0
  199. data/po/en_US/rbot-threat.po +0 -0
  200. data/po/en_US/rbot-time.po +0 -0
  201. data/po/en_US/rbot-topic.po +0 -0
  202. data/po/en_US/rbot-translator.po +77 -0
  203. data/po/en_US/rbot-tube.po +0 -0
  204. data/po/en_US/rbot-twitter.po +24 -0
  205. data/po/en_US/rbot-uno.po +512 -0
  206. data/po/en_US/rbot-urban.po +0 -0
  207. data/po/en_US/rbot-url.po +0 -0
  208. data/po/en_US/rbot-usermodes.po +0 -0
  209. data/po/en_US/rbot-wall.po +33 -0
  210. data/po/en_US/rbot-weather.po +0 -0
  211. data/po/en_US/rbot-wheelfortune.po +205 -0
  212. data/po/en_US/rbot-wow.po +0 -0
  213. data/po/en_US/rbot-wserver.po +0 -0
  214. data/po/en_US/rbot-youtube.po +58 -0
  215. data/po/en_US/rbot.po +1097 -0
  216. data/po/fr/rbot-alias.po +97 -0
  217. data/po/fr/rbot-autoop.po +0 -0
  218. data/po/fr/rbot-autorejoin.po +0 -0
  219. data/po/fr/rbot-azgame.po +204 -0
  220. data/po/fr/rbot-bans.po +0 -0
  221. data/po/fr/rbot-bash.po +0 -0
  222. data/po/fr/rbot-botsnack.po +0 -0
  223. data/po/fr/rbot-cal.po +3 -0
  224. data/po/fr/rbot-chanserv.po +0 -0
  225. data/po/fr/rbot-chucknorris.po +0 -0
  226. data/po/fr/rbot-debugger.po +0 -0
  227. data/po/fr/rbot-deepthoughts.po +0 -0
  228. data/po/fr/rbot-delicious.po +0 -0
  229. data/po/fr/rbot-dice.po +0 -0
  230. data/po/fr/rbot-dict.po +0 -0
  231. data/po/fr/rbot-dictclient.po +134 -0
  232. data/po/fr/rbot-digg.po +0 -0
  233. data/po/fr/rbot-eightball.po +0 -0
  234. data/po/fr/rbot-excuse.po +0 -0
  235. data/po/fr/rbot-factoids.po +111 -0
  236. data/po/fr/rbot-figlet.po +37 -0
  237. data/po/fr/rbot-fish.po +0 -0
  238. data/po/fr/rbot-forecast.po +0 -0
  239. data/po/fr/rbot-fortune.po +0 -0
  240. data/po/fr/rbot-freshmeat.po +0 -0
  241. data/po/fr/rbot-grouphug.po +18 -0
  242. data/po/fr/rbot-hl2.po +0 -0
  243. data/po/fr/rbot-host.po +3 -0
  244. data/po/fr/rbot-imdb.po +0 -0
  245. data/po/fr/rbot-insult.po +0 -0
  246. data/po/fr/rbot-iplookup.po +0 -0
  247. data/po/fr/rbot-karma.po +0 -0
  248. data/po/fr/rbot-keywords.po +24 -0
  249. data/po/fr/rbot-lart.po +0 -0
  250. data/po/fr/rbot-lastfm.po +172 -0
  251. data/po/fr/rbot-linkbot.po +0 -0
  252. data/po/fr/rbot-markov.po +20 -0
  253. data/po/fr/rbot-math.po +0 -0
  254. data/po/fr/rbot-modes.po +0 -0
  255. data/po/fr/rbot-nickrecover.po +36 -0
  256. data/po/fr/rbot-nickserv.po +116 -0
  257. data/po/fr/rbot-nslookup.po +0 -0
  258. data/po/fr/rbot-quakeauth.po +0 -0
  259. data/po/fr/rbot-quiz.po +0 -0
  260. data/po/fr/rbot-quotes.po +138 -0
  261. data/po/fr/rbot-reaction.po +0 -0
  262. data/po/fr/rbot-remind.po +0 -0
  263. data/po/fr/rbot-remotectl.po +0 -0
  264. data/po/fr/rbot-ri.po +0 -0
  265. data/po/fr/rbot-roshambo.po +0 -0
  266. data/po/fr/rbot-rot13.po +0 -0
  267. data/po/fr/rbot-roulette.po +0 -0
  268. data/po/fr/rbot-rss.po +20 -0
  269. data/po/fr/rbot-salut.po +0 -0
  270. data/po/fr/rbot-script.po +0 -0
  271. data/po/fr/rbot-search.po +0 -0
  272. data/po/fr/rbot-seen.po +0 -0
  273. data/po/fr/rbot-shiritori.po +119 -0
  274. data/po/fr/rbot-shortenurls.po +0 -0
  275. data/po/fr/rbot-slashdot.po +0 -0
  276. data/po/fr/rbot-spell.po +56 -0
  277. data/po/fr/rbot-theyfightcrime.po +0 -0
  278. data/po/fr/rbot-threat.po +0 -0
  279. data/po/fr/rbot-time.po +0 -0
  280. data/po/fr/rbot-topic.po +0 -0
  281. data/po/fr/rbot-translator.po +90 -0
  282. data/po/fr/rbot-tube.po +0 -0
  283. data/po/fr/rbot-twitter.po +24 -0
  284. data/po/fr/rbot-uno.po +521 -0
  285. data/po/fr/rbot-urban.po +0 -0
  286. data/po/fr/rbot-url.po +0 -0
  287. data/po/fr/rbot-usermodes.po +0 -0
  288. data/po/fr/rbot-wall.po +33 -0
  289. data/po/fr/rbot-weather.po +0 -0
  290. data/po/fr/rbot-wheelfortune.po +255 -0
  291. data/po/fr/rbot-wow.po +0 -0
  292. data/po/fr/rbot-wserver.po +0 -0
  293. data/po/fr/rbot-youtube.po +64 -0
  294. data/po/fr/rbot.po +1133 -0
  295. data/po/it/rbot-alias.po +82 -0
  296. data/po/it/rbot-autoop.po +0 -0
  297. data/po/it/rbot-autorejoin.po +0 -0
  298. data/po/it/rbot-azgame.po +186 -0
  299. data/po/it/rbot-bans.po +0 -0
  300. data/po/it/rbot-bash.po +0 -0
  301. data/po/it/rbot-botsnack.po +0 -0
  302. data/po/it/rbot-cal.po +3 -0
  303. data/po/it/rbot-chanserv.po +0 -0
  304. data/po/it/rbot-chucknorris.po +0 -0
  305. data/po/it/rbot-debugger.po +0 -0
  306. data/po/it/rbot-deepthoughts.po +0 -0
  307. data/po/it/rbot-delicious.po +0 -0
  308. data/po/it/rbot-dice.po +0 -0
  309. data/po/it/rbot-dict.po +0 -0
  310. data/po/it/rbot-dictclient.po +111 -0
  311. data/po/it/rbot-digg.po +0 -0
  312. data/po/it/rbot-eightball.po +0 -0
  313. data/po/it/rbot-excuse.po +0 -0
  314. data/po/it/rbot-factoids.po +110 -0
  315. data/po/it/rbot-figlet.po +36 -0
  316. data/po/it/rbot-fish.po +0 -0
  317. data/po/it/rbot-forecast.po +0 -0
  318. data/po/it/rbot-fortune.po +0 -0
  319. data/po/it/rbot-freshmeat.po +0 -0
  320. data/po/it/rbot-grouphug.po +18 -0
  321. data/po/it/rbot-hl2.po +0 -0
  322. data/po/it/rbot-host.po +3 -0
  323. data/po/it/rbot-imdb.po +0 -0
  324. data/po/it/rbot-insult.po +0 -0
  325. data/po/it/rbot-iplookup.po +0 -0
  326. data/po/it/rbot-karma.po +0 -0
  327. data/po/it/rbot-keywords.po +24 -0
  328. data/po/it/rbot-lart.po +0 -0
  329. data/po/it/rbot-lastfm.po +172 -0
  330. data/po/it/rbot-linkbot.po +0 -0
  331. data/po/it/rbot-markov.po +20 -0
  332. data/po/it/rbot-math.po +0 -0
  333. data/po/it/rbot-modes.po +0 -0
  334. data/po/it/rbot-nickrecover.po +36 -0
  335. data/po/it/rbot-nickserv.po +104 -0
  336. data/po/it/rbot-nslookup.po +0 -0
  337. data/po/it/rbot-quakeauth.po +0 -0
  338. data/po/it/rbot-quiz.po +0 -0
  339. data/po/it/rbot-quotes.po +108 -0
  340. data/po/it/rbot-reaction.po +0 -0
  341. data/po/it/rbot-remind.po +0 -0
  342. data/po/it/rbot-remotectl.po +0 -0
  343. data/po/it/rbot-ri.po +0 -0
  344. data/po/it/rbot-roshambo.po +0 -0
  345. data/po/it/rbot-rot13.po +0 -0
  346. data/po/it/rbot-roulette.po +0 -0
  347. data/po/it/rbot-rss.po +20 -0
  348. data/po/it/rbot-salut.po +0 -0
  349. data/po/it/rbot-script.po +0 -0
  350. data/po/it/rbot-search.po +0 -0
  351. data/po/it/rbot-seen.po +0 -0
  352. data/po/it/rbot-shiritori.po +102 -0
  353. data/po/it/rbot-shortenurls.po +0 -0
  354. data/po/it/rbot-slashdot.po +0 -0
  355. data/po/it/rbot-spell.po +54 -0
  356. data/po/it/rbot-theyfightcrime.po +0 -0
  357. data/po/it/rbot-threat.po +0 -0
  358. data/po/it/rbot-time.po +0 -0
  359. data/po/it/rbot-topic.po +0 -0
  360. data/po/it/rbot-translator.po +77 -0
  361. data/po/it/rbot-tube.po +0 -0
  362. data/po/it/rbot-twitter.po +24 -0
  363. data/po/it/rbot-uno.po +564 -0
  364. data/po/it/rbot-urban.po +0 -0
  365. data/po/it/rbot-url.po +0 -0
  366. data/po/it/rbot-usermodes.po +0 -0
  367. data/po/it/rbot-wall.po +33 -0
  368. data/po/it/rbot-weather.po +0 -0
  369. data/po/it/rbot-wheelfortune.po +251 -0
  370. data/po/it/rbot-wow.po +0 -0
  371. data/po/it/rbot-wserver.po +0 -0
  372. data/po/it/rbot-youtube.po +64 -0
  373. data/po/it/rbot.po +1127 -0
  374. data/po/ja/rbot-alias.po +82 -0
  375. data/po/ja/rbot-autoop.po +0 -0
  376. data/po/ja/rbot-autorejoin.po +0 -0
  377. data/po/ja/rbot-azgame.po +194 -0
  378. data/po/ja/rbot-bans.po +0 -0
  379. data/po/ja/rbot-bash.po +0 -0
  380. data/po/ja/rbot-botsnack.po +0 -0
  381. data/po/ja/rbot-cal.po +3 -0
  382. data/po/ja/rbot-chanserv.po +0 -0
  383. data/po/ja/rbot-chucknorris.po +0 -0
  384. data/po/ja/rbot-debugger.po +0 -0
  385. data/po/ja/rbot-deepthoughts.po +0 -0
  386. data/po/ja/rbot-delicious.po +0 -0
  387. data/po/ja/rbot-dice.po +0 -0
  388. data/po/ja/rbot-dict.po +0 -0
  389. data/po/ja/rbot-dictclient.po +111 -0
  390. data/po/ja/rbot-digg.po +0 -0
  391. data/po/ja/rbot-eightball.po +0 -0
  392. data/po/ja/rbot-excuse.po +0 -0
  393. data/po/ja/rbot-factoids.po +107 -0
  394. data/po/ja/rbot-figlet.po +36 -0
  395. data/po/ja/rbot-fish.po +0 -0
  396. data/po/ja/rbot-forecast.po +0 -0
  397. data/po/ja/rbot-fortune.po +0 -0
  398. data/po/ja/rbot-freshmeat.po +0 -0
  399. data/po/ja/rbot-grouphug.po +18 -0
  400. data/po/ja/rbot-hl2.po +0 -0
  401. data/po/ja/rbot-host.po +3 -0
  402. data/po/ja/rbot-imdb.po +0 -0
  403. data/po/ja/rbot-insult.po +0 -0
  404. data/po/ja/rbot-iplookup.po +0 -0
  405. data/po/ja/rbot-karma.po +0 -0
  406. data/po/ja/rbot-keywords.po +24 -0
  407. data/po/ja/rbot-lart.po +0 -0
  408. data/po/ja/rbot-lastfm.po +172 -0
  409. data/po/ja/rbot-linkbot.po +0 -0
  410. data/po/ja/rbot-markov.po +20 -0
  411. data/po/ja/rbot-math.po +0 -0
  412. data/po/ja/rbot-modes.po +0 -0
  413. data/po/ja/rbot-nickrecover.po +36 -0
  414. data/po/ja/rbot-nickserv.po +104 -0
  415. data/po/ja/rbot-nslookup.po +0 -0
  416. data/po/ja/rbot-quakeauth.po +0 -0
  417. data/po/ja/rbot-quiz.po +0 -0
  418. data/po/ja/rbot-quotes.po +108 -0
  419. data/po/ja/rbot-reaction.po +0 -0
  420. data/po/ja/rbot-remind.po +0 -0
  421. data/po/ja/rbot-remotectl.po +0 -0
  422. data/po/ja/rbot-ri.po +0 -0
  423. data/po/ja/rbot-roshambo.po +0 -0
  424. data/po/ja/rbot-rot13.po +0 -0
  425. data/po/ja/rbot-roulette.po +0 -0
  426. data/po/ja/rbot-rss.po +20 -0
  427. data/po/ja/rbot-salut.po +0 -0
  428. data/po/ja/rbot-script.po +0 -0
  429. data/po/ja/rbot-search.po +0 -0
  430. data/po/ja/rbot-seen.po +0 -0
  431. data/po/ja/rbot-shiritori.po +115 -0
  432. data/po/ja/rbot-shortenurls.po +0 -0
  433. data/po/ja/rbot-slashdot.po +0 -0
  434. data/po/ja/rbot-spell.po +54 -0
  435. data/po/ja/rbot-theyfightcrime.po +0 -0
  436. data/po/ja/rbot-threat.po +0 -0
  437. data/po/ja/rbot-time.po +0 -0
  438. data/po/ja/rbot-topic.po +0 -0
  439. data/po/ja/rbot-translator.po +77 -0
  440. data/po/ja/rbot-tube.po +0 -0
  441. data/po/ja/rbot-twitter.po +24 -0
  442. data/po/ja/rbot-uno.po +512 -0
  443. data/po/ja/rbot-urban.po +0 -0
  444. data/po/ja/rbot-url.po +0 -0
  445. data/po/ja/rbot-usermodes.po +0 -0
  446. data/po/ja/rbot-wall.po +33 -0
  447. data/po/ja/rbot-weather.po +0 -0
  448. data/po/ja/rbot-wheelfortune.po +205 -0
  449. data/po/ja/rbot-wow.po +0 -0
  450. data/po/ja/rbot-wserver.po +0 -0
  451. data/po/ja/rbot-youtube.po +58 -0
  452. data/po/ja/rbot.po +999 -0
  453. data/po/rbot-alias.pot +83 -0
  454. data/po/rbot-autoop.pot +0 -0
  455. data/po/rbot-autorejoin.pot +0 -0
  456. data/po/rbot-azgame.pot +187 -0
  457. data/po/rbot-bans.pot +0 -0
  458. data/po/rbot-bash.pot +0 -0
  459. data/po/rbot-botsnack.pot +0 -0
  460. data/po/rbot-cal.pot +21 -0
  461. data/po/rbot-chanserv.pot +0 -0
  462. data/po/rbot-chucknorris.pot +0 -0
  463. data/po/rbot-debugger.pot +0 -0
  464. data/po/rbot-deepthoughts.pot +0 -0
  465. data/po/rbot-delicious.pot +0 -0
  466. data/po/rbot-dice.pot +0 -0
  467. data/po/rbot-dict.pot +0 -0
  468. data/po/rbot-dictclient.pot +112 -0
  469. data/po/rbot-digg.pot +0 -0
  470. data/po/rbot-eightball.pot +0 -0
  471. data/po/rbot-excuse.pot +0 -0
  472. data/po/rbot-factoids.pot +108 -0
  473. data/po/rbot-figlet.pot +37 -0
  474. data/po/rbot-fish.pot +0 -0
  475. data/po/rbot-forecast.pot +0 -0
  476. data/po/rbot-fortune.pot +0 -0
  477. data/po/rbot-freshmeat.pot +0 -0
  478. data/po/rbot-grouphug.pot +36 -0
  479. data/po/rbot-hl2.pot +0 -0
  480. data/po/rbot-host.pot +21 -0
  481. data/po/rbot-imdb.pot +0 -0
  482. data/po/rbot-insult.pot +0 -0
  483. data/po/rbot-iplookup.pot +0 -0
  484. data/po/rbot-karma.pot +0 -0
  485. data/po/rbot-keywords.pot +25 -0
  486. data/po/rbot-lart.pot +0 -0
  487. data/po/rbot-lastfm.pot +190 -0
  488. data/po/rbot-linkbot.pot +0 -0
  489. data/po/rbot-markov.pot +21 -0
  490. data/po/rbot-math.pot +0 -0
  491. data/po/rbot-modes.pot +0 -0
  492. data/po/rbot-nickrecover.pot +37 -0
  493. data/po/rbot-nickserv.pot +105 -0
  494. data/po/rbot-nslookup.pot +0 -0
  495. data/po/rbot-quakeauth.pot +0 -0
  496. data/po/rbot-quiz.pot +0 -0
  497. data/po/rbot-quotes.pot +109 -0
  498. data/po/rbot-reaction.pot +0 -0
  499. data/po/rbot-remind.pot +0 -0
  500. data/po/rbot-remotectl.pot +0 -0
  501. data/po/rbot-ri.pot +0 -0
  502. data/po/rbot-roshambo.pot +0 -0
  503. data/po/rbot-rot13.pot +0 -0
  504. data/po/rbot-roulette.pot +0 -0
  505. data/po/rbot-rss.pot +21 -0
  506. data/po/rbot-salut.pot +0 -0
  507. data/po/rbot-script.pot +0 -0
  508. data/po/rbot-search.pot +0 -0
  509. data/po/rbot-seen.pot +0 -0
  510. data/po/rbot-shiritori.pot +103 -0
  511. data/po/rbot-shortenurls.pot +0 -0
  512. data/po/rbot-slashdot.pot +0 -0
  513. data/po/rbot-spell.pot +55 -0
  514. data/po/rbot-theyfightcrime.pot +0 -0
  515. data/po/rbot-threat.pot +0 -0
  516. data/po/rbot-time.pot +0 -0
  517. data/po/rbot-topic.pot +0 -0
  518. data/po/rbot-translator.pot +78 -0
  519. data/po/rbot-tube.pot +0 -0
  520. data/po/rbot-twitter.pot +25 -0
  521. data/po/rbot-uno.pot +513 -0
  522. data/po/rbot-urban.pot +0 -0
  523. data/po/rbot-url.pot +0 -0
  524. data/po/rbot-usermodes.pot +0 -0
  525. data/po/rbot-wall.pot +33 -0
  526. data/po/rbot-weather.pot +0 -0
  527. data/po/rbot-wheelfortune.pot +206 -0
  528. data/po/rbot-wow.pot +0 -0
  529. data/po/rbot-wserver.pot +0 -0
  530. data/po/rbot-youtube.pot +59 -0
  531. data/po/rbot.pot +1004 -0
  532. data/po/zh_CN/rbot-alias.po +82 -0
  533. data/po/zh_CN/rbot-autoop.po +0 -0
  534. data/po/zh_CN/rbot-autorejoin.po +0 -0
  535. data/po/zh_CN/rbot-azgame.po +186 -0
  536. data/po/zh_CN/rbot-bans.po +0 -0
  537. data/po/zh_CN/rbot-bash.po +0 -0
  538. data/po/zh_CN/rbot-botsnack.po +0 -0
  539. data/po/zh_CN/rbot-cal.po +3 -0
  540. data/po/zh_CN/rbot-chanserv.po +0 -0
  541. data/po/zh_CN/rbot-chucknorris.po +0 -0
  542. data/po/zh_CN/rbot-debugger.po +0 -0
  543. data/po/zh_CN/rbot-deepthoughts.po +0 -0
  544. data/po/zh_CN/rbot-delicious.po +0 -0
  545. data/po/zh_CN/rbot-dice.po +0 -0
  546. data/po/zh_CN/rbot-dict.po +0 -0
  547. data/po/zh_CN/rbot-dictclient.po +111 -0
  548. data/po/zh_CN/rbot-digg.po +0 -0
  549. data/po/zh_CN/rbot-eightball.po +0 -0
  550. data/po/zh_CN/rbot-excuse.po +0 -0
  551. data/po/zh_CN/rbot-factoids.po +107 -0
  552. data/po/zh_CN/rbot-figlet.po +36 -0
  553. data/po/zh_CN/rbot-fish.po +0 -0
  554. data/po/zh_CN/rbot-forecast.po +0 -0
  555. data/po/zh_CN/rbot-fortune.po +0 -0
  556. data/po/zh_CN/rbot-freshmeat.po +0 -0
  557. data/po/zh_CN/rbot-grouphug.po +18 -0
  558. data/po/zh_CN/rbot-hl2.po +0 -0
  559. data/po/zh_CN/rbot-host.po +3 -0
  560. data/po/zh_CN/rbot-imdb.po +0 -0
  561. data/po/zh_CN/rbot-insult.po +0 -0
  562. data/po/zh_CN/rbot-iplookup.po +0 -0
  563. data/po/zh_CN/rbot-karma.po +0 -0
  564. data/po/zh_CN/rbot-keywords.po +24 -0
  565. data/po/zh_CN/rbot-lart.po +0 -0
  566. data/po/zh_CN/rbot-lastfm.po +172 -0
  567. data/po/zh_CN/rbot-linkbot.po +0 -0
  568. data/po/zh_CN/rbot-markov.po +20 -0
  569. data/po/zh_CN/rbot-math.po +0 -0
  570. data/po/zh_CN/rbot-modes.po +0 -0
  571. data/po/zh_CN/rbot-nickrecover.po +36 -0
  572. data/po/zh_CN/rbot-nickserv.po +104 -0
  573. data/po/zh_CN/rbot-nslookup.po +0 -0
  574. data/po/zh_CN/rbot-quakeauth.po +0 -0
  575. data/po/zh_CN/rbot-quiz.po +0 -0
  576. data/po/zh_CN/rbot-quotes.po +108 -0
  577. data/po/zh_CN/rbot-reaction.po +0 -0
  578. data/po/zh_CN/rbot-remind.po +0 -0
  579. data/po/zh_CN/rbot-remotectl.po +0 -0
  580. data/po/zh_CN/rbot-ri.po +0 -0
  581. data/po/zh_CN/rbot-roshambo.po +0 -0
  582. data/po/zh_CN/rbot-rot13.po +0 -0
  583. data/po/zh_CN/rbot-roulette.po +0 -0
  584. data/po/zh_CN/rbot-rss.po +20 -0
  585. data/po/zh_CN/rbot-salut.po +0 -0
  586. data/po/zh_CN/rbot-script.po +0 -0
  587. data/po/zh_CN/rbot-search.po +0 -0
  588. data/po/zh_CN/rbot-seen.po +0 -0
  589. data/po/zh_CN/rbot-shiritori.po +102 -0
  590. data/po/zh_CN/rbot-shortenurls.po +0 -0
  591. data/po/zh_CN/rbot-slashdot.po +0 -0
  592. data/po/zh_CN/rbot-spell.po +54 -0
  593. data/po/zh_CN/rbot-theyfightcrime.po +0 -0
  594. data/po/zh_CN/rbot-threat.po +0 -0
  595. data/po/zh_CN/rbot-time.po +0 -0
  596. data/po/zh_CN/rbot-topic.po +0 -0
  597. data/po/zh_CN/rbot-translator.po +77 -0
  598. data/po/zh_CN/rbot-tube.po +0 -0
  599. data/po/zh_CN/rbot-twitter.po +24 -0
  600. data/po/zh_CN/rbot-uno.po +512 -0
  601. data/po/zh_CN/rbot-urban.po +0 -0
  602. data/po/zh_CN/rbot-url.po +0 -0
  603. data/po/zh_CN/rbot-usermodes.po +0 -0
  604. data/po/zh_CN/rbot-wall.po +33 -0
  605. data/po/zh_CN/rbot-weather.po +0 -0
  606. data/po/zh_CN/rbot-wheelfortune.po +205 -0
  607. data/po/zh_CN/rbot-wow.po +0 -0
  608. data/po/zh_CN/rbot-wserver.po +0 -0
  609. data/po/zh_CN/rbot-youtube.po +58 -0
  610. data/po/zh_CN/rbot.po +1006 -0
  611. data/po/zh_TW/rbot-alias.po +82 -0
  612. data/po/zh_TW/rbot-autoop.po +0 -0
  613. data/po/zh_TW/rbot-autorejoin.po +0 -0
  614. data/po/zh_TW/rbot-azgame.po +186 -0
  615. data/po/zh_TW/rbot-bans.po +0 -0
  616. data/po/zh_TW/rbot-bash.po +0 -0
  617. data/po/zh_TW/rbot-botsnack.po +0 -0
  618. data/po/zh_TW/rbot-cal.po +3 -0
  619. data/po/zh_TW/rbot-chanserv.po +0 -0
  620. data/po/zh_TW/rbot-chucknorris.po +0 -0
  621. data/po/zh_TW/rbot-debugger.po +0 -0
  622. data/po/zh_TW/rbot-deepthoughts.po +0 -0
  623. data/po/zh_TW/rbot-delicious.po +0 -0
  624. data/po/zh_TW/rbot-dice.po +0 -0
  625. data/po/zh_TW/rbot-dict.po +0 -0
  626. data/po/zh_TW/rbot-dictclient.po +111 -0
  627. data/po/zh_TW/rbot-digg.po +0 -0
  628. data/po/zh_TW/rbot-eightball.po +0 -0
  629. data/po/zh_TW/rbot-excuse.po +0 -0
  630. data/po/zh_TW/rbot-factoids.po +107 -0
  631. data/po/zh_TW/rbot-figlet.po +36 -0
  632. data/po/zh_TW/rbot-fish.po +0 -0
  633. data/po/zh_TW/rbot-forecast.po +0 -0
  634. data/po/zh_TW/rbot-fortune.po +0 -0
  635. data/po/zh_TW/rbot-freshmeat.po +0 -0
  636. data/po/zh_TW/rbot-grouphug.po +18 -0
  637. data/po/zh_TW/rbot-hl2.po +0 -0
  638. data/po/zh_TW/rbot-host.po +3 -0
  639. data/po/zh_TW/rbot-imdb.po +0 -0
  640. data/po/zh_TW/rbot-insult.po +0 -0
  641. data/po/zh_TW/rbot-iplookup.po +0 -0
  642. data/po/zh_TW/rbot-karma.po +0 -0
  643. data/po/zh_TW/rbot-keywords.po +24 -0
  644. data/po/zh_TW/rbot-lart.po +0 -0
  645. data/po/zh_TW/rbot-lastfm.po +172 -0
  646. data/po/zh_TW/rbot-linkbot.po +0 -0
  647. data/po/zh_TW/rbot-markov.po +20 -0
  648. data/po/zh_TW/rbot-math.po +0 -0
  649. data/po/zh_TW/rbot-modes.po +0 -0
  650. data/po/zh_TW/rbot-nickrecover.po +36 -0
  651. data/po/zh_TW/rbot-nickserv.po +104 -0
  652. data/po/zh_TW/rbot-nslookup.po +0 -0
  653. data/po/zh_TW/rbot-quakeauth.po +0 -0
  654. data/po/zh_TW/rbot-quiz.po +0 -0
  655. data/po/zh_TW/rbot-quotes.po +108 -0
  656. data/po/zh_TW/rbot-reaction.po +0 -0
  657. data/po/zh_TW/rbot-remind.po +0 -0
  658. data/po/zh_TW/rbot-remotectl.po +0 -0
  659. data/po/zh_TW/rbot-ri.po +0 -0
  660. data/po/zh_TW/rbot-roshambo.po +0 -0
  661. data/po/zh_TW/rbot-rot13.po +0 -0
  662. data/po/zh_TW/rbot-roulette.po +0 -0
  663. data/po/zh_TW/rbot-rss.po +20 -0
  664. data/po/zh_TW/rbot-salut.po +0 -0
  665. data/po/zh_TW/rbot-script.po +0 -0
  666. data/po/zh_TW/rbot-search.po +0 -0
  667. data/po/zh_TW/rbot-seen.po +0 -0
  668. data/po/zh_TW/rbot-shiritori.po +102 -0
  669. data/po/zh_TW/rbot-shortenurls.po +0 -0
  670. data/po/zh_TW/rbot-slashdot.po +0 -0
  671. data/po/zh_TW/rbot-spell.po +54 -0
  672. data/po/zh_TW/rbot-theyfightcrime.po +0 -0
  673. data/po/zh_TW/rbot-threat.po +0 -0
  674. data/po/zh_TW/rbot-time.po +0 -0
  675. data/po/zh_TW/rbot-topic.po +0 -0
  676. data/po/zh_TW/rbot-translator.po +77 -0
  677. data/po/zh_TW/rbot-tube.po +0 -0
  678. data/po/zh_TW/rbot-twitter.po +24 -0
  679. data/po/zh_TW/rbot-uno.po +512 -0
  680. data/po/zh_TW/rbot-urban.po +0 -0
  681. data/po/zh_TW/rbot-url.po +0 -0
  682. data/po/zh_TW/rbot-usermodes.po +0 -0
  683. data/po/zh_TW/rbot-wall.po +33 -0
  684. data/po/zh_TW/rbot-weather.po +0 -0
  685. data/po/zh_TW/rbot-wheelfortune.po +205 -0
  686. data/po/zh_TW/rbot-wow.po +0 -0
  687. data/po/zh_TW/rbot-wserver.po +0 -0
  688. data/po/zh_TW/rbot-youtube.po +58 -0
  689. data/po/zh_TW/rbot.po +1034 -0
  690. data/setup.rb +800 -574
  691. metadata +723 -108
  692. data/data/rbot/contrib/plugins/figlet.rb +0 -20
  693. data/data/rbot/contrib/plugins/ri.rb +0 -83
  694. data/data/rbot/plugins/demauro.rb +0 -95
  695. data/data/rbot/plugins/google.rb +0 -53
  696. data/data/rbot/plugins/opme.rb +0 -19
  697. data/data/rbot/plugins/roshambo.rb +0 -54
  698. data/data/rbot/plugins/rubyurl.rb +0 -39
  699. data/data/rbot/plugins/tinyurl.rb +0 -39
  700. data/data/rbot/plugins/xmlrpc.rb.disabled +0 -52
  701. data/data/rbot/templates/levels.rbot +0 -32
  702. data/data/rbot/templates/users.rbot +0 -1
  703. data/lib/rbot/auth.rb +0 -314
  704. data/lib/rbot/channel.rb +0 -54
  705. data/lib/rbot/httputil.rb +0 -309
  706. data/lib/rbot/utils.rb +0 -83
@@ -1,3 +1,8 @@
1
+ #-- vim:sw=2:et
2
+ #++
3
+ #
4
+ # :title: rbot core
5
+
1
6
  require 'thread'
2
7
 
3
8
  require 'etc'
@@ -10,9 +15,32 @@ $daemonize = false unless $daemonize
10
15
  $dateformat = "%Y/%m/%d %H:%M:%S"
11
16
  $logger = Logger.new($stderr)
12
17
  $logger.datetime_format = $dateformat
13
- $logger.level = $cl_loglevel if $cl_loglevel
18
+ $logger.level = $cl_loglevel if defined? $cl_loglevel
14
19
  $logger.level = 0 if $debug
15
20
 
21
+ $log_queue = Queue.new
22
+ $log_thread = nil
23
+
24
+ require 'pp'
25
+
26
+ unless Kernel.instance_methods.include?("pretty_inspect")
27
+ def pretty_inspect
28
+ PP.pp(self, '')
29
+ end
30
+ public :pretty_inspect
31
+ end
32
+
33
+ class Exception
34
+ def pretty_print(q)
35
+ q.group(1, "#<%s: %s" % [self.class, self.message], ">") {
36
+ if self.backtrace and not self.backtrace.empty?
37
+ q.text "\n"
38
+ q.seplist(self.backtrace, lambda { q.text "\n" } ) { |l| q.text l }
39
+ end
40
+ }
41
+ end
42
+ end
43
+
16
44
  def rawlog(level, message=nil, who_pos=1)
17
45
  call_stack = caller
18
46
  if call_stack.length > who_pos
@@ -23,18 +51,54 @@ def rawlog(level, message=nil, who_pos=1)
23
51
  # Output each line. To distinguish between separate messages and multi-line
24
52
  # messages originating at the same time, we blank #{who} after the first message
25
53
  # is output.
26
- message.to_s.each_line { |l|
27
- $logger.add(level, l.chomp, who)
28
- who.gsub!(/./," ")
54
+ # Also, we output strings as-is but for other objects we use pretty_inspect
55
+ case message
56
+ when String
57
+ str = message
58
+ else
59
+ str = message.pretty_inspect
60
+ end
61
+ qmsg = Array.new
62
+ str.each_line { |l|
63
+ qmsg.push [level, l.chomp, who]
64
+ who = ' ' * who.size
29
65
  }
66
+ $log_queue.push qmsg
67
+ end
68
+
69
+ def halt_logger
70
+ if $log_thread && $log_thread.alive?
71
+ $log_queue << nil
72
+ $log_thread.join
73
+ $log_thread = nil
74
+ end
30
75
  end
31
76
 
77
+ END { halt_logger }
78
+
79
+ def restart_logger(newlogger = false)
80
+ halt_logger
81
+
82
+ $logger = newlogger if newlogger
83
+
84
+ $log_thread = Thread.new do
85
+ ls = nil
86
+ while ls = $log_queue.pop
87
+ ls.each { |l| $logger.add(*l) }
88
+ end
89
+ end
90
+ end
91
+
92
+ restart_logger
93
+
32
94
  def log_session_start
33
95
  $logger << "\n\n=== #{botclass} session started on #{Time.now.strftime($dateformat)} ===\n\n"
96
+ restart_logger
34
97
  end
35
98
 
36
99
  def log_session_end
37
100
  $logger << "\n\n=== #{botclass} session ended on #{Time.now.strftime($dateformat)} ===\n\n"
101
+ $log_queue << nil
38
102
  end
39
103
 
40
104
  def debug(message=nil, who_pos=1)
@@ -68,33 +132,32 @@ $interrupted = 0
68
132
 
69
133
  # these first
70
134
  require 'rbot/rbotconfig'
135
+ require 'rbot/load-gettext'
71
136
  require 'rbot/config'
72
- require 'rbot/utils'
137
+ require 'rbot/config-compat'
73
138
 
139
+ require 'rbot/irc'
74
140
  require 'rbot/rfc2812'
75
141
  require 'rbot/ircsocket'
76
- require 'rbot/auth'
142
+ require 'rbot/botuser'
77
143
  require 'rbot/timer'
78
144
  require 'rbot/plugins'
79
- require 'rbot/channel'
80
145
  require 'rbot/message'
81
146
  require 'rbot/language'
82
147
  require 'rbot/dbhash'
83
148
  require 'rbot/registry'
84
- require 'rbot/httputil'
85
149
 
86
150
  module Irc
87
151
 
88
152
  # Main bot class, which manages the various components, receives messages,
89
153
  # handles them or passes them to plugins, and contains core functionality.
90
- class IrcBot
91
- # the bot's current nickname
92
- attr_reader :nick
93
-
94
- # the bot's IrcAuth data
154
+ class Bot
155
+ COPYRIGHT_NOTICE = "(c) Tom Gilbert and the rbot development team"
156
+ SOURCE_URL = "http://ruby-rbot.org"
157
+ # the bot's Auth data
95
158
  attr_reader :auth
96
159
 
97
- # the bot's BotConfig data
160
+ # the bot's Config data
98
161
  attr_reader :config
99
162
 
100
163
  # the botclass for this bot (determines configdir among other things)
@@ -104,16 +167,15 @@ class IrcBot
104
167
  # by default)
105
168
  attr_reader :timer
106
169
 
170
+ # synchronize with this mutex while touching permanent data files:
171
+ # saving, flushing, cleaning up ...
172
+ attr_reader :save_mutex
173
+
107
174
  # bot's Language data
108
175
  attr_reader :lang
109
176
 
110
- # capabilities info for the server
111
- attr_reader :capabilities
112
-
113
- # channel info for channels the bot is in
114
- attr_reader :channels
115
-
116
177
  # bot's irc socket
178
+ # TODO multiserver
117
179
  attr_reader :socket
118
180
 
119
181
  # bot's object registry, plugins get an interface to this for persistant
@@ -126,90 +188,229 @@ class IrcBot
126
188
 
127
189
  # bot's httputil help object, for fetching resources via http. Sets up
128
190
  # proxies etc as defined by the bot configuration/environment
129
- attr_reader :httputil
191
+ attr_accessor :httputil
192
+
193
+ # server we are connected to
194
+ # TODO multiserver
195
+ def server
196
+ @client.server
197
+ end
198
+
199
+ # bot User in the client/server connection
200
+ # TODO multiserver
201
+ def myself
202
+ @client.user
203
+ end
204
+
205
+ # bot User in the client/server connection
206
+ def nick
207
+ myself.nick
208
+ end
209
+
210
+ # nick wanted by the bot. This defaults to the irc.nick config value,
211
+ # but may be overridden by a manual !nick command
212
+ def wanted_nick
213
+ @wanted_nick || config['irc.nick']
214
+ end
215
+
216
+ # set the nick wanted by the bot
217
+ def wanted_nick=(wn)
218
+ if wn.nil? or wn.to_s.downcase == config['irc.nick'].downcase
219
+ @wanted_nick = nil
220
+ else
221
+ @wanted_nick = wn.to_s.dup
222
+ end
223
+ end
224
+
225
+
226
+ # bot inspection
227
+ # TODO multiserver
228
+ def inspect
229
+ ret = self.to_s[0..-2]
230
+ ret << ' version=' << $version.inspect
231
+ ret << ' botclass=' << botclass.inspect
232
+ ret << ' lang="' << lang.language
233
+ if defined?(GetText)
234
+ ret << '/' << locale
235
+ end
236
+ ret << '"'
237
+ ret << ' nick=' << nick.inspect
238
+ ret << ' server='
239
+ if server
240
+ ret << (server.to_s + (socket ?
241
+ ' [' << socket.server_uri.to_s << ']' : '')).inspect
242
+ unless server.channels.empty?
243
+ ret << " channels="
244
+ ret << server.channels.map { |c|
245
+ "%s%s" % [c.modes_of(nick).map { |m|
246
+ server.prefix_for_mode(m)
247
+ }, c.name]
248
+ }.inspect
249
+ end
250
+ else
251
+ ret << '(none)'
252
+ end
253
+ ret << ' plugins=' << plugins.inspect
254
+ ret << ">"
255
+ end
256
+
130
257
 
131
- # create a new IrcBot with botclass +botclass+
258
+ # create a new Bot with botclass +botclass+
132
259
  def initialize(botclass, params = {})
133
- # BotConfig for the core bot
260
+ # Config for the core bot
134
261
  # TODO should we split socket stuff into ircsocket, etc?
135
- BotConfig.register BotConfigStringValue.new('server.name',
136
- :default => "localhost", :requires_restart => true,
137
- :desc => "What server should the bot connect to?",
138
- :wizard => true)
139
- BotConfig.register BotConfigIntegerValue.new('server.port',
140
- :default => 6667, :type => :integer, :requires_restart => true,
141
- :desc => "What port should the bot connect to?",
142
- :validate => Proc.new {|v| v > 0}, :wizard => true)
143
- BotConfig.register BotConfigStringValue.new('server.password',
262
+ Config.register Config::ArrayValue.new('server.list',
263
+ :default => ['irc://localhost'], :wizard => true,
264
+ :requires_restart => true,
265
+ :desc => "List of irc servers rbot should try to connect to. Use comma to separate values. Servers are in format 'server.doma.in:port'. If port is not specified, default value (6667) is used.")
266
+ Config.register Config::BooleanValue.new('server.ssl',
267
+ :default => false, :requires_restart => true, :wizard => true,
268
+ :desc => "Use SSL to connect to this server?")
269
+ Config.register Config::StringValue.new('server.password',
144
270
  :default => false, :requires_restart => true,
145
271
  :desc => "Password for connecting to this server (if required)",
146
272
  :wizard => true)
147
- BotConfig.register BotConfigStringValue.new('server.bindhost',
273
+ Config.register Config::StringValue.new('server.bindhost',
148
274
  :default => false, :requires_restart => true,
149
275
  :desc => "Specific local host or IP for the bot to bind to (if required)",
150
276
  :wizard => true)
151
- BotConfig.register BotConfigIntegerValue.new('server.reconnect_wait',
277
+ Config.register Config::IntegerValue.new('server.reconnect_wait',
152
278
  :default => 5, :validate => Proc.new{|v| v >= 0},
153
279
  :desc => "Seconds to wait before attempting to reconnect, on disconnect")
154
- BotConfig.register BotConfigFloatValue.new('server.sendq_delay',
280
+ Config.register Config::FloatValue.new('server.sendq_delay',
155
281
  :default => 2.0, :validate => Proc.new{|v| v >= 0},
156
282
  :desc => "(flood prevention) the delay between sending messages to the server (in seconds)",
157
283
  :on_change => Proc.new {|bot, v| bot.socket.sendq_delay = v })
158
- BotConfig.register BotConfigIntegerValue.new('server.sendq_burst',
284
+ Config.register Config::IntegerValue.new('server.sendq_burst',
159
285
  :default => 4, :validate => Proc.new{|v| v >= 0},
160
286
  :desc => "(flood prevention) max lines to burst to the server before throttling. Most ircd's allow bursts of up 5 lines",
161
287
  :on_change => Proc.new {|bot, v| bot.socket.sendq_burst = v })
162
- BotConfig.register BotConfigStringValue.new('server.byterate',
163
- :default => "400/2", :validate => Proc.new{|v| v.match(/\d+\/\d/)},
164
- :desc => "(flood prevention) max bytes/seconds rate to send the server. Most ircd's have limits of 512 bytes/2 seconds",
165
- :on_change => Proc.new {|bot, v| bot.socket.byterate = v })
166
- BotConfig.register BotConfigIntegerValue.new('server.ping_timeout',
288
+ Config.register Config::IntegerValue.new('server.ping_timeout',
167
289
  :default => 30, :validate => Proc.new{|v| v >= 0},
168
- :on_change => Proc.new {|bot, v| bot.start_server_pings},
169
290
  :desc => "reconnect if server doesn't respond to PING within this many seconds (set to 0 to disable)")
291
+ Config.register Config::ArrayValue.new('server.nocolor_modes',
292
+ :default => ['c'], :wizard => false,
293
+ :requires_restart => false,
294
+ :desc => "List of channel modes that require messages to be without colors")
170
295
 
171
- BotConfig.register BotConfigStringValue.new('irc.nick', :default => "rbot",
296
+ Config.register Config::StringValue.new('irc.nick', :default => "rbot",
172
297
  :desc => "IRC nickname the bot should attempt to use", :wizard => true,
173
298
  :on_change => Proc.new{|bot, v| bot.sendq "NICK #{v}" })
174
- BotConfig.register BotConfigStringValue.new('irc.user', :default => "rbot",
299
+ Config.register Config::StringValue.new('irc.name',
300
+ :default => "Ruby bot", :requires_restart => true,
301
+ :desc => "IRC realname the bot should use")
302
+ Config.register Config::BooleanValue.new('irc.name_copyright',
303
+ :default => true, :requires_restart => true,
304
+ :desc => "Append copyright notice to bot realname? (please don't disable unless it's really necessary)")
305
+ Config.register Config::StringValue.new('irc.user', :default => "rbot",
175
306
  :requires_restart => true,
176
307
  :desc => "local user the bot should appear to be", :wizard => true)
177
- BotConfig.register BotConfigArrayValue.new('irc.join_channels',
308
+ Config.register Config::ArrayValue.new('irc.join_channels',
178
309
  :default => [], :wizard => true,
179
310
  :desc => "What channels the bot should always join at startup. List multiple channels using commas to separate. If a channel requires a password, use a space after the channel name. e.g: '#chan1, #chan2, #secretchan secritpass, #chan3'")
180
- BotConfig.register BotConfigArrayValue.new('irc.ignore_users',
181
- :default => [],
311
+ Config.register Config::ArrayValue.new('irc.ignore_users',
312
+ :default => [],
182
313
  :desc => "Which users to ignore input from. This is mainly to avoid bot-wars triggered by creative people")
183
314
 
184
- BotConfig.register BotConfigIntegerValue.new('core.save_every',
315
+ Config.register Config::IntegerValue.new('core.save_every',
185
316
  :default => 60, :validate => Proc.new{|v| v >= 0},
186
- # TODO change timer via on_change proc
317
+ :on_change => Proc.new { |bot, v|
318
+ if @save_timer
319
+ if v > 0
320
+ @timer.reschedule(@save_timer, v)
321
+ @timer.unblock(@save_timer)
322
+ else
323
+ @timer.block(@save_timer)
324
+ end
325
+ else
326
+ if v > 0
327
+ @save_timer = @timer.add(v) { bot.save }
328
+ end
329
+ # Nothing to do when v == 0
330
+ end
331
+ },
187
332
  :desc => "How often the bot should persist all configuration to disk (in case of a server crash, for example)")
188
333
 
189
- BotConfig.register BotConfigBooleanValue.new('core.run_as_daemon',
334
+ Config.register Config::BooleanValue.new('core.run_as_daemon',
190
335
  :default => false, :requires_restart => true,
191
336
  :desc => "Should the bot run as a daemon?")
192
337
 
193
- BotConfig.register BotConfigStringValue.new('log.file',
338
+ Config.register Config::StringValue.new('log.file',
194
339
  :default => false, :requires_restart => true,
195
340
  :desc => "Name of the logfile to which console messages will be redirected when the bot is run as a daemon")
196
- BotConfig.register BotConfigIntegerValue.new('log.level',
341
+ Config.register Config::IntegerValue.new('log.level',
197
342
  :default => 1, :requires_restart => false,
198
343
  :validate => Proc.new { |v| (0..5).include?(v) },
199
344
  :on_change => Proc.new { |bot, v|
200
345
  $logger.level = v
201
346
  },
202
347
  :desc => "The minimum logging level (0=DEBUG,1=INFO,2=WARN,3=ERROR,4=FATAL) for console messages")
203
- BotConfig.register BotConfigIntegerValue.new('log.keep',
348
+ Config.register Config::IntegerValue.new('log.keep',
204
349
  :default => 1, :requires_restart => true,
205
350
  :validate => Proc.new { |v| v >= 0 },
206
351
  :desc => "How many old console messages logfiles to keep")
207
- BotConfig.register BotConfigIntegerValue.new('log.max_size',
352
+ Config.register Config::IntegerValue.new('log.max_size',
208
353
  :default => 10, :requires_restart => true,
209
354
  :validate => Proc.new { |v| v > 0 },
210
355
  :desc => "Maximum console messages logfile size (in megabytes)")
211
356
 
357
+ Config.register Config::ArrayValue.new('plugins.path',
358
+ :wizard => true, :default => ['(default)', '(default)/games', '(default)/contrib'],
359
+ :requires_restart => false,
360
+ :on_change => Proc.new { |bot, v| bot.setup_plugins_path },
361
+ :desc => "Where the bot should look for plugins. List multiple directories using commas to separate. Use '(default)' for default prepackaged plugins collection, '(default)/contrib' for prepackaged unsupported plugins collection")
362
+
363
+ Config.register Config::EnumValue.new('send.newlines',
364
+ :values => ['split', 'join'], :default => 'split',
365
+ :on_change => Proc.new { |bot, v|
366
+ bot.set_default_send_options :newlines => v.to_sym
367
+ },
368
+ :desc => "When set to split, messages with embedded newlines will be sent as separate lines. When set to join, newlines will be replaced by the value of join_with")
369
+ Config.register Config::StringValue.new('send.join_with',
370
+ :default => ' ',
371
+ :on_change => Proc.new { |bot, v|
372
+ bot.set_default_send_options :join_with => v.dup
373
+ },
374
+ :desc => "String used to replace newlines when send.newlines is set to join")
375
+ Config.register Config::IntegerValue.new('send.max_lines',
376
+ :default => 5,
377
+ :validate => Proc.new { |v| v >= 0 },
378
+ :on_change => Proc.new { |bot, v|
379
+ bot.set_default_send_options :max_lines => v
380
+ },
381
+ :desc => "Maximum number of IRC lines to send for each message (set to 0 for no limit)")
382
+ Config.register Config::EnumValue.new('send.overlong',
383
+ :values => ['split', 'truncate'], :default => 'split',
384
+ :on_change => Proc.new { |bot, v|
385
+ bot.set_default_send_options :overlong => v.to_sym
386
+ },
387
+ :desc => "When set to split, messages which are too long to fit in a single IRC line are split into multiple lines. When set to truncate, long messages are truncated to fit the IRC line length")
388
+ Config.register Config::StringValue.new('send.split_at',
389
+ :default => '\s+',
390
+ :on_change => Proc.new { |bot, v|
391
+ bot.set_default_send_options :split_at => Regexp.new(v)
392
+ },
393
+ :desc => "A regular expression that should match the split points for overlong messages (see send.overlong)")
394
+ Config.register Config::BooleanValue.new('send.purge_split',
395
+ :default => true,
396
+ :on_change => Proc.new { |bot, v|
397
+ bot.set_default_send_options :purge_split => v
398
+ },
399
+ :desc => "Set to true if the splitting boundary (set in send.split_at) should be removed when splitting overlong messages (see send.overlong)")
400
+ Config.register Config::StringValue.new('send.truncate_text',
401
+ :default => "#{Reverse}...#{Reverse}",
402
+ :on_change => Proc.new { |bot, v|
403
+ bot.set_default_send_options :truncate_text => v.dup
404
+ },
405
+ :desc => "When truncating overlong messages (see send.overlong) or when sending too many lines per message (see send.max_lines) replace the end of the last line with this text")
406
+
212
407
  @argv = params[:argv]
408
+ @run_dir = params[:run_dir] || Dir.pwd
409
+
410
+ unless FileTest.directory? Config::coredir
411
+ error "core directory '#{Config::coredir}' not found, did you setup.rb?"
412
+ exit 2
413
+ end
213
414
 
214
415
  unless FileTest.directory? Config::datadir
215
416
  error "data directory '#{Config::datadir}' not found, did you setup.rb?"
@@ -218,9 +419,9 @@ class IrcBot
218
419
 
219
420
  unless botclass and not botclass.empty?
220
421
  # We want to find a sensible default.
221
- # * On POSIX systems we prefer ~/.rbot for the effective uid of the process
222
- # * On Windows (at least the NT versions) we want to put our stuff in the
223
- # Application Data folder.
422
+ # * On POSIX systems we prefer ~/.rbot for the effective uid of the process
423
+ # * On Windows (at least the NT versions) we want to put our stuff in the
424
+ # Application Data folder.
224
425
  # We don't use any particular O/S detection magic, exploiting the fact that
225
426
  # Etc.getpwuid is nil on Windows
226
427
  if Etc.getpwuid(Process::Sys.geteuid)
@@ -236,7 +437,21 @@ class IrcBot
236
437
  botclass = File.expand_path(botclass)
237
438
  @botclass = botclass.gsub(/\/$/, "")
238
439
 
239
- unless FileTest.directory? botclass
440
+ if FileTest.directory? botclass
441
+ # compare the templates dir with the current botclass, and fill it in with
442
+ # any missing file.
443
+ # Sadly, FileUtils.cp_r doesn't have an :update option, so we have to do it
444
+ # manually
445
+ template = File.join Config::datadir, 'templates'
446
+ # note that we use the */** pattern because we don't want to match
447
+ # keywords.rbot, which gets deleted on load and would therefore be missing always
448
+ missing = Dir.chdir(template) { Dir.glob('*/**') } - Dir.chdir(botclass) { Dir.glob('*/**') }
449
+ missing.map do |f|
450
+ dest = File.join(botclass, f)
451
+ FileUtils.mkdir_p File.dirname dest
452
+ FileUtils.cp File.join(template, f), dest
453
+ end
454
+ else
240
455
  log "no #{botclass} directory found, creating from templates.."
241
456
  if FileTest.exist? botclass
242
457
  error "file #{botclass} exists but isn't a directory"
@@ -245,14 +460,24 @@ class IrcBot
245
460
  FileUtils.cp_r Config::datadir+'/templates', botclass
246
461
  end
247
462
 
248
- Dir.mkdir("#{botclass}/logs") unless File.exist?("#{botclass}/logs")
249
463
  Dir.mkdir("#{botclass}/registry") unless File.exist?("#{botclass}/registry")
464
+ Dir.mkdir("#{botclass}/safe_save") unless File.exist?("#{botclass}/safe_save")
250
465
 
251
- @ping_timer = nil
252
- @pong_timer = nil
466
+ # Time at which the last PING was sent
253
467
  @last_ping = nil
468
+ # Time at which the last line was RECV'd from the server
469
+ @last_rec = nil
470
+
254
471
  @startup_time = Time.new
255
- @config = BotConfig.new(self)
472
+
473
+ begin
474
+ @config = Config.manager
475
+ @config.bot_associate(self)
476
+ rescue Exception => e
477
+ fatal e
478
+ log_session_end
479
+ exit 2
480
+ end
256
481
 
257
482
  if @config['core.run_as_daemon']
258
483
  $daemonize = true
@@ -261,10 +486,11 @@ class IrcBot
261
486
  @logfile = @config['log.file']
262
487
  if @logfile.class!=String || @logfile.empty?
263
488
  @logfile = "#{botclass}/#{File.basename(botclass).gsub(/^\.+/,'')}.log"
489
+ debug "Using `#{@logfile}' as debug log"
264
490
  end
265
491
 
266
492
  # See http://blog.humlab.umu.se/samuel/archives/000107.html
267
- # for the backgrounding code
493
+ # for the backgrounding code
268
494
  if $daemonize
269
495
  begin
270
496
  exit if fork
@@ -272,276 +498,383 @@ class IrcBot
272
498
  exit if fork
273
499
  rescue NotImplementedError
274
500
  warning "Could not background, fork not supported"
275
- rescue => e
276
- warning "Could not background. #{e.inspect}"
501
+ rescue SystemExit
502
+ exit 0
503
+ rescue Exception => e
504
+ warning "Could not background. #{e.pretty_inspect}"
277
505
  end
278
506
  Dir.chdir botclass
279
507
  # File.umask 0000 # Ensure sensible umask. Adjust as needed.
508
+ end
509
+
510
+ logger = Logger.new(@logfile,
511
+ @config['log.keep'],
512
+ @config['log.max_size']*1024*1024)
513
+ logger.datetime_format= $dateformat
514
+ logger.level = @config['log.level']
515
+ logger.level = $cl_loglevel if defined? $cl_loglevel
516
+ logger.level = 0 if $debug
517
+
518
+ restart_logger(logger)
519
+
520
+ log_session_start
521
+
522
+ if $daemonize
280
523
  log "Redirecting standard input/output/error"
281
- begin
282
- STDIN.reopen "/dev/null"
283
- rescue Errno::ENOENT
284
- # On Windows, there's not such thing as /dev/null
285
- STDIN.reopen "NUL"
524
+ [$stdin, $stdout, $stderr].each do |fd|
525
+ begin
526
+ fd.reopen "/dev/null"
527
+ rescue Errno::ENOENT
528
+ # On Windows, there's not such thing as /dev/null
529
+ fd.reopen "NUL"
530
+ end
286
531
  end
287
- def STDOUT.write(str=nil)
532
+
533
+ def $stdout.write(str=nil)
288
534
  log str, 2
289
- return str.to_s.length
535
+ return str.to_s.size
290
536
  end
291
- def STDERR.write(str=nil)
537
+ def $stdout.write(str=nil)
292
538
  if str.to_s.match(/:\d+: warning:/)
293
539
  warning str, 2
294
540
  else
295
541
  error str, 2
296
542
  end
297
- return str.to_s.length
543
+ return str.to_s.size
298
544
  end
299
545
  end
300
546
 
301
- # Set the new logfile and loglevel. This must be done after the daemonizing
302
- $logger = Logger.new(@logfile, @config['log.keep'], @config['log.max_size']*1024*1024)
303
- $logger.datetime_format= $dateformat
304
- $logger.level = @config['log.level']
305
- $logger.level = $cl_loglevel if $cl_loglevel
306
- $logger.level = 0 if $debug
547
+ File.open($opts['pidfile'] || "#{@botclass}/rbot.pid", 'w') do |pf|
548
+ pf << "#{$$}\n"
549
+ end
307
550
 
308
- log_session_start
551
+ @registry = Registry.new self
309
552
 
310
- @timer = Timer::Timer.new(1.0) # only need per-second granularity
311
- @registry = BotRegistry.new self
553
+ @timer = Timer.new
312
554
  @save_mutex = Mutex.new
313
- @timer.add(@config['core.save_every']) { save } if @config['core.save_every']
314
- @channels = Hash.new
315
- @logs = Hash.new
316
- @httputil = Utils::HttpUtil.new(self)
317
- @lang = Language::Language.new(@config['core.language'])
555
+ if @config['core.save_every'] > 0
556
+ @save_timer = @timer.add(@config['core.save_every']) { save }
557
+ else
558
+ @save_timer = nil
559
+ end
560
+ @quit_mutex = Mutex.new
561
+
562
+ @plugins = nil
563
+ @lang = Language.new(self, @config['core.language'])
564
+
318
565
  begin
319
- @auth = IrcAuth.new(self)
320
- rescue => e
321
- fatal e.inspect
322
- fatal e.backtrace.join("\n")
566
+ @auth = Auth::manager
567
+ @auth.bot_associate(self)
568
+ # @auth.load("#{botclass}/botusers.yaml")
569
+ rescue Exception => e
570
+ fatal e
323
571
  log_session_end
324
572
  exit 2
325
573
  end
574
+ @auth.everyone.set_default_permission("*", true)
575
+ @auth.botowner.password= @config['auth.password']
326
576
 
327
577
  Dir.mkdir("#{botclass}/plugins") unless File.exist?("#{botclass}/plugins")
328
- @plugins = Plugins::Plugins.new(self, ["#{botclass}/plugins"])
578
+ @plugins = Plugins::manager
579
+ @plugins.bot_associate(self)
580
+ setup_plugins_path()
581
+
582
+ if @config['server.name']
583
+ debug "upgrading configuration (server.name => server.list)"
584
+ srv_uri = 'irc://' + @config['server.name']
585
+ srv_uri += ":#{@config['server.port']}" if @config['server.port']
586
+ @config.items['server.list'.to_sym].set_string(srv_uri)
587
+ @config.delete('server.name'.to_sym)
588
+ @config.delete('server.port'.to_sym)
589
+ debug "server.list is now #{@config['server.list'].inspect}"
590
+ end
591
+
592
+ @socket = Irc::Socket.new(@config['server.list'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst'], :ssl => @config['server.ssl'])
593
+ @client = Client.new
594
+
595
+ @plugins.scan
329
596
 
330
- @socket = IrcSocket.new(@config['server.name'], @config['server.port'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst'])
331
- @nick = @config['irc.nick']
597
+ # Channels where we are quiet
598
+ # Array of channels names where the bot should be quiet
599
+ # '*' means all channels
600
+ #
601
+ @quiet = Set.new
332
602
 
333
- @client = IrcClient.new
603
+ # the nick we want, if it's different from the irc.nick config value
604
+ # (e.g. as set by a !nick command)
605
+ @wanted_nick = nil
606
+
607
+ @client[:welcome] = proc {|data|
608
+ m = WelcomeMessage.new(self, server, data[:source], data[:target], data[:message])
609
+
610
+ @plugins.delegate("welcome", m)
611
+ @plugins.delegate("connect")
612
+ }
613
+
614
+ # TODO the next two @client should go into rfc2812.rb, probably
615
+ # Since capabs are two-steps processes, server.supports[:capab]
616
+ # should be a three-state: nil, [], [....]
617
+ asked_for = { :"identify-msg" => false }
334
618
  @client[:isupport] = proc { |data|
335
- if data[:capab]
619
+ if server.supports[:capab] and !asked_for[:"identify-msg"]
336
620
  sendq "CAPAB IDENTIFY-MSG"
621
+ asked_for[:"identify-msg"] = true
337
622
  end
338
623
  }
339
624
  @client[:datastr] = proc { |data|
340
- debug data.inspect
341
625
  if data[:text] == "IDENTIFY-MSG"
342
- @capabilities["identify-msg".to_sym] = true
626
+ server.capabilities[:"identify-msg"] = true
343
627
  else
344
628
  debug "Not handling RPL_DATASTR #{data[:servermessage]}"
345
629
  end
346
630
  }
631
+
347
632
  @client[:privmsg] = proc { |data|
348
- message = PrivMessage.new(self, data[:source], data[:target], data[:message])
349
- onprivmsg(message)
633
+ m = PrivMessage.new(self, server, data[:source], data[:target], data[:message], :handle_id => true)
634
+ # debug "Message source is #{data[:source].inspect}"
635
+ # debug "Message target is #{data[:target].inspect}"
636
+ # debug "Bot is #{myself.inspect}"
637
+
638
+ @config['irc.ignore_users'].each { |mask|
639
+ if m.source.matches?(server.new_netmask(mask))
640
+ m.ignored = true
641
+ end
642
+ }
643
+
644
+ @plugins.irc_delegate('privmsg', m)
350
645
  }
351
646
  @client[:notice] = proc { |data|
352
- message = NoticeMessage.new(self, data[:source], data[:target], data[:message])
647
+ message = NoticeMessage.new(self, server, data[:source], data[:target], data[:message], :handle_id => true)
353
648
  # pass it off to plugins that want to hear everything
354
- @plugins.delegate "listen", message
649
+ @plugins.irc_delegate "notice", message
355
650
  }
356
651
  @client[:motd] = proc { |data|
357
- data[:motd].each_line { |line|
358
- irclog "MOTD: #{line}", "server"
359
- }
652
+ m = MotdMessage.new(self, server, data[:source], data[:target], data[:motd])
653
+ @plugins.delegate "motd", m
360
654
  }
361
655
  @client[:nicktaken] = proc { |data|
362
- nickchg "#{data[:nick]}_"
656
+ new = "#{data[:nick]}_"
657
+ nickchg new
658
+ # If we're setting our nick at connection because our choice was taken,
659
+ # we have to fix our nick manually, because there will be no NICK message
660
+ # to inform us that our nick has been changed.
661
+ if data[:target] == '*'
662
+ debug "setting my connection nick to #{new}"
663
+ nick = new
664
+ end
363
665
  @plugins.delegate "nicktaken", data[:nick]
364
666
  }
365
667
  @client[:badnick] = proc {|data|
366
668
  warning "bad nick (#{data[:nick]})"
367
669
  }
368
670
  @client[:ping] = proc {|data|
369
- @socket.queue "PONG #{data[:pingid]}"
671
+ sendq "PONG #{data[:pingid]}"
370
672
  }
371
673
  @client[:pong] = proc {|data|
372
674
  @last_ping = nil
373
675
  }
374
676
  @client[:nick] = proc {|data|
375
- sourcenick = data[:sourcenick]
376
- nick = data[:nick]
377
- m = NickMessage.new(self, data[:source], data[:sourcenick], data[:nick])
378
- if(sourcenick == @nick)
379
- debug "my nick is now #{nick}"
380
- @nick = nick
677
+ # debug "Message source is #{data[:source].inspect}"
678
+ # debug "Bot is #{myself.inspect}"
679
+ source = data[:source]
680
+ old = data[:oldnick]
681
+ new = data[:newnick]
682
+ m = NickMessage.new(self, server, source, old, new)
683
+ m.is_on = data[:is_on]
684
+ if source == myself
685
+ debug "my nick is now #{new}"
381
686
  end
382
- @channels.each {|k,v|
383
- if(v.users.has_key?(sourcenick))
384
- irclog "@ #{sourcenick} is now known as #{nick}", k
385
- v.users[nick] = v.users[sourcenick]
386
- v.users.delete(sourcenick)
387
- end
388
- }
389
- @plugins.delegate("listen", m)
390
- @plugins.delegate("nick", m)
687
+ @plugins.irc_delegate("nick", m)
391
688
  }
392
689
  @client[:quit] = proc {|data|
393
690
  source = data[:source]
394
- sourcenick = data[:sourcenick]
395
- sourceurl = data[:sourceaddress]
396
691
  message = data[:message]
397
- m = QuitMessage.new(self, data[:source], data[:sourcenick], data[:message])
398
- if(data[:sourcenick] =~ /#{Regexp.escape(@nick)}/i)
399
- else
400
- @channels.each {|k,v|
401
- if(v.users.has_key?(sourcenick))
402
- irclog "@ Quit: #{sourcenick}: #{message}", k
403
- v.users.delete(sourcenick)
404
- end
405
- }
406
- end
407
- @plugins.delegate("listen", m)
408
- @plugins.delegate("quit", m)
692
+ m = QuitMessage.new(self, server, source, source, message)
693
+ m.was_on = data[:was_on]
694
+ @plugins.irc_delegate("quit", m)
409
695
  }
410
696
  @client[:mode] = proc {|data|
411
- source = data[:source]
412
- sourcenick = data[:sourcenick]
413
- sourceurl = data[:sourceaddress]
414
- channel = data[:channel]
415
- targets = data[:targets]
416
- modestring = data[:modestring]
417
- irclog "@ Mode #{modestring} #{targets} by #{sourcenick}", channel
418
- }
419
- @client[:welcome] = proc {|data|
420
- irclog "joined server #{data[:source]} as #{data[:nick]}", "server"
421
- debug "I think my nick is #{@nick}, server thinks #{data[:nick]}"
422
- if data[:nick] && data[:nick].length > 0
423
- @nick = data[:nick]
424
- end
425
-
426
- @plugins.delegate("connect")
427
-
428
- @config['irc.join_channels'].each {|c|
429
- debug "autojoining channel #{c}"
430
- if(c =~ /^(\S+)\s+(\S+)$/i)
431
- join $1, $2
432
- else
433
- join c if(c)
434
- end
435
- }
697
+ m = ModeChangeMessage.new(self, server, data[:source], data[:target], data[:modestring])
698
+ m.modes = data[:modes]
699
+ @plugins.delegate "modechange", m
436
700
  }
437
701
  @client[:join] = proc {|data|
438
- m = JoinMessage.new(self, data[:source], data[:channel], data[:message])
439
- onjoin(m)
702
+ m = JoinMessage.new(self, server, data[:source], data[:channel], data[:message])
703
+ sendq("MODE #{data[:channel]}", nil, 0) if m.address?
704
+ @plugins.irc_delegate("join", m)
705
+ sendq("WHO #{data[:channel]}", data[:channel], 2) if m.address?
440
706
  }
441
707
  @client[:part] = proc {|data|
442
- m = PartMessage.new(self, data[:source], data[:channel], data[:message])
443
- onpart(m)
708
+ m = PartMessage.new(self, server, data[:source], data[:channel], data[:message])
709
+ @plugins.irc_delegate("part", m)
444
710
  }
445
711
  @client[:kick] = proc {|data|
446
- m = KickMessage.new(self, data[:source], data[:target],data[:channel],data[:message])
447
- onkick(m)
712
+ m = KickMessage.new(self, server, data[:source], data[:target], data[:channel],data[:message])
713
+ @plugins.irc_delegate("kick", m)
448
714
  }
449
715
  @client[:invite] = proc {|data|
450
- if(data[:target] =~ /^#{Regexp.escape(@nick)}$/i)
451
- join data[:channel] if (@auth.allow?("join", data[:source], data[:sourcenick]))
452
- end
716
+ m = InviteMessage.new(self, server, data[:source], data[:target], data[:channel])
717
+ @plugins.irc_delegate("invite", m)
453
718
  }
454
719
  @client[:changetopic] = proc {|data|
455
- channel = data[:channel]
456
- sourcenick = data[:sourcenick]
457
- topic = data[:topic]
458
- timestamp = data[:unixtime] || Time.now.to_i
459
- if(sourcenick == @nick)
460
- irclog "@ I set topic \"#{topic}\"", channel
461
- else
462
- irclog "@ #{sourcenick} set topic \"#{topic}\"", channel
463
- end
464
- m = TopicMessage.new(self, data[:source], data[:channel], timestamp, data[:topic])
465
-
466
- ontopic(m)
467
- @plugins.delegate("listen", m)
468
- @plugins.delegate("topic", m)
720
+ m = TopicMessage.new(self, server, data[:source], data[:channel], data[:topic])
721
+ m.info_or_set = :set
722
+ @plugins.irc_delegate("topic", m)
469
723
  }
470
- @client[:topic] = @client[:topicinfo] = proc {|data|
724
+ # @client[:topic] = proc { |data|
725
+ # irclog "@ Topic is \"#{data[:topic]}\"", data[:channel]
726
+ # }
727
+ @client[:topicinfo] = proc { |data|
471
728
  channel = data[:channel]
472
- m = TopicMessage.new(self, data[:source], data[:channel], data[:unixtime], data[:topic])
473
- ontopic(m)
729
+ topic = channel.topic
730
+ m = TopicMessage.new(self, server, data[:source], channel, topic)
731
+ m.info_or_set = :info
732
+ @plugins.irc_delegate("topic", m)
474
733
  }
475
- @client[:names] = proc {|data|
476
- channel = data[:channel]
477
- users = data[:users]
478
- unless(@channels[channel])
479
- warning "got names for channel '#{channel}' I didn't think I was in\n"
480
- # exit 2
481
- end
482
- @channels[channel].users.clear
483
- users.each {|u|
484
- @channels[channel].users[u[0].sub(/^[@&~+]/, '')] = ["mode", u[1]]
485
- }
486
- @plugins.delegate "names", data[:channel], data[:users]
734
+ @client[:names] = proc { |data|
735
+ m = NamesMessage.new(self, server, server, data[:channel])
736
+ m.users = data[:users]
737
+ @plugins.delegate "names", m
487
738
  }
488
- @client[:unknown] = proc {|data|
739
+ @client[:unknown] = proc { |data|
489
740
  #debug "UNKNOWN: #{data[:serverstring]}"
490
- irclog data[:serverstring], ".unknown"
741
+ m = UnknownMessage.new(self, server, server, nil, data[:serverstring])
742
+ @plugins.delegate "unknown_message", m
491
743
  }
744
+
745
+ set_default_send_options :newlines => @config['send.newlines'].to_sym,
746
+ :join_with => @config['send.join_with'].dup,
747
+ :max_lines => @config['send.max_lines'],
748
+ :overlong => @config['send.overlong'].to_sym,
749
+ :split_at => Regexp.new(@config['send.split_at']),
750
+ :purge_split => @config['send.purge_split'],
751
+ :truncate_text => @config['send.truncate_text'].dup
752
+
753
+ trap_sigs
754
+ end
755
+
756
+ def setup_plugins_path
757
+ @plugins.clear_botmodule_dirs
758
+ @plugins.add_botmodule_dir(Config::coredir + "/utils")
759
+ @plugins.add_botmodule_dir(Config::coredir)
760
+ @plugins.add_botmodule_dir("#{botclass}/plugins")
761
+
762
+ @config['plugins.path'].each do |_|
763
+ path = _.sub(/^\(default\)/, Config::datadir + '/plugins')
764
+ @plugins.add_botmodule_dir(path)
765
+ end
492
766
  end
493
767
 
494
- def got_sig(sig)
495
- debug "received #{sig}, queueing quit"
768
+ def set_default_send_options(opts={})
769
+ # Default send options for NOTICE and PRIVMSG
770
+ unless defined? @default_send_options
771
+ @default_send_options = {
772
+ :queue_channel => nil, # use default queue channel
773
+ :queue_ring => nil, # use default queue ring
774
+ :newlines => :split, # or :join
775
+ :join_with => ' ', # by default, use a single space
776
+ :max_lines => 0, # maximum number of lines to send with a single command
777
+ :overlong => :split, # or :truncate
778
+ # TODO an array of splitpoints would be preferrable for this option:
779
+ :split_at => /\s+/, # by default, split overlong lines at whitespace
780
+ :purge_split => true, # should the split string be removed?
781
+ :truncate_text => "#{Reverse}...#{Reverse}" # text to be appened when truncating
782
+ }
783
+ end
784
+ @default_send_options.update opts unless opts.empty?
785
+ end
786
+
787
+ # checks if we should be quiet on a channel
788
+ def quiet_on?(channel)
789
+ return @quiet.include?('*') || @quiet.include?(channel.downcase)
790
+ end
791
+
792
+ def set_quiet(channel = nil)
793
+ if channel
794
+ ch = channel.downcase.dup
795
+ @quiet << ch
796
+ else
797
+ @quiet.clear
798
+ @quiet << '*'
799
+ end
800
+ end
801
+
802
+ def reset_quiet(channel = nil)
803
+ if channel
804
+ @quiet.delete channel.downcase
805
+ else
806
+ @quiet.clear
807
+ end
808
+ end
809
+
810
+ # things to do when we receive a signal
811
+ def got_sig(sig, func=:quit)
812
+ debug "received #{sig}, queueing #{func}"
496
813
  $interrupted += 1
814
+ self.send(func) unless @quit_mutex.locked?
497
815
  debug "interrupted #{$interrupted} times"
498
- if $interrupted >= 5
816
+ if $interrupted >= 3
499
817
  debug "drastic!"
500
818
  log_session_end
501
819
  exit 2
502
- elsif $interrupted >= 3
503
- debug "quitting"
504
- quit
505
820
  end
506
821
  end
507
822
 
508
- # connect the bot to IRC
509
- def connect
823
+ # trap signals
824
+ def trap_sigs
510
825
  begin
511
826
  trap("SIGINT") { got_sig("SIGINT") }
512
827
  trap("SIGTERM") { got_sig("SIGTERM") }
513
- trap("SIGHUP") { got_sig("SIGHUP") }
828
+ trap("SIGHUP") { got_sig("SIGHUP", :restart) }
514
829
  rescue ArgumentError => e
515
- debug "failed to trap signals (#{e.inspect}): running on Windows?"
516
- rescue => e
517
- debug "failed to trap signals: #{e.inspect}"
830
+ debug "failed to trap signals (#{e.pretty_inspect}): running on Windows?"
831
+ rescue Exception => e
832
+ debug "failed to trap signals: #{e.pretty_inspect}"
518
833
  end
834
+ end
835
+
836
+ # connect the bot to IRC
837
+ def connect
519
838
  begin
520
839
  quit if $interrupted > 0
521
840
  @socket.connect
522
841
  rescue => e
523
- raise e.class, "failed to connect to IRC server at #{@config['server.name']} #{@config['server.port']}: " + e
842
+ raise e.class, "failed to connect to IRC server at #{@socket.server_uri}: " + e
524
843
  end
844
+ quit if $interrupted > 0
845
+
846
+ realname = @config['irc.name'].clone || 'Ruby bot'
847
+ realname << ' ' + COPYRIGHT_NOTICE if @config['irc.name_copyright']
848
+
525
849
  @socket.emergency_puts "PASS " + @config['server.password'] if @config['server.password']
526
- @socket.emergency_puts "NICK #{@nick}\nUSER #{@config['irc.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
527
- @capabilities = Hash.new
528
- start_server_pings
850
+ @socket.emergency_puts "NICK #{@config['irc.nick']}\nUSER #{@config['irc.user']} 4 #{@socket.server_uri.host} :#{realname}"
851
+ quit if $interrupted > 0
852
+ myself.nick = @config['irc.nick']
853
+ myself.user = @config['irc.user']
529
854
  end
530
855
 
531
856
  # begin event handling loop
532
857
  def mainloop
533
858
  while true
534
859
  begin
535
- quit if $interrupted > 0
860
+ quit if $interrupted > 0
536
861
  connect
537
- @timer.start
538
862
 
863
+ quit_msg = nil
539
864
  while @socket.connected?
540
- if @socket.select
865
+ quit if $interrupted > 0
866
+
867
+ # Wait for messages and process them as they arrive. If nothing is
868
+ # received, we call the ping_server() method that will PING the
869
+ # server if appropriate, or raise a TimeoutError if no PONG has been
870
+ # received in the user-chosen timeout since the last PING sent.
871
+ if @socket.select(1)
541
872
  break unless reply = @socket.gets
873
+ @last_rec = Time.now
542
874
  @client.process reply
875
+ else
876
+ ping_server
543
877
  end
544
- quit if $interrupted > 0
545
878
  end
546
879
 
547
880
  # I despair of this. Some of my users get "connection reset by peer"
@@ -550,39 +883,32 @@ class IrcBot
550
883
  rescue SystemExit
551
884
  log_session_end
552
885
  exit 0
553
- rescue Errno::ETIMEDOUT, TimeoutError, SocketError => e
554
- error "network exception: #{e.class}: #{e}"
555
- debug e.backtrace.join("\n")
886
+ rescue Errno::ETIMEDOUT, Errno::ECONNABORTED, TimeoutError, SocketError => e
887
+ error "network exception: #{e.pretty_inspect}"
888
+ quit_msg = e.to_s
556
889
  rescue BDB::Fatal => e
557
- fatal "fatal bdb error: #{e.class}: #{e}"
558
- fatal e.backtrace.join("\n")
890
+ fatal "fatal bdb error: #{e.pretty_inspect}"
559
891
  DBTree.stats
560
892
  # Why restart? DB problems are serious stuff ...
561
893
  # restart("Oops, we seem to have registry problems ...")
562
894
  log_session_end
563
895
  exit 2
564
896
  rescue Exception => e
565
- error "non-net exception: #{e.class}: #{e}"
566
- error e.backtrace.join("\n")
897
+ error "non-net exception: #{e.pretty_inspect}"
898
+ quit_msg = e.to_s
567
899
  rescue => e
568
- fatal "unexpected exception: #{e.class}: #{e}"
569
- fatal e.backtrace.join("\n")
900
+ fatal "unexpected exception: #{e.pretty_inspect}"
570
901
  log_session_end
571
902
  exit 2
572
903
  end
573
904
 
574
- stop_server_pings
575
- @channels.clear
576
- if @socket.connected?
577
- @socket.clearq
578
- @socket.shutdown
579
- end
905
+ disconnect(quit_msg)
580
906
 
581
- log "disconnected"
907
+ log "\n\nDisconnected\n\n"
582
908
 
583
909
  quit if $interrupted > 0
584
910
 
585
- log "waiting to reconnect"
911
+ log "\n\nWaiting to reconnect\n\n"
586
912
  sleep @config['server.reconnect_wait']
587
913
  end
588
914
  end
@@ -594,108 +920,147 @@ class IrcBot
594
920
  # Type can be PRIVMSG, NOTICE, etc, but those you should really use the
595
921
  # relevant say() or notice() methods. This one should be used for IRCd
596
922
  # extensions you want to use in modules.
597
- def sendmsg(type, where, message, chan=nil, ring=0)
598
- # limit it according to the byterate, splitting the message
599
- # taking into consideration the actual message length
600
- # and all the extra stuff
601
- # TODO allow something to do for commands that produce too many messages
602
- # TODO example: math 10**10000
603
- left = @socket.bytes_per - type.length - where.length - 4
604
- begin
605
- if(left >= message.length)
606
- sendq "#{type} #{where} :#{message}", chan, ring
607
- log_sent(type, where, message)
608
- return
609
- end
610
- line = message.slice!(0, left)
611
- lastspace = line.rindex(/\s+/)
612
- if(lastspace)
613
- message = line.slice!(lastspace, line.length) + message
614
- message.gsub!(/^\s+/, "")
615
- end
616
- sendq "#{type} #{where} :#{line}", chan, ring
617
- log_sent(type, where, line)
618
- end while(message.length > 0)
619
- end
620
-
621
- # queue an arbitraty message for the server
622
- def sendq(message="", chan=nil, ring=0)
623
- # temporary
624
- @socket.queue(message, chan, ring)
625
- end
923
+ def sendmsg(type, where, original_message, options={})
924
+ opts = @default_send_options.merge(options)
626
925
 
627
- # send a notice message to channel/nick +where+
628
- def notice(where, message, mchan=nil, mring=-1)
629
- if mchan == ""
630
- chan = where
631
- else
926
+ # For starters, set up appropriate queue channels and rings
927
+ mchan = opts[:queue_channel]
928
+ mring = opts[:queue_ring]
929
+ if mchan
632
930
  chan = mchan
931
+ else
932
+ chan = where
633
933
  end
634
- if mring < 0
635
- if where =~ /^#/
636
- ring = 2
637
- else
934
+ if mring
935
+ ring = mring
936
+ else
937
+ case where
938
+ when User
638
939
  ring = 1
940
+ else
941
+ ring = 2
639
942
  end
640
- else
641
- ring = mring
642
943
  end
643
- message.each_line { |line|
644
- line.chomp!
645
- next unless(line.length > 0)
646
- sendmsg "NOTICE", where, line, chan, ring
647
- }
648
- end
649
944
 
650
- # say something (PRIVMSG) to channel/nick +where+
651
- def say(where, message, mchan="", mring=-1)
652
- if mchan == ""
653
- chan = where
945
+ multi_line = original_message.to_s.gsub(/[\r\n]+/, "\n")
946
+
947
+ # if target is a channel with nocolor modes, strip colours
948
+ if where.kind_of?(Channel) and where.mode.any?(*config['server.nocolor_modes'])
949
+ multi_line.replace BasicUserMessage.strip_formatting(multi_line)
950
+ end
951
+
952
+ messages = Array.new
953
+ case opts[:newlines]
954
+ when :join
955
+ messages << [multi_line.gsub("\n", opts[:join_with])]
956
+ when :split
957
+ multi_line.each_line { |line|
958
+ line.chomp!
959
+ next unless(line.size > 0)
960
+ messages << line
961
+ }
654
962
  else
655
- chan = mchan
963
+ raise "Unknown :newlines option #{opts[:newlines]} while sending #{original_message.inspect}"
656
964
  end
657
- if mring < 0
658
- if where =~ /^#/
659
- ring = 2
965
+
966
+ # The IRC protocol requires that each raw message must be not longer
967
+ # than 512 characters. From this length with have to subtract the EOL
968
+ # terminators (CR+LF) and the length of ":botnick!botuser@bothost "
969
+ # that will be prepended by the server to all of our messages.
970
+
971
+ # The maximum raw message length we can send is therefore 512 - 2 - 2
972
+ # minus the length of our hostmask.
973
+
974
+ max_len = 508 - myself.fullform.size
975
+
976
+ # On servers that support IDENTIFY-MSG, we have to subtract 1, because messages
977
+ # will have a + or - prepended
978
+ if server.capabilities[:"identify-msg"]
979
+ max_len -= 1
980
+ end
981
+
982
+ # When splitting the message, we'll be prefixing the following string:
983
+ # (e.g. "PRIVMSG #rbot :")
984
+ fixed = "#{type} #{where} :"
985
+
986
+ # And this is what's left
987
+ left = max_len - fixed.size
988
+
989
+ truncate = opts[:truncate_text]
990
+ truncate = @default_send_options[:truncate_text] if truncate.size > left
991
+ truncate = "" if truncate.size > left
992
+
993
+ all_lines = messages.map { |line|
994
+ if line.size < left
995
+ line
660
996
  else
661
- ring = 1
997
+ case opts[:overlong]
998
+ when :split
999
+ msg = line.dup
1000
+ sub_lines = Array.new
1001
+ begin
1002
+ sub_lines << msg.slice!(0, left)
1003
+ break if msg.empty?
1004
+ lastspace = sub_lines.last.rindex(opts[:split_at])
1005
+ if lastspace
1006
+ msg.replace sub_lines.last.slice!(lastspace, sub_lines.last.size) + msg
1007
+ msg.gsub!(/^#{opts[:split_at]}/, "") if opts[:purge_split]
1008
+ end
1009
+ end until msg.empty?
1010
+ sub_lines
1011
+ when :truncate
1012
+ line.slice(0, left - truncate.size) << truncate
1013
+ else
1014
+ raise "Unknown :overlong option #{opts[:overlong]} while sending #{original_message.inspect}"
1015
+ end
662
1016
  end
1017
+ }.flatten
1018
+
1019
+ if opts[:max_lines] > 0 and all_lines.length > opts[:max_lines]
1020
+ lines = all_lines[0...opts[:max_lines]]
1021
+ new_last = lines.last.slice(0, left - truncate.size) << truncate
1022
+ lines.last.replace(new_last)
663
1023
  else
664
- ring = mring
1024
+ lines = all_lines
665
1025
  end
666
- message.to_s.gsub(/[\r\n]+/, "\n").each_line { |line|
667
- line.chomp!
668
- next unless(line.length > 0)
669
- unless((where =~ /^#/) && (@channels.has_key?(where) && @channels[where].quiet))
670
- sendmsg "PRIVMSG", where, line, chan, ring
671
- end
1026
+
1027
+ lines.each { |line|
1028
+ sendq "#{fixed}#{line}", chan, ring
1029
+ delegate_sent(type, where, line)
672
1030
  }
673
1031
  end
674
1032
 
1033
+ # queue an arbitraty message for the server
1034
+ def sendq(message="", chan=nil, ring=0)
1035
+ # temporary
1036
+ @socket.queue(message, chan, ring)
1037
+ end
1038
+
1039
+ # send a notice message to channel/nick +where+
1040
+ def notice(where, message, options={})
1041
+ return if where.kind_of?(Channel) and quiet_on?(where)
1042
+ sendmsg "NOTICE", where, message, options
1043
+ end
1044
+
1045
+ # say something (PRIVMSG) to channel/nick +where+
1046
+ def say(where, message, options={})
1047
+ return if where.kind_of?(Channel) and quiet_on?(where)
1048
+ sendmsg "PRIVMSG", where, message, options
1049
+ end
1050
+
1051
+ def ctcp_notice(where, command, message, options={})
1052
+ return if where.kind_of?(Channel) and quiet_on?(where)
1053
+ sendmsg "NOTICE", where, "\001#{command} #{message}\001", options
1054
+ end
1055
+
1056
+ def ctcp_say(where, command, message, options={})
1057
+ return if where.kind_of?(Channel) and quiet_on?(where)
1058
+ sendmsg "PRIVMSG", where, "\001#{command} #{message}\001", options
1059
+ end
1060
+
675
1061
  # perform a CTCP action with message +message+ to channel/nick +where+
676
- def action(where, message, mchan="", mring=-1)
677
- if mchan == ""
678
- chan = where
679
- else
680
- chan = mchan
681
- end
682
- if mring < 0
683
- if where =~ /^#/
684
- ring = 2
685
- else
686
- ring = 1
687
- end
688
- else
689
- ring = mring
690
- end
691
- sendq "PRIVMSG #{where} :\001ACTION #{message}\001", chan, ring
692
- if(where =~ /^#/)
693
- irclog "* #{@nick} #{message}", where
694
- elsif (where =~ /^(\S*)!.*$/)
695
- irclog "* #{@nick}[#{where}] #{message}", $1
696
- else
697
- irclog "* #{@nick}[#{where}] #{message}", where
698
- end
1062
+ def action(where, message, options={})
1063
+ ctcp_say(where, 'ACTION', message, options)
699
1064
  end
700
1065
 
701
1066
  # quick way to say "okay" (or equivalent) to +where+
@@ -703,60 +1068,69 @@ class IrcBot
703
1068
  say where, @lang.get("okay")
704
1069
  end
705
1070
 
706
- # log IRC-related message +message+ to a file determined by +where+.
707
- # +where+ can be a channel name, or a nick for private message logging
708
- def irclog(message, where="server")
709
- message = message.chomp
710
- stamp = Time.now.strftime("%Y/%m/%d %H:%M:%S")
711
- where = where.gsub(/[:!?$*()\/\\<>|"']/, "_")
712
- unless(@logs.has_key?(where))
713
- @logs[where] = File.new("#{@botclass}/logs/#{where}", "a")
714
- @logs[where].sync = true
715
- end
716
- @logs[where].puts "[#{stamp}] #{message}"
717
- #debug "[#{stamp}] <#{where}> #{message}"
718
- end
719
-
720
1071
  # set topic of channel +where+ to +topic+
721
- def topic(where, topic)
722
- sendq "TOPIC #{where} :#{topic}", where, 2
1072
+ # can also be used to retrieve the topic of channel +where+
1073
+ # by omitting the last argument
1074
+ def topic(where, topic=nil)
1075
+ if topic.nil?
1076
+ sendq "TOPIC #{where}", where, 2
1077
+ else
1078
+ sendq "TOPIC #{where} :#{topic}", where, 2
1079
+ end
723
1080
  end
724
1081
 
725
- # disconnect from the server and cleanup all plugins and modules
726
- def shutdown(message = nil)
727
- debug "Shutting down ..."
728
- ## No we don't restore them ... let everything run through
729
- # begin
730
- # trap("SIGINT", "DEFAULT")
731
- # trap("SIGTERM", "DEFAULT")
732
- # trap("SIGHUP", "DEFAULT")
733
- # rescue => e
734
- # debug "failed to restore signals: #{e.inspect}\nProbably running on windows?"
735
- # end
736
- message = @lang.get("quit") if (message.nil? || message.empty?)
1082
+ def disconnect(message=nil)
1083
+ message = @lang.get("quit") if (!message || message.empty?)
737
1084
  if @socket.connected?
738
- debug "Clearing socket"
739
- @socket.clearq
740
- debug "Sending quit message"
741
- @socket.emergency_puts "QUIT :#{message}"
742
- debug "Flushing socket"
743
- @socket.flush
1085
+ begin
1086
+ debug "Clearing socket"
1087
+ @socket.clearq
1088
+ debug "Sending quit message"
1089
+ @socket.emergency_puts "QUIT :#{message}"
1090
+ debug "Logging quits"
1091
+ delegate_sent('QUIT', myself, message)
1092
+ debug "Flushing socket"
1093
+ @socket.flush
1094
+ rescue SocketError => e
1095
+ error "error while disconnecting socket: #{e.pretty_inspect}"
1096
+ end
744
1097
  debug "Shutting down socket"
745
1098
  @socket.shutdown
746
1099
  end
747
- debug "Logging quits"
748
- @channels.each_value {|v|
749
- irclog "@ quit (#{message})", v.name
750
- }
751
- debug "Saving"
752
- save
753
- debug "Cleaning up"
754
- @plugins.cleanup
755
- # debug "Closing registries"
756
- # @registry.close
757
- debug "Cleaning up the db environment"
758
- DBTree.cleanup_env
759
- log "rbot quit (#{message})"
1100
+ stop_server_pings
1101
+ @client.reset
1102
+ end
1103
+
1104
+ # disconnect from the server and cleanup all plugins and modules
1105
+ def shutdown(message=nil)
1106
+ @quit_mutex.synchronize do
1107
+ debug "Shutting down: #{message}"
1108
+ ## No we don't restore them ... let everything run through
1109
+ # begin
1110
+ # trap("SIGINT", "DEFAULT")
1111
+ # trap("SIGTERM", "DEFAULT")
1112
+ # trap("SIGHUP", "DEFAULT")
1113
+ # rescue => e
1114
+ # debug "failed to restore signals: #{e.inspect}\nProbably running on windows?"
1115
+ # end
1116
+ debug "\tdisconnecting..."
1117
+ disconnect(message)
1118
+ debug "\tstopping timer..."
1119
+ @timer.stop
1120
+ debug "\tsaving ..."
1121
+ save
1122
+ debug "\tcleaning up ..."
1123
+ @save_mutex.synchronize do
1124
+ @plugins.cleanup
1125
+ end
1126
+ # debug "\tstopping timers ..."
1127
+ # @timer.stop
1128
+ # debug "Closing registries"
1129
+ # @registry.close
1130
+ debug "\t\tcleaning up the db environment ..."
1131
+ DBTree.cleanup_env
1132
+ log "rbot quit (#{message})"
1133
+ end
760
1134
  end
761
1135
 
762
1136
  # message:: optional IRC quit message
@@ -770,29 +1144,43 @@ class IrcBot
770
1144
  end
771
1145
 
772
1146
  # totally shutdown and respawn the bot
773
- def restart(message = false)
774
- msg = message ? message : "restarting, back in #{@config['server.reconnect_wait']}..."
775
- shutdown(msg)
1147
+ def restart(message=nil)
1148
+ message = "restarting, back in #{@config['server.reconnect_wait']}..." if (!message || message.empty?)
1149
+ shutdown(message)
776
1150
  sleep @config['server.reconnect_wait']
777
- # now we re-exec
778
- # Note, this fails on Windows
779
- exec($0, *@argv)
1151
+ begin
1152
+ # now we re-exec
1153
+ # Note, this fails on Windows
1154
+ debug "going to exec #{$0} #{@argv.inspect} from #{@run_dir}"
1155
+ log_session_end
1156
+ Dir.chdir(@run_dir)
1157
+ exec($0, *@argv)
1158
+ rescue Errno::ENOENT
1159
+ log_session_end
1160
+ exec("ruby", *(@argv.unshift $0))
1161
+ rescue Exception => e
1162
+ $interrupted += 1
1163
+ raise e
1164
+ end
780
1165
  end
781
1166
 
782
- # call the save method for bot's config, auth and all plugins
1167
+ # call the save method for all of the botmodules
783
1168
  def save
784
1169
  @save_mutex.synchronize do
785
- @config.save
786
- @auth.save
787
1170
  @plugins.save
788
1171
  DBTree.cleanup_logs
789
1172
  end
790
1173
  end
791
1174
 
792
- # call the rescan method for the bot's lang and all plugins
1175
+ # call the rescan method for all of the botmodules
793
1176
  def rescan
794
- @lang.rescan
795
- @plugins.rescan
1177
+ debug "\tstopping timer..."
1178
+ @timer.stop
1179
+ @save_mutex.synchronize do
1180
+ @lang.rescan
1181
+ @plugins.rescan
1182
+ end
1183
+ @timer.start
796
1184
  end
797
1185
 
798
1186
  # channel:: channel to join
@@ -813,12 +1201,17 @@ class IrcBot
813
1201
 
814
1202
  # attempt to change bot's nick to +name+
815
1203
  def nickchg(name)
816
- sendq "NICK #{name}"
1204
+ sendq "NICK #{name}"
817
1205
  end
818
1206
 
819
1207
  # changing mode
820
- def mode(channel, mode, target)
821
- sendq "MODE #{channel} #{mode} #{target}", channel, 2
1208
+ def mode(channel, mode, target=nil)
1209
+ sendq "MODE #{channel} #{mode} #{target}", channel, 2
1210
+ end
1211
+
1212
+ # kicking a user
1213
+ def kick(channel, user, msg)
1214
+ sendq "KICK #{channel} #{user} :#{msg}", channel, 2
822
1215
  end
823
1216
 
824
1217
  # m:: message asking for help
@@ -828,20 +1221,12 @@ class IrcBot
828
1221
  topic = nil if topic == ""
829
1222
  case topic
830
1223
  when nil
831
- helpstr = "help topics: core, auth"
1224
+ helpstr = _("help topics: ")
832
1225
  helpstr += @plugins.helptopics
833
- helpstr += " (help <topic> for more info)"
834
- when /^core$/i
835
- helpstr = corehelp
836
- when /^core\s+(.+)$/i
837
- helpstr = corehelp $1
838
- when /^auth$/i
839
- helpstr = @auth.help
840
- when /^auth\s+(.+)$/i
841
- helpstr = @auth.help $1
1226
+ helpstr += _(" (help <topic> for more info)")
842
1227
  else
843
1228
  unless(helpstr = @plugins.help(topic))
844
- helpstr = "no help for topic #{topic}"
1229
+ helpstr = _("no help for topic %{topic}") % { :topic => topic }
845
1230
  end
846
1231
  end
847
1232
  return helpstr
@@ -852,303 +1237,59 @@ class IrcBot
852
1237
  secs_up = Time.new - @startup_time
853
1238
  uptime = Utils.secs_to_string secs_up
854
1239
  # return "Uptime #{uptime}, #{@plugins.length} plugins active, #{@registry.length} items stored in registry, #{@socket.lines_sent} lines sent, #{@socket.lines_received} received."
855
- return "Uptime #{uptime}, #{@plugins.length} plugins active, #{@socket.lines_sent} lines sent, #{@socket.lines_received} received."
1240
+ return (_("Uptime %{up}, %{plug} plugins active, %{sent} lines sent, %{recv} received.") %
1241
+ {
1242
+ :up => uptime, :plug => @plugins.length,
1243
+ :sent => @socket.lines_sent, :recv => @socket.lines_received
1244
+ })
856
1245
  end
857
1246
 
858
- # we'll ping the server every 30 seconds or so, and expect a response
859
- # before the next one come around..
860
- def start_server_pings
861
- stop_server_pings
862
- return unless @config['server.ping_timeout'] > 0
863
- # we want to respond to a hung server within 30 secs or so
864
- @ping_timer = @timer.add(30) {
865
- @last_ping = Time.now
866
- @socket.queue "PING :rbot"
867
- }
868
- @pong_timer = @timer.add(10) {
869
- unless @last_ping.nil?
870
- diff = Time.now - @last_ping
871
- unless diff < @config['server.ping_timeout']
872
- debug "no PONG from server for #{diff} seconds, reconnecting"
873
- begin
874
- @socket.shutdown
875
- rescue
876
- debug "couldn't shutdown connection (already shutdown?)"
877
- end
878
- @last_ping = nil
1247
+ # We want to respond to a hung server in a timely manner. If nothing was received
1248
+ # in the user-selected timeout and we haven't PINGed the server yet, we PING
1249
+ # the server. If the PONG is not received within the user-defined timeout, we
1250
+ # assume we're in ping timeout and act accordingly.
1251
+ def ping_server
1252
+ act_timeout = @config['server.ping_timeout']
1253
+ return if act_timeout <= 0
1254
+ now = Time.now
1255
+ if @last_rec && now > @last_rec + act_timeout
1256
+ if @last_ping.nil?
1257
+ # No previous PING pending, send a new one
1258
+ sendq "PING :rbot"
1259
+ @last_ping = Time.now
1260
+ else
1261
+ diff = now - @last_ping
1262
+ if diff > act_timeout
1263
+ debug "no PONG from server in #{diff} seconds, reconnecting"
1264
+ # the actual reconnect is handled in the main loop:
879
1265
  raise TimeoutError, "no PONG from server in #{diff} seconds"
880
1266
  end
881
1267
  end
882
- }
1268
+ end
883
1269
  end
884
1270
 
885
1271
  def stop_server_pings
1272
+ # cancel previous PINGs and reset time of last RECV
886
1273
  @last_ping = nil
887
- # stop existing timers if running
888
- unless @ping_timer.nil?
889
- @timer.remove @ping_timer
890
- @ping_timer = nil
891
- end
892
- unless @pong_timer.nil?
893
- @timer.remove @pong_timer
894
- @pong_timer = nil
895
- end
1274
+ @last_rec = nil
896
1275
  end
897
1276
 
898
1277
  private
899
1278
 
900
- # handle help requests for "core" topics
901
- def corehelp(topic="")
902
- case topic
903
- when "quit"
904
- return "quit [<message>] => quit IRC with message <message>"
905
- when "restart"
906
- return "restart => completely stop and restart the bot (including reconnect)"
907
- when "join"
908
- return "join <channel> [<key>] => join channel <channel> with secret key <key> if specified. #{@nick} also responds to invites if you have the required access level"
909
- when "part"
910
- return "part <channel> => part channel <channel>"
911
- when "hide"
912
- return "hide => part all channels"
913
- when "save"
914
- return "save => save current dynamic data and configuration"
915
- when "rescan"
916
- return "rescan => reload modules and static facts"
917
- when "nick"
918
- return "nick <nick> => attempt to change nick to <nick>"
919
- when "say"
920
- return "say <channel>|<nick> <message> => say <message> to <channel> or in private message to <nick>"
921
- when "action"
922
- return "action <channel>|<nick> <message> => does a /me <message> to <channel> or in private message to <nick>"
923
- # when "topic"
924
- # return "topic <channel> <message> => set topic of <channel> to <message>"
925
- when "quiet"
926
- return "quiet [in here|<channel>] => with no arguments, stop speaking in all channels, if \"in here\", stop speaking in this channel, or stop speaking in <channel>"
927
- when "talk"
928
- return "talk [in here|<channel>] => with no arguments, resume speaking in all channels, if \"in here\", resume speaking in this channel, or resume speaking in <channel>"
929
- when "version"
930
- return "version => describes software version"
931
- when "botsnack"
932
- return "botsnack => reward #{@nick} for being good"
933
- when "hello"
934
- return "hello|hi|hey|yo [#{@nick}] => greet the bot"
935
- else
936
- return "Core help topics: quit, restart, config, join, part, hide, save, rescan, nick, say, action, topic, quiet, talk, version, botsnack, hello"
937
- end
938
- end
939
-
940
- # handle incoming IRC PRIVMSG +m+
941
- def onprivmsg(m)
942
- # log it first
943
- if(m.action?)
944
- if(m.private?)
945
- irclog "* [#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick
946
- else
947
- irclog "* #{m.sourcenick} #{m.message}", m.target
948
- end
949
- else
950
- if(m.public?)
951
- irclog "<#{m.sourcenick}> #{m.message}", m.target
952
- else
953
- irclog "[#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick
954
- end
955
- end
956
-
957
- @config['irc.ignore_users'].each { |mask| return if Irc.netmaskmatch(mask,m.source) }
958
-
959
- # pass it off to plugins that want to hear everything
960
- @plugins.delegate "listen", m
961
-
962
- if(m.private? && m.message =~ /^\001PING\s+(.+)\001/)
963
- notice m.sourcenick, "\001PING #$1\001"
964
- irclog "@ #{m.sourcenick} pinged me"
965
- return
966
- end
967
-
968
- if(m.address?)
969
- delegate_privmsg(m)
970
- case m.message
971
- when (/^join\s+(\S+)\s+(\S+)$/i)
972
- join $1, $2 if(@auth.allow?("join", m.source, m.replyto))
973
- when (/^join\s+(\S+)$/i)
974
- join $1 if(@auth.allow?("join", m.source, m.replyto))
975
- when (/^part$/i)
976
- part m.target if(m.public? && @auth.allow?("join", m.source, m.replyto))
977
- when (/^part\s+(\S+)$/i)
978
- part $1 if(@auth.allow?("join", m.source, m.replyto))
979
- when (/^quit(?:\s+(.*))?$/i)
980
- quit $1 if(@auth.allow?("quit", m.source, m.replyto))
981
- when (/^restart(?:\s+(.*))?$/i)
982
- restart $1 if(@auth.allow?("quit", m.source, m.replyto))
983
- when (/^hide$/i)
984
- join 0 if(@auth.allow?("join", m.source, m.replyto))
985
- when (/^save$/i)
986
- if(@auth.allow?("config", m.source, m.replyto))
987
- save
988
- m.okay
989
- end
990
- when (/^nick\s+(\S+)$/i)
991
- nickchg($1) if(@auth.allow?("nick", m.source, m.replyto))
992
- when (/^say\s+(\S+)\s+(.*)$/i)
993
- say $1, $2 if(@auth.allow?("say", m.source, m.replyto))
994
- when (/^action\s+(\S+)\s+(.*)$/i)
995
- action $1, $2 if(@auth.allow?("say", m.source, m.replyto))
996
- # when (/^topic\s+(\S+)\s+(.*)$/i)
997
- # topic $1, $2 if(@auth.allow?("topic", m.source, m.replyto))
998
- when (/^mode\s+(\S+)\s+(\S+)\s+(.*)$/i)
999
- mode $1, $2, $3 if(@auth.allow?("mode", m.source, m.replyto))
1000
- when (/^ping$/i)
1001
- say m.replyto, "pong"
1002
- when (/^rescan$/i)
1003
- if(@auth.allow?("config", m.source, m.replyto))
1004
- m.reply "saving ..."
1005
- save
1006
- m.reply "rescanning ..."
1007
- rescan
1008
- m.reply "done. #{@plugins.status(true)}"
1009
- end
1010
- when (/^quiet$/i)
1011
- if(auth.allow?("talk", m.source, m.replyto))
1012
- m.okay
1013
- @channels.each_value {|c| c.quiet = true }
1014
- end
1015
- when (/^quiet in (\S+)$/i)
1016
- where = $1
1017
- if(auth.allow?("talk", m.source, m.replyto))
1018
- m.okay
1019
- where.gsub!(/^here$/, m.target) if m.public?
1020
- @channels[where].quiet = true if(@channels.has_key?(where))
1021
- end
1022
- when (/^talk$/i)
1023
- if(auth.allow?("talk", m.source, m.replyto))
1024
- @channels.each_value {|c| c.quiet = false }
1025
- m.okay
1026
- end
1027
- when (/^talk in (\S+)$/i)
1028
- where = $1
1029
- if(auth.allow?("talk", m.source, m.replyto))
1030
- where.gsub!(/^here$/, m.target) if m.public?
1031
- @channels[where].quiet = false if(@channels.has_key?(where))
1032
- m.okay
1033
- end
1034
- when (/^status\??$/i)
1035
- m.reply status if auth.allow?("status", m.source, m.replyto)
1036
- when (/^registry stats$/i)
1037
- if auth.allow?("config", m.source, m.replyto)
1038
- m.reply @registry.stat.inspect
1039
- end
1040
- when (/^(help\s+)?config(\s+|$)/)
1041
- @config.privmsg(m)
1042
- when (/^(version)|(introduce yourself)$/i)
1043
- say m.replyto, "I'm a v. #{$version} rubybot, (c) Tom Gilbert - http://linuxbrit.co.uk/rbot/"
1044
- when (/^help(?:\s+(.*))?$/i)
1045
- say m.replyto, help($1)
1046
- #TODO move these to a "chatback" plugin
1047
- when (/^(botsnack|ciggie)$/i)
1048
- say m.replyto, @lang.get("thanks_X") % m.sourcenick if(m.public?)
1049
- say m.replyto, @lang.get("thanks") if(m.private?)
1050
- when (/^(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$)).*/i)
1051
- say m.replyto, @lang.get("hello_X") % m.sourcenick if(m.public?)
1052
- say m.replyto, @lang.get("hello") if(m.private?)
1053
- end
1054
- else
1055
- # stuff to handle when not addressed
1056
- case m.message
1057
- when (/^\s*(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi|yo(\W|$))[\s,-.]+#{Regexp.escape(@nick)}$/i)
1058
- say m.replyto, @lang.get("hello_X") % m.sourcenick
1059
- when (/^#{Regexp.escape(@nick)}!*$/)
1060
- say m.replyto, @lang.get("hello_X") % m.sourcenick
1061
- end
1062
- end
1063
- end
1064
-
1065
- # log a message. Internal use only.
1066
- def log_sent(type, where, message)
1279
+ # delegate sent messages
1280
+ def delegate_sent(type, where, message)
1281
+ args = [self, server, myself, server.user_or_channel(where.to_s), message]
1067
1282
  case type
1068
1283
  when "NOTICE"
1069
- if(where =~ /^#/)
1070
- irclog "-=#{@nick}=- #{message}", where
1071
- elsif (where =~ /(\S*)!.*/)
1072
- irclog "[-=#{where}=-] #{message}", $1
1073
- else
1074
- irclog "[-=#{where}=-] #{message}"
1075
- end
1284
+ m = NoticeMessage.new(*args)
1076
1285
  when "PRIVMSG"
1077
- if(where =~ /^#/)
1078
- irclog "<#{@nick}> #{message}", where
1079
- elsif (where =~ /^(\S*)!.*$/)
1080
- irclog "[msg(#{where})] #{message}", $1
1081
- else
1082
- irclog "[msg(#{where})] #{message}", where
1083
- end
1286
+ m = PrivMessage.new(*args)
1287
+ when "QUIT"
1288
+ m = QuitMessage.new(*args)
1084
1289
  end
1290
+ @plugins.delegate('sent', m)
1085
1291
  end
1086
1292
 
1087
- def onjoin(m)
1088
- @channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
1089
- if(m.address?)
1090
- debug "joined channel #{m.channel}"
1091
- irclog "@ Joined channel #{m.channel}", m.channel
1092
- else
1093
- irclog "@ #{m.sourcenick} joined channel #{m.channel}", m.channel
1094
- @channels[m.channel].users[m.sourcenick] = Hash.new
1095
- @channels[m.channel].users[m.sourcenick]["mode"] = ""
1096
- end
1097
-
1098
- @plugins.delegate("listen", m)
1099
- @plugins.delegate("join", m)
1100
- end
1101
-
1102
- def onpart(m)
1103
- if(m.address?)
1104
- debug "left channel #{m.channel}"
1105
- irclog "@ Left channel #{m.channel} (#{m.message})", m.channel
1106
- @channels.delete(m.channel)
1107
- else
1108
- irclog "@ #{m.sourcenick} left channel #{m.channel} (#{m.message})", m.channel
1109
- if @channels.has_key?(m.channel)
1110
- @channels[m.channel].users.delete(m.sourcenick)
1111
- else
1112
- warning "got part for channel '#{channel}' I didn't think I was in\n"
1113
- # exit 2
1114
- end
1115
- end
1116
-
1117
- # delegate to plugins
1118
- @plugins.delegate("listen", m)
1119
- @plugins.delegate("part", m)
1120
- end
1121
-
1122
- # respond to being kicked from a channel
1123
- def onkick(m)
1124
- if(m.address?)
1125
- debug "kicked from channel #{m.channel}"
1126
- @channels.delete(m.channel)
1127
- irclog "@ You have been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel
1128
- else
1129
- @channels[m.channel].users.delete(m.sourcenick)
1130
- irclog "@ #{m.target} has been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel
1131
- end
1132
-
1133
- @plugins.delegate("listen", m)
1134
- @plugins.delegate("kick", m)
1135
- end
1136
-
1137
- def ontopic(m)
1138
- @channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
1139
- @channels[m.channel].topic = m.topic if !m.topic.nil?
1140
- @channels[m.channel].topic.timestamp = m.timestamp if !m.timestamp.nil?
1141
- @channels[m.channel].topic.by = m.source if !m.source.nil?
1142
-
1143
- debug "topic of channel #{m.channel} is now #{@channels[m.channel].topic}"
1144
- end
1145
-
1146
- # delegate a privmsg to auth or plugin handlers
1147
- def delegate_privmsg(message)
1148
- [@auth, @plugins].each {|m|
1149
- break if m.privmsg(message)
1150
- }
1151
- end
1152
1293
  end
1153
1294
 
1154
1295
  end