rbot 0.9.14 → 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (368) hide show
  1. data/AUTHORS +6 -2
  2. data/REQUIREMENTS +7 -1
  3. data/Rakefile +10 -32
  4. data/bin/rbot +6 -1
  5. data/bin/svnwatch-postcommit-hook +68 -0
  6. data/data/rbot/contrib/plugins/stats.rb +3 -3
  7. data/data/rbot/contrib/plugins/vandale.rb +1 -1
  8. data/data/rbot/filters/rss.rb +72 -0
  9. data/data/rbot/languages/finnish.lang +50 -0
  10. data/data/rbot/plugins/alias.rb +6 -6
  11. data/data/rbot/plugins/autorejoin.rb +41 -2
  12. data/data/rbot/plugins/bans.rb +100 -6
  13. data/data/rbot/plugins/bash.rb +9 -4
  14. data/data/rbot/plugins/cal.rb +1 -1
  15. data/data/rbot/plugins/chucknorris.rb +6 -6
  16. data/data/rbot/plugins/debugger.rb +7 -3
  17. data/data/rbot/plugins/deepthoughts.rb +1 -1
  18. data/data/rbot/plugins/delicious.rb +6 -2
  19. data/data/rbot/plugins/dice.rb +7 -7
  20. data/data/rbot/plugins/dict.rb +4 -3
  21. data/data/rbot/plugins/dictclient.rb +17 -13
  22. data/data/rbot/plugins/digg.rb +3 -3
  23. data/data/rbot/plugins/eightball.rb +1 -1
  24. data/data/rbot/plugins/factoids.rb +13 -4
  25. data/data/rbot/plugins/figlet.rb +4 -4
  26. data/data/rbot/plugins/forecast.rb +3 -3
  27. data/data/rbot/plugins/fortune.rb +14 -8
  28. data/data/rbot/plugins/freshmeat.rb +2 -2
  29. data/data/rbot/plugins/games/azgame.rb +72 -19
  30. data/data/rbot/plugins/games/hangman.rb +499 -0
  31. data/data/rbot/plugins/games/quiz.rb +15 -13
  32. data/data/rbot/plugins/games/roshambo.rb +1 -1
  33. data/data/rbot/plugins/games/roulette.rb +4 -4
  34. data/data/rbot/plugins/games/shiritori.rb +31 -31
  35. data/data/rbot/plugins/games/uno.rb +28 -6
  36. data/data/rbot/plugins/games/wheelfortune.rb +1 -3
  37. data/data/rbot/plugins/geoip.rb +83 -28
  38. data/data/rbot/plugins/googlefight.rb +64 -0
  39. data/data/rbot/plugins/greet.rb +45 -0
  40. data/data/rbot/plugins/grouphug.rb +40 -12
  41. data/data/rbot/plugins/imdb.rb +4 -4
  42. data/data/rbot/plugins/insult.rb +2 -2
  43. data/data/rbot/plugins/karma.rb +6 -5
  44. data/data/rbot/plugins/keywords.rb +26 -22
  45. data/data/rbot/plugins/lart.rb +5 -6
  46. data/data/rbot/plugins/lastfm.rb +488 -125
  47. data/data/rbot/plugins/lib_spotify.rb +84 -0
  48. data/data/rbot/plugins/linkbot.rb +1 -1
  49. data/data/rbot/plugins/markov.rb +567 -78
  50. data/data/rbot/plugins/math.rb +3 -3
  51. data/data/rbot/plugins/modes.rb +1 -1
  52. data/data/rbot/plugins/nickrecover.rb +1 -1
  53. data/data/rbot/plugins/nickserv.rb +7 -7
  54. data/data/rbot/plugins/note.rb +55 -0
  55. data/data/rbot/plugins/nslookup.rb +2 -2
  56. data/data/rbot/plugins/quakeauth.rb +4 -4
  57. data/data/rbot/plugins/quotes.rb +53 -19
  58. data/data/rbot/plugins/reaction.rb +76 -19
  59. data/data/rbot/plugins/remind.rb +3 -96
  60. data/data/rbot/plugins/ri.rb +1 -1
  61. data/data/rbot/plugins/rot13.rb +1 -1
  62. data/data/rbot/plugins/rss.rb +296 -190
  63. data/data/rbot/plugins/salut.rb +8 -8
  64. data/data/rbot/plugins/script.rb +48 -11
  65. data/data/rbot/plugins/search.rb +124 -28
  66. data/data/rbot/plugins/seen.rb +162 -31
  67. data/data/rbot/plugins/shortenurls.rb +1 -1
  68. data/data/rbot/plugins/slashdot.rb +19 -6
  69. data/data/rbot/plugins/spotify.rb +78 -0
  70. data/data/rbot/plugins/theyfightcrime.rb +10 -10
  71. data/data/rbot/plugins/time.rb +2 -2
  72. data/data/rbot/plugins/translator.rb +161 -85
  73. data/data/rbot/plugins/tube.rb +2 -2
  74. data/data/rbot/plugins/tumblr.rb +143 -0
  75. data/data/rbot/plugins/twitter.rb +25 -6
  76. data/data/rbot/plugins/urban.rb +6 -4
  77. data/data/rbot/plugins/url.rb +49 -10
  78. data/data/rbot/plugins/weather.rb +6 -6
  79. data/data/rbot/plugins/wserver.rb +5 -5
  80. data/data/rbot/plugins/youtube.rb +12 -12
  81. data/data/rbot/templates/lart/larts-italian +1 -1
  82. data/launch_here.rb +68 -0
  83. data/lib/rbot/botuser.rb +1 -1
  84. data/lib/rbot/compat19.rb +70 -0
  85. data/lib/rbot/config.rb +8 -6
  86. data/lib/rbot/core/auth.rb +37 -21
  87. data/lib/rbot/core/basics.rb +33 -2
  88. data/lib/rbot/core/config.rb +24 -17
  89. data/lib/rbot/core/filters_ui.rb +2 -2
  90. data/lib/rbot/core/irclog.rb +20 -11
  91. data/lib/rbot/core/remote.rb +9 -9
  92. data/lib/rbot/core/unicode.rb +4 -0
  93. data/lib/rbot/core/userdata.rb +16 -1
  94. data/lib/rbot/core/utils/extends.rb +76 -0
  95. data/lib/rbot/core/utils/filters.rb +47 -0
  96. data/lib/rbot/core/utils/httputil.rb +36 -26
  97. data/lib/rbot/core/utils/parse_time.rb +193 -0
  98. data/lib/rbot/core/utils/utils.rb +81 -56
  99. data/lib/rbot/core/utils/wordlist.rb +66 -0
  100. data/lib/rbot/core/wordlist_ui.rb +27 -0
  101. data/lib/rbot/irc.rb +59 -19
  102. data/lib/rbot/ircbot.rb +190 -58
  103. data/lib/rbot/ircsocket.rb +14 -8
  104. data/lib/rbot/language.rb +4 -3
  105. data/lib/rbot/load-gettext.rb +22 -9
  106. data/lib/rbot/message.rb +89 -18
  107. data/lib/rbot/messagemapper.rb +71 -19
  108. data/lib/rbot/plugins.rb +112 -44
  109. data/lib/rbot/{registry.rb → registry/bdb.rb} +226 -22
  110. data/lib/rbot/registry/tc.rb +531 -0
  111. data/lib/rbot/rfc2812.rb +33 -8
  112. data/lib/rbot/timer.rb +12 -20
  113. data/po/en_US/rbot-autorejoin.po +3 -0
  114. data/po/en_US/rbot-azgame.po +51 -43
  115. data/po/en_US/rbot-bash.po +15 -0
  116. data/po/en_US/rbot-dictclient.po +20 -20
  117. data/po/en_US/rbot-factoids.po +9 -9
  118. data/po/en_US/rbot-geoip.po +0 -0
  119. data/po/en_US/rbot-googlefight.po +24 -0
  120. data/po/en_US/rbot-grouphug.po +4 -4
  121. data/po/en_US/rbot-hangman.po +114 -0
  122. data/po/en_US/rbot-keywords.po +3 -3
  123. data/po/en_US/rbot-lastfm.po +268 -70
  124. data/po/en_US/rbot-markov.po +73 -2
  125. data/po/en_US/rbot-quotes.po +21 -21
  126. data/po/en_US/rbot-rss.po +6 -2
  127. data/po/en_US/rbot-script.po +3 -0
  128. data/po/en_US/rbot-seen.po +72 -0
  129. data/po/en_US/rbot-spell.po +2 -2
  130. data/po/en_US/rbot-translator.po +13 -13
  131. data/po/en_US/rbot-twitter.po +3 -3
  132. data/po/en_US/rbot-uno.po +131 -114
  133. data/po/en_US/rbot-wall.po +12 -13
  134. data/po/en_US/rbot-wheelfortune.po +41 -41
  135. data/po/en_US/rbot.po +254 -194
  136. data/po/fi/rbot-alias.po +82 -0
  137. data/po/fi/rbot-autoop.po +0 -0
  138. data/po/fi/rbot-autorejoin.po +20 -0
  139. data/po/fi/rbot-azgame.po +194 -0
  140. data/po/fi/rbot-bans.po +0 -0
  141. data/po/fi/rbot-bash.po +32 -0
  142. data/po/fi/rbot-botsnack.po +0 -0
  143. data/po/fi/rbot-cal.po +20 -0
  144. data/po/fi/rbot-chanserv.po +0 -0
  145. data/po/fi/rbot-chucknorris.po +0 -0
  146. data/po/fi/rbot-debugger.po +0 -0
  147. data/po/fi/rbot-deepthoughts.po +0 -0
  148. data/po/fi/rbot-delicious.po +0 -0
  149. data/po/fi/rbot-dice.po +0 -0
  150. data/po/fi/rbot-dict.po +0 -0
  151. data/po/fi/rbot-dictclient.po +111 -0
  152. data/po/fi/rbot-digg.po +0 -0
  153. data/po/fi/rbot-eightball.po +0 -0
  154. data/po/fi/rbot-excuse.po +0 -0
  155. data/po/fi/rbot-factoids.po +107 -0
  156. data/po/fi/rbot-figlet.po +36 -0
  157. data/po/fi/rbot-fish.po +0 -0
  158. data/po/fi/rbot-forecast.po +0 -0
  159. data/po/fi/rbot-fortune.po +0 -0
  160. data/po/fi/rbot-freshmeat.po +0 -0
  161. data/po/fi/rbot-geoip.po +0 -0
  162. data/po/fi/rbot-googlefight.po +24 -0
  163. data/po/fi/rbot-grouphug.po +35 -0
  164. data/po/fi/rbot-hangman.po +121 -0
  165. data/po/fi/rbot-hl2.po +0 -0
  166. data/po/fi/rbot-host.po +20 -0
  167. data/po/fi/rbot-imdb.po +0 -0
  168. data/po/fi/rbot-insult.po +0 -0
  169. data/po/fi/rbot-iplookup.po +0 -0
  170. data/po/fi/rbot-karma.po +0 -0
  171. data/po/fi/rbot-keywords.po +24 -0
  172. data/po/fi/rbot-lart.po +0 -0
  173. data/po/fi/rbot-lastfm.po +377 -0
  174. data/po/fi/rbot-linkbot.po +0 -0
  175. data/po/fi/rbot-markov.po +91 -0
  176. data/po/fi/rbot-math.po +0 -0
  177. data/po/fi/rbot-modes.po +0 -0
  178. data/po/fi/rbot-nickrecover.po +36 -0
  179. data/po/fi/rbot-nickserv.po +104 -0
  180. data/po/fi/rbot-nslookup.po +0 -0
  181. data/po/fi/rbot-quakeauth.po +0 -0
  182. data/po/fi/rbot-quiz.po +0 -0
  183. data/po/fi/rbot-quotes.po +108 -0
  184. data/po/fi/rbot-reaction.po +0 -0
  185. data/po/fi/rbot-remind.po +0 -0
  186. data/po/fi/rbot-remotectl.po +0 -0
  187. data/po/fi/rbot-ri.po +0 -0
  188. data/po/fi/rbot-roshambo.po +0 -0
  189. data/po/fi/rbot-rot13.po +0 -0
  190. data/po/fi/rbot-roulette.po +0 -0
  191. data/po/fi/rbot-rss.po +24 -0
  192. data/po/fi/rbot-salut.po +0 -0
  193. data/po/fi/rbot-script.po +20 -0
  194. data/po/fi/rbot-search.po +0 -0
  195. data/po/fi/rbot-seen.po +92 -0
  196. data/po/fi/rbot-shiritori.po +102 -0
  197. data/po/fi/rbot-shortenurls.po +0 -0
  198. data/po/fi/rbot-slashdot.po +0 -0
  199. data/po/fi/rbot-spell.po +54 -0
  200. data/po/fi/rbot-theyfightcrime.po +0 -0
  201. data/po/fi/rbot-threat.po +0 -0
  202. data/po/fi/rbot-time.po +0 -0
  203. data/po/fi/rbot-topic.po +0 -0
  204. data/po/fi/rbot-translator.po +77 -0
  205. data/po/fi/rbot-tube.po +0 -0
  206. data/po/fi/rbot-twitter.po +24 -0
  207. data/po/fi/rbot-uno.po +529 -0
  208. data/po/fi/rbot-urban.po +0 -0
  209. data/po/fi/rbot-url.po +0 -0
  210. data/po/fi/rbot-usermodes.po +0 -0
  211. data/po/fi/rbot-wall.po +32 -0
  212. data/po/fi/rbot-weather.po +0 -0
  213. data/po/fi/rbot-wheelfortune.po +205 -0
  214. data/po/fi/rbot-wow.po +0 -0
  215. data/po/fi/rbot-wserver.po +0 -0
  216. data/po/fi/rbot-youtube.po +58 -0
  217. data/po/fi/rbot.po +1152 -0
  218. data/po/fr/rbot-autorejoin.po +3 -0
  219. data/po/fr/rbot-azgame.po +51 -43
  220. data/po/fr/rbot-bash.po +15 -0
  221. data/po/fr/rbot-dictclient.po +20 -20
  222. data/po/fr/rbot-factoids.po +9 -9
  223. data/po/fr/rbot-geoip.po +0 -0
  224. data/po/fr/rbot-googlefight.po +24 -0
  225. data/po/fr/rbot-grouphug.po +4 -4
  226. data/po/fr/rbot-hangman.po +114 -0
  227. data/po/fr/rbot-keywords.po +3 -3
  228. data/po/fr/rbot-lastfm.po +268 -70
  229. data/po/fr/rbot-markov.po +74 -2
  230. data/po/fr/rbot-quotes.po +21 -21
  231. data/po/fr/rbot-rss.po +6 -2
  232. data/po/fr/rbot-script.po +3 -0
  233. data/po/fr/rbot-seen.po +72 -0
  234. data/po/fr/rbot-spell.po +2 -2
  235. data/po/fr/rbot-translator.po +13 -13
  236. data/po/fr/rbot-twitter.po +3 -3
  237. data/po/fr/rbot-uno.po +132 -114
  238. data/po/fr/rbot-wall.po +8 -9
  239. data/po/fr/rbot-wheelfortune.po +41 -41
  240. data/po/fr/rbot.po +268 -197
  241. data/po/it/rbot-autorejoin.po +3 -0
  242. data/po/it/rbot-azgame.po +50 -42
  243. data/po/it/rbot-bash.po +15 -0
  244. data/po/it/rbot-dictclient.po +20 -20
  245. data/po/it/rbot-factoids.po +9 -9
  246. data/po/it/rbot-geoip.po +0 -0
  247. data/po/it/rbot-googlefight.po +24 -0
  248. data/po/it/rbot-grouphug.po +4 -4
  249. data/po/it/rbot-hangman.po +114 -0
  250. data/po/it/rbot-keywords.po +3 -3
  251. data/po/it/rbot-lastfm.po +268 -70
  252. data/po/it/rbot-markov.po +75 -3
  253. data/po/it/rbot-quotes.po +21 -21
  254. data/po/it/rbot-rss.po +7 -3
  255. data/po/it/rbot-script.po +19 -0
  256. data/po/it/rbot-seen.po +72 -0
  257. data/po/it/rbot-spell.po +2 -2
  258. data/po/it/rbot-translator.po +13 -13
  259. data/po/it/rbot-twitter.po +3 -3
  260. data/po/it/rbot-uno.po +137 -116
  261. data/po/it/rbot-wall.po +8 -9
  262. data/po/it/rbot-wheelfortune.po +41 -41
  263. data/po/it/rbot.po +265 -208
  264. data/po/ja/rbot-autorejoin.po +3 -0
  265. data/po/ja/rbot-azgame.po +51 -43
  266. data/po/ja/rbot-bash.po +15 -0
  267. data/po/ja/rbot-dictclient.po +20 -20
  268. data/po/ja/rbot-factoids.po +9 -9
  269. data/po/ja/rbot-geoip.po +0 -0
  270. data/po/ja/rbot-googlefight.po +24 -0
  271. data/po/ja/rbot-grouphug.po +4 -4
  272. data/po/ja/rbot-hangman.po +114 -0
  273. data/po/ja/rbot-keywords.po +3 -3
  274. data/po/ja/rbot-lastfm.po +268 -70
  275. data/po/ja/rbot-markov.po +73 -2
  276. data/po/ja/rbot-quotes.po +21 -21
  277. data/po/ja/rbot-rss.po +6 -2
  278. data/po/ja/rbot-script.po +3 -0
  279. data/po/ja/rbot-seen.po +72 -0
  280. data/po/ja/rbot-spell.po +2 -2
  281. data/po/ja/rbot-translator.po +13 -13
  282. data/po/ja/rbot-twitter.po +3 -3
  283. data/po/ja/rbot-uno.po +131 -114
  284. data/po/ja/rbot-wall.po +8 -9
  285. data/po/ja/rbot-wheelfortune.po +41 -41
  286. data/po/ja/rbot.po +248 -192
  287. data/po/rbot-alias.pot +2 -2
  288. data/po/rbot-autorejoin.pot +21 -0
  289. data/po/rbot-azgame.pot +51 -43
  290. data/po/rbot-bash.pot +33 -0
  291. data/po/rbot-cal.pot +2 -2
  292. data/po/rbot-dictclient.pot +21 -21
  293. data/po/rbot-factoids.pot +10 -10
  294. data/po/rbot-figlet.pot +2 -2
  295. data/po/rbot-geoip.pot +0 -0
  296. data/po/rbot-googlefight.pot +25 -0
  297. data/po/rbot-grouphug.pot +6 -6
  298. data/po/rbot-hangman.pot +115 -0
  299. data/po/rbot-host.pot +2 -2
  300. data/po/rbot-keywords.pot +4 -4
  301. data/po/rbot-lastfm.pot +270 -72
  302. data/po/rbot-markov.pot +74 -3
  303. data/po/rbot-nickrecover.pot +2 -2
  304. data/po/rbot-nickserv.pot +2 -2
  305. data/po/rbot-quotes.pot +22 -22
  306. data/po/rbot-rss.pot +7 -3
  307. data/po/rbot-script.pot +21 -0
  308. data/po/rbot-seen.pot +90 -0
  309. data/po/rbot-shiritori.pot +2 -2
  310. data/po/rbot-spell.pot +3 -3
  311. data/po/rbot-translator.pot +14 -14
  312. data/po/rbot-twitter.pot +4 -4
  313. data/po/rbot-uno.pot +132 -115
  314. data/po/rbot-wall.pot +2 -2
  315. data/po/rbot-wheelfortune.pot +42 -42
  316. data/po/rbot-youtube.pot +2 -2
  317. data/po/rbot.pot +249 -193
  318. data/po/zh_CN/rbot-autorejoin.po +3 -0
  319. data/po/zh_CN/rbot-azgame.po +50 -42
  320. data/po/zh_CN/rbot-bash.po +15 -0
  321. data/po/zh_CN/rbot-dictclient.po +20 -20
  322. data/po/zh_CN/rbot-factoids.po +9 -9
  323. data/po/zh_CN/rbot-geoip.po +0 -0
  324. data/po/zh_CN/rbot-googlefight.po +24 -0
  325. data/po/zh_CN/rbot-grouphug.po +4 -4
  326. data/po/zh_CN/rbot-hangman.po +114 -0
  327. data/po/zh_CN/rbot-keywords.po +3 -3
  328. data/po/zh_CN/rbot-lastfm.po +268 -70
  329. data/po/zh_CN/rbot-markov.po +73 -2
  330. data/po/zh_CN/rbot-quotes.po +21 -21
  331. data/po/zh_CN/rbot-rss.po +6 -2
  332. data/po/zh_CN/rbot-script.po +3 -0
  333. data/po/zh_CN/rbot-seen.po +72 -0
  334. data/po/zh_CN/rbot-spell.po +2 -2
  335. data/po/zh_CN/rbot-translator.po +13 -13
  336. data/po/zh_CN/rbot-twitter.po +3 -3
  337. data/po/zh_CN/rbot-uno.po +131 -114
  338. data/po/zh_CN/rbot-wall.po +7 -8
  339. data/po/zh_CN/rbot-wheelfortune.po +41 -41
  340. data/po/zh_CN/rbot.po +248 -192
  341. data/po/zh_TW/rbot-autorejoin.po +3 -0
  342. data/po/zh_TW/rbot-azgame.po +50 -42
  343. data/po/zh_TW/rbot-bash.po +15 -0
  344. data/po/zh_TW/rbot-dictclient.po +20 -20
  345. data/po/zh_TW/rbot-factoids.po +9 -9
  346. data/po/zh_TW/rbot-geoip.po +0 -0
  347. data/po/zh_TW/rbot-googlefight.po +24 -0
  348. data/po/zh_TW/rbot-grouphug.po +4 -4
  349. data/po/zh_TW/rbot-hangman.po +114 -0
  350. data/po/zh_TW/rbot-keywords.po +3 -3
  351. data/po/zh_TW/rbot-lastfm.po +268 -70
  352. data/po/zh_TW/rbot-markov.po +73 -2
  353. data/po/zh_TW/rbot-quotes.po +21 -21
  354. data/po/zh_TW/rbot-rss.po +6 -2
  355. data/po/zh_TW/rbot-script.po +3 -0
  356. data/po/zh_TW/rbot-seen.po +72 -0
  357. data/po/zh_TW/rbot-spell.po +2 -2
  358. data/po/zh_TW/rbot-translator.po +13 -13
  359. data/po/zh_TW/rbot-twitter.po +3 -3
  360. data/po/zh_TW/rbot-uno.po +131 -114
  361. data/po/zh_TW/rbot-wall.po +7 -8
  362. data/po/zh_TW/rbot-wheelfortune.po +41 -41
  363. data/po/zh_TW/rbot.po +253 -194
  364. data/setup.rb +4 -4
  365. metadata +127 -18
  366. data/README +0 -43
  367. data/data/rbot/plugins/fish.rb +0 -121
  368. data/lib/rbot/dbhash.rb +0 -199
@@ -24,6 +24,7 @@ class Bot
24
24
  :dutch => 'nl',
25
25
  :japanese => 'ja',
26
26
  :russian => 'ru',
27
+ :finnish => 'fi',
27
28
  :traditional_chinese => 'zh_TW',
28
29
  :simplified_chinese => 'zh_CN'
29
30
  }
@@ -57,16 +58,16 @@ class Bot
57
58
  return 'english'
58
59
  end
59
60
 
60
- Config.register Config::EnumValue.new('core.language',
61
+ Config.register Config::EnumValue.new('core.language',
61
62
  :default => Irc::Bot::Language.from_locale, :wizard => true,
62
63
  :values => Proc.new{|bot|
63
64
  Dir.new(Config::datadir + "/languages").collect {|f|
64
65
  f =~ /\.lang$/ ? f.gsub(/\.lang$/, "") : nil
65
66
  }.compact
66
- },
67
+ },
67
68
  :on_change => Proc.new {|bot, v| bot.lang.set_language v},
68
69
  :desc => "Which language file the bot should use")
69
-
70
+
70
71
  def initialize(bot, language)
71
72
  @bot = bot
72
73
  set_language language
@@ -10,6 +10,11 @@ end
10
10
 
11
11
  # try to load gettext, or provide fake getttext functions
12
12
  begin
13
+ # workaround for gettext not checking empty LANGUAGE
14
+ if ENV["LANGUAGE"] and ENV["LANGUAGE"].empty?
15
+ ENV.delete "LANGUAGE"
16
+ end
17
+
13
18
  require 'gettext/version'
14
19
 
15
20
  gettext_version = GetText::VERSION.split('.').map {|n| n.to_i}
@@ -22,25 +27,33 @@ begin
22
27
 
23
28
  include GetText
24
29
 
25
- add_default_locale_path(File.join(Irc::Bot::Config.datadir, "../locale/%{locale}/LC_MESSAGES/%{name}.mo"))
30
+ rbot_locale_path = File.join(Irc::Bot::Config.datadir, "../locale/%{locale}/LC_MESSAGES/%{name}.mo")
31
+ if gettext_version < [2, 0, 0]
32
+ add_default_locale_path(rbot_locale_path)
33
+ else
34
+ LocalePath.add_default_rule(rbot_locale_path)
35
+ end
26
36
 
27
37
  if GetText.respond_to? :cached=
28
38
  GetText.cached = false
39
+ elsif TextDomain.respond_to? :cached=
40
+ TextDomain.cached = false
29
41
  else
30
42
  warning 'This version of ruby-gettext does not support non-cached mode; mo files are not reloaded when setting language'
31
43
  end
32
44
  bindtextdomain 'rbot'
33
45
 
34
46
  module GetText
35
- # patch for ruby-gettext 1.8.0 up to 1.10.0 (and more?) to cope with anonymous
36
- # modules used by rbot
37
- # FIXME remove the patch when ruby-gettext is fixed, or rbot switches to named modules
38
- if !instance_methods.include?('orig_bound_targets')
47
+ # patch for ruby-gettext 1.x to cope with anonymous modules used by rbot.
48
+ # bound_targets and related methods are not used nor present in 2.x, and
49
+ # this patch is not needed
50
+ if respond_to? :bound_targets, true
39
51
  alias :orig_bound_targets :bound_targets
40
- end
41
- def bound_targets(*a) # :nodoc:
42
- bt = orig_bound_targets(*a) rescue []
43
- bt.empty? ? orig_bound_targets(Object) : bt
52
+
53
+ def bound_targets(*a) # :nodoc:
54
+ bt = orig_bound_targets(*a) rescue []
55
+ bt.empty? ? orig_bound_targets(Object) : bt
56
+ end
44
57
  end
45
58
 
46
59
  require 'stringio'
@@ -22,6 +22,10 @@ module Irc
22
22
  :default => ':', :wizard => true,
23
23
  :desc => "when replying with nick put this character after the nick of the user the bot is replying to"
24
24
  )
25
+ Config.register BooleanValue.new('core.private_replies',
26
+ :default => false,
27
+ :desc => 'Should the bot reply to private instead of the channel?'
28
+ )
25
29
  end
26
30
  end
27
31
 
@@ -82,7 +86,7 @@ module Irc
82
86
  data
83
87
  end
84
88
  if ColorCode.key?(f)
85
- ColorCode[f]
89
+ ColorCode[f]
86
90
  else
87
91
  0
88
92
  end
@@ -155,7 +159,11 @@ module Irc
155
159
  ret << ' plainmessage=' << plainmessage.inspect
156
160
  ret << fields if fields
157
161
  ret << ' (identified)' if identified?
158
- ret << ' (addressed to me)' if address?
162
+ if address?
163
+ ret << ' (addressed to me'
164
+ ret << ', with prefix' if prefixed?
165
+ ret << ')'
166
+ end
159
167
  ret << ' (replied)' if replied?
160
168
  ret << ' (ignored)' if ignored?
161
169
  ret << ' (in thread)' if in_thread?
@@ -175,6 +183,7 @@ module Irc
175
183
  @bot = bot
176
184
  @source = source
177
185
  @address = false
186
+ @prefixed = false
178
187
  @target = target
179
188
  @message = message || ""
180
189
  @replied = false
@@ -233,6 +242,12 @@ module Irc
233
242
  return @address
234
243
  end
235
244
 
245
+ # returns true if the messaged was addressed to the bot via the address
246
+ # prefix. This can be used to tell appart "!do this" from "botname, do this"
247
+ def prefixed?
248
+ return @prefixed
249
+ end
250
+
236
251
  # strip mIRC colour escapes from a string
237
252
  def BasicUserMessage.stripcolour(string)
238
253
  return "" unless string
@@ -338,6 +353,7 @@ module Irc
338
353
  bot.config['core.address_prefix'].each {|mprefix|
339
354
  if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
340
355
  @address = true
356
+ @prefixed = true
341
357
  break
342
358
  end
343
359
  }
@@ -390,26 +406,56 @@ module Irc
390
406
  # So if the message is private, it will reply to the user. If it was
391
407
  # in a channel, it will reply in the channel.
392
408
  def plainreply(string, options={})
393
- @bot.say @replyto, string, options
394
- @replied = true
409
+ reply string, {:nick => false}.merge(options)
395
410
  end
396
411
 
397
412
  # Same as reply, but when replying in public it adds the nick of the user
398
413
  # the bot is replying to
399
414
  def nickreply(string, options={})
400
- extra = self.public? ? "#{@source}#{@bot.config['core.nick_postfix']} " : ""
401
- @bot.say @replyto, extra + string, options
402
- @replied = true
415
+ reply string, {:nick => true}.merge(options)
416
+ end
417
+
418
+ # Same as nickreply, but always prepend the target's nick.
419
+ def nickreply!(string, options={})
420
+ reply string, {:nick => true, :forcenick => true}.merge(options)
403
421
  end
404
422
 
405
- # the default reply style is to nickreply unless the reply already contains
406
- # the nick or core.reply_with_nick is set to false
423
+ # The general way to reply to a command. The following options are available:
424
+ # :nick [false, true, :auto]
425
+ # state if the nick of the user calling the command should be prepended
426
+ # :auto uses core.reply_with_nick
427
+ #
428
+ # :forcenick [false, true]
429
+ # if :nick is true, always prepend the target's nick, even if the nick
430
+ # already appears in the reply. Defaults to false.
407
431
  #
432
+ # :to [:private, :public, :auto]
433
+ # where should the bot reply?
434
+ # :private always reply to the nick
435
+ # :public reply to the channel (if available)
436
+ # :auto uses core.private_replies
408
437
  def reply(string, options={})
409
- if @bot.config['core.reply_with_nick'] and not string =~ /(?:^|\W)#{Regexp.escape(@source.to_s)}(?:$|\W)/
410
- return nickreply(string, options)
438
+ opts = {:nick => :auto, :forcenick => false, :to => :auto}.merge options
439
+
440
+ if opts[:nick] == :auto
441
+ opts[:nick] = @bot.config['core.reply_with_nick']
442
+ end
443
+
444
+ if !self.public?
445
+ opts[:to] = :private
446
+ elsif opts[:to] == :auto
447
+ opts[:to] = @bot.config['core.private_replies'] ? :private : :public
448
+ end
449
+
450
+ if (opts[:nick] &&
451
+ opts[:to] != :private &&
452
+ (string !~ /(?:^|\W)#{Regexp.escape(@source.to_s)}(?:$|\W)/ ||
453
+ opts[:forcenick]))
454
+ string = "#{@source}#{@bot.config['core.nick_postfix']} #{string}"
411
455
  end
412
- plainreply(string, options)
456
+ to = (opts[:to] == :private) ? source : @channel
457
+ @bot.say to, string, options
458
+ @replied = true
413
459
  end
414
460
 
415
461
  # convenience method to reply to a message with an action. It's the
@@ -431,7 +477,7 @@ module Irc
431
477
  # convenience method to reply "okay" in the current language to the
432
478
  # message
433
479
  def plainokay
434
- self.plainreply @bot.lang.get("okay")
480
+ self.reply @bot.lang.get("okay"), :nick => false
435
481
  end
436
482
 
437
483
  # Like the above, but append the username
@@ -442,16 +488,13 @@ module Irc
442
488
  str.gsub!(/[!,.]$/,"")
443
489
  str += ", #{@source}"
444
490
  end
445
- self.plainreply str
491
+ self.reply str, :nick => false
446
492
  end
447
493
 
448
494
  # the default okay style is the same as the default reply style
449
495
  #
450
496
  def okay
451
- if @bot.config['core.reply_with_nick']
452
- return nickokay
453
- end
454
- plainokay
497
+ @bot.config['core.reply_with_nick'] ? nickokay : plainokay
455
498
  end
456
499
 
457
500
  # send a NOTICE to the message source
@@ -583,6 +626,22 @@ module Irc
583
626
  end
584
627
  end
585
628
 
629
+ # class to manager Ban list replies
630
+ class BanlistMessage < BasicUserMessage
631
+ # the bans
632
+ attr_accessor :bans
633
+
634
+ def initialize(bot, server, source, target, message="")
635
+ super(bot, server, source, target, message)
636
+ @bans = []
637
+ end
638
+
639
+ def inspect
640
+ fields = ' bans=' << bans.inspect
641
+ super(fields)
642
+ end
643
+ end
644
+
586
645
  class QuitMessage < BasicUserMessage
587
646
  attr_accessor :was_on
588
647
  def initialize(bot, server, source, target, message="")
@@ -639,6 +698,18 @@ module Irc
639
698
  class PartMessage < JoinMessage
640
699
  end
641
700
 
701
+ # class to handle ERR_NOSUCHNICK and ERR_NOSUCHCHANNEL
702
+ class NoSuchTargetMessage < BasicUserMessage
703
+ # the channel or nick that was not found
704
+ attr_reader :target
705
+
706
+ def initialize(bot, server, source, target, message='')
707
+ super(bot, server, source, target, message)
708
+
709
+ @target = target
710
+ end
711
+ end
712
+
642
713
  class UnknownMessage < BasicUserMessage
643
714
  end
644
715
  end
@@ -52,6 +52,57 @@ class Bot
52
52
  # {:option => "bar", :otheroption => "baz"}
53
53
  # See the #map method for more details.
54
54
  class MessageMapper
55
+
56
+ class Failure
57
+ STRING = "template %{template} failed to recognize message %{message}"
58
+ FRIENDLY = "I failed to understand the command"
59
+ attr_reader :template
60
+ attr_reader :message
61
+ def initialize(tmpl, msg)
62
+ @template = tmpl
63
+ @message = msg
64
+ end
65
+
66
+ def to_s
67
+ STRING % {
68
+ :template => template.template,
69
+ :regexp => template.regexp,
70
+ :message => message.message,
71
+ :action => template.options[:action]
72
+ }
73
+ end
74
+ end
75
+
76
+ # failures with a friendly message
77
+ class FriendlyFailure < Failure
78
+ def friendly
79
+ self.class::FRIENDLY rescue FRIENDLY
80
+ end
81
+ end
82
+
83
+ class NotPrivateFailure < FriendlyFailure
84
+ STRING = "template %{template} is not configured for private messages"
85
+ FRIENDLY = "the command must not be given in private"
86
+ end
87
+
88
+ class NotPublicFailure < FriendlyFailure
89
+ STRING = "template %{template} is not configured for public messages"
90
+ FRIENDLY = "the command must not be given in public"
91
+ end
92
+
93
+ class NoMatchFailure < Failure
94
+ STRING = "%{message} does not match %{template} (%{regex})"
95
+ end
96
+
97
+ class PartialMatchFailure < Failure
98
+ STRING = "%{message} only matches %{template} (%{regex}) partially"
99
+ end
100
+
101
+ class NoActionFailure < FriendlyFailure
102
+ STRING = "%{template} calls undefined action %{action}"
103
+ FRIENDLY = "uh-ho, somebody forgot to tell me how to do that ..."
104
+ end
105
+
55
106
  # used to set the method name used as a fallback for unmatched messages.
56
107
  # The default fallback is a method called "usage".
57
108
  attr_writer :fallback
@@ -144,7 +195,7 @@ class Bot
144
195
  # threaded::
145
196
  # a boolean (defaults to false) that determines whether the action should be
146
197
  # called in a separate thread.
147
- #
198
+ #
148
199
  #
149
200
  # Further examples:
150
201
  #
@@ -194,13 +245,13 @@ class Bot
194
245
  return false if @templates.empty?
195
246
  failures = []
196
247
  @templates.each do |tmpl|
197
- options, failure = tmpl.recognize(m)
198
- if options.nil?
199
- failures << [tmpl, failure]
248
+ options = tmpl.recognize(m)
249
+ if options.kind_of? Failure
250
+ failures << options
200
251
  else
201
252
  action = tmpl.options[:action]
202
253
  unless @parent.respond_to?(action)
203
- failures << [tmpl, "class does not respond to action #{action}"]
254
+ failures << NoActionFailure.new(tmpl, m)
204
255
  next
205
256
  end
206
257
  auth = tmpl.options[:full_auth_path]
@@ -228,13 +279,13 @@ class Bot
228
279
  return false
229
280
  end
230
281
  end
231
- failures.each {|f, r|
232
- debug "#{f.inspect} => #{r}"
282
+ failures.each {|r|
283
+ debug "#{r.template.inspect} => #{r}"
233
284
  }
234
285
  debug "no handler found, trying fallback"
235
286
  if @fallback && @parent.respond_to?(@fallback)
236
287
  if m.bot.auth.allow?(@fallback, m.source, m.replyto)
237
- @parent.send(@fallback, m, {})
288
+ @parent.send(@fallback, m, {:failures => failures})
238
289
  return true
239
290
  end
240
291
  end
@@ -333,7 +384,7 @@ class Bot
333
384
  mul = multi? ? " multi" : " single"
334
385
  opt = optional? ? " optional" : " needed"
335
386
  if @regexp
336
- reg = " regexp=%s index=%d" % [@regexp, @index]
387
+ reg = " regexp=%s index=%s" % [@regexp, @index]
337
388
  else
338
389
  reg = nil
339
390
  end
@@ -514,7 +565,9 @@ class Bot
514
565
  s = "#{not_needed ? "(?:" : ""}#{whites}(#{sub})#{ not_needed ? ")?" : ""}"
515
566
  }
516
567
  # debug "Replaced dyns: #{rx.inspect}"
517
- rx.gsub!(/((?:\\ )*)\\\[/, "(?:\\1")
568
+ rx.gsub!(/((?:\\ )*)((?:\\\[)+)/, '\2\1')
569
+ # debug "Corrected optionals spacing: #{rx.inspect}"
570
+ rx.gsub!(/\\\[/, "(?:")
518
571
  rx.gsub!(/\\\]/, ")?")
519
572
  # debug "Delimited optionals: #{rx.inspect}"
520
573
  rx.gsub!(/(?:\\ )+/, "\\s+")
@@ -529,20 +582,19 @@ class Bot
529
582
 
530
583
  debug "Testing #{m.message.inspect} against #{self.inspect}"
531
584
 
532
- # Early out
533
- return nil, "template #{@template} is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private?
534
- return nil, "template #{@template} is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private?
535
-
536
- options = {}
537
-
538
585
  matching = @regexp.match(m.message)
539
- return nil, "#{m.message.inspect} doesn't match #{@template} (#{@regexp})" unless matching
540
- return nil, "#{m.message.inspect} only matches #{@template} (#{@regexp}) partially: #{matching[0].inspect}" unless matching[0] == m.message
586
+ return MessageMapper::NoMatchFailure.new(self, m) unless matching
587
+ return MessageMapper::PartialMatchFailure.new(self, m) unless matching[0] == m.message
588
+
589
+ return MessageMapper::NotPrivateFailure.new(self, m) if @options.has_key?(:private) && !@options[:private] && m.private?
590
+ return MessageMapper::NotPublicFailure.new(self, m) if @options.has_key?(:public) && !@options[:public] && !m.private?
541
591
 
542
592
  debug_match = matching[1..-1].collect{ |d| d.inspect}.join(', ')
543
593
  debug "#{m.message.inspect} matched #{@regexp} with #{debug_match}"
544
594
  debug "Associating #{debug_match} with dyn items #{@dyn_items.join(', ')}"
545
595
 
596
+ options = @defaults.dup
597
+
546
598
  @dyn_items.each_with_index { |it, i|
547
599
  next if i == 0
548
600
  item = it.name
@@ -587,7 +639,7 @@ class Bot
587
639
  }
588
640
 
589
641
  options.delete_if {|k, v| v.nil?} # Remove nil values.
590
- return options, nil
642
+ return options
591
643
  end
592
644
 
593
645
  def inspect
@@ -10,6 +10,9 @@ class Bot
10
10
  Config.register Config::ArrayValue.new('plugins.blacklist',
11
11
  :default => [], :wizard => false, :requires_rescan => true,
12
12
  :desc => "Plugins that should not be loaded")
13
+ Config.register Config::ArrayValue.new('plugins.whitelist',
14
+ :default => [], :wizard => false, :requires_rescan => true,
15
+ :desc => "Only whitelisted plugins will be loaded unless the list is empty")
13
16
  module Plugins
14
17
  require 'rbot/messagemapper'
15
18
 
@@ -198,7 +201,7 @@ module Plugins
198
201
  @priority ||= 1
199
202
  end
200
203
 
201
- # Returns the symbol :BotModule
204
+ # Returns the symbol :BotModule
202
205
  def botmodule_class
203
206
  :BotModule
204
207
  end
@@ -318,7 +321,7 @@ module Plugins
318
321
  #
319
322
  # This command is now superceded by the #map() command, which should be used
320
323
  # instead whenever possible.
321
- #
324
+ #
322
325
  def register(cmd, opts={})
323
326
  raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
324
327
  who = @manager.who_handles?(cmd)
@@ -338,10 +341,19 @@ module Plugins
338
341
  # MessageMapper uses 'usage' as its default fallback method.
339
342
  #
340
343
  def usage(m, params = {})
344
+ if params[:failures].respond_to? :find
345
+ friendly = params[:failures].find do |f|
346
+ f.kind_of? MessageMapper::FriendlyFailure
347
+ end
348
+ if friendly
349
+ m.reply friendly.friendly
350
+ return
351
+ end
352
+ end
341
353
  m.reply(_("incorrect usage, ask for help using '%{command}'") % {:command => "#{@bot.nick}: help #{m.plugin}"})
342
354
  end
343
355
 
344
- # Define the priority of the module. During event delegation, lower
356
+ # Define the priority of the module. During event delegation, lower
345
357
  # priority modules will be called first. Default priority is 1
346
358
  def priority=(prio)
347
359
  if @priority != prio
@@ -349,6 +361,20 @@ module Plugins
349
361
  @bot.plugins.mark_priorities_dirty
350
362
  end
351
363
  end
364
+
365
+ # Directory name to be joined to the botclass to access data files. By
366
+ # default this is the plugin name itself, but may be overridden, for
367
+ # example by plugins that share their datafiles or for backwards
368
+ # compatibilty
369
+ def dirname
370
+ name
371
+ end
372
+
373
+ # Filename for a datafile built joining the botclass, plugin dirname and
374
+ # actual file name
375
+ def datafile(*fname)
376
+ @bot.path dirname, *fname
377
+ end
352
378
  end
353
379
 
354
380
  # A CoreBotModule is a BotModule that provides core functionality.
@@ -410,7 +436,8 @@ module Plugins
410
436
  h[k] = Array.new
411
437
  }
412
438
 
413
- @dirs = []
439
+ @core_module_dirs = []
440
+ @plugin_dirs = []
414
441
 
415
442
  @failed = Array.new
416
443
  @ignored = Array.new
@@ -555,10 +582,27 @@ module Plugins
555
582
  "#{fname}#{$1}#{$3}"
556
583
  }
557
584
  }
558
- msg = err.to_str.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
585
+ msg = err.to_s.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
559
586
  "#{fname}#{$1}#{$3}"
560
587
  }
561
- newerr = err.class.new(msg)
588
+ begin
589
+ newerr = err.class.new(msg)
590
+ rescue ArgumentError => err_in_err
591
+ # Somebody should hang the ActiveSupport developers by their balls
592
+ # with barbed wire. Their MissingSourceFile extension to LoadError
593
+ # _expects_ a second argument, breaking the usual Exception interface
594
+ # (instead, the smart thing to do would have been to make the second
595
+ # parameter optional and run the code in the from_message method if
596
+ # it was missing).
597
+ # Anyway, we try to cope with this in the simplest possible way. On
598
+ # the upside, this new block can be extended to handle other similar
599
+ # idiotic approaches
600
+ if err.class.respond_to? :from_message
601
+ newerr = err.class.from_message(msg)
602
+ else
603
+ raise err_in_err
604
+ end
605
+ end
562
606
  newerr.set_backtrace(bt)
563
607
  return newerr
564
608
  end
@@ -566,42 +610,58 @@ module Plugins
566
610
  private :load_botmodule_file
567
611
 
568
612
  # add one or more directories to the list of directories to
569
- # load botmodules from
570
- #
571
- # TODO find a way to specify necessary plugins which _must_ be loaded
572
- #
573
- def add_botmodule_dir(*dirlist)
574
- @dirs += dirlist
575
- debug "Botmodule loading path: #{@dirs.join(', ')}"
613
+ # load core modules from
614
+ def add_core_module_dir(*dirlist)
615
+ @core_module_dirs += dirlist
616
+ debug "Core module loading paths: #{@core_module_dirs.join(', ')}"
576
617
  end
577
618
 
578
- def clear_botmodule_dirs
579
- @dirs.clear
580
- debug "Botmodule loading path cleared"
619
+ # add one or more directories to the list of directories to
620
+ # load plugins from
621
+ def add_plugin_dir(*dirlist)
622
+ @plugin_dirs += dirlist
623
+ debug "Plugin loading paths: #{@plugin_dirs.join(', ')}"
581
624
  end
582
625
 
583
- # load plugins from pre-assigned list of directories
584
- def scan
585
- @failed.clear
586
- @ignored.clear
587
- @delegate_list.clear
626
+ def clear_botmodule_dirs
627
+ @core_module_dirs.clear
628
+ @plugin_dirs.clear
629
+ debug "Core module and plugin loading paths cleared"
630
+ end
588
631
 
632
+ def scan_botmodules(opts={})
633
+ type = opts[:type]
589
634
  processed = Hash.new
590
635
 
591
- @bot.config['plugins.blacklist'].each { |p|
592
- pn = p + ".rb"
593
- processed[pn.intern] = :blacklisted
594
- }
636
+ case type
637
+ when :core
638
+ dirs = @core_module_dirs
639
+ when :plugins
640
+ dirs = @plugin_dirs
595
641
 
596
- dirs = @dirs
597
- dirs.each {|dir|
598
- if(FileTest.directory?(dir))
599
- d = Dir.new(dir)
600
- d.sort.each {|file|
642
+ @bot.config['plugins.blacklist'].each { |p|
643
+ pn = p + ".rb"
644
+ processed[pn.intern] = :blacklisted
645
+ }
601
646
 
602
- next if(file =~ /^\./)
647
+ whitelist = @bot.config['plugins.whitelist'].map { |p|
648
+ p + ".rb"
649
+ }
650
+ end
603
651
 
604
- if processed.has_key?(file.intern)
652
+ dirs.each do |dir|
653
+ next unless FileTest.directory?(dir)
654
+ d = Dir.new(dir)
655
+ d.sort.each do |file|
656
+ next unless file =~ /\.rb$/
657
+ next if file =~ /^\./
658
+
659
+ case type
660
+ when :plugins
661
+ if !whitelist.empty? && !whitelist.include?(file)
662
+ @ignored << {:name => file, :dir => dir, :reason => :"not whitelisted" }
663
+ next
664
+ elsif processed.has_key?(file.intern)
605
665
  @ignored << {:name => file, :dir => dir, :reason => processed[file.intern]}
606
666
  next
607
667
  end
@@ -615,20 +675,28 @@ module Plugins
615
675
  @ignored << {:name => $1, :dir => dir, :reason => processed[$1.intern]}
616
676
  next
617
677
  end
678
+ end
679
+
680
+ did_it = load_botmodule_file("#{dir}/#{file}", "plugin")
681
+ case did_it
682
+ when Symbol
683
+ processed[file.intern] = did_it
684
+ when Exception
685
+ @failed << { :name => file, :dir => dir, :reason => did_it }
686
+ end
687
+ end
688
+ end
689
+ end
618
690
 
619
- next unless(file =~ /\.rb$/)
691
+ # load plugins from pre-assigned list of directories
692
+ def scan
693
+ @failed.clear
694
+ @ignored.clear
695
+ @delegate_list.clear
620
696
 
621
- did_it = load_botmodule_file("#{dir}/#{file}", "plugin")
622
- case did_it
623
- when Symbol
624
- processed[file.intern] = did_it
625
- when Exception
626
- @failed << { :name => file, :dir => dir, :reason => did_it }
627
- end
697
+ scan_botmodules(:type => :core)
698
+ scan_botmodules(:type => :plugins)
628
699
 
629
- }
630
- end
631
- }
632
700
  debug "finished loading plugins: #{status(true)}"
633
701
  (core_modules + plugins).each { |p|
634
702
  p.methods.grep(DEFAULT_DELEGATE_PATTERNS).each { |m|
@@ -798,7 +866,7 @@ module Plugins
798
866
  end
799
867
 
800
868
  def sort_modules
801
- @sorted_modules = (core_modules + plugins).sort do |a, b|
869
+ @sorted_modules = (core_modules + plugins).sort do |a, b|
802
870
  a.priority <=> b.priority
803
871
  end || []
804
872