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
@@ -9,7 +9,7 @@ class DiggPlugin < Plugin
9
9
  def help(plugin, topic="")
10
10
  "digg [<max>=5] => show digg headlines, [<max>=5] => return up to <max> headlines (use a negative number to show all the headlines on one line)"
11
11
  end
12
-
12
+
13
13
  def digg(m, params)
14
14
  max = params[:limit].to_i
15
15
  debug "max is #{max}"
@@ -32,11 +32,11 @@ class DiggPlugin < Plugin
32
32
  max = 8 if max > 8
33
33
  matches = Array.new
34
34
  doc.elements.each("rss/channel/item") {|e|
35
- matches << [ e.elements["title"].text,
35
+ matches << [ e.elements["title"].text,
36
36
  Time.parse(e.elements["pubDate"].text).strftime('%a @ %I:%M%p') ]
37
37
  done += 1
38
38
  break if done >= max
39
- }
39
+ }
40
40
  if oneline
41
41
  m.reply matches.collect{|mat| mat[0]}.join(" | ")
42
42
  else
@@ -10,7 +10,7 @@ class EightBallPlugin < Plugin
10
10
  end
11
11
  def eightball(m, params)
12
12
  answers = @answers[rand(@answers.length)]
13
- action = "shakes the magic 8-ball... #{answers}"
13
+ action = "shakes the magic 8-ball for #{m.source} ... #{answers}"
14
14
  @bot.action m.replyto, action
15
15
  end
16
16
  end
@@ -120,7 +120,7 @@ class FactoidsPlugin < Plugin
120
120
  super
121
121
 
122
122
  # TODO config
123
- @dir = File.join(@bot.botclass,"factoids")
123
+ @dir = datafile
124
124
  @filename = "factoids.rbot"
125
125
  @factoids = FactoidList.new
126
126
  @triggers = Set.new
@@ -341,7 +341,7 @@ class FactoidsPlugin < Plugin
341
341
  if params[:words].nil_or_empty? and params[:rx].nil_or_empty?
342
342
  m.reply _("I know %{total} facts" % { :total => total })
343
343
  else
344
- if params[:words].empty?
344
+ unless params.key? :words and not params[:words].empty?
345
345
  rx = Regexp.new(params[:rx].to_s, true)
346
346
  else
347
347
  rx = words2rx(params[:words])
@@ -349,7 +349,11 @@ class FactoidsPlugin < Plugin
349
349
  known = @factoids.grep(rx)
350
350
  reply = []
351
351
  if known.empty?
352
- reply << _("I know nothing about %{words}" % params)
352
+ if params.key? :words
353
+ reply << _("I know nothing about %{words}" % params)
354
+ else params.key? :rx
355
+ reply << _("I know nothing matching %{rx}" % params)
356
+ end
353
357
  else
354
358
  max_facts = @bot.config['factoids.search_results']
355
359
  len = known.length
@@ -379,7 +383,12 @@ class FactoidsPlugin < Plugin
379
383
  return if @triggers.empty?
380
384
  query = $1.strip.downcase
381
385
  if @triggers.include?(query)
382
- facts(m, :words => query.split)
386
+ words = query.split
387
+ words.instance_variable_set(:@string_value, query)
388
+ def words.to_s
389
+ @string_value
390
+ end
391
+ facts(m, :words => words)
383
392
  end
384
393
  else
385
394
  return if m.address? # we don't learn stuff directed at us which is not an explicit learn command
@@ -58,10 +58,10 @@ class FigletPlugin < Plugin
58
58
 
59
59
  def test_figlet
60
60
  #check that figlet is present
61
- @has[:figlet] = !!(Utils.safe_exec("#{figlet_path} -v") ; $?)
61
+ @has[:figlet] = Utils.try_exec("#{figlet_path} -v")
62
62
 
63
63
  # check that figlet actually has the font installed
64
- @has[:figlet_font] = !!(Utils.safe_exec("#{figlet_path} -f #{figlet_font} test test test") ; $?)
64
+ @has[:figlet_font] = Utils.try_exec("#{figlet_path} -f #{figlet_font} test test test")
65
65
 
66
66
  # set the commandline params
67
67
  @params[:figlet] = ['-k', '-w', MAX_WIDTH.to_s, '-C', 'utf8']
@@ -72,10 +72,10 @@ class FigletPlugin < Plugin
72
72
 
73
73
  def test_toilet
74
74
  #check that toilet is present
75
- @has[:toilet] = !!(Utils.safe_exec("#{toilet_path} -v") ; $?)
75
+ @has[:toilet] = Utils.try_exec("#{toilet_path} -v")
76
76
 
77
77
  # check that toilet actually has the font installed
78
- @has[:toilet_font] = !!(Utils.safe_exec("#{toilet_path} -f #{toilet_font} test test test") ; $?)
78
+ @has[:toilet_font] = Utils.try_exec("#{toilet_path} -f #{toilet_font} test test test")
79
79
 
80
80
  # set the commandline params
81
81
  @params[:toilet] = ['-k', '-w', MAX_WIDTH.to_s, '-E', 'utf8', '--irc']
@@ -8,7 +8,7 @@
8
8
 
9
9
  require 'soap/wsdlDriver'
10
10
  # TODO why not use HttpUtil instead of open-uri?
11
- require 'open-uri'
11
+ require 'open-uri'
12
12
  require 'rexml/document'
13
13
  require 'erb'
14
14
 
@@ -48,7 +48,7 @@ class Forecast
48
48
  return parse(retrieve),Time.new
49
49
  end
50
50
  private
51
- def retrieve
51
+ def retrieve
52
52
  forecast = @forecaster.NDFDgenByDay(
53
53
  @lat,@long,Time.now.strftime("%Y-%m-%d"),2,"24 hourly")
54
54
  (REXML::Document.new(forecast)).root
@@ -105,7 +105,7 @@ class ForecastPlugin < Plugin
105
105
  end
106
106
  end
107
107
  end
108
-
108
+
109
109
  def get_forecast(m,loc)
110
110
  begin
111
111
  @cache_mutex.synchronize do
@@ -7,14 +7,17 @@ class FortunePlugin < Plugin
7
7
  Config.register Config::StringValue.new('fortune.path',
8
8
  :default => '',
9
9
  :desc => "Full path to the fortune executable")
10
+ Config.register Config::ArrayValue.new('fortune.options',
11
+ :default => ['-n', '350', '-s'],
12
+ :desc => "Options to be passed on to fortune")
10
13
 
11
14
  def help(plugin, topic="")
12
15
  "fortune [<category>] => get a (short) fortune, optionally specifying fortune category || fortune categories => show categories"
13
16
  end
14
17
 
15
- def find_fortune
18
+ def find_fortune(m)
16
19
  fortune = @bot.config['fortune.path']
17
- return fortune if fortune
20
+ return fortune if fortune and not fortune.empty?
18
21
 
19
22
  ["/usr/bin/fortune",
20
23
  "/usr/share/bin/fortune",
@@ -28,7 +31,7 @@ class FortunePlugin < Plugin
28
31
  end
29
32
 
30
33
  return nil unless fortune
31
-
34
+
32
35
  # Try setting the config entry
33
36
  config_par = {:key => 'fortune.path', :value => [fortune], :silent => true }
34
37
  debug "Setting fortune.path to #{fortune}"
@@ -44,12 +47,15 @@ class FortunePlugin < Plugin
44
47
 
45
48
  ## Pick a fortune
46
49
  def fortune(m, params)
47
- db = params[:db]
48
- fortune = find_fortune
50
+ fortune = find_fortune(m)
49
51
  m.reply "fortune executable not found (try setting the 'fortune.path' variable)" unless fortune
50
52
 
53
+ command = [fortune] + @bot.config['fortune.options']
54
+ command << params[:db]
55
+ command.compact!
56
+
51
57
  begin
52
- ret = Utils.safe_exec(fortune, "-n", "350", "-s", db)
58
+ ret = Utils.safe_exec(*command)
53
59
 
54
60
  ## cleanup ret
55
61
  ret = ret.split(/\n+/).map do |l|
@@ -75,7 +81,7 @@ class FortunePlugin < Plugin
75
81
 
76
82
  # Print the fortune categories
77
83
  def categories(m, params)
78
- fortune = find_fortune
84
+ fortune = find_fortune(m)
79
85
  m.reply "fortune executable not found (try setting the 'fortune.path' variable)" unless fortune
80
86
 
81
87
  ## list all fortune databases
@@ -88,7 +94,7 @@ class FortunePlugin < Plugin
88
94
  ## say 'em!
89
95
  m.reply "Fortune categories: #{categories.join ', '}"
90
96
  end
91
-
97
+
92
98
  end
93
99
  plugin = FortunePlugin.new
94
100
  plugin.map 'fortune categories', :action => "categories"
@@ -88,7 +88,7 @@ class FreshmeatPlugin < Plugin
88
88
  m.reply reply
89
89
  }
90
90
  end
91
-
91
+
92
92
  def freshmeat(m, params)
93
93
  max = params[:limit].to_i
94
94
  max = 8 if max > 8
@@ -134,5 +134,5 @@ end
134
134
  plugin = FreshmeatPlugin.new
135
135
  plugin.map 'freshmeat search :limit *search', :action => 'search_freshmeat',
136
136
  :defaults => {:limit => 4}, :requirements => {:limit => /^\d+$/}
137
- plugin.map 'freshmeat :limit', :defaults => {:limit => 4},
137
+ plugin.map 'freshmeat :limit', :defaults => {:limit => 4},
138
138
  :requirements => {:limit => /^\d+$/}
@@ -35,6 +35,34 @@ class AzGame
35
35
  def @range.to_s
36
36
  return "%s -- %s" % self
37
37
  end
38
+ if @rules[:list]
39
+ @check_method = "is_#{@rules[:addlang]}?"
40
+ # trick: if addlang was not in rules, this will be is_? which is
41
+ # not a method of the plugin
42
+ if @check_method and not @plugin.respond_to? @check_method
43
+ @check_method = nil
44
+ end
45
+ @check = Proc.new do |w|
46
+ wl = @rules[:list].include?(w)
47
+ if !wl and @check_method
48
+ if wl = @plugin.send(@check_method, w)
49
+ debug "adding #{w} to #{@rules[:addfile]}"
50
+ begin
51
+ File.open(@rules[:addfile], "a") do |f|
52
+ f.puts w
53
+ end
54
+ rescue Exception => e
55
+ error "failed to add #{w} to #{@rules[:addfile]}"
56
+ error e
57
+ end
58
+ end
59
+ end
60
+ wl
61
+ end
62
+ else
63
+ @check_method = "is_#{@lang}?"
64
+ @check = Proc.new { |w| @plugin.send(@check_method, w) }
65
+ end
38
66
  end
39
67
 
40
68
  def check(word)
@@ -47,7 +75,7 @@ class AzGame
47
75
  return [:out, @range] if w < @range.first or w > @range.last
48
76
  return [:ignore, @range] if w == @range.first or w == @range.last
49
77
  # This is potentially slow (for languages that check online)
50
- return [:noexist, @range] unless @plugin.send("is_#{@lang}?", w)
78
+ return [:noexist, @range] unless @check.call(w)
51
79
  debug "we like it"
52
80
  # Check again if there was a winner in the mean time,
53
81
  # and bail out if there was
@@ -122,21 +150,36 @@ class AzGamePlugin < Plugin
122
150
  },
123
151
  }
124
152
 
125
- japanese_wordlist = "#{@bot.botclass}/azgame/wordlist-japanese"
126
- if File.exist?(japanese_wordlist)
127
- words = File.readlines(japanese_wordlist) \
128
- .map {|line| line.strip} .uniq
153
+ @autoadd_base = datafile "autoadd-"
154
+ end
155
+
156
+ def initialize_wordlist(params)
157
+ lang = params[:lang]
158
+ addlang = params[:addlang]
159
+ autoadd = @autoadd_base + addlang.to_s
160
+ if Wordlist.exist?(lang)
161
+ # wordlists are assumed to be UTF-8, but we need to strip the BOM, if present
162
+ words = Wordlist.get(lang)
163
+ if addlang and File.exist?(autoadd)
164
+ word += File.readlines(autoadd).map {|line| line.sub("\xef\xbb\xbf",'').strip}
165
+ end
166
+ words.uniq!
167
+ words.sort!
129
168
  if(words.length >= 4) # something to guess
130
- @rules[:japanese] = {
169
+ rules = {
131
170
  :good => /^\S+$/,
132
171
  :list => words,
133
172
  :first => words[0],
134
173
  :last => words[-1],
174
+ :addlang => addlang,
175
+ :addfile => autoadd,
135
176
  :listener => /^\S+$/
136
177
  }
137
- debug "Japanese wordlist loaded, #{@rules[:japanese][:list].length} lines; first word: #{@rules[:japanese][:first]}, last word: #{@rules[:japanese][:last]}"
178
+ debug "#{lang} wordlist loaded, #{rules[:list].length} lines; first word: #{rules[:first]}, last word: #{rules[:last]}"
179
+ return rules
138
180
  end
139
181
  end
182
+ return false
140
183
  end
141
184
 
142
185
  def save
@@ -233,12 +276,20 @@ class AzGamePlugin < Plugin
233
276
  m.reply _("couldn't think of anything ...")
234
277
  return
235
278
  end
279
+ m.reply _("got it!")
280
+ @games[k] = AzGame.new(self, lang, @rules[lang], word)
281
+ elsif !@rules.has_key?(lang) and rules = initialize_wordlist(params)
282
+ word = random_pick_wordlist(rules)
283
+ if word.empty?
284
+ m.reply _("couldn't think of anything ...")
285
+ return
286
+ end
287
+ m.reply _("got it!")
288
+ @games[k] = AzGame.new(self, lang, rules, word)
236
289
  else
237
290
  m.reply _("I can't play A-Z in %{lang}, sorry") % {:lang => lang}
238
291
  return
239
292
  end
240
- m.reply _("got it!")
241
- @games[k] = AzGame.new(self, lang, @rules[lang], word)
242
293
  end
243
294
  tr = @games[k].total_tries
244
295
  # this message building code is rewritten to make translation easier
@@ -304,10 +355,10 @@ class AzGamePlugin < Plugin
304
355
  if wc[word].key?(:when)
305
356
  tr = _("%{word} learned from %{user} on %{date}") % {:word => word, :user => wc[word][:who], :date => wc[word][:when]}
306
357
  else
307
- tr = _("%{word} learned from %{user}") % {:word => word, :user => wc[word][:who]}
358
+ tr = _("%{word} learned from %{user}") % {:word => word, :user => wc[word][:who]}
308
359
  end
309
360
  m.reply tr
310
- when :delete
361
+ when :delete
311
362
  if pars.empty?
312
363
  m.reply _("provide a word")
313
364
  return
@@ -335,17 +386,12 @@ class AzGamePlugin < Plugin
335
386
  end
336
387
  end
337
388
 
338
- def is_japanese?(word)
339
- @rules[:japanese][:list].include?(word)
340
- end
341
-
342
389
  # return integer between min and max, inclusive
343
390
  def rand_between(min, max)
344
391
  rand(max - min + 1) + min
345
392
  end
346
393
 
347
- def random_pick_japanese(min=nil, max=nil)
348
- rules = @rules[:japanese]
394
+ def random_pick_wordlist(rules, min=nil, max=nil)
349
395
  min = rules[:first] if min.nil_or_empty?
350
396
  max = rules[:last] if max.nil_or_empty?
351
397
  debug "Randomly picking word between #{min} and #{max}"
@@ -552,7 +598,14 @@ class AzGamePlugin < Plugin
552
598
  when 'play'
553
599
  return _("az => start a game if none is running, show the current word range otherwise; you can say 'az <language>' if you want to play in a language different from the current bot default")
554
600
  end
555
- return _("az topics: play, rules, cancel, manage, check")
601
+ langs = @rules.keys
602
+ wls = Wordlist.list
603
+ return [
604
+ _("az topics: play, rules, cancel, manage, check"),
605
+ _("available languages: %{langs}") % { :langs => langs.join(", ") },
606
+ wls.empty? ? nil : _("available wordlists: %{wls}") % { :wls => wls.join(", ") },
607
+ ].compact.join(". ")
608
+
556
609
  end
557
610
 
558
611
  end
@@ -561,5 +614,5 @@ plugin = AzGamePlugin.new
561
614
  plugin.map 'az [:lang] word :cmd *params', :action=>'wordlist', :defaults => { :lang => nil, :cmd => 'count', :params => [] }, :auth_path => '!az::edit!'
562
615
  plugin.map 'az cancel', :action=>'stop_game', :private => false
563
616
  plugin.map 'az check :word', :action => 'manual_word_check', :private => false
564
- plugin.map 'az [play] [:lang]', :action=>'start_game', :private => false, :defaults => { :lang => nil }
617
+ plugin.map 'az [play] [:lang] [autoadd :addlang]', :action=>'start_game', :private => false, :defaults => { :lang => nil, :addlang => nil }
565
618
 
@@ -0,0 +1,499 @@
1
+ #-- vim:sw=2:et
2
+ #++
3
+ #
4
+ # :title: Hangman Plugin
5
+ #
6
+ # Author:: Raine Virta <rane@kapsi.fi>
7
+ # Copyright:: (C) 2009 Raine Virta
8
+ # License:: GPL v2
9
+ #
10
+ # Description:: Hangman game for rbot
11
+ #
12
+ # TODO:: some sort of turn-basedness, maybe
13
+
14
+ module RandomWord
15
+ SITE = "http://coyotecult.com/tools/randomwordgenerator.php"
16
+
17
+ def self.get(count=1)
18
+ res = Net::HTTP.post_form(URI.parse(SITE), {'numwords' => count})
19
+ words = res.body.scan(%r{<a.*?\?w=(.*?)\n}).flatten
20
+
21
+ count == 1 ? words.first : words
22
+ end
23
+ end
24
+
25
+ module Google
26
+ URL = "http://www.google.com/wml/search?hl=en&q=define:"
27
+ REGEX = %r{Web definitions for .*?<br/>(.*?)<br/>}
28
+
29
+ def self.define(phrase)
30
+ raw = Net::HTTP.get(URI.parse(URL+CGI.escape(phrase)))
31
+ res = raw.scan(REGEX).flatten.map { |e| e.strip }
32
+
33
+ res.empty? ? false : res.last
34
+ end
35
+ end
36
+
37
+ class Hangman
38
+ LETTER_VALUE = 5
39
+
40
+ module Scoring
41
+ def self.correct_word_guess(game)
42
+ # (2 - (amt of visible chars / word length)) * (amt of chars not visible * 5)
43
+ length = game.word.size
44
+ visible = game.visible_characters.size
45
+ invisible = length - visible
46
+ score = (2-(visible/length.to_f))*(invisible*5)
47
+ score *= 1.5
48
+ score.round
49
+ end
50
+
51
+ def self.incorrect_word_guess(game)
52
+ incorrect_letter(game)
53
+ end
54
+
55
+ def self.correct_letter(game)
56
+ ((1/Math.log(game.word.size))+1) * LETTER_VALUE
57
+ end
58
+
59
+ def self.incorrect_letter(game)
60
+ Math.log(game.word.size) * -LETTER_VALUE
61
+ end
62
+ end
63
+ end
64
+
65
+ class Hangman
66
+ attr_reader :misses, :guesses, :word, :scores
67
+
68
+ STAGES = [' (x_x) ', ' (;_;) ', ' (>_<) ', ' (-_-) ', ' (o_~) ', ' (^_^) ', '\(^o^)/']
69
+ HEALTH = STAGES.size-1
70
+ LETTER = /[^\W0-9_]/u
71
+
72
+ def initialize(word)
73
+ @word = word
74
+ @guesses = []
75
+ @misses = []
76
+ @health = HEALTH
77
+ @canceled = false
78
+ @solved = false
79
+ @scores = {}
80
+ end
81
+
82
+ def visible_characters
83
+ # array of visible characters
84
+ characters.reject { |c| !@guesses.include?(c) && c =~ LETTER }
85
+ end
86
+
87
+ def letters
88
+ # array of the letters in the word
89
+ characters.reject { |c| c !~ LETTER }.map { |c| c.downcase }
90
+ end
91
+
92
+ def characters
93
+ @word.split(//u)
94
+ end
95
+
96
+ def face
97
+ STAGES[@health]
98
+ end
99
+
100
+ def to_s
101
+ # creates a string that presents the word with unknown letters shown as underscores
102
+ characters.map { |c|
103
+ @guesses.include?(c.downcase) || c !~ LETTER ? c : "_"
104
+ }.join
105
+ end
106
+
107
+ def guess(player, str)
108
+ @scores[player] ||= 0
109
+
110
+ str.downcase!
111
+ # full word guess
112
+ if str !~ /^#{LETTER}$/u
113
+ if word.downcase == str
114
+ @scores[player] += Scoring::correct_word_guess(self)
115
+ @solved = true
116
+ else
117
+ @scores[player] += Scoring::incorrect_word_guess(self)
118
+ punish
119
+ end
120
+ else # single letter guess
121
+ return false if @guesses.include?(str) # letter has been guessed before
122
+
123
+ unless letters.include?(str)
124
+ @scores[player] += Scoring::incorrect_letter(self)
125
+ @misses << str
126
+ punish
127
+ else
128
+ @scores[player] += Scoring::correct_letter(self)
129
+ end
130
+
131
+ @guesses << str
132
+ end
133
+
134
+ return true
135
+ end
136
+
137
+ def over?
138
+ won? || lost? || @canceled
139
+ end
140
+
141
+ def won?
142
+ (letters - @guesses).empty? || @solved
143
+ end
144
+
145
+ def lost?
146
+ @health.zero?
147
+ end
148
+
149
+ def punish
150
+ @health -= 1
151
+ end
152
+
153
+ def cancel
154
+ @canceled = true
155
+ end
156
+ end
157
+
158
+ class GameManager
159
+ def initialize
160
+ @games = {}
161
+ end
162
+
163
+ def current(target)
164
+ game = all(target).last
165
+ game if game && !game.over?
166
+ end
167
+
168
+ def all(target)
169
+ @games[target] || @games[target] = []
170
+ end
171
+
172
+ def previous(target)
173
+ all(target).select { |game| game.over? }.last
174
+ end
175
+
176
+ def new(game)
177
+ all(game.channel) << game
178
+ end
179
+ end
180
+
181
+ define_structure :HangmanPlayerStats, :played, :score
182
+ define_structure :HangmanPrivateStats, :played, :score
183
+
184
+ class StatsHandler
185
+ def initialize(registry)
186
+ @registry = registry
187
+ end
188
+
189
+ def save_gamestats(game)
190
+ target = game.channel
191
+
192
+ if target.is_a?(User)
193
+ stats = priv_reg[target]
194
+ stats.played += 1
195
+ stats.score += game.scores.values.last.round
196
+ priv_reg[target] = stats
197
+ elsif target.is_a?(Channel)
198
+ stats = chan_stats(target)
199
+ stats['played'] += 1
200
+
201
+ reg = player_stats(target)
202
+ game.scores.each do |user, score|
203
+ pstats = reg[user]
204
+ pstats.played += 1
205
+ pstats.score += score.round
206
+ reg[user] = pstats
207
+ end
208
+ end
209
+ end
210
+
211
+ def player_stats(channel)
212
+ reg = chan_reg(channel).sub_registry('player')
213
+ reg.set_default(HangmanPlayerStats.new(0,0))
214
+ reg
215
+ end
216
+
217
+ def chan_stats(channel)
218
+ reg = chan_reg(channel).sub_registry('stats')
219
+ reg.set_default(0)
220
+ reg
221
+ end
222
+
223
+ def chan_reg(channel)
224
+ @registry.sub_registry(channel.downcase)
225
+ end
226
+
227
+ def priv_reg
228
+ reg = @registry.sub_registry('private')
229
+ reg.set_default(HangmanPrivateStats.new(0,0))
230
+ reg
231
+ end
232
+ end
233
+
234
+ class HangmanPlugin < Plugin
235
+ def initialize
236
+ super
237
+ @games = GameManager.new
238
+ @stats = StatsHandler.new(@registry)
239
+ @settings = {}
240
+ end
241
+
242
+ def help(plugin, topic="")
243
+ case topic
244
+ when ""
245
+ return _("hangman game plugin - topics: play, stop")
246
+ when "play"
247
+ return [_("hangman play on <channel> with word <word> => use in private chat with the bot to start a game with custom word\n"),
248
+ _("hangman play random [with [max|min] length [<|>|== <length>]] => hangman with a random word from %{site}\n"),
249
+ _("hangman play with wordlist <wordlist> => hangman with random word from <wordlist>")].join % { :site => RandomWord::SITE }
250
+ when "stop"
251
+ return _("hangman stop => quits the current game")
252
+ when "define"
253
+ return _("define => seeks a definition for the previous answer using google")
254
+ end
255
+ end
256
+
257
+ def get_word(params)
258
+ if params[:word]
259
+ params[:word].join(" ")
260
+ elsif params[:wordlist]
261
+ begin
262
+ wordlist = Wordlist.get(params[:wordlist].join("/"), :spaces => true)
263
+ rescue
264
+ raise _("no such wordlist")
265
+ end
266
+
267
+ wordlist[rand(wordlist.size)]
268
+ else # getting a random word
269
+ words = RandomWord::get(100)
270
+
271
+ if adj = params[:adj]
272
+ words = words.sort_by { |e| e.size }
273
+
274
+ if adj == "max"
275
+ words.last
276
+ else
277
+ words.first
278
+ end
279
+ elsif params[:relation] && params[:size]
280
+ words = words.select { |w| w.size.send(params[:relation], params[:size].to_i) }
281
+
282
+ unless words.empty?
283
+ words.first
284
+ else
285
+ m.reply _("suitable word not found in the set")
286
+ nil
287
+ end
288
+ else
289
+ words.first
290
+ end
291
+ end
292
+ end
293
+
294
+ def start(m, params)
295
+ begin
296
+ word = get_word(params) || return
297
+ rescue => e
298
+ m.reply e.message
299
+ return
300
+ end
301
+
302
+ if params[:channel] || m.public?
303
+ target = if m.public?
304
+ m.channel
305
+ else
306
+ params[:channel]
307
+ end
308
+
309
+ # is the bot on the channel?
310
+ unless @bot.server.channels.names.include?(target.to_s)
311
+ m.reply _("i'm not on that channel")
312
+ return
313
+ end
314
+
315
+ if @games.current(target)
316
+ m.reply _("there's already a hangman game in progress on the channel")
317
+ return
318
+ end
319
+
320
+ @bot.say target, _("%{nick} has started a hangman -- join the fun!") % {
321
+ :nick => m.source
322
+ }
323
+ else
324
+ target = m.source
325
+ end
326
+
327
+ game = Hangman.new(word)
328
+
329
+ class << game = Hangman.new(word)
330
+ attr_accessor :channel
331
+ end
332
+
333
+ game.channel = target
334
+
335
+ @games.new(game)
336
+ @settings[target] = params
337
+
338
+ @bot.say target, game_status(@games.current(target))
339
+ end
340
+
341
+ def stop(m, params)
342
+ target = m.replyto
343
+ if game = @games.current(target)
344
+ @bot.say target, _("oh well, the answer would've been %{answer}") % {
345
+ :answer => Bold + game.word + Bold
346
+ }
347
+
348
+ game.cancel
349
+ @stats.save_gamestats(game)
350
+ else
351
+ @bot.say target, _("no ongoing game")
352
+ end
353
+ end
354
+
355
+ def message(m)
356
+ target = m.replyto
357
+
358
+ if game = @games.current(target)
359
+ return unless m.message =~ /^[^\W0-9_]$/u || m.message =~ prepare_guess_regex(game)
360
+
361
+ if game.guess(m.source, m.message)
362
+ m.reply game_status(game)
363
+ else
364
+ return
365
+ end
366
+
367
+ if game.over?
368
+ sentence = if game.won?
369
+ _("you nailed it!")
370
+ elsif game.lost?
371
+ _("you've killed the poor guy :(")
372
+ end
373
+
374
+ again = _("go %{b}again%{b}?") % { :b => Bold }
375
+
376
+ scores = []
377
+ game.scores.each do |user, score|
378
+ str = "#{user.nick}: "
379
+ str << if score > 0
380
+ Irc.color(:green)+'+'
381
+ elsif score < 0
382
+ Irc.color(:brown)
383
+ end.to_s
384
+
385
+ str << score.round.to_s
386
+ str << Irc.color
387
+
388
+ scores << str
389
+ end
390
+
391
+ m.reply _("%{sentence} %{again} %{scores}") % {
392
+ :sentence => sentence, :again => again, :scores => scores.join(' ')
393
+ }, :nick => true
394
+
395
+ if rand(5).zero?
396
+ m.reply _("wondering what that means? try ´%{prefix}define´") % {
397
+ :prefix => @bot.config['core.address_prefix']
398
+ }
399
+ end
400
+
401
+ @stats.save_gamestats(game)
402
+ end
403
+ elsif @settings[target] && m.message =~ /^(?:again|more!?$)/i
404
+ start(m, @settings[target])
405
+ end
406
+ end
407
+
408
+ def prepare_guess_regex(game)
409
+ Regexp.new("^#{game.characters.map { |c|
410
+ game.guesses.include?(c) || c !~ Hangman::LETTER ? c : '[^\W0-9_]'
411
+ }.join("")}$")
412
+ end
413
+
414
+ def game_status(game)
415
+ str = "%{word} %{face}" % {
416
+ :word => game.over? ? "#{Bold}#{game.word}#{Bold}" : game.to_s,
417
+ :face => game.face,
418
+ :misses => game.misses.map { |e| e.upcase }.join(" ")
419
+ }
420
+
421
+ str << " %{misses}" % {
422
+ :misses => game.misses.map { |e| e.upcase }.join(" ")
423
+ } unless game.misses.empty?
424
+
425
+ str
426
+ end
427
+
428
+ def score(m, params)
429
+ target = m.replyto
430
+
431
+ unless params[:nick]
432
+ stats = if m.private?
433
+ @stats.priv_reg[target]
434
+ else
435
+ @stats.player_stats(target)[m.source]
436
+ end
437
+
438
+ unless stats.played.zero?
439
+ m.reply _("you got %{score} points after %{games} games") % {
440
+ :score => stats.score.round,
441
+ :games => stats.played
442
+ }
443
+ else
444
+ m.reply _("you haven't played hangman, how about playing it right now? :)")
445
+ end
446
+ else
447
+ return unless m.public?
448
+
449
+ nick = params[:nick]
450
+ stats = @stats.player_stats(target)[nick]
451
+
452
+ unless stats.played.zero?
453
+ m.reply _("%{nick} has %{score} points after %{games} games") % {
454
+ :nick => nick,
455
+ :score => stats.score.round,
456
+ :games => stats.played
457
+ }
458
+ else
459
+ m.reply _("%{nick} hasn't played hangman :(") % {
460
+ :nick => nick
461
+ }
462
+ end
463
+ end
464
+ end
465
+
466
+ def stats(m, params)
467
+ target = m.replyto
468
+ stats = @stats.chan_stats(target)
469
+
470
+ if m.public?
471
+ m.reply _("%{games} games have been played on %{channel}") % {
472
+ :games => stats['played'],
473
+ :channel => target.to_s
474
+ }
475
+ else
476
+ score(m, params)
477
+ end
478
+ end
479
+
480
+ def define(m, params)
481
+ if game = @games.previous(m.replyto)
482
+ return unless res = Google.define(game.word)
483
+ m.reply "#{Bold}#{game.word}#{Bold} -- #{res}"
484
+ end
485
+ end
486
+ end
487
+
488
+ plugin = HangmanPlugin.new
489
+ plugin.map "hangman [play] with wordlist *wordlist", :action => 'start'
490
+ plugin.map "hangman [play] on :channel with word *word", :action => 'start'
491
+ plugin.map "hangman [play] [random] [with [:adj] length [:relation :size]]",
492
+ :action => 'start',
493
+ :requirements => { :adj => /min|max/, :relation => /<|<=|>=|>|==/, :size => /\d+/ }
494
+
495
+ plugin.map "hangman stop", :action => 'stop'
496
+
497
+ plugin.map "hangman score [:nick]", :action => 'score'
498
+ plugin.map "hangman stats", :action => 'stats'
499
+ plugin.map "define", :action => 'define'