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
@@ -5,100 +5,7 @@ class RemindPlugin < Plugin
5
5
  #
6
6
  # Throws:: RunTimeError "invalid time string" on parse failure
7
7
  def timestr_offset(timestr)
8
- case timestr
9
- when (/^(\S+)\s+(\S+)$/)
10
- mult = $1
11
- unit = $2
12
- if(mult =~ /^([\d.]+)$/)
13
- num = $1.to_f
14
- raise "invalid time string" unless num
15
- else
16
- case mult
17
- when(/^(one|an|a)$/)
18
- num = 1
19
- when(/^two$/)
20
- num = 2
21
- when(/^three$/)
22
- num = 3
23
- when(/^four$/)
24
- num = 4
25
- when(/^five$/)
26
- num = 5
27
- when(/^six$/)
28
- num = 6
29
- when(/^seven$/)
30
- num = 7
31
- when(/^eight$/)
32
- num = 8
33
- when(/^nine$/)
34
- num = 9
35
- when(/^ten$/)
36
- num = 10
37
- when(/^fifteen$/)
38
- num = 15
39
- when(/^twenty$/)
40
- num = 20
41
- when(/^thirty$/)
42
- num = 30
43
- when(/^sixty$/)
44
- num = 60
45
- else
46
- raise "invalid time string"
47
- end
48
- end
49
- case unit
50
- when (/^(s|sec(ond)?s?)$/)
51
- return num
52
- when (/^(m|min(ute)?s?)$/)
53
- return num * 60
54
- when (/^(h|h(ou)?rs?)$/)
55
- return num * 60 * 60
56
- when (/^(d|days?)$/)
57
- return num * 60 * 60 * 24
58
- else
59
- raise "invalid time string"
60
- end
61
- when (/^(\d+):(\d+):(\d+)$/)
62
- hour = $1.to_i
63
- min = $2.to_i
64
- sec = $3.to_i
65
- now = Time.now
66
- later = Time.mktime(now.year, now.month, now.day, hour, min, sec)
67
- return later - now
68
- when (/^(\d+):(\d+)$/)
69
- hour = $1.to_i
70
- min = $2.to_i
71
- now = Time.now
72
- later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
73
- return later - now
74
- when (/^(\d+):(\d+)(am|pm)$/)
75
- hour = $1.to_i
76
- min = $2.to_i
77
- ampm = $3
78
- if ampm == "pm"
79
- hour += 12
80
- end
81
- now = Time.now
82
- later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
83
- return later - now
84
- when (/^(\S+)$/)
85
- num = 1
86
- unit = $1
87
- case unit
88
- when (/^(s|sec(ond)?s?)$/)
89
- return num
90
- when (/^(m|min(ute)?s?)$/)
91
- return num * 60
92
- when (/^(h|h(ou)?rs?)$/)
93
- return num * 60 * 60
94
- when (/^(d|days?)$/)
95
- return num * 60 * 60 * 24
96
- else
97
- raise "invalid time string"
98
- end
99
- else
100
- raise "invalid time string"
101
- end
8
+ Utils.parse_time_offset(timestr)
102
9
  end
103
10
 
104
11
  def initialize
@@ -202,11 +109,11 @@ class RemindPlugin < Plugin
202
109
  end
203
110
  def no_more(m, params)
204
111
  who = params.has_key?(:who) ? params[:who] : m.sourcenick
205
- deleted = params.has_key?(:string) ?
112
+ deleted = params.has_key?(:string) ?
206
113
  del_reminder(who, params[:string].to_s) : del_reminder(who)
207
114
  if deleted
208
115
  m.okay
209
- else
116
+ else
210
117
  m.reply "but I wasn't going to :/"
211
118
  end
212
119
  end
@@ -52,7 +52,7 @@ class RiPlugin < Plugin
52
52
  return m.reply("failed to execute ri")
53
53
  end
54
54
  ret = ret.gsub(/\t/, " ").split(/\n/).join(" ").gsub(/\s\s+/, ' ')
55
-
55
+
56
56
  if ret.length > @bot.config['ri.max_length']
57
57
  if !m.private? && tgt.to_s != m.sourcenick
58
58
  return m.reply('entry is too long to send to the channel or to some other user, use /msg to ask me about it')
@@ -18,7 +18,7 @@ class RotPlugin < Plugin
18
18
  end
19
19
 
20
20
  def rot13(m, params)
21
- m.reply @bot.filter(:rot13, params[:string].to_s)
21
+ m.reply @bot.filter(:rot13, params[:string].to_s).to_s
22
22
  end
23
23
  end
24
24
  plugin = RotPlugin.new
@@ -16,7 +16,7 @@
16
16
 
17
17
  require 'rss'
18
18
 
19
- # Try to load rss/content/2.0 so we can access the data in <content:encoded>
19
+ # Try to load rss/content/2.0 so we can access the data in <content:encoded>
20
20
  # tags.
21
21
  begin
22
22
  require 'rss/content/2.0'
@@ -150,12 +150,50 @@ module ::RSS
150
150
 
151
151
  SlashModel::ELEMENTS.collect! {|name| "#{SLASH_PREFIX}_#{name}"}
152
152
  end
153
+
154
+ class Element
155
+ class << self
156
+ def def_bang(name, chain)
157
+ class_eval %<
158
+ def #{name}!
159
+ blank2nil { #{chain.join(' rescue ')} rescue nil }
160
+ end
161
+ >, *get_file_and_line_from_caller(0)
162
+ end
163
+ end
164
+
165
+ {
166
+ :link => %w{link.href link},
167
+ :guid => %w{guid.content guid},
168
+ :content => %w{content.content content},
169
+ :description => %w{description.content description},
170
+ :title => %w{title.content title},
171
+ :category => %w{category.content category},
172
+ :dc_subject => %w{dc_subject},
173
+ :author => %w{author.name.content author.name author},
174
+ :dc_creator => %w{dc_creator}
175
+ }.each { |name, chain| def_bang name, chain }
176
+
177
+ def categories!
178
+ return nil unless self.respond_to? :categories
179
+ cats = categories.map do |c|
180
+ blank2nil { c.content rescue c rescue nil }
181
+ end.compact
182
+ cats.empty? ? nil : cats
183
+ end
184
+
185
+ protected
186
+ def blank2nil(&block)
187
+ x = yield
188
+ (x && !x.empty?) ? x : nil
189
+ end
190
+ end
153
191
  end
154
192
 
155
193
 
156
194
  class ::RssBlob
157
195
  attr_accessor :url, :handle, :type, :refresh_rate, :xml, :title, :items,
158
- :mutex, :watchers, :last_fetched
196
+ :mutex, :watchers, :last_fetched, :http_cache, :last_success
159
197
 
160
198
  def initialize(url,handle=nil,type=nil,watchers=[], xml=nil, lf = nil)
161
199
  @url = url
@@ -167,11 +205,13 @@ class ::RssBlob
167
205
  @type = type
168
206
  @watchers=[]
169
207
  @refresh_rate = nil
208
+ @http_cache = false
170
209
  @xml = xml
171
210
  @title = nil
172
211
  @items = nil
173
212
  @mutex = Mutex.new
174
213
  @last_fetched = lf
214
+ @last_success = nil
175
215
  sanitize_watchers(watchers)
176
216
  end
177
217
 
@@ -246,6 +286,14 @@ class RSSFeedsPlugin < Plugin
246
286
  :default => 300, :validate => Proc.new{|v| v > 30},
247
287
  :desc => "How many seconds to sleep before checking RSS feeds again")
248
288
 
289
+ Config.register Config::IntegerValue.new('rss.announce_timeout',
290
+ :default => 0,
291
+ :desc => "Don't announce watched feed if these many seconds elapsed since the last successful update")
292
+
293
+ Config.register Config::IntegerValue.new('rss.announce_max',
294
+ :default => 3,
295
+ :desc => "Maximum number of new items to announce when a watched feed is updated")
296
+
249
297
  Config.register Config::BooleanValue.new('rss.show_updated',
250
298
  :default => true,
251
299
  :desc => "Whether feed items for which the description was changed should be shown as new")
@@ -254,32 +302,20 @@ class RSSFeedsPlugin < Plugin
254
302
  :default => true,
255
303
  :desc => "Whether to display links from the text of a feed item.")
256
304
 
305
+ Config.register Config::EnumValue.new('rss.announce_method',
306
+ :values => ['say', 'notice'],
307
+ :default => 'say',
308
+ :desc => "Whether to display links from the text of a feed item.")
309
+
257
310
  # Make an 'unique' ID for a given item, based on appropriate bot options
258
311
  # Currently only suppored is bot.config['rss.show_updated']: when false,
259
312
  # only the guid/link is accounted for.
260
-
261
- def block_rescue(df = nil, &block)
262
- v = block.call rescue nil
263
- (String === v && '' != v) ? v : nil
264
- end
265
313
 
266
314
  def make_uid(item)
267
- uid = [
268
- (block_rescue do item.guid.content end ||
269
- block_rescue do item.guid end ||
270
- block_rescue do item.link.href end ||
271
- block_rescue do item.link end
272
- )
273
- ]
315
+ uid = [item.guid! || item.link!]
274
316
  if @bot.config['rss.show_updated']
275
- uid.push(
276
- block_rescue do item.content.content end ||
277
- block_rescue do item.description end
278
- )
279
- uid.unshift(
280
- block_rescue do item.title.content end ||
281
- block_rescue do item.title end
282
- )
317
+ uid.push(item.content! || item.description!)
318
+ uid.unshift item.title!
283
319
  end
284
320
  # debug "taking hash of #{uid.inspect}"
285
321
  uid.hash
@@ -287,7 +323,7 @@ class RSSFeedsPlugin < Plugin
287
323
 
288
324
 
289
325
  # We used to save the Mutex with the RssBlob, which was idiotic. And
290
- # since Mutexes dumped in one version might not be resotrable in another,
326
+ # since Mutexes dumped in one version might not be restorable in another,
291
327
  # we need a few tricks to be able to restore data from other versions of Ruby
292
328
  #
293
329
  # When migrating 1.8.6 => 1.8.5, all we need to do is define an empty
@@ -303,88 +339,98 @@ class RSSFeedsPlugin < Plugin
303
339
 
304
340
  # Auxiliary method used to collect two lines for rss output filters,
305
341
  # running substitutions against DataStream _s_ optionally joined
306
- # with hash _h_
342
+ # with hash _h_.
343
+ #
344
+ # For substitutions, *_wrap keys can be used to alter the content of
345
+ # other nonempty keys. If the value of *_wrap is a String, it will be
346
+ # put before and after the corresponding key; if it's an Array, the first
347
+ # and second elements will be used for wrapping; if it's nil, no wrapping
348
+ # will be done (useful to override a default wrapping).
349
+ #
350
+ # For example:
351
+ # :handle_wrap => '::'::
352
+ # will wrap s[:handle] by prefixing and postfixing it with '::'
353
+ # :date_wrap => [nil, ' :: ']::
354
+ # will put ' :: ' after s[:date]
307
355
  def make_stream(line1, line2, s, h={})
308
356
  ss = s.merge(h)
309
- DataStream.new([line1, line2].compact.join("\n") % ss, ss)
357
+ subs = {}
358
+ wraps = {}
359
+ ss.each do |k, v|
360
+ kk = k.to_s.chomp!('_wrap')
361
+ if kk
362
+ nk = kk.intern
363
+ case v
364
+ when String
365
+ wraps[nk] = ss[nk].wrap_nonempty(v, v)
366
+ when Array
367
+ wraps[nk] = ss[nk].wrap_nonempty(*v)
368
+ when nil
369
+ # do nothing
370
+ else
371
+ warning "ignoring #{v.inspect} wrapping of unknown class"
372
+ end
373
+ else
374
+ subs[k] = v
375
+ end
376
+ end
377
+ subs.merge! wraps
378
+ DataStream.new([line1, line2].compact.join("\n") % subs, ss)
379
+ end
380
+
381
+ # Auxiliary method used to define rss output filters
382
+ def rss_type(key, &block)
383
+ @bot.register_filter(key, @outkey, &block)
310
384
  end
311
385
 
312
- # Define default RSS filters
386
+ # Define default output filters (rss types), and load custom ones.
387
+ # Custom filters are looked for in the plugin's default filter locations
388
+ # and in rss/types.rb under botclass.
389
+ # Preferably, the rss_type method should be used in these files, e.g.:
390
+ # rss_type :my_type do |s|
391
+ # line1 = "%{handle} and some %{author} info"
392
+ # make_stream(line1, nil, s)
393
+ # end
394
+ # to define the new type 'my_type'. The keys available in the DataStream
395
+ # are:
396
+ # item::
397
+ # the actual rss item
398
+ # handle::
399
+ # the item handle
400
+ # date::
401
+ # the item date
402
+ # title::
403
+ # the item title
404
+ # desc, link, category, author::
405
+ # the item description, link, category, author
406
+ # at::
407
+ # the string ' @ ' if the item has both an title and a link
408
+ # handle_wrap, date_wrap, title_wrap, ...::
409
+ # these keys can be defined to wrap the corresponding elements if they
410
+ # are nonempty. By default handle is wrapped with '::', date has a ' ::'
411
+ # appended and title is enbolden
313
412
  #
314
- # TODO: load personal ones
315
413
  def define_filters
316
- @outkey = :"rss.out"
317
- @bot.register_filter(:headlines, @outkey) { |s|
318
- line1 = (s[:handle].empty? ? "%{date}" : "%{handle}") << "%{title}"
319
- make_stream(line1, nil, s)
320
- }
321
- @bot.register_filter(:blog, @outkey) { |s|
322
- author = s[:author] ? (s[:author] + " ") : ""
323
- abt = s[:category] ? "about #{s[:category]} " : ""
324
- line1 = "%{handle}%{date}%{author}blogged %{abt}at %{link}"
325
- line2 = "%{handle}%{title} - %{desc}"
326
- make_stream(line1, line2, s, :author => author, :abt => abt)
327
- }
328
- @bot.register_filter(:photoblog, @outkey) { |s|
329
- author = s[:author] ? (s[:author] + " ") : ""
330
- abt = s[:category] ? "under #{s[:category]} " : ""
331
- line1 = "%{handle}%{date}%{author}added an image %{abt}at %{link}"
332
- line2 = "%{handle}%{title} - %{desc}"
333
- make_stream(line1, line2, s, :author => author, :abt => abt)
334
- }
335
- @bot.register_filter(:news, @outkey) { |s|
336
- line1 = "%{handle}%{date}%{title}%{at}%{link}" % s
337
- line2 = "%{handle}%{date}%{desc}" % s
338
- make_stream(line1, line2, s)
339
- }
340
- @bot.register_filter(:git, @outkey) { |s|
341
- author = s[:author] ? (s[:author] + " ") : ""
342
- line1 = "%{handle}%{date}%{author}committed %{title}%{at}%{link}"
343
- make_stream(line1, nil, s, :author => author)
344
- }
345
- @bot.register_filter(:forum, @outkey) { |s|
346
- line1 = "%{handle}%{date}%{title}%{at}%{link}"
347
- make_stream(line1, nil, s)
348
- }
349
- @bot.register_filter(:wiki, @outkey) { |s|
350
- line1 = "%{handle}%{date}%{title}%{at}%{link}"
351
- line1 << "has been edited by %{author}. %{desc}"
352
- make_stream(line1, nil, s)
353
- }
354
- @bot.register_filter(:gmane, @outkey) { |s|
355
- line1 = "%{handle}%{date}Message %{title} sent by %{author}. %{desc}"
356
- make_stream(line1, nil, s)
357
- }
358
- @bot.register_filter(:trac, @outkey) { |s|
359
- author = s[:author].sub(/@\S+?\s*>/, "@...>") + ": " if s[:author]
360
- line1 = "%{handle}%{date}%{author}%{title}%{at}%{link}"
361
- line2 = nil
362
- unless s[:item].title =~ /^(?:Changeset \[(?:[\da-f]+)\]|\(git commit\))/
363
- line2 = "%{handle}%{date}%{desc}"
364
- end
365
- make_stream(line1, line2, s, :author => author)
366
- }
367
- @bot.register_filter(:"/.", @outkey) { |s|
368
- dept = "(from the #{s[:item].slash_department} dept) " rescue nil
369
- sec = " in section #{s[:item].slash_section}" rescue nil
370
- line1 = "%{handle}%{date}%{dept}%{title}%{at}%{link} "
371
- line1 << "(posted by %{author}%{sec})"
372
- make_stream(line1, nil, s, :dept => dept, :sec => sec)
373
- }
374
- @bot.register_filter(:default, @outkey) { |s|
375
- line1 = "%{handle}%{date}%{title}%{at}%{link}"
376
- line1 << " (by %{author})" if s[:author]
377
- make_stream(line1, nil, s)
378
- }
414
+ @outkey ||= :"rss.out"
379
415
 
380
- # Define an HTML info filter too
416
+ # Define an HTML info filter
381
417
  @bot.register_filter(:rss, :htmlinfo) { |s| htmlinfo_filter(s) }
382
-
383
418
  # This is the output format used by the input filter
384
- @bot.register_filter(:htmlinfo, @outkey) { |s|
419
+ rss_type :htmlinfo do |s|
385
420
  line1 = "%{title}%{at}%{link}"
386
421
  make_stream(line1, nil, s)
387
- }
422
+ end
423
+
424
+ # the default filter
425
+ rss_type :default do |s|
426
+ line1 = "%{handle}%{date}%{title}%{at}%{link}"
427
+ line1 << " (by %{author})" if s[:author]
428
+ make_stream(line1, nil, s)
429
+ end
430
+
431
+ @user_types ||= datafile 'types.rb'
432
+ load_filters
433
+ load_filters :path => @user_types
388
434
  end
389
435
 
390
436
  FEED_NS = %r{xmlns.*http://(purl\.org/rss|www.w3c.org/1999/02/22-rdf)}
@@ -445,7 +491,7 @@ class RSSFeedsPlugin < Plugin
445
491
  }
446
492
 
447
493
  @feeds = @registry[:feeds]
448
- raise unless @feeds
494
+ raise LoadError, "corrupted feed database" unless @feeds
449
495
 
450
496
  @registry.recovery = nil
451
497
 
@@ -535,7 +581,7 @@ class RSSFeedsPlugin < Plugin
535
581
  when "rewatch"
536
582
  "rss rewatch : restart threads that watch for changes in watched rss"
537
583
  when "types"
538
- "rss types : show the rss types for which an output format existi (all other types will use the default one)"
584
+ "rss types : show the rss types for which an output format exist (all other types will use the default one)"
539
585
  else
540
586
  "manage RSS feeds: rss types|show|list|watched|add|change|del(ete)|rm|(force)replace|watch|unwatch|rmwatch|rewatch|who watches"
541
587
  end
@@ -599,7 +645,7 @@ class RSSFeedsPlugin < Plugin
599
645
  parsed = parseRss(feed, m)
600
646
  end
601
647
  return unless feed.items
602
- m.reply "using old data" unless fetched and parsed
648
+ m.reply "using old data" unless fetched and parsed and parsed > 0
603
649
 
604
650
  title = feed.title
605
651
  items = feed.items
@@ -611,7 +657,12 @@ class RSSFeedsPlugin < Plugin
611
657
 
612
658
  m.reply "Channel : #{title}"
613
659
  disp.each do |item|
614
- printFormattedRss(feed, item, {:places=>[m.replyto],:handle=>nil,:date=>true})
660
+ printFormattedRss(feed, item, {
661
+ :places => [m.replyto],
662
+ :handle => nil,
663
+ :date => true,
664
+ :announce_method => :say
665
+ })
615
666
  end
616
667
  end
617
668
 
@@ -630,19 +681,34 @@ class RSSFeedsPlugin < Plugin
630
681
 
631
682
  def list_rss(m, params)
632
683
  wanted = params[:handle]
633
- reply = String.new
634
- @feeds.each { |handle, feed|
635
- next if wanted and !handle.match(/#{wanted}/i)
636
- reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})"
637
- (reply << " refreshing every #{Utils.secs_to_string(feed.refresh_rate)}") if feed.refresh_rate
638
- (reply << " (watched)") if feed.watched_by?(m.replyto)
639
- reply << "\n"
640
- }
641
- if reply.empty?
684
+ listed = @feeds.keys
685
+ if wanted
686
+ wanted_rx = Regexp.new(wanted, true)
687
+ listed.reject! { |handle| !handle.match(wanted_rx) }
688
+ end
689
+ listed.sort!
690
+ debug listed
691
+ if @bot.config['send.max_lines'] > 0 and listed.size > @bot.config['send.max_lines']
692
+ reply = listed.inject([]) do |ar, handle|
693
+ feed = @feeds[handle]
694
+ string = handle.dup
695
+ (string << " (#{feed.type})") if feed.type
696
+ (string << " (watched)") if feed.watched_by?(m.replyto)
697
+ ar << string
698
+ end.join(', ')
699
+ elsif listed.size > 0
700
+ reply = listed.inject([]) do |ar, handle|
701
+ feed = @feeds[handle]
702
+ string = "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})"
703
+ (string << " refreshing every #{Utils.secs_to_string(feed.refresh_rate)}") if feed.refresh_rate
704
+ (string << " (watched)") if feed.watched_by?(m.replyto)
705
+ ar << string
706
+ end.join("\n")
707
+ else
642
708
  reply = "no feeds found"
643
709
  reply << " matching #{wanted}" if wanted
644
710
  end
645
- m.reply reply, :max_lines => reply.length
711
+ m.reply reply, :max_lines => 0
646
712
  end
647
713
 
648
714
  def watched_rss(m, params)
@@ -823,6 +889,7 @@ class RSSFeedsPlugin < Plugin
823
889
  if params and handle = params[:handle]
824
890
  feed = @feeds.fetch(handle.downcase, nil)
825
891
  if feed
892
+ feed.http_cache = false
826
893
  @bot.timer.reschedule(@watch[feed.handle], (params[:delay] || 0).to_f)
827
894
  m.okay if m
828
895
  else
@@ -842,7 +909,7 @@ class RSSFeedsPlugin < Plugin
842
909
  private
843
910
  def watchRss(feed, m=nil)
844
911
  if @watch.has_key?(feed.handle)
845
- report_problem("watcher thread for #{feed.handle} is already running", nil, m)
912
+ # report_problem("watcher thread for #{feed.handle} is already running", nil, m)
846
913
  return
847
914
  end
848
915
  status = Hash.new
@@ -858,11 +925,18 @@ class RSSFeedsPlugin < Plugin
858
925
  failures = status[:failures]
859
926
  begin
860
927
  debug "fetching #{feed}"
861
- first_run = !feed.last_fetched
928
+
929
+ first_run = !feed.last_success
930
+ if (@bot.config['rss.announce_timeout'] > 0 &&
931
+ (Time.now - feed.last_success > @bot.config['rss.announce_timeout']))
932
+ debug "#{feed} wasn't polled for too long, supressing output"
933
+ first_run = true
934
+ end
862
935
  oldxml = feed.xml ? feed.xml.dup : nil
863
- unless fetchRss(feed)
936
+ unless fetchRss(feed, nil, feed.http_cache)
864
937
  failures += 1
865
938
  else
939
+ feed.http_cache = true
866
940
  if first_run
867
941
  debug "first run for #{feed}, getting items"
868
942
  parseRss(feed)
@@ -870,14 +944,12 @@ class RSSFeedsPlugin < Plugin
870
944
  debug "xml for #{feed} didn't change"
871
945
  failures -= 1 if failures > 0
872
946
  else
873
- if not feed.items
874
- debug "no previous items in feed #{feed}"
875
- parseRss(feed)
876
- failures -= 1 if failures > 0
877
- else
878
- # This one is used for debugging
879
- otxt = []
947
+ # This one is used for debugging
948
+ otxt = []
880
949
 
950
+ if feed.items.nil?
951
+ oids = []
952
+ else
881
953
  # These are used for checking new items vs old ones
882
954
  oids = Set.new feed.items.map { |item|
883
955
  uid = make_uid item
@@ -886,10 +958,13 @@ class RSSFeedsPlugin < Plugin
886
958
  debug [uid, otxt.last].inspect
887
959
  uid
888
960
  }
961
+ end
889
962
 
890
- unless parseRss(feed)
891
- debug "no items in feed #{feed}"
963
+ nitems = parseRss(feed)
964
+ if nitems.nil?
892
965
  failures += 1
966
+ elsif nitems == 0
967
+ debug "no items in feed #{feed}"
893
968
  else
894
969
  debug "Checking if new items are available for #{feed}"
895
970
  failures -= 1 if failures > 0
@@ -914,7 +989,19 @@ class RSSFeedsPlugin < Plugin
914
989
  }
915
990
 
916
991
  if dispItems.length > 0
992
+ max = @bot.config['rss.announce_max']
917
993
  debug "Found #{dispItems.length} new items in #{feed}"
994
+ if max > 0 and dispItems.length > max
995
+ debug "showing only the latest #{dispItems.length}"
996
+ feed.watchers.each do |loc|
997
+ @bot.say loc, (_("feed %{feed} had %{num} updates, showing the latest %{max}") % {
998
+ :feed => feed.handle,
999
+ :num => dispItems.length,
1000
+ :max => max
1001
+ })
1002
+ end
1003
+ dispItems.slice!(max..-1)
1004
+ end
918
1005
  # When displaying watched feeds, publish them from older to newer
919
1006
  dispItems.reverse.each { |item|
920
1007
  printFormattedRss(feed, item)
@@ -923,7 +1010,6 @@ class RSSFeedsPlugin < Plugin
923
1010
  debug "No new items found in #{feed}"
924
1011
  end
925
1012
  end
926
- end
927
1013
  end
928
1014
  end
929
1015
  rescue Exception => e
@@ -955,50 +1041,43 @@ class RSSFeedsPlugin < Plugin
955
1041
  return seconds
956
1042
  end
957
1043
 
958
- def select_nonempty(*ar)
959
- # debug ar
960
- ar.each { |i| return i unless i.nil_or_empty? }
961
- return nil
1044
+ def make_date(obj)
1045
+ if obj.kind_of? Time
1046
+ obj.strftime("%Y/%m/%d %H:%M")
1047
+ else
1048
+ obj.to_s
1049
+ end
962
1050
  end
963
1051
 
964
- def printFormattedRss(feed, item, opts=nil)
1052
+ def printFormattedRss(feed, item, options={})
965
1053
  # debug item
966
- places = feed.watchers
967
- handle = feed.handle.empty? ? "" : "::#{feed.handle}:: "
968
- date = String.new
969
- if opts
970
- places = opts[:places] if opts.key?(:places)
971
- handle = opts[:handle].to_s if opts.key?(:handle)
972
- if opts.key?(:date) && opts[:date]
973
- if item.respond_to?(:updated)
974
- if item.updated.content.class <= Time
975
- date = item.updated.content.strftime("%Y/%m/%d %H:%M")
976
- else
977
- date = item.updated.content.to_s
978
- end
979
- elsif item.respond_to?(:source) and item.source.respond_to?(:updated)
980
- if item.source.updated.content.class <= Time
981
- date = item.source.updated.content.strftime("%Y/%m/%d %H:%M")
982
- else
983
- date = item.source.updated.content.to_s
984
- end
985
- elsif item.respond_to?(:pubDate)
986
- if item.pubDate.class <= Time
987
- date = item.pubDate.strftime("%Y/%m/%d %H:%M")
988
- else
989
- date = item.pubDate.to_s
990
- end
991
- elsif item.respond_to?(:date)
992
- if item.date.class <= Time
993
- date = item.date.strftime("%Y/%m/%d %H:%M")
994
- else
995
- date = item.date.to_s
996
- end
997
- else
998
- date = "(no date)"
999
- end
1000
- date += " :: "
1054
+ opts = {
1055
+ :places => feed.watchers,
1056
+ :handle => feed.handle,
1057
+ :date => false,
1058
+ :announce_method => @bot.config['rss.announce_method']
1059
+ }.merge options
1060
+
1061
+ places = opts[:places]
1062
+ announce_method = opts[:announce_method]
1063
+
1064
+ handle = opts[:handle].to_s
1065
+
1066
+ date = \
1067
+ if opts[:date]
1068
+ if item.respond_to?(:updated)
1069
+ make_date(item.updated.content)
1070
+ elsif item.respond_to?(:source) and item.source.respond_to?(:updated)
1071
+ make_date(item.source.updated.content)
1072
+ elsif item.respond_to?(:pubDate)
1073
+ make_date(item.pubDate)
1074
+ elsif item.respond_to?(:date)
1075
+ make_date(item.date)
1076
+ else
1077
+ "(no date)"
1001
1078
  end
1079
+ else
1080
+ String.new
1002
1081
  end
1003
1082
 
1004
1083
  tit_opt = {}
@@ -1012,14 +1091,14 @@ class RSSFeedsPlugin < Plugin
1012
1091
  # visible in the URL anyway
1013
1092
  # TODO make this optional?
1014
1093
  base_title.sub!(/^Changeset \[([\da-f]{40})\]:/) { |c| "(git commit)"} if feed.type == 'trac'
1015
- title = "#{Bold}#{base_title.ircify_html(tit_opt)}#{Bold}"
1094
+ title = base_title.ircify_html(tit_opt)
1016
1095
  end
1017
1096
 
1018
1097
  desc_opt = {}
1019
1098
  desc_opt[:limit] = @bot.config['rss.text_max']
1020
1099
  desc_opt[:a_href] = :link_out if @bot.config['rss.show_links']
1021
1100
 
1022
- # We prefer content_encoded here as it tends to provide more html formatting
1101
+ # We prefer content_encoded here as it tends to provide more html formatting
1023
1102
  # for use with ircify_html.
1024
1103
  if item.respond_to?(:content_encoded) && item.content_encoded
1025
1104
  desc = item.content_encoded.ircify_html(desc_opt)
@@ -1038,12 +1117,13 @@ class RSSFeedsPlugin < Plugin
1038
1117
  desc = "(?)"
1039
1118
  end
1040
1119
 
1041
- link = item.link.href rescue item.link rescue nil
1120
+ link = item.link!
1042
1121
  link.strip! if link
1043
1122
 
1044
- category = select_nonempty((item.category.content rescue nil), (item.dc_subject rescue nil))
1123
+ categories = item.categories!
1124
+ category = item.category! || item.dc_subject!
1045
1125
  category.strip! if category
1046
- author = select_nonempty((item.author.name.content rescue nil), (item.dc_creator rescue nil), (item.author rescue nil))
1126
+ author = item.dc_creator! || item.author!
1047
1127
  author.strip! if author
1048
1128
 
1049
1129
  line1 = nil
@@ -1054,15 +1134,25 @@ class RSSFeedsPlugin < Plugin
1054
1134
  key = @bot.global_filter_name(feed.type, @outkey)
1055
1135
  key = @bot.global_filter_name(:default, @outkey) unless @bot.has_filter?(key)
1056
1136
 
1057
- output = @bot.filter(key, :item => item, :handle => handle, :date => date,
1058
- :title => title, :desc => desc, :link => link,
1059
- :category => category, :author => author, :at => at)
1137
+ stream_hash = {
1138
+ :item => item,
1139
+ :handle => handle,
1140
+ :handle_wrap => ['::', ':: '],
1141
+ :date => date,
1142
+ :date_wrap => [nil, ' :: '],
1143
+ :title => title,
1144
+ :title_wrap => Bold,
1145
+ :desc => desc, :link => link,
1146
+ :categories => categories,
1147
+ :category => category, :author => author, :at => at
1148
+ }
1149
+ output = @bot.filter(key, stream_hash)
1060
1150
 
1061
1151
  return output if places.empty?
1062
1152
 
1063
1153
  places.each { |loc|
1064
1154
  output.to_s.each_line { |line|
1065
- @bot.say loc, line, :overlong => :truncate
1155
+ @bot.__send__(announce_method, loc, line, :overlong => :truncate)
1066
1156
  }
1067
1157
  }
1068
1158
  end
@@ -1092,8 +1182,16 @@ class RSSFeedsPlugin < Plugin
1092
1182
  # reassign the 0.9 RDFs to 1.0, and hope it goes right.
1093
1183
  xml.gsub!("xmlns=\"http://my.netscape.com/rdf/simple/0.9/\"",
1094
1184
  "xmlns=\"http://purl.org/rss/1.0/\"")
1185
+ # make sure the parser doesn't double-convert in case the feed is not UTF-8
1186
+ xml.sub!(/<\?xml (.*?)\?>/) do |match|
1187
+ if /\bencoding=(['"])(.*?)\1/.match(match)
1188
+ match.sub!(/\bencoding=(['"])(?:.*?)\1/,'encoding="UTF-8"')
1189
+ end
1190
+ match
1191
+ end
1095
1192
  feed.mutex.synchronize do
1096
1193
  feed.xml = xml
1194
+ feed.last_success = Time.now
1097
1195
  end
1098
1196
  return true
1099
1197
  end
@@ -1102,25 +1200,33 @@ class RSSFeedsPlugin < Plugin
1102
1200
  return nil unless feed.xml
1103
1201
  feed.mutex.synchronize do
1104
1202
  xml = feed.xml
1105
- begin
1106
- ## do validate parse
1107
- rss = RSS::Parser.parse(xml)
1108
- debug "parsed and validated #{feed}"
1109
- rescue RSS::InvalidRSSError
1110
- ## do non validate parse for invalid RSS 1.0
1203
+ rss = nil
1204
+ errors = []
1205
+ RSS::AVAILABLE_PARSERS.each do |parser|
1111
1206
  begin
1112
- rss = RSS::Parser.parse(xml, false)
1113
- debug "parsed but not validated #{feed}"
1207
+ ## do validate parse
1208
+ rss = RSS::Parser.parse(xml, true, true, parser)
1209
+ debug "parsed and validated #{feed} with #{parser}"
1210
+ break
1211
+ rescue RSS::InvalidRSSError
1212
+ begin
1213
+ ## do non validate parse for invalid RSS 1.0
1214
+ rss = RSS::Parser.parse(xml, false, true, parser)
1215
+ debug "parsed but not validated #{feed} with #{parser}"
1216
+ break
1217
+ rescue RSS::Error => e
1218
+ errors << [parser, e, "parsing rss stream failed, whoops =("]
1219
+ end
1114
1220
  rescue RSS::Error => e
1115
- report_problem("parsing rss stream failed, whoops =(", e, m)
1116
- return nil
1221
+ errors << [parser, e, "parsing rss stream failed, oioi"]
1222
+ rescue => e
1223
+ errors << [parser, e, "processing error occured, sorry =("]
1117
1224
  end
1118
- rescue RSS::Error => e
1119
- report_problem("parsing rss stream failed, oioi", e, m)
1120
- return nil
1121
- rescue => e
1122
- report_problem("processing error occured, sorry =(", e, m)
1123
- return nil
1225
+ end
1226
+ unless errors.empty?
1227
+ debug errors
1228
+ self.send(:report_problem, errors.last[2], errors.last[1], m)
1229
+ return nil unless rss
1124
1230
  end
1125
1231
  items = []
1126
1232
  if rss.nil?
@@ -1151,11 +1257,11 @@ class RSSFeedsPlugin < Plugin
1151
1257
 
1152
1258
  if items.empty?
1153
1259
  report_problem("no items found in the feed, maybe try weed?", e, m)
1154
- return nil
1260
+ else
1261
+ feed.title = title.strip
1262
+ feed.items = items
1155
1263
  end
1156
- feed.title = title.strip
1157
- feed.items = items
1158
- return true
1264
+ return items.length
1159
1265
  end
1160
1266
  end
1161
1267
  end