rbot 0.9.10 → 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (706) hide show
  1. data/AUTHORS +22 -4
  2. data/COPYING +5 -18
  3. data/ChangeLog +396 -0
  4. data/README +23 -25
  5. data/REQUIREMENTS +58 -12
  6. data/Rakefile +215 -0
  7. data/bin/rbot +47 -25
  8. data/bin/rbot-remote +76 -0
  9. data/data/rbot/languages/french.lang +5 -5
  10. data/data/rbot/languages/german.lang +4 -4
  11. data/data/rbot/languages/italian.lang +53 -0
  12. data/data/rbot/languages/japanese.lang +43 -0
  13. data/data/rbot/languages/russian.lang +60 -60
  14. data/data/rbot/languages/traditional_chinese.lang +75 -0
  15. data/data/rbot/plugins/alias.rb +192 -0
  16. data/data/rbot/plugins/autoop.rb +74 -37
  17. data/data/rbot/plugins/autorejoin.rb +15 -5
  18. data/data/rbot/plugins/bans.rb +454 -150
  19. data/data/rbot/plugins/bash.rb +173 -31
  20. data/data/rbot/plugins/botsnack.rb +37 -0
  21. data/data/rbot/plugins/cal.rb +11 -2
  22. data/data/rbot/plugins/chanserv.rb +132 -0
  23. data/data/rbot/plugins/chucknorris.rb +2 -2
  24. data/data/rbot/plugins/chucknorris.yml.gz +0 -0
  25. data/data/rbot/plugins/debugger.rb +136 -0
  26. data/data/rbot/plugins/deepthoughts.rb +89 -95
  27. data/data/rbot/plugins/delicious.rb +109 -0
  28. data/data/rbot/plugins/dice.rb +77 -25
  29. data/data/rbot/plugins/dict.rb +243 -0
  30. data/data/rbot/plugins/dictclient.rb +211 -0
  31. data/data/rbot/plugins/digg.rb +2 -2
  32. data/data/rbot/plugins/excuse.rb +4 -4
  33. data/data/rbot/plugins/factoids.rb +506 -0
  34. data/data/rbot/plugins/figlet.rb +127 -18
  35. data/data/rbot/plugins/fish.rb +93 -33
  36. data/data/rbot/plugins/forecast.rb +40 -27
  37. data/data/rbot/plugins/fortune.rb +86 -11
  38. data/data/rbot/plugins/freshmeat.rb +46 -12
  39. data/data/rbot/plugins/games/azgame.rb +565 -0
  40. data/data/rbot/plugins/games/quiz.rb +961 -0
  41. data/data/rbot/plugins/games/roshambo.rb +62 -0
  42. data/data/rbot/plugins/{roulette.rb → games/roulette.rb} +58 -8
  43. data/data/rbot/plugins/games/shiritori.rb +485 -0
  44. data/data/rbot/plugins/games/uno.rb +1205 -0
  45. data/data/rbot/plugins/games/wheelfortune.rb +615 -0
  46. data/data/rbot/plugins/grouphug.rb +76 -22
  47. data/data/rbot/plugins/hl2.rb +97 -0
  48. data/data/rbot/plugins/host.rb +10 -1
  49. data/data/rbot/plugins/httpd.rb.disabled +1 -0
  50. data/data/rbot/plugins/imdb.rb +511 -47
  51. data/data/rbot/plugins/insult.rb +32 -37
  52. data/data/rbot/plugins/iplookup.rb +224 -224
  53. data/data/rbot/plugins/karma.rb +45 -21
  54. data/data/rbot/plugins/keywords.rb +258 -160
  55. data/data/rbot/plugins/lart.rb +126 -95
  56. data/data/rbot/plugins/lastfm.rb +421 -12
  57. data/data/rbot/plugins/linkbot.rb +75 -0
  58. data/data/rbot/plugins/markov.rb +103 -46
  59. data/data/rbot/plugins/math.rb +6 -5
  60. data/data/rbot/plugins/modes.rb +114 -0
  61. data/data/rbot/plugins/nickrecover.rb +97 -0
  62. data/data/rbot/plugins/nickserv.rb +111 -48
  63. data/data/rbot/plugins/nslookup.rb +15 -19
  64. data/data/rbot/plugins/plugin.header +11 -0
  65. data/data/rbot/plugins/quakeauth.rb +74 -11
  66. data/data/rbot/plugins/quotes.rb +189 -233
  67. data/data/rbot/plugins/reaction.rb +415 -0
  68. data/data/rbot/plugins/remind.rb +11 -15
  69. data/data/rbot/plugins/remotectl.rb +29 -0
  70. data/data/rbot/plugins/ri.rb +74 -0
  71. data/data/rbot/plugins/rot13.rb +16 -1
  72. data/data/rbot/plugins/rss.rb +1219 -585
  73. data/data/rbot/plugins/salut.rb +226 -0
  74. data/data/rbot/plugins/script.rb +184 -0
  75. data/data/rbot/plugins/search.rb +233 -0
  76. data/data/rbot/plugins/seen.rb +44 -40
  77. data/data/rbot/plugins/shortenurls.rb +93 -0
  78. data/data/rbot/plugins/slashdot.rb +49 -11
  79. data/data/rbot/plugins/spell.rb +37 -27
  80. data/data/rbot/plugins/theyfightcrime.rb +36 -39
  81. data/data/rbot/plugins/threat.rb +25 -22
  82. data/data/rbot/plugins/time.rb +135 -0
  83. data/data/rbot/plugins/topic.rb +107 -50
  84. data/data/rbot/plugins/translator.rb +360 -0
  85. data/data/rbot/plugins/tube.rb +28 -40
  86. data/data/rbot/plugins/twitter.rb +187 -0
  87. data/data/rbot/plugins/urban.rb +62 -53
  88. data/data/rbot/plugins/url.rb +193 -409
  89. data/data/rbot/plugins/usermodes.rb +26 -0
  90. data/data/rbot/plugins/wall.rb +60 -0
  91. data/data/rbot/plugins/weather.rb +254 -598
  92. data/data/rbot/plugins/wow.rb +41 -37
  93. data/data/rbot/plugins/wserver.rb +17 -25
  94. data/data/rbot/plugins/youtube.rb +237 -0
  95. data/data/rbot/templates/lart/{larts → larts-english} +0 -0
  96. data/data/rbot/templates/lart/larts-italian +25 -0
  97. data/data/rbot/templates/lart/larts-japanese +10 -0
  98. data/data/rbot/templates/lart/{praises → praises-english} +0 -0
  99. data/data/rbot/templates/lart/praises-italian +4 -0
  100. data/data/rbot/templates/lart/praises-japanese +3 -0
  101. data/data/rbot/templates/quiz/win_messages +11 -0
  102. data/data/rbot/templates/salut/salut-english +40 -0
  103. data/data/rbot/templates/salut/salut-french +29 -0
  104. data/data/rbot/templates/salut/salut-italian +51 -0
  105. data/data/rbot/templates/salut/salut-japanese +31 -0
  106. data/lib/rbot/botuser.rb +940 -0
  107. data/lib/rbot/config-compat.rb +23 -0
  108. data/lib/rbot/config.rb +211 -276
  109. data/lib/rbot/core/auth.rb +1063 -0
  110. data/lib/rbot/core/basics.rb +214 -0
  111. data/lib/rbot/core/config.rb +330 -0
  112. data/lib/rbot/core/filters_ui.rb +64 -0
  113. data/lib/rbot/core/irclog.rb +292 -0
  114. data/lib/rbot/core/remote.rb +401 -0
  115. data/lib/rbot/core/unicode.rb +91 -0
  116. data/lib/rbot/core/userdata.rb +200 -0
  117. data/lib/rbot/core/utils/extends.rb +444 -0
  118. data/lib/rbot/core/utils/filters.rb +154 -0
  119. data/lib/rbot/core/utils/httputil.rb +689 -0
  120. data/lib/rbot/core/utils/utils.rb +717 -0
  121. data/lib/rbot/dbhash.rb +30 -9
  122. data/lib/rbot/irc.rb +1966 -0
  123. data/lib/rbot/ircbot.rb +816 -675
  124. data/lib/rbot/ircsocket.rb +246 -232
  125. data/lib/rbot/language.rb +86 -8
  126. data/lib/rbot/load-gettext.rb +154 -0
  127. data/lib/rbot/maskdb.rb +162 -0
  128. data/lib/rbot/message.rb +392 -65
  129. data/lib/rbot/messagemapper.rb +438 -94
  130. data/lib/rbot/pkgconfig.rb +6 -0
  131. data/lib/rbot/plugins.rb +713 -172
  132. data/lib/rbot/post-config.rb +1 -0
  133. data/lib/rbot/rbotconfig.rb +31 -12
  134. data/lib/rbot/registry.rb +79 -48
  135. data/lib/rbot/rfc2812.rb +927 -519
  136. data/lib/rbot/timer.rb +232 -167
  137. data/po/en_US/rbot-alias.po +82 -0
  138. data/po/en_US/rbot-autoop.po +0 -0
  139. data/po/en_US/rbot-autorejoin.po +0 -0
  140. data/po/en_US/rbot-azgame.po +200 -0
  141. data/po/en_US/rbot-bans.po +0 -0
  142. data/po/en_US/rbot-bash.po +0 -0
  143. data/po/en_US/rbot-botsnack.po +0 -0
  144. data/po/en_US/rbot-cal.po +3 -0
  145. data/po/en_US/rbot-chanserv.po +0 -0
  146. data/po/en_US/rbot-chucknorris.po +0 -0
  147. data/po/en_US/rbot-debugger.po +0 -0
  148. data/po/en_US/rbot-deepthoughts.po +0 -0
  149. data/po/en_US/rbot-delicious.po +0 -0
  150. data/po/en_US/rbot-dice.po +0 -0
  151. data/po/en_US/rbot-dict.po +0 -0
  152. data/po/en_US/rbot-dictclient.po +124 -0
  153. data/po/en_US/rbot-digg.po +0 -0
  154. data/po/en_US/rbot-eightball.po +0 -0
  155. data/po/en_US/rbot-excuse.po +0 -0
  156. data/po/en_US/rbot-factoids.po +107 -0
  157. data/po/en_US/rbot-figlet.po +36 -0
  158. data/po/en_US/rbot-fish.po +0 -0
  159. data/po/en_US/rbot-forecast.po +0 -0
  160. data/po/en_US/rbot-fortune.po +0 -0
  161. data/po/en_US/rbot-freshmeat.po +0 -0
  162. data/po/en_US/rbot-grouphug.po +18 -0
  163. data/po/en_US/rbot-hl2.po +0 -0
  164. data/po/en_US/rbot-host.po +3 -0
  165. data/po/en_US/rbot-imdb.po +0 -0
  166. data/po/en_US/rbot-insult.po +0 -0
  167. data/po/en_US/rbot-iplookup.po +0 -0
  168. data/po/en_US/rbot-karma.po +0 -0
  169. data/po/en_US/rbot-keywords.po +24 -0
  170. data/po/en_US/rbot-lart.po +0 -0
  171. data/po/en_US/rbot-lastfm.po +172 -0
  172. data/po/en_US/rbot-linkbot.po +0 -0
  173. data/po/en_US/rbot-markov.po +20 -0
  174. data/po/en_US/rbot-math.po +0 -0
  175. data/po/en_US/rbot-modes.po +0 -0
  176. data/po/en_US/rbot-nickrecover.po +40 -0
  177. data/po/en_US/rbot-nickserv.po +104 -0
  178. data/po/en_US/rbot-nslookup.po +0 -0
  179. data/po/en_US/rbot-quakeauth.po +0 -0
  180. data/po/en_US/rbot-quiz.po +0 -0
  181. data/po/en_US/rbot-quotes.po +108 -0
  182. data/po/en_US/rbot-reaction.po +0 -0
  183. data/po/en_US/rbot-remind.po +0 -0
  184. data/po/en_US/rbot-remotectl.po +0 -0
  185. data/po/en_US/rbot-ri.po +0 -0
  186. data/po/en_US/rbot-roshambo.po +0 -0
  187. data/po/en_US/rbot-rot13.po +0 -0
  188. data/po/en_US/rbot-roulette.po +0 -0
  189. data/po/en_US/rbot-rss.po +20 -0
  190. data/po/en_US/rbot-salut.po +0 -0
  191. data/po/en_US/rbot-script.po +0 -0
  192. data/po/en_US/rbot-search.po +0 -0
  193. data/po/en_US/rbot-seen.po +0 -0
  194. data/po/en_US/rbot-shiritori.po +113 -0
  195. data/po/en_US/rbot-shortenurls.po +0 -0
  196. data/po/en_US/rbot-slashdot.po +0 -0
  197. data/po/en_US/rbot-spell.po +54 -0
  198. data/po/en_US/rbot-theyfightcrime.po +0 -0
  199. data/po/en_US/rbot-threat.po +0 -0
  200. data/po/en_US/rbot-time.po +0 -0
  201. data/po/en_US/rbot-topic.po +0 -0
  202. data/po/en_US/rbot-translator.po +77 -0
  203. data/po/en_US/rbot-tube.po +0 -0
  204. data/po/en_US/rbot-twitter.po +24 -0
  205. data/po/en_US/rbot-uno.po +512 -0
  206. data/po/en_US/rbot-urban.po +0 -0
  207. data/po/en_US/rbot-url.po +0 -0
  208. data/po/en_US/rbot-usermodes.po +0 -0
  209. data/po/en_US/rbot-wall.po +33 -0
  210. data/po/en_US/rbot-weather.po +0 -0
  211. data/po/en_US/rbot-wheelfortune.po +205 -0
  212. data/po/en_US/rbot-wow.po +0 -0
  213. data/po/en_US/rbot-wserver.po +0 -0
  214. data/po/en_US/rbot-youtube.po +58 -0
  215. data/po/en_US/rbot.po +1097 -0
  216. data/po/fr/rbot-alias.po +97 -0
  217. data/po/fr/rbot-autoop.po +0 -0
  218. data/po/fr/rbot-autorejoin.po +0 -0
  219. data/po/fr/rbot-azgame.po +204 -0
  220. data/po/fr/rbot-bans.po +0 -0
  221. data/po/fr/rbot-bash.po +0 -0
  222. data/po/fr/rbot-botsnack.po +0 -0
  223. data/po/fr/rbot-cal.po +3 -0
  224. data/po/fr/rbot-chanserv.po +0 -0
  225. data/po/fr/rbot-chucknorris.po +0 -0
  226. data/po/fr/rbot-debugger.po +0 -0
  227. data/po/fr/rbot-deepthoughts.po +0 -0
  228. data/po/fr/rbot-delicious.po +0 -0
  229. data/po/fr/rbot-dice.po +0 -0
  230. data/po/fr/rbot-dict.po +0 -0
  231. data/po/fr/rbot-dictclient.po +134 -0
  232. data/po/fr/rbot-digg.po +0 -0
  233. data/po/fr/rbot-eightball.po +0 -0
  234. data/po/fr/rbot-excuse.po +0 -0
  235. data/po/fr/rbot-factoids.po +111 -0
  236. data/po/fr/rbot-figlet.po +37 -0
  237. data/po/fr/rbot-fish.po +0 -0
  238. data/po/fr/rbot-forecast.po +0 -0
  239. data/po/fr/rbot-fortune.po +0 -0
  240. data/po/fr/rbot-freshmeat.po +0 -0
  241. data/po/fr/rbot-grouphug.po +18 -0
  242. data/po/fr/rbot-hl2.po +0 -0
  243. data/po/fr/rbot-host.po +3 -0
  244. data/po/fr/rbot-imdb.po +0 -0
  245. data/po/fr/rbot-insult.po +0 -0
  246. data/po/fr/rbot-iplookup.po +0 -0
  247. data/po/fr/rbot-karma.po +0 -0
  248. data/po/fr/rbot-keywords.po +24 -0
  249. data/po/fr/rbot-lart.po +0 -0
  250. data/po/fr/rbot-lastfm.po +172 -0
  251. data/po/fr/rbot-linkbot.po +0 -0
  252. data/po/fr/rbot-markov.po +20 -0
  253. data/po/fr/rbot-math.po +0 -0
  254. data/po/fr/rbot-modes.po +0 -0
  255. data/po/fr/rbot-nickrecover.po +36 -0
  256. data/po/fr/rbot-nickserv.po +116 -0
  257. data/po/fr/rbot-nslookup.po +0 -0
  258. data/po/fr/rbot-quakeauth.po +0 -0
  259. data/po/fr/rbot-quiz.po +0 -0
  260. data/po/fr/rbot-quotes.po +138 -0
  261. data/po/fr/rbot-reaction.po +0 -0
  262. data/po/fr/rbot-remind.po +0 -0
  263. data/po/fr/rbot-remotectl.po +0 -0
  264. data/po/fr/rbot-ri.po +0 -0
  265. data/po/fr/rbot-roshambo.po +0 -0
  266. data/po/fr/rbot-rot13.po +0 -0
  267. data/po/fr/rbot-roulette.po +0 -0
  268. data/po/fr/rbot-rss.po +20 -0
  269. data/po/fr/rbot-salut.po +0 -0
  270. data/po/fr/rbot-script.po +0 -0
  271. data/po/fr/rbot-search.po +0 -0
  272. data/po/fr/rbot-seen.po +0 -0
  273. data/po/fr/rbot-shiritori.po +119 -0
  274. data/po/fr/rbot-shortenurls.po +0 -0
  275. data/po/fr/rbot-slashdot.po +0 -0
  276. data/po/fr/rbot-spell.po +56 -0
  277. data/po/fr/rbot-theyfightcrime.po +0 -0
  278. data/po/fr/rbot-threat.po +0 -0
  279. data/po/fr/rbot-time.po +0 -0
  280. data/po/fr/rbot-topic.po +0 -0
  281. data/po/fr/rbot-translator.po +90 -0
  282. data/po/fr/rbot-tube.po +0 -0
  283. data/po/fr/rbot-twitter.po +24 -0
  284. data/po/fr/rbot-uno.po +521 -0
  285. data/po/fr/rbot-urban.po +0 -0
  286. data/po/fr/rbot-url.po +0 -0
  287. data/po/fr/rbot-usermodes.po +0 -0
  288. data/po/fr/rbot-wall.po +33 -0
  289. data/po/fr/rbot-weather.po +0 -0
  290. data/po/fr/rbot-wheelfortune.po +255 -0
  291. data/po/fr/rbot-wow.po +0 -0
  292. data/po/fr/rbot-wserver.po +0 -0
  293. data/po/fr/rbot-youtube.po +64 -0
  294. data/po/fr/rbot.po +1133 -0
  295. data/po/it/rbot-alias.po +82 -0
  296. data/po/it/rbot-autoop.po +0 -0
  297. data/po/it/rbot-autorejoin.po +0 -0
  298. data/po/it/rbot-azgame.po +186 -0
  299. data/po/it/rbot-bans.po +0 -0
  300. data/po/it/rbot-bash.po +0 -0
  301. data/po/it/rbot-botsnack.po +0 -0
  302. data/po/it/rbot-cal.po +3 -0
  303. data/po/it/rbot-chanserv.po +0 -0
  304. data/po/it/rbot-chucknorris.po +0 -0
  305. data/po/it/rbot-debugger.po +0 -0
  306. data/po/it/rbot-deepthoughts.po +0 -0
  307. data/po/it/rbot-delicious.po +0 -0
  308. data/po/it/rbot-dice.po +0 -0
  309. data/po/it/rbot-dict.po +0 -0
  310. data/po/it/rbot-dictclient.po +111 -0
  311. data/po/it/rbot-digg.po +0 -0
  312. data/po/it/rbot-eightball.po +0 -0
  313. data/po/it/rbot-excuse.po +0 -0
  314. data/po/it/rbot-factoids.po +110 -0
  315. data/po/it/rbot-figlet.po +36 -0
  316. data/po/it/rbot-fish.po +0 -0
  317. data/po/it/rbot-forecast.po +0 -0
  318. data/po/it/rbot-fortune.po +0 -0
  319. data/po/it/rbot-freshmeat.po +0 -0
  320. data/po/it/rbot-grouphug.po +18 -0
  321. data/po/it/rbot-hl2.po +0 -0
  322. data/po/it/rbot-host.po +3 -0
  323. data/po/it/rbot-imdb.po +0 -0
  324. data/po/it/rbot-insult.po +0 -0
  325. data/po/it/rbot-iplookup.po +0 -0
  326. data/po/it/rbot-karma.po +0 -0
  327. data/po/it/rbot-keywords.po +24 -0
  328. data/po/it/rbot-lart.po +0 -0
  329. data/po/it/rbot-lastfm.po +172 -0
  330. data/po/it/rbot-linkbot.po +0 -0
  331. data/po/it/rbot-markov.po +20 -0
  332. data/po/it/rbot-math.po +0 -0
  333. data/po/it/rbot-modes.po +0 -0
  334. data/po/it/rbot-nickrecover.po +36 -0
  335. data/po/it/rbot-nickserv.po +104 -0
  336. data/po/it/rbot-nslookup.po +0 -0
  337. data/po/it/rbot-quakeauth.po +0 -0
  338. data/po/it/rbot-quiz.po +0 -0
  339. data/po/it/rbot-quotes.po +108 -0
  340. data/po/it/rbot-reaction.po +0 -0
  341. data/po/it/rbot-remind.po +0 -0
  342. data/po/it/rbot-remotectl.po +0 -0
  343. data/po/it/rbot-ri.po +0 -0
  344. data/po/it/rbot-roshambo.po +0 -0
  345. data/po/it/rbot-rot13.po +0 -0
  346. data/po/it/rbot-roulette.po +0 -0
  347. data/po/it/rbot-rss.po +20 -0
  348. data/po/it/rbot-salut.po +0 -0
  349. data/po/it/rbot-script.po +0 -0
  350. data/po/it/rbot-search.po +0 -0
  351. data/po/it/rbot-seen.po +0 -0
  352. data/po/it/rbot-shiritori.po +102 -0
  353. data/po/it/rbot-shortenurls.po +0 -0
  354. data/po/it/rbot-slashdot.po +0 -0
  355. data/po/it/rbot-spell.po +54 -0
  356. data/po/it/rbot-theyfightcrime.po +0 -0
  357. data/po/it/rbot-threat.po +0 -0
  358. data/po/it/rbot-time.po +0 -0
  359. data/po/it/rbot-topic.po +0 -0
  360. data/po/it/rbot-translator.po +77 -0
  361. data/po/it/rbot-tube.po +0 -0
  362. data/po/it/rbot-twitter.po +24 -0
  363. data/po/it/rbot-uno.po +564 -0
  364. data/po/it/rbot-urban.po +0 -0
  365. data/po/it/rbot-url.po +0 -0
  366. data/po/it/rbot-usermodes.po +0 -0
  367. data/po/it/rbot-wall.po +33 -0
  368. data/po/it/rbot-weather.po +0 -0
  369. data/po/it/rbot-wheelfortune.po +251 -0
  370. data/po/it/rbot-wow.po +0 -0
  371. data/po/it/rbot-wserver.po +0 -0
  372. data/po/it/rbot-youtube.po +64 -0
  373. data/po/it/rbot.po +1127 -0
  374. data/po/ja/rbot-alias.po +82 -0
  375. data/po/ja/rbot-autoop.po +0 -0
  376. data/po/ja/rbot-autorejoin.po +0 -0
  377. data/po/ja/rbot-azgame.po +194 -0
  378. data/po/ja/rbot-bans.po +0 -0
  379. data/po/ja/rbot-bash.po +0 -0
  380. data/po/ja/rbot-botsnack.po +0 -0
  381. data/po/ja/rbot-cal.po +3 -0
  382. data/po/ja/rbot-chanserv.po +0 -0
  383. data/po/ja/rbot-chucknorris.po +0 -0
  384. data/po/ja/rbot-debugger.po +0 -0
  385. data/po/ja/rbot-deepthoughts.po +0 -0
  386. data/po/ja/rbot-delicious.po +0 -0
  387. data/po/ja/rbot-dice.po +0 -0
  388. data/po/ja/rbot-dict.po +0 -0
  389. data/po/ja/rbot-dictclient.po +111 -0
  390. data/po/ja/rbot-digg.po +0 -0
  391. data/po/ja/rbot-eightball.po +0 -0
  392. data/po/ja/rbot-excuse.po +0 -0
  393. data/po/ja/rbot-factoids.po +107 -0
  394. data/po/ja/rbot-figlet.po +36 -0
  395. data/po/ja/rbot-fish.po +0 -0
  396. data/po/ja/rbot-forecast.po +0 -0
  397. data/po/ja/rbot-fortune.po +0 -0
  398. data/po/ja/rbot-freshmeat.po +0 -0
  399. data/po/ja/rbot-grouphug.po +18 -0
  400. data/po/ja/rbot-hl2.po +0 -0
  401. data/po/ja/rbot-host.po +3 -0
  402. data/po/ja/rbot-imdb.po +0 -0
  403. data/po/ja/rbot-insult.po +0 -0
  404. data/po/ja/rbot-iplookup.po +0 -0
  405. data/po/ja/rbot-karma.po +0 -0
  406. data/po/ja/rbot-keywords.po +24 -0
  407. data/po/ja/rbot-lart.po +0 -0
  408. data/po/ja/rbot-lastfm.po +172 -0
  409. data/po/ja/rbot-linkbot.po +0 -0
  410. data/po/ja/rbot-markov.po +20 -0
  411. data/po/ja/rbot-math.po +0 -0
  412. data/po/ja/rbot-modes.po +0 -0
  413. data/po/ja/rbot-nickrecover.po +36 -0
  414. data/po/ja/rbot-nickserv.po +104 -0
  415. data/po/ja/rbot-nslookup.po +0 -0
  416. data/po/ja/rbot-quakeauth.po +0 -0
  417. data/po/ja/rbot-quiz.po +0 -0
  418. data/po/ja/rbot-quotes.po +108 -0
  419. data/po/ja/rbot-reaction.po +0 -0
  420. data/po/ja/rbot-remind.po +0 -0
  421. data/po/ja/rbot-remotectl.po +0 -0
  422. data/po/ja/rbot-ri.po +0 -0
  423. data/po/ja/rbot-roshambo.po +0 -0
  424. data/po/ja/rbot-rot13.po +0 -0
  425. data/po/ja/rbot-roulette.po +0 -0
  426. data/po/ja/rbot-rss.po +20 -0
  427. data/po/ja/rbot-salut.po +0 -0
  428. data/po/ja/rbot-script.po +0 -0
  429. data/po/ja/rbot-search.po +0 -0
  430. data/po/ja/rbot-seen.po +0 -0
  431. data/po/ja/rbot-shiritori.po +115 -0
  432. data/po/ja/rbot-shortenurls.po +0 -0
  433. data/po/ja/rbot-slashdot.po +0 -0
  434. data/po/ja/rbot-spell.po +54 -0
  435. data/po/ja/rbot-theyfightcrime.po +0 -0
  436. data/po/ja/rbot-threat.po +0 -0
  437. data/po/ja/rbot-time.po +0 -0
  438. data/po/ja/rbot-topic.po +0 -0
  439. data/po/ja/rbot-translator.po +77 -0
  440. data/po/ja/rbot-tube.po +0 -0
  441. data/po/ja/rbot-twitter.po +24 -0
  442. data/po/ja/rbot-uno.po +512 -0
  443. data/po/ja/rbot-urban.po +0 -0
  444. data/po/ja/rbot-url.po +0 -0
  445. data/po/ja/rbot-usermodes.po +0 -0
  446. data/po/ja/rbot-wall.po +33 -0
  447. data/po/ja/rbot-weather.po +0 -0
  448. data/po/ja/rbot-wheelfortune.po +205 -0
  449. data/po/ja/rbot-wow.po +0 -0
  450. data/po/ja/rbot-wserver.po +0 -0
  451. data/po/ja/rbot-youtube.po +58 -0
  452. data/po/ja/rbot.po +999 -0
  453. data/po/rbot-alias.pot +83 -0
  454. data/po/rbot-autoop.pot +0 -0
  455. data/po/rbot-autorejoin.pot +0 -0
  456. data/po/rbot-azgame.pot +187 -0
  457. data/po/rbot-bans.pot +0 -0
  458. data/po/rbot-bash.pot +0 -0
  459. data/po/rbot-botsnack.pot +0 -0
  460. data/po/rbot-cal.pot +21 -0
  461. data/po/rbot-chanserv.pot +0 -0
  462. data/po/rbot-chucknorris.pot +0 -0
  463. data/po/rbot-debugger.pot +0 -0
  464. data/po/rbot-deepthoughts.pot +0 -0
  465. data/po/rbot-delicious.pot +0 -0
  466. data/po/rbot-dice.pot +0 -0
  467. data/po/rbot-dict.pot +0 -0
  468. data/po/rbot-dictclient.pot +112 -0
  469. data/po/rbot-digg.pot +0 -0
  470. data/po/rbot-eightball.pot +0 -0
  471. data/po/rbot-excuse.pot +0 -0
  472. data/po/rbot-factoids.pot +108 -0
  473. data/po/rbot-figlet.pot +37 -0
  474. data/po/rbot-fish.pot +0 -0
  475. data/po/rbot-forecast.pot +0 -0
  476. data/po/rbot-fortune.pot +0 -0
  477. data/po/rbot-freshmeat.pot +0 -0
  478. data/po/rbot-grouphug.pot +36 -0
  479. data/po/rbot-hl2.pot +0 -0
  480. data/po/rbot-host.pot +21 -0
  481. data/po/rbot-imdb.pot +0 -0
  482. data/po/rbot-insult.pot +0 -0
  483. data/po/rbot-iplookup.pot +0 -0
  484. data/po/rbot-karma.pot +0 -0
  485. data/po/rbot-keywords.pot +25 -0
  486. data/po/rbot-lart.pot +0 -0
  487. data/po/rbot-lastfm.pot +190 -0
  488. data/po/rbot-linkbot.pot +0 -0
  489. data/po/rbot-markov.pot +21 -0
  490. data/po/rbot-math.pot +0 -0
  491. data/po/rbot-modes.pot +0 -0
  492. data/po/rbot-nickrecover.pot +37 -0
  493. data/po/rbot-nickserv.pot +105 -0
  494. data/po/rbot-nslookup.pot +0 -0
  495. data/po/rbot-quakeauth.pot +0 -0
  496. data/po/rbot-quiz.pot +0 -0
  497. data/po/rbot-quotes.pot +109 -0
  498. data/po/rbot-reaction.pot +0 -0
  499. data/po/rbot-remind.pot +0 -0
  500. data/po/rbot-remotectl.pot +0 -0
  501. data/po/rbot-ri.pot +0 -0
  502. data/po/rbot-roshambo.pot +0 -0
  503. data/po/rbot-rot13.pot +0 -0
  504. data/po/rbot-roulette.pot +0 -0
  505. data/po/rbot-rss.pot +21 -0
  506. data/po/rbot-salut.pot +0 -0
  507. data/po/rbot-script.pot +0 -0
  508. data/po/rbot-search.pot +0 -0
  509. data/po/rbot-seen.pot +0 -0
  510. data/po/rbot-shiritori.pot +103 -0
  511. data/po/rbot-shortenurls.pot +0 -0
  512. data/po/rbot-slashdot.pot +0 -0
  513. data/po/rbot-spell.pot +55 -0
  514. data/po/rbot-theyfightcrime.pot +0 -0
  515. data/po/rbot-threat.pot +0 -0
  516. data/po/rbot-time.pot +0 -0
  517. data/po/rbot-topic.pot +0 -0
  518. data/po/rbot-translator.pot +78 -0
  519. data/po/rbot-tube.pot +0 -0
  520. data/po/rbot-twitter.pot +25 -0
  521. data/po/rbot-uno.pot +513 -0
  522. data/po/rbot-urban.pot +0 -0
  523. data/po/rbot-url.pot +0 -0
  524. data/po/rbot-usermodes.pot +0 -0
  525. data/po/rbot-wall.pot +33 -0
  526. data/po/rbot-weather.pot +0 -0
  527. data/po/rbot-wheelfortune.pot +206 -0
  528. data/po/rbot-wow.pot +0 -0
  529. data/po/rbot-wserver.pot +0 -0
  530. data/po/rbot-youtube.pot +59 -0
  531. data/po/rbot.pot +1004 -0
  532. data/po/zh_CN/rbot-alias.po +82 -0
  533. data/po/zh_CN/rbot-autoop.po +0 -0
  534. data/po/zh_CN/rbot-autorejoin.po +0 -0
  535. data/po/zh_CN/rbot-azgame.po +186 -0
  536. data/po/zh_CN/rbot-bans.po +0 -0
  537. data/po/zh_CN/rbot-bash.po +0 -0
  538. data/po/zh_CN/rbot-botsnack.po +0 -0
  539. data/po/zh_CN/rbot-cal.po +3 -0
  540. data/po/zh_CN/rbot-chanserv.po +0 -0
  541. data/po/zh_CN/rbot-chucknorris.po +0 -0
  542. data/po/zh_CN/rbot-debugger.po +0 -0
  543. data/po/zh_CN/rbot-deepthoughts.po +0 -0
  544. data/po/zh_CN/rbot-delicious.po +0 -0
  545. data/po/zh_CN/rbot-dice.po +0 -0
  546. data/po/zh_CN/rbot-dict.po +0 -0
  547. data/po/zh_CN/rbot-dictclient.po +111 -0
  548. data/po/zh_CN/rbot-digg.po +0 -0
  549. data/po/zh_CN/rbot-eightball.po +0 -0
  550. data/po/zh_CN/rbot-excuse.po +0 -0
  551. data/po/zh_CN/rbot-factoids.po +107 -0
  552. data/po/zh_CN/rbot-figlet.po +36 -0
  553. data/po/zh_CN/rbot-fish.po +0 -0
  554. data/po/zh_CN/rbot-forecast.po +0 -0
  555. data/po/zh_CN/rbot-fortune.po +0 -0
  556. data/po/zh_CN/rbot-freshmeat.po +0 -0
  557. data/po/zh_CN/rbot-grouphug.po +18 -0
  558. data/po/zh_CN/rbot-hl2.po +0 -0
  559. data/po/zh_CN/rbot-host.po +3 -0
  560. data/po/zh_CN/rbot-imdb.po +0 -0
  561. data/po/zh_CN/rbot-insult.po +0 -0
  562. data/po/zh_CN/rbot-iplookup.po +0 -0
  563. data/po/zh_CN/rbot-karma.po +0 -0
  564. data/po/zh_CN/rbot-keywords.po +24 -0
  565. data/po/zh_CN/rbot-lart.po +0 -0
  566. data/po/zh_CN/rbot-lastfm.po +172 -0
  567. data/po/zh_CN/rbot-linkbot.po +0 -0
  568. data/po/zh_CN/rbot-markov.po +20 -0
  569. data/po/zh_CN/rbot-math.po +0 -0
  570. data/po/zh_CN/rbot-modes.po +0 -0
  571. data/po/zh_CN/rbot-nickrecover.po +36 -0
  572. data/po/zh_CN/rbot-nickserv.po +104 -0
  573. data/po/zh_CN/rbot-nslookup.po +0 -0
  574. data/po/zh_CN/rbot-quakeauth.po +0 -0
  575. data/po/zh_CN/rbot-quiz.po +0 -0
  576. data/po/zh_CN/rbot-quotes.po +108 -0
  577. data/po/zh_CN/rbot-reaction.po +0 -0
  578. data/po/zh_CN/rbot-remind.po +0 -0
  579. data/po/zh_CN/rbot-remotectl.po +0 -0
  580. data/po/zh_CN/rbot-ri.po +0 -0
  581. data/po/zh_CN/rbot-roshambo.po +0 -0
  582. data/po/zh_CN/rbot-rot13.po +0 -0
  583. data/po/zh_CN/rbot-roulette.po +0 -0
  584. data/po/zh_CN/rbot-rss.po +20 -0
  585. data/po/zh_CN/rbot-salut.po +0 -0
  586. data/po/zh_CN/rbot-script.po +0 -0
  587. data/po/zh_CN/rbot-search.po +0 -0
  588. data/po/zh_CN/rbot-seen.po +0 -0
  589. data/po/zh_CN/rbot-shiritori.po +102 -0
  590. data/po/zh_CN/rbot-shortenurls.po +0 -0
  591. data/po/zh_CN/rbot-slashdot.po +0 -0
  592. data/po/zh_CN/rbot-spell.po +54 -0
  593. data/po/zh_CN/rbot-theyfightcrime.po +0 -0
  594. data/po/zh_CN/rbot-threat.po +0 -0
  595. data/po/zh_CN/rbot-time.po +0 -0
  596. data/po/zh_CN/rbot-topic.po +0 -0
  597. data/po/zh_CN/rbot-translator.po +77 -0
  598. data/po/zh_CN/rbot-tube.po +0 -0
  599. data/po/zh_CN/rbot-twitter.po +24 -0
  600. data/po/zh_CN/rbot-uno.po +512 -0
  601. data/po/zh_CN/rbot-urban.po +0 -0
  602. data/po/zh_CN/rbot-url.po +0 -0
  603. data/po/zh_CN/rbot-usermodes.po +0 -0
  604. data/po/zh_CN/rbot-wall.po +33 -0
  605. data/po/zh_CN/rbot-weather.po +0 -0
  606. data/po/zh_CN/rbot-wheelfortune.po +205 -0
  607. data/po/zh_CN/rbot-wow.po +0 -0
  608. data/po/zh_CN/rbot-wserver.po +0 -0
  609. data/po/zh_CN/rbot-youtube.po +58 -0
  610. data/po/zh_CN/rbot.po +1006 -0
  611. data/po/zh_TW/rbot-alias.po +82 -0
  612. data/po/zh_TW/rbot-autoop.po +0 -0
  613. data/po/zh_TW/rbot-autorejoin.po +0 -0
  614. data/po/zh_TW/rbot-azgame.po +186 -0
  615. data/po/zh_TW/rbot-bans.po +0 -0
  616. data/po/zh_TW/rbot-bash.po +0 -0
  617. data/po/zh_TW/rbot-botsnack.po +0 -0
  618. data/po/zh_TW/rbot-cal.po +3 -0
  619. data/po/zh_TW/rbot-chanserv.po +0 -0
  620. data/po/zh_TW/rbot-chucknorris.po +0 -0
  621. data/po/zh_TW/rbot-debugger.po +0 -0
  622. data/po/zh_TW/rbot-deepthoughts.po +0 -0
  623. data/po/zh_TW/rbot-delicious.po +0 -0
  624. data/po/zh_TW/rbot-dice.po +0 -0
  625. data/po/zh_TW/rbot-dict.po +0 -0
  626. data/po/zh_TW/rbot-dictclient.po +111 -0
  627. data/po/zh_TW/rbot-digg.po +0 -0
  628. data/po/zh_TW/rbot-eightball.po +0 -0
  629. data/po/zh_TW/rbot-excuse.po +0 -0
  630. data/po/zh_TW/rbot-factoids.po +107 -0
  631. data/po/zh_TW/rbot-figlet.po +36 -0
  632. data/po/zh_TW/rbot-fish.po +0 -0
  633. data/po/zh_TW/rbot-forecast.po +0 -0
  634. data/po/zh_TW/rbot-fortune.po +0 -0
  635. data/po/zh_TW/rbot-freshmeat.po +0 -0
  636. data/po/zh_TW/rbot-grouphug.po +18 -0
  637. data/po/zh_TW/rbot-hl2.po +0 -0
  638. data/po/zh_TW/rbot-host.po +3 -0
  639. data/po/zh_TW/rbot-imdb.po +0 -0
  640. data/po/zh_TW/rbot-insult.po +0 -0
  641. data/po/zh_TW/rbot-iplookup.po +0 -0
  642. data/po/zh_TW/rbot-karma.po +0 -0
  643. data/po/zh_TW/rbot-keywords.po +24 -0
  644. data/po/zh_TW/rbot-lart.po +0 -0
  645. data/po/zh_TW/rbot-lastfm.po +172 -0
  646. data/po/zh_TW/rbot-linkbot.po +0 -0
  647. data/po/zh_TW/rbot-markov.po +20 -0
  648. data/po/zh_TW/rbot-math.po +0 -0
  649. data/po/zh_TW/rbot-modes.po +0 -0
  650. data/po/zh_TW/rbot-nickrecover.po +36 -0
  651. data/po/zh_TW/rbot-nickserv.po +104 -0
  652. data/po/zh_TW/rbot-nslookup.po +0 -0
  653. data/po/zh_TW/rbot-quakeauth.po +0 -0
  654. data/po/zh_TW/rbot-quiz.po +0 -0
  655. data/po/zh_TW/rbot-quotes.po +108 -0
  656. data/po/zh_TW/rbot-reaction.po +0 -0
  657. data/po/zh_TW/rbot-remind.po +0 -0
  658. data/po/zh_TW/rbot-remotectl.po +0 -0
  659. data/po/zh_TW/rbot-ri.po +0 -0
  660. data/po/zh_TW/rbot-roshambo.po +0 -0
  661. data/po/zh_TW/rbot-rot13.po +0 -0
  662. data/po/zh_TW/rbot-roulette.po +0 -0
  663. data/po/zh_TW/rbot-rss.po +20 -0
  664. data/po/zh_TW/rbot-salut.po +0 -0
  665. data/po/zh_TW/rbot-script.po +0 -0
  666. data/po/zh_TW/rbot-search.po +0 -0
  667. data/po/zh_TW/rbot-seen.po +0 -0
  668. data/po/zh_TW/rbot-shiritori.po +102 -0
  669. data/po/zh_TW/rbot-shortenurls.po +0 -0
  670. data/po/zh_TW/rbot-slashdot.po +0 -0
  671. data/po/zh_TW/rbot-spell.po +54 -0
  672. data/po/zh_TW/rbot-theyfightcrime.po +0 -0
  673. data/po/zh_TW/rbot-threat.po +0 -0
  674. data/po/zh_TW/rbot-time.po +0 -0
  675. data/po/zh_TW/rbot-topic.po +0 -0
  676. data/po/zh_TW/rbot-translator.po +77 -0
  677. data/po/zh_TW/rbot-tube.po +0 -0
  678. data/po/zh_TW/rbot-twitter.po +24 -0
  679. data/po/zh_TW/rbot-uno.po +512 -0
  680. data/po/zh_TW/rbot-urban.po +0 -0
  681. data/po/zh_TW/rbot-url.po +0 -0
  682. data/po/zh_TW/rbot-usermodes.po +0 -0
  683. data/po/zh_TW/rbot-wall.po +33 -0
  684. data/po/zh_TW/rbot-weather.po +0 -0
  685. data/po/zh_TW/rbot-wheelfortune.po +205 -0
  686. data/po/zh_TW/rbot-wow.po +0 -0
  687. data/po/zh_TW/rbot-wserver.po +0 -0
  688. data/po/zh_TW/rbot-youtube.po +58 -0
  689. data/po/zh_TW/rbot.po +1034 -0
  690. data/setup.rb +800 -574
  691. metadata +723 -108
  692. data/data/rbot/contrib/plugins/figlet.rb +0 -20
  693. data/data/rbot/contrib/plugins/ri.rb +0 -83
  694. data/data/rbot/plugins/demauro.rb +0 -95
  695. data/data/rbot/plugins/google.rb +0 -53
  696. data/data/rbot/plugins/opme.rb +0 -19
  697. data/data/rbot/plugins/roshambo.rb +0 -54
  698. data/data/rbot/plugins/rubyurl.rb +0 -39
  699. data/data/rbot/plugins/tinyurl.rb +0 -39
  700. data/data/rbot/plugins/xmlrpc.rb.disabled +0 -52
  701. data/data/rbot/templates/levels.rbot +0 -32
  702. data/data/rbot/templates/users.rbot +0 -1
  703. data/lib/rbot/auth.rb +0 -314
  704. data/lib/rbot/channel.rb +0 -54
  705. data/lib/rbot/httputil.rb +0 -309
  706. data/lib/rbot/utils.rb +0 -83
@@ -1,11 +1,32 @@
1
+ #-- vim:sw=2:et
2
+ #++
3
+ #
4
+ # :title: Berkeley DB interface
5
+
1
6
  begin
2
7
  require 'bdb'
8
+ rescue LoadError
9
+ fatal "rbot couldn't load the bdb module, perhaps you need to install it? try: http://www.ruby-lang.org/en/raa-list.rhtml?name=bdb"
3
10
  rescue Exception => e
4
- error "Got exception: "+e
5
- error "rbot couldn't load the bdb module, perhaps you need to install it? try: http://www.ruby-lang.org/en/raa-list.rhtml?name=bdb"
11
+ fatal "rbot couldn't load the bdb module: #{e.pretty_inspect}"
12
+ end
13
+
14
+ if not defined? BDB
6
15
  exit 2
7
16
  end
8
17
 
18
+ if BDB::VERSION_MAJOR < 4
19
+ fatal "Your bdb (Berkeley DB) version #{BDB::VERSION} is too old!"
20
+ fatal "rbot will only run with bdb version 4 or higher, please upgrade."
21
+ fatal "For maximum reliability, upgrade to version 4.2 or higher."
22
+ raise BDB::Fatal, BDB::VERSION + " is too old"
23
+ end
24
+
25
+ if BDB::VERSION_MAJOR == 4 and BDB::VERSION_MINOR < 2
26
+ warning "Your bdb (Berkeley DB) version #{BDB::VERSION} may not be reliable."
27
+ warning "If possible, try upgrade version 4.2 or later."
28
+ end
29
+
9
30
  # make BTree lookups case insensitive
10
31
  module BDB
11
32
  class CIBtree < Btree
@@ -80,7 +101,7 @@ module Irc
80
101
  @@env = BDB::Env.open("#{@bot.botclass}", BDB::INIT_TRANSACTION | BDB::CREATE | BDB::RECOVER, "set_lg_max" => @@lg_max)
81
102
  debug "DBTree: environment opened with max log size #{@@env.conf['lg_max']}"
82
103
  rescue => e
83
- debug "DBTree: failed to open environment: #{e}. Retrying ..."
104
+ debug "DBTree: failed to open environment: #{e.pretty_inspect}. Retrying ..."
84
105
  @@env = BDB::Env.open("#{@bot.botclass}", BDB::INIT_TRANSACTION | BDB::CREATE | BDB::RECOVER)
85
106
  end
86
107
  #@@env = BDB::Env.open("#{@bot.botclass}", BDB::CREATE | BDB::INIT_MPOOL | BDB::RECOVER)
@@ -119,8 +140,8 @@ module Irc
119
140
  begin
120
141
  debug "DBTree: checkpointing ..."
121
142
  @@env.checkpoint
122
- rescue => e
123
- debug "Failed: #{e}"
143
+ rescue Exception => e
144
+ debug "Failed: #{e.pretty_inspect}"
124
145
  end
125
146
  begin
126
147
  debug "DBTree: flushing log ..."
@@ -130,8 +151,8 @@ module Irc
130
151
  logs.each { |log|
131
152
  File.delete(log)
132
153
  }
133
- rescue => e
134
- debug "Failed: #{e}"
154
+ rescue Exception => e
155
+ debug "Failed: #{e.pretty_inspect}"
135
156
  end
136
157
  end
137
158
 
@@ -168,8 +189,8 @@ module Irc
168
189
  debug "DBTree: cleaning up environment in #{path}"
169
190
  BDB::Env.remove("#{path}")
170
191
  end
171
- rescue => e
172
- error "failed to clean up environment: #{e.inspect}"
192
+ rescue Exception => e
193
+ error "failed to clean up environment: #{e.pretty_inspect}"
173
194
  end
174
195
  end
175
196
 
@@ -0,0 +1,1966 @@
1
+ #-- vim:sw=2:et
2
+ # General TODO list
3
+ # * do we want to handle a Channel list for each User telling which
4
+ # Channels is the User on (of those the client is on too)?
5
+ # We may want this so that when a User leaves all Channels and he hasn't
6
+ # sent us privmsgs, we know we can remove him from the Server @users list
7
+ # * Maybe ChannelList and UserList should be HashesOf instead of ArrayOf?
8
+ # See items marked as TODO Ho.
9
+ # The framework to do this is now in place, thanks to the new [] method
10
+ # for NetmaskList, which allows retrieval by Netmask or String
11
+ #++
12
+ # :title: IRC module
13
+ #
14
+ # Basic IRC stuff
15
+ #
16
+ # This module defines the fundamental building blocks for IRC
17
+ #
18
+ # Author:: Giuseppe Bilotta (giuseppe.bilotta@gmail.com)
19
+
20
+ require 'singleton'
21
+
22
+ class Object
23
+
24
+ # We extend the Object class with a method that
25
+ # checks if the receiver is nil or empty
26
+ def nil_or_empty?
27
+ return true unless self
28
+ return true if self.respond_to? :empty? and self.empty?
29
+ return false
30
+ end
31
+
32
+ # We alias the to_s method to __to_s__ to make
33
+ # it accessible in all classes
34
+ alias :__to_s__ :to_s
35
+ end
36
+
37
+ # The Irc module is used to keep all IRC-related classes
38
+ # in the same namespace
39
+ #
40
+ module Irc
41
+
42
+
43
+ # Due to its Scandinavian origins, IRC has strange case mappings, which
44
+ # consider the characters <tt>{}|^</tt> as the uppercase
45
+ # equivalents of # <tt>[]\~</tt>.
46
+ #
47
+ # This is however not the same on all IRC servers: some use standard ASCII
48
+ # casemapping, other do not consider <tt>^</tt> as the uppercase of
49
+ # <tt>~</tt>
50
+ #
51
+ class Casemap
52
+ @@casemaps = {}
53
+
54
+ # Create a new casemap with name _name_, uppercase characters _upper_ and
55
+ # lowercase characters _lower_
56
+ #
57
+ def initialize(name, upper, lower)
58
+ @key = name.to_sym
59
+ raise "Casemap #{name.inspect} already exists!" if @@casemaps.has_key?(@key)
60
+ @@casemaps[@key] = {
61
+ :upper => upper,
62
+ :lower => lower,
63
+ :casemap => self
64
+ }
65
+ end
66
+
67
+ # Returns the Casemap with the given name
68
+ #
69
+ def Casemap.get(name)
70
+ @@casemaps[name.to_sym][:casemap]
71
+ end
72
+
73
+ # Retrieve the 'uppercase characters' of this Casemap
74
+ #
75
+ def upper
76
+ @@casemaps[@key][:upper]
77
+ end
78
+
79
+ # Retrieve the 'lowercase characters' of this Casemap
80
+ #
81
+ def lower
82
+ @@casemaps[@key][:lower]
83
+ end
84
+
85
+ # Return a Casemap based on the receiver
86
+ #
87
+ def to_irc_casemap
88
+ self
89
+ end
90
+
91
+ # A Casemap is represented by its lower/upper mappings
92
+ #
93
+ def inspect
94
+ self.__to_s__[0..-2] + " #{upper.inspect} ~(#{self})~ #{lower.inspect}>"
95
+ end
96
+
97
+ # As a String we return our name
98
+ #
99
+ def to_s
100
+ @key.to_s
101
+ end
102
+
103
+ # Two Casemaps are equal if they have the same upper and lower ranges
104
+ #
105
+ def ==(arg)
106
+ other = arg.to_irc_casemap
107
+ return self.upper == other.upper && self.lower == other.lower
108
+ end
109
+
110
+ # Give a warning if _arg_ and self are not the same Casemap
111
+ #
112
+ def must_be(arg)
113
+ other = arg.to_irc_casemap
114
+ if self == other
115
+ return true
116
+ else
117
+ warn "Casemap mismatch (#{self.inspect} != #{other.inspect})"
118
+ return false
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ # The rfc1459 casemap
125
+ #
126
+ class RfcCasemap < Casemap
127
+ include Singleton
128
+
129
+ def initialize
130
+ super('rfc1459', "\x41-\x5e", "\x61-\x7e")
131
+ end
132
+
133
+ end
134
+ RfcCasemap.instance
135
+
136
+ # The strict-rfc1459 Casemap
137
+ #
138
+ class StrictRfcCasemap < Casemap
139
+ include Singleton
140
+
141
+ def initialize
142
+ super('strict-rfc1459', "\x41-\x5d", "\x61-\x7d")
143
+ end
144
+
145
+ end
146
+ StrictRfcCasemap.instance
147
+
148
+ # The ascii Casemap
149
+ #
150
+ class AsciiCasemap < Casemap
151
+ include Singleton
152
+
153
+ def initialize
154
+ super('ascii', "\x41-\x5a", "\x61-\x7a")
155
+ end
156
+
157
+ end
158
+ AsciiCasemap.instance
159
+
160
+
161
+ # This module is included by all classes that are either bound to a server
162
+ # or should have a casemap.
163
+ #
164
+ module ServerOrCasemap
165
+
166
+ attr_reader :server
167
+
168
+ # This method initializes the instance variables @server and @casemap
169
+ # according to the values of the hash keys :server and :casemap in _opts_
170
+ #
171
+ def init_server_or_casemap(opts={})
172
+ @server = opts.fetch(:server, nil)
173
+ raise TypeError, "#{@server} is not a valid Irc::Server" if @server and not @server.kind_of?(Server)
174
+
175
+ @casemap = opts.fetch(:casemap, nil)
176
+ if @server
177
+ if @casemap
178
+ @server.casemap.must_be(@casemap)
179
+ @casemap = nil
180
+ end
181
+ else
182
+ @casemap = (@casemap || 'rfc1459').to_irc_casemap
183
+ end
184
+ end
185
+
186
+ # This is an auxiliary method: it returns true if the receiver fits the
187
+ # server and casemap specified in _opts_, false otherwise.
188
+ #
189
+ def fits_with_server_and_casemap?(opts={})
190
+ srv = opts.fetch(:server, nil)
191
+ cmap = opts.fetch(:casemap, nil)
192
+ cmap = cmap.to_irc_casemap unless cmap.nil?
193
+
194
+ if srv.nil?
195
+ return true if cmap.nil? or cmap == casemap
196
+ else
197
+ return true if srv == @server and (cmap.nil? or cmap == casemap)
198
+ end
199
+ return false
200
+ end
201
+
202
+ # Returns the casemap of the receiver, by looking at the bound
203
+ # @server (if possible) or at the @casemap otherwise
204
+ #
205
+ def casemap
206
+ return @server.casemap if defined?(@server) and @server
207
+ return @casemap
208
+ end
209
+
210
+ # Returns a hash with the current @server and @casemap as values of
211
+ # :server and :casemap
212
+ #
213
+ def server_and_casemap
214
+ h = {}
215
+ h[:server] = @server if defined?(@server) and @server
216
+ h[:casemap] = @casemap if defined?(@casemap) and @casemap
217
+ return h
218
+ end
219
+
220
+ # We allow up/downcasing with a different casemap
221
+ #
222
+ def irc_downcase(cmap=casemap)
223
+ self.to_s.irc_downcase(cmap)
224
+ end
225
+
226
+ # Up/downcasing something that includes this module returns its
227
+ # Up/downcased to_s form
228
+ #
229
+ def downcase
230
+ self.irc_downcase
231
+ end
232
+
233
+ # We allow up/downcasing with a different casemap
234
+ #
235
+ def irc_upcase(cmap=casemap)
236
+ self.to_s.irc_upcase(cmap)
237
+ end
238
+
239
+ # Up/downcasing something that includes this module returns its
240
+ # Up/downcased to_s form
241
+ #
242
+ def upcase
243
+ self.irc_upcase
244
+ end
245
+
246
+ end
247
+
248
+ end
249
+
250
+
251
+ # We start by extending the String class
252
+ # with some IRC-specific methods
253
+ #
254
+ class String
255
+
256
+ # This method returns the Irc::Casemap whose name is the receiver
257
+ #
258
+ def to_irc_casemap
259
+ Irc::Casemap.get(self) rescue raise TypeError, "Unkown Irc::Casemap #{self.inspect}"
260
+ end
261
+
262
+ # This method returns a string which is the downcased version of the
263
+ # receiver, according to the given _casemap_
264
+ #
265
+ #
266
+ def irc_downcase(casemap='rfc1459')
267
+ cmap = casemap.to_irc_casemap
268
+ self.tr(cmap.upper, cmap.lower)
269
+ end
270
+
271
+ # This is the same as the above, except that the string is altered in place
272
+ #
273
+ # See also the discussion about irc_downcase
274
+ #
275
+ def irc_downcase!(casemap='rfc1459')
276
+ cmap = casemap.to_irc_casemap
277
+ self.tr!(cmap.upper, cmap.lower)
278
+ end
279
+
280
+ # Upcasing functions are provided too
281
+ #
282
+ # See also the discussion about irc_downcase
283
+ #
284
+ def irc_upcase(casemap='rfc1459')
285
+ cmap = casemap.to_irc_casemap
286
+ self.tr(cmap.lower, cmap.upper)
287
+ end
288
+
289
+ # In-place upcasing
290
+ #
291
+ # See also the discussion about irc_downcase
292
+ #
293
+ def irc_upcase!(casemap='rfc1459')
294
+ cmap = casemap.to_irc_casemap
295
+ self.tr!(cmap.lower, cmap.upper)
296
+ end
297
+
298
+ # This method checks if the receiver contains IRC glob characters
299
+ #
300
+ # IRC has a very primitive concept of globs: a <tt>*</tt> stands for "any
301
+ # number of arbitrary characters", a <tt>?</tt> stands for "one and exactly
302
+ # one arbitrary character". These characters can be escaped by prefixing them
303
+ # with a slash (<tt>\\</tt>).
304
+ #
305
+ # A known limitation of this glob syntax is that there is no way to escape
306
+ # the escape character itself, so it's not possible to build a glob pattern
307
+ # where the escape character precedes a glob.
308
+ #
309
+ def has_irc_glob?
310
+ self =~ /^[*?]|[^\\][*?]/
311
+ end
312
+
313
+ # This method is used to convert the receiver into a Regular Expression
314
+ # that matches according to the IRC glob syntax
315
+ #
316
+ def to_irc_regexp
317
+ regmask = Regexp.escape(self)
318
+ regmask.gsub!(/(\\\\)?\\[*?]/) { |m|
319
+ case m
320
+ when /\\(\\[*?])/
321
+ $1
322
+ when /\\\*/
323
+ '.*'
324
+ when /\\\?/
325
+ '.'
326
+ else
327
+ raise "Unexpected match #{m} when converting #{self}"
328
+ end
329
+ }
330
+ Regexp.new("^#{regmask}$")
331
+ end
332
+
333
+ end
334
+
335
+
336
+ # ArrayOf is a subclass of Array whose elements are supposed to be all
337
+ # of the same class. This is not intended to be used directly, but rather
338
+ # to be subclassed as needed (see for example Irc::UserList and Irc::NetmaskList)
339
+ #
340
+ # Presently, only very few selected methods from Array are overloaded to check
341
+ # if the new elements are the correct class. An orthodox? method is provided
342
+ # to check the entire ArrayOf against the appropriate class.
343
+ #
344
+ class ArrayOf < Array
345
+
346
+ attr_reader :element_class
347
+
348
+ # Create a new ArrayOf whose elements are supposed to be all of type _kl_,
349
+ # optionally filling it with the elements from the Array argument.
350
+ #
351
+ def initialize(kl, ar=[])
352
+ raise TypeError, "#{kl.inspect} must be a class name" unless kl.kind_of?(Class)
353
+ super()
354
+ @element_class = kl
355
+ case ar
356
+ when Array
357
+ insert(0, *ar)
358
+ else
359
+ raise TypeError, "#{self.class} can only be initialized from an Array"
360
+ end
361
+ end
362
+
363
+ def inspect
364
+ self.__to_s__[0..-2].sub(/:[^:]+$/,"[#{@element_class}]\\0") + " #{super}>"
365
+ end
366
+
367
+ # Private method to check the validity of the elements passed to it
368
+ # and optionally raise an error
369
+ #
370
+ # TODO should it accept nils as valid?
371
+ #
372
+ def internal_will_accept?(raising, *els)
373
+ els.each { |el|
374
+ unless el.kind_of?(@element_class)
375
+ raise TypeError, "#{el.inspect} is not of class #{@element_class}" if raising
376
+ return false
377
+ end
378
+ }
379
+ return true
380
+ end
381
+ private :internal_will_accept?
382
+
383
+ # This method checks if the passed arguments are acceptable for our ArrayOf
384
+ #
385
+ def will_accept?(*els)
386
+ internal_will_accept?(false, *els)
387
+ end
388
+
389
+ # This method checks that all elements are of the appropriate class
390
+ #
391
+ def valid?
392
+ will_accept?(*self)
393
+ end
394
+
395
+ # This method is similar to the above, except that it raises an exception
396
+ # if the receiver is not valid
397
+ #
398
+ def validate
399
+ raise TypeError unless valid?
400
+ end
401
+
402
+ # Overloaded from Array#<<, checks for appropriate class of argument
403
+ #
404
+ def <<(el)
405
+ super(el) if internal_will_accept?(true, el)
406
+ end
407
+
408
+ # Overloaded from Array#&, checks for appropriate class of argument elements
409
+ #
410
+ def &(ar)
411
+ r = super(ar)
412
+ ArrayOf.new(@element_class, r) if internal_will_accept?(true, *r)
413
+ end
414
+
415
+ # Overloaded from Array#+, checks for appropriate class of argument elements
416
+ #
417
+ def +(ar)
418
+ ArrayOf.new(@element_class, super(ar)) if internal_will_accept?(true, *ar)
419
+ end
420
+
421
+ # Overloaded from Array#-, so that an ArrayOf is returned. There is no need
422
+ # to check the validity of the elements in the argument
423
+ #
424
+ def -(ar)
425
+ ArrayOf.new(@element_class, super(ar)) # if internal_will_accept?(true, *ar)
426
+ end
427
+
428
+ # Overloaded from Array#|, checks for appropriate class of argument elements
429
+ #
430
+ def |(ar)
431
+ ArrayOf.new(@element_class, super(ar)) if internal_will_accept?(true, *ar)
432
+ end
433
+
434
+ # Overloaded from Array#concat, checks for appropriate class of argument
435
+ # elements
436
+ #
437
+ def concat(ar)
438
+ super(ar) if internal_will_accept?(true, *ar)
439
+ end
440
+
441
+ # Overloaded from Array#insert, checks for appropriate class of argument
442
+ # elements
443
+ #
444
+ def insert(idx, *ar)
445
+ super(idx, *ar) if internal_will_accept?(true, *ar)
446
+ end
447
+
448
+ # Overloaded from Array#replace, checks for appropriate class of argument
449
+ # elements
450
+ #
451
+ def replace(ar)
452
+ super(ar) if (ar.kind_of?(ArrayOf) && ar.element_class <= @element_class) or internal_will_accept?(true, *ar)
453
+ end
454
+
455
+ # Overloaded from Array#push, checks for appropriate class of argument
456
+ # elements
457
+ #
458
+ def push(*ar)
459
+ super(*ar) if internal_will_accept?(true, *ar)
460
+ end
461
+
462
+ # Overloaded from Array#unshift, checks for appropriate class of argument(s)
463
+ #
464
+ def unshift(*els)
465
+ els.each { |el|
466
+ super(el) if internal_will_accept?(true, *els)
467
+ }
468
+ end
469
+
470
+ # We introduce the 'downcase' method, which maps downcase() to all the Array
471
+ # elements, properly failing when the elements don't have a downcase method
472
+ #
473
+ def downcase
474
+ self.map { |el| el.downcase }
475
+ end
476
+
477
+ # Modifying methods which we don't handle yet are made private
478
+ #
479
+ private :[]=, :collect!, :map!, :fill, :flatten!
480
+
481
+ end
482
+
483
+
484
+ # We extend the Regexp class with an Irc module which will contain some
485
+ # Irc-specific regexps
486
+ #
487
+ class Regexp
488
+
489
+ # We start with some general-purpose ones which will be used in the
490
+ # Irc module too, but are useful regardless
491
+ DIGITS = /\d+/
492
+ HEX_DIGIT = /[0-9A-Fa-f]/
493
+ HEX_DIGITS = /#{HEX_DIGIT}+/
494
+ HEX_OCTET = /#{HEX_DIGIT}#{HEX_DIGIT}?/
495
+ DEC_OCTET = /[01]?\d?\d|2[0-4]\d|25[0-5]/
496
+ DEC_IP_ADDR = /#{DEC_OCTET}\.#{DEC_OCTET}\.#{DEC_OCTET}\.#{DEC_OCTET}/
497
+ HEX_IP_ADDR = /#{HEX_OCTET}\.#{HEX_OCTET}\.#{HEX_OCTET}\.#{HEX_OCTET}/
498
+ IP_ADDR = /#{DEC_IP_ADDR}|#{HEX_IP_ADDR}/
499
+
500
+ # IPv6, from Resolv::IPv6, without the \A..\z anchors
501
+ HEX_16BIT = /#{HEX_DIGIT}{1,4}/
502
+ IP6_8Hex = /(?:#{HEX_16BIT}:){7}#{HEX_16BIT}/
503
+ IP6_CompressedHex = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)/
504
+ IP6_6Hex4Dec = /((?:#{HEX_16BIT}:){6,6})#{DEC_IP_ADDR}/
505
+ IP6_CompressedHex4Dec = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}:)*)#{DEC_IP_ADDR}/
506
+ IP6_ADDR = /(?:#{IP6_8Hex})|(?:#{IP6_CompressedHex})|(?:#{IP6_6Hex4Dec})|(?:#{IP6_CompressedHex4Dec})/
507
+
508
+ # We start with some IRC related regular expressions, used to match
509
+ # Irc::User nicks and users and Irc::Channel names
510
+ #
511
+ # For each of them we define two versions of the regular expression:
512
+ # * a generic one, which should match for any server but may turn out to
513
+ # match more than a specific server would accept
514
+ # * an RFC-compliant matcher
515
+ #
516
+ module Irc
517
+
518
+ # Channel-name-matching regexps
519
+ CHAN_FIRST = /[#&+]/
520
+ CHAN_SAFE = /![A-Z0-9]{5}/
521
+ CHAN_ANY = /[^\x00\x07\x0A\x0D ,:]/
522
+ GEN_CHAN = /(?:#{CHAN_FIRST}|#{CHAN_SAFE})#{CHAN_ANY}+/
523
+ RFC_CHAN = /#{CHAN_FIRST}#{CHAN_ANY}{1,49}|#{CHAN_SAFE}#{CHAN_ANY}{1,44}/
524
+
525
+ # Nick-matching regexps
526
+ SPECIAL_CHAR = /[\x5b-\x60\x7b-\x7d]/
527
+ NICK_FIRST = /#{SPECIAL_CHAR}|[[:alpha:]]/
528
+ NICK_ANY = /#{SPECIAL_CHAR}|[[:alnum:]]|-/
529
+ GEN_NICK = /#{NICK_FIRST}#{NICK_ANY}+/
530
+ RFC_NICK = /#{NICK_FIRST}#{NICK_ANY}{0,8}/
531
+
532
+ USER_CHAR = /[^\x00\x0a\x0d @]/
533
+ GEN_USER = /#{USER_CHAR}+/
534
+
535
+ # Host-matching regexps
536
+ HOSTNAME_COMPONENT = /[[:alnum:]](?:[[:alnum:]]|-)*[[:alnum:]]*/
537
+ HOSTNAME = /#{HOSTNAME_COMPONENT}(?:\.#{HOSTNAME_COMPONENT})*/
538
+ HOSTADDR = /#{IP_ADDR}|#{IP6_ADDR}/
539
+
540
+ GEN_HOST = /#{HOSTNAME}|#{HOSTADDR}/
541
+
542
+ # # FreeNode network replaces the host of affiliated users with
543
+ # # 'virtual hosts'
544
+ # # FIXME we need the true syntax to match it properly ...
545
+ # PDPC_HOST_PART = /[0-9A-Za-z.-]+/
546
+ # PDPC_HOST = /#{PDPC_HOST_PART}(?:\/#{PDPC_HOST_PART})+/
547
+
548
+ # # NOTE: the final optional and non-greedy dot is needed because some
549
+ # # servers (e.g. FreeNode) send the hostname of the services as "services."
550
+ # # which is not RFC compliant, but sadly done.
551
+ # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/
552
+
553
+ # Sadly, different networks have different, RFC-breaking ways of cloaking
554
+ # the actualy host address: see above for an example to handle FreeNode.
555
+ # Another example would be Azzurra, wich also inserts a "=" in the
556
+ # cloacked host. So let's just not care about this and go with the simplest
557
+ # thing:
558
+ GEN_HOST_EXT = /\S+/
559
+
560
+ # User-matching Regexp
561
+ GEN_USER_ID = /(#{GEN_NICK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/
562
+
563
+ # Things such has the BIP proxy send invalid nicks in a complete netmask,
564
+ # so we want to match this, rather: this matches either a compliant nick
565
+ # or a a string with a very generic nick, a very generic hostname after an
566
+ # @ sign, and an optional user after a !
567
+ BANG_AT = /#{GEN_NICK}|\S+?(?:!\S+?)?@\S+?/
568
+
569
+ # # For Netmask, we want to allow wildcards * and ? in the nick
570
+ # # (they are already allowed in the user and host part
571
+ # GEN_NICK_MASK = /(?:#{NICK_FIRST}|[?*])?(?:#{NICK_ANY}|[?*])+/
572
+
573
+ # # Netmask-matching Regexp
574
+ # GEN_MASK = /(#{GEN_NICK_MASK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/
575
+
576
+ end
577
+
578
+ end
579
+
580
+
581
+ module Irc
582
+
583
+
584
+ # A Netmask identifies each user by collecting its nick, username and
585
+ # hostname in the form <tt>nick!user@host</tt>
586
+ #
587
+ # Netmasks can also contain glob patterns in any of their components; in
588
+ # this form they are used to refer to more than a user or to a user
589
+ # appearing under different forms.
590
+ #
591
+ # Example:
592
+ # * <tt>*!*@*</tt> refers to everybody
593
+ # * <tt>*!someuser@somehost</tt> refers to user +someuser+ on host +somehost+
594
+ # regardless of the nick used.
595
+ #
596
+ class Netmask
597
+
598
+ # Netmasks have an associated casemap unless they are bound to a server
599
+ #
600
+ include ServerOrCasemap
601
+
602
+ attr_reader :nick, :user, :host
603
+ alias :ident :user
604
+
605
+ # Create a new Netmask from string _str_, which must be in the form
606
+ # _nick_!_user_@_host_
607
+ #
608
+ # It is possible to specify a server or a casemap in the optional Hash:
609
+ # these are used to associate the Netmask with the given server and to set
610
+ # its casemap: if a server is specified and a casemap is not, the server's
611
+ # casemap is used. If both a server and a casemap are specified, the
612
+ # casemap must match the server's casemap or an exception will be raised.
613
+ #
614
+ # Empty +nick+, +user+ or +host+ are converted to the generic glob pattern
615
+ #
616
+ def initialize(str="", opts={})
617
+ # First of all, check for server/casemap option
618
+ #
619
+ init_server_or_casemap(opts)
620
+
621
+ # Now we can see if the given string _str_ is an actual Netmask
622
+ if str.respond_to?(:to_str)
623
+ case str.to_str
624
+ # We match a pretty generic string, to work around non-compliant
625
+ # servers
626
+ when /^(?:(\S+?)(?:(?:!(\S+?))?@(\S+))?)?$/
627
+ # We do assignment using our internal methods
628
+ self.nick = $1
629
+ self.user = $2
630
+ self.host = $3
631
+ else
632
+ raise ArgumentError, "#{str.to_str.inspect} does not represent a valid #{self.class}"
633
+ end
634
+ else
635
+ raise TypeError, "#{str} cannot be converted to a #{self.class}"
636
+ end
637
+ end
638
+
639
+ # A Netmask is easily converted to a String for the usual representation.
640
+ # We skip the user or host parts if they are "*", unless we've been asked
641
+ # for the full form
642
+ #
643
+ def to_s
644
+ ret = nick.dup
645
+ ret << "!" << user unless user == "*"
646
+ ret << "@" << host unless host == "*"
647
+ return ret
648
+ end
649
+
650
+ def fullform
651
+ "#{nick}!#{user}@#{host}"
652
+ end
653
+
654
+ alias :to_str :fullform
655
+
656
+ # This method downcases the fullform of the netmask. While this may not be
657
+ # significantly different from the #downcase() method provided by the
658
+ # ServerOrCasemap mixin, it's significantly different for Netmask
659
+ # subclasses such as User whose simple downcasing uses the nick only.
660
+ #
661
+ def full_irc_downcase(cmap=casemap)
662
+ self.fullform.irc_downcase(cmap)
663
+ end
664
+
665
+ # full_downcase() will return the fullform downcased according to the
666
+ # User's own casemap
667
+ #
668
+ def full_downcase
669
+ self.full_irc_downcase
670
+ end
671
+
672
+ # This method returns a new Netmask which is the fully downcased version
673
+ # of the receiver
674
+ def downcased
675
+ return self.full_downcase.to_irc_netmask(server_and_casemap)
676
+ end
677
+
678
+ # Converts the receiver into a Netmask with the given (optional)
679
+ # server/casemap association. We return self unless a conversion
680
+ # is needed (different casemap/server)
681
+ #
682
+ # Subclasses of Netmask will return a new Netmask, using full_downcase
683
+ #
684
+ def to_irc_netmask(opts={})
685
+ if self.class == Netmask
686
+ return self if fits_with_server_and_casemap?(opts)
687
+ end
688
+ return self.full_downcase.to_irc_netmask(server_and_casemap.merge(opts))
689
+ end
690
+
691
+ # Converts the receiver into a User with the given (optional)
692
+ # server/casemap association. We return self unless a conversion
693
+ # is needed (different casemap/server)
694
+ #
695
+ def to_irc_user(opts={})
696
+ self.fullform.to_irc_user(server_and_casemap.merge(opts))
697
+ end
698
+
699
+ # Inspection of a Netmask reveals the server it's bound to (if there is
700
+ # one), its casemap and the nick, user and host part
701
+ #
702
+ def inspect
703
+ str = self.__to_s__[0..-2]
704
+ str << " @server=#{@server}" if defined?(@server) and @server
705
+ str << " @nick=#{@nick.inspect} @user=#{@user.inspect}"
706
+ str << " @host=#{@host.inspect} casemap=#{casemap.inspect}"
707
+ str << ">"
708
+ end
709
+
710
+ # Equality: two Netmasks are equal if they downcase to the same thing
711
+ #
712
+ # TODO we may want it to try other.to_irc_netmask
713
+ #
714
+ def ==(other)
715
+ return false unless other.kind_of?(self.class)
716
+ self.downcase == other.downcase
717
+ end
718
+
719
+ # This method changes the nick of the Netmask, defaulting to the generic
720
+ # glob pattern if the result is the null string.
721
+ #
722
+ def nick=(newnick)
723
+ @nick = newnick.to_s
724
+ @nick = "*" if @nick.empty?
725
+ end
726
+
727
+ # This method changes the user of the Netmask, defaulting to the generic
728
+ # glob pattern if the result is the null string.
729
+ #
730
+ def user=(newuser)
731
+ @user = newuser.to_s
732
+ @user = "*" if @user.empty?
733
+ end
734
+ alias :ident= :user=
735
+
736
+ # This method changes the hostname of the Netmask, defaulting to the generic
737
+ # glob pattern if the result is the null string.
738
+ #
739
+ def host=(newhost)
740
+ @host = newhost.to_s
741
+ @host = "*" if @host.empty?
742
+ end
743
+
744
+ # We can replace everything at once with data from another Netmask
745
+ #
746
+ def replace(other)
747
+ case other
748
+ when Netmask
749
+ nick = other.nick
750
+ user = other.user
751
+ host = other.host
752
+ @server = other.server
753
+ @casemap = other.casemap unless @server
754
+ else
755
+ replace(other.to_irc_netmask(server_and_casemap))
756
+ end
757
+ end
758
+
759
+ # This method checks if a Netmask is definite or not, by seeing if
760
+ # any of its components are defined by globs
761
+ #
762
+ def has_irc_glob?
763
+ return @nick.has_irc_glob? || @user.has_irc_glob? || @host.has_irc_glob?
764
+ end
765
+
766
+ def generalize
767
+ u = user.dup
768
+ unless u.has_irc_glob?
769
+ u.sub!(/^[in]=/, '=') or u.sub!(/^\W(\w+)/, '\1')
770
+ u = '*' + u
771
+ end
772
+
773
+ h = host.dup
774
+ unless h.has_irc_glob?
775
+ if h.include? '/'
776
+ h.sub!(/x-\w+$/, 'x-*')
777
+ else
778
+ h.match(/^[^\.]+\.[^\.]+$/) or
779
+ h.sub!(/azzurra[=-][0-9a-f]+/i, '*') or # hello, azzurra, you suck!
780
+ h.sub!(/^(\d+\.\d+\.\d+\.)\d+$/, '\1*') or
781
+ h.sub!(/^[^\.]+\./, '*.')
782
+ end
783
+ end
784
+ return Netmask.new("*!#{u}@#{h}", server_and_casemap)
785
+ end
786
+
787
+ # This method is used to match the current Netmask against another one
788
+ #
789
+ # The method returns true if each component of the receiver matches the
790
+ # corresponding component of the argument. By _matching_ here we mean
791
+ # that any netmask described by the receiver is also described by the
792
+ # argument.
793
+ #
794
+ # In this sense, matching is rather simple to define in the case when the
795
+ # receiver has no globs: it is just necessary to check if the argument
796
+ # describes the receiver, which can be done by matching it against the
797
+ # argument converted into an IRC Regexp (see String#to_irc_regexp).
798
+ #
799
+ # The situation is also easy when the receiver has globs and the argument
800
+ # doesn't, since in this case the result is false.
801
+ #
802
+ # The more complex case in which both the receiver and the argument have
803
+ # globs is not handled yet.
804
+ #
805
+ def matches?(arg)
806
+ cmp = arg.to_irc_netmask(:casemap => casemap)
807
+ debug "Matching #{self.fullform} against #{arg.inspect} (#{cmp.fullform})"
808
+ [:nick, :user, :host].each { |component|
809
+ us = self.send(component).irc_downcase(casemap)
810
+ them = cmp.send(component).irc_downcase(casemap)
811
+ if us.has_irc_glob? && them.has_irc_glob?
812
+ next if us == them
813
+ warn NotImplementedError
814
+ return false
815
+ end
816
+ return false if us.has_irc_glob? && !them.has_irc_glob?
817
+ return false unless us =~ them.to_irc_regexp
818
+ }
819
+ return true
820
+ end
821
+
822
+ # Case equality. Checks if arg matches self
823
+ #
824
+ def ===(arg)
825
+ arg.to_irc_netmask(:casemap => casemap).matches?(self)
826
+ end
827
+
828
+ # Sorting is done via the fullform
829
+ #
830
+ def <=>(arg)
831
+ case arg
832
+ when Netmask
833
+ self.fullform.irc_downcase(casemap) <=> arg.fullform.irc_downcase(casemap)
834
+ else
835
+ self.downcase <=> arg.downcase
836
+ end
837
+ end
838
+
839
+ end
840
+
841
+
842
+ # A NetmaskList is an ArrayOf <code>Netmask</code>s
843
+ #
844
+ class NetmaskList < ArrayOf
845
+
846
+ # Create a new NetmaskList, optionally filling it with the elements from
847
+ # the Array argument fed to it.
848
+ #
849
+ def initialize(ar=[])
850
+ super(Netmask, ar)
851
+ end
852
+
853
+ # We enhance the [] method by allowing it to pick an element that matches
854
+ # a given Netmask, a String or a Regexp
855
+ # TODO take into consideration the opportunity to use select() instead of
856
+ # find(), and/or a way to let the user choose which one to take (second
857
+ # argument?)
858
+ #
859
+ def [](*args)
860
+ if args.length == 1
861
+ case args[0]
862
+ when Netmask
863
+ self.find { |mask|
864
+ mask.matches?(args[0])
865
+ }
866
+ when String
867
+ self.find { |mask|
868
+ mask.matches?(args[0].to_irc_netmask(:casemap => mask.casemap))
869
+ }
870
+ when Regexp
871
+ self.find { |mask|
872
+ mask.fullform =~ args[0]
873
+ }
874
+ else
875
+ super(*args)
876
+ end
877
+ else
878
+ super(*args)
879
+ end
880
+ end
881
+
882
+ end
883
+
884
+ end
885
+
886
+
887
+ class String
888
+
889
+ # We keep extending String, this time adding a method that converts a
890
+ # String into an Irc::Netmask object
891
+ #
892
+ def to_irc_netmask(opts={})
893
+ Irc::Netmask.new(self, opts)
894
+ end
895
+
896
+ end
897
+
898
+
899
+ module Irc
900
+
901
+
902
+ # An IRC User is identified by his/her Netmask (which must not have globs).
903
+ # In fact, User is just a subclass of Netmask.
904
+ #
905
+ # Ideally, the user and host information of an IRC User should never
906
+ # change, and it shouldn't contain glob patterns. However, IRC is somewhat
907
+ # idiosincratic and it may be possible to know the nick of a User much before
908
+ # its user and host are known. Moreover, some networks (namely Freenode) may
909
+ # change the hostname of a User when (s)he identifies with Nickserv.
910
+ #
911
+ # As a consequence, we must allow changes to a User host and user attributes.
912
+ # We impose a restriction, though: they may not contain glob patterns, except
913
+ # for the special case of an unknown user/host which is represented by a *.
914
+ #
915
+ # It is possible to create a totally unknown User (e.g. for initializations)
916
+ # by setting the nick to * too.
917
+ #
918
+ # TODO list:
919
+ # * see if it's worth to add the other USER data
920
+ # * see if it's worth to add NICKSERV status
921
+ #
922
+ class User < Netmask
923
+ alias :to_s :nick
924
+
925
+ attr_accessor :real_name
926
+
927
+ # Create a new IRC User from a given Netmask (or anything that can be converted
928
+ # into a Netmask) provided that the given Netmask does not have globs.
929
+ #
930
+ def initialize(str="", opts={})
931
+ super
932
+ raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if nick.has_irc_glob? && nick != "*"
933
+ raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if user.has_irc_glob? && user != "*"
934
+ raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if host.has_irc_glob? && host != "*"
935
+ @away = false
936
+ @real_name = String.new
937
+ end
938
+
939
+ # The nick of a User may be changed freely, but it must not contain glob patterns.
940
+ #
941
+ def nick=(newnick)
942
+ raise "Can't change the nick to #{newnick}" if defined?(@nick) and newnick.has_irc_glob?
943
+ super
944
+ end
945
+
946
+ # We have to allow changing the user of an Irc User due to some networks
947
+ # (e.g. Freenode) changing hostmasks on the fly. We still check if the new
948
+ # user data has glob patterns though.
949
+ #
950
+ def user=(newuser)
951
+ raise "Can't change the username to #{newuser}" if defined?(@user) and newuser.has_irc_glob?
952
+ super
953
+ end
954
+
955
+ # We have to allow changing the host of an Irc User due to some networks
956
+ # (e.g. Freenode) changing hostmasks on the fly. We still check if the new
957
+ # host data has glob patterns though.
958
+ #
959
+ def host=(newhost)
960
+ raise "Can't change the hostname to #{newhost}" if defined?(@host) and newhost.has_irc_glob?
961
+ super
962
+ end
963
+
964
+ # Checks if a User is well-known or not by looking at the hostname and user
965
+ #
966
+ def known?
967
+ return nick != "*" && user != "*" && host != "*"
968
+ end
969
+
970
+ # Is the user away?
971
+ #
972
+ def away?
973
+ return @away
974
+ end
975
+
976
+ # Set the away status of the user. Use away=(nil) or away=(false)
977
+ # to unset away
978
+ #
979
+ def away=(msg="")
980
+ if msg
981
+ @away = msg
982
+ else
983
+ @away = false
984
+ end
985
+ end
986
+
987
+ # Since to_irc_user runs the same checks on server and channel as
988
+ # to_irc_netmask, we just try that and return self if it works.
989
+ #
990
+ # Subclasses of User will return self if possible.
991
+ #
992
+ def to_irc_user(opts={})
993
+ return self if fits_with_server_and_casemap?(opts)
994
+ return self.full_downcase.to_irc_user(opts)
995
+ end
996
+
997
+ # We can replace everything at once with data from another User
998
+ #
999
+ def replace(other)
1000
+ case other
1001
+ when User
1002
+ self.nick = other.nick
1003
+ self.user = other.user
1004
+ self.host = other.host
1005
+ @server = other.server
1006
+ @casemap = other.casemap unless @server
1007
+ @away = other.away?
1008
+ else
1009
+ self.replace(other.to_irc_user(server_and_casemap))
1010
+ end
1011
+ end
1012
+
1013
+ def modes_on(channel)
1014
+ case channel
1015
+ when Channel
1016
+ channel.modes_of(self)
1017
+ else
1018
+ return @server.channel(channel).modes_of(self) if @server
1019
+ raise "Can't resolve channel #{channel}"
1020
+ end
1021
+ end
1022
+
1023
+ def is_op?(channel)
1024
+ case channel
1025
+ when Channel
1026
+ channel.has_op?(self)
1027
+ else
1028
+ return @server.channel(channel).has_op?(self) if @server
1029
+ raise "Can't resolve channel #{channel}"
1030
+ end
1031
+ end
1032
+
1033
+ def is_voice?(channel)
1034
+ case channel
1035
+ when Channel
1036
+ channel.has_voice?(self)
1037
+ else
1038
+ return @server.channel(channel).has_voice?(self) if @server
1039
+ raise "Can't resolve channel #{channel}"
1040
+ end
1041
+ end
1042
+ end
1043
+
1044
+
1045
+ # A UserList is an ArrayOf <code>User</code>s
1046
+ # We derive it from NetmaskList, which allows us to inherit any special
1047
+ # NetmaskList method
1048
+ #
1049
+ class UserList < NetmaskList
1050
+
1051
+ # Create a new UserList, optionally filling it with the elements from
1052
+ # the Array argument fed to it.
1053
+ #
1054
+ def initialize(ar=[])
1055
+ super(ar)
1056
+ @element_class = User
1057
+ end
1058
+
1059
+ # Convenience method: convert the UserList to a list of nicks. The indices
1060
+ # are preserved
1061
+ #
1062
+ def nicks
1063
+ self.map { |user| user.nick }
1064
+ end
1065
+
1066
+ end
1067
+
1068
+ end
1069
+
1070
+ class String
1071
+
1072
+ # We keep extending String, this time adding a method that converts a
1073
+ # String into an Irc::User object
1074
+ #
1075
+ def to_irc_user(opts={})
1076
+ Irc::User.new(self, opts)
1077
+ end
1078
+
1079
+ end
1080
+
1081
+ module Irc
1082
+
1083
+ # An IRC Channel is identified by its name, and it has a set of properties:
1084
+ # * a Channel::Topic
1085
+ # * a UserList
1086
+ # * a set of Channel::Modes
1087
+ #
1088
+ # The Channel::Topic and Channel::Mode classes are defined within the
1089
+ # Channel namespace because they only make sense there
1090
+ #
1091
+ class Channel
1092
+
1093
+
1094
+ # Mode on a Channel
1095
+ #
1096
+ class Mode
1097
+ attr_reader :channel
1098
+ def initialize(ch)
1099
+ @channel = ch
1100
+ end
1101
+
1102
+ end
1103
+
1104
+ # Hash of modes. Subclass of Hash that defines any? and all?
1105
+ # to check if boolean modes (Type D) are set
1106
+ class ModeHash < Hash
1107
+ def any?(*ar)
1108
+ !!ar.find { |m| s = m.to_sym ; self[s] && self[s].set? }
1109
+ end
1110
+ def all?(*ar)
1111
+ !ar.find { |m| s = m.to_sym ; !(self[s] && self[s].set?) }
1112
+ end
1113
+ end
1114
+
1115
+ # Channel modes of type A manipulate lists
1116
+ #
1117
+ # Example: b (banlist)
1118
+ #
1119
+ class ModeTypeA < Mode
1120
+ attr_reader :list
1121
+ def initialize(ch)
1122
+ super
1123
+ @list = NetmaskList.new
1124
+ end
1125
+
1126
+ def set(val)
1127
+ nm = @channel.server.new_netmask(val)
1128
+ @list << nm unless @list.include?(nm)
1129
+ end
1130
+
1131
+ def reset(val)
1132
+ nm = @channel.server.new_netmask(val)
1133
+ @list.delete(nm)
1134
+ end
1135
+
1136
+ end
1137
+
1138
+
1139
+ # Channel modes of type B need an argument
1140
+ #
1141
+ # Example: k (key)
1142
+ #
1143
+ class ModeTypeB < Mode
1144
+ def initialize(ch)
1145
+ super
1146
+ @arg = nil
1147
+ end
1148
+
1149
+ def status
1150
+ @arg
1151
+ end
1152
+ alias :value :status
1153
+
1154
+ def set(val)
1155
+ @arg = val
1156
+ end
1157
+
1158
+ def reset(val)
1159
+ @arg = nil if @arg == val
1160
+ end
1161
+
1162
+ end
1163
+
1164
+
1165
+ # Channel modes that change the User prefixes are like
1166
+ # Channel modes of type B, except that they manipulate
1167
+ # lists of Users, so they are somewhat similar to channel
1168
+ # modes of type A
1169
+ #
1170
+ class UserMode < ModeTypeB
1171
+ attr_reader :list
1172
+ alias :users :list
1173
+ def initialize(ch)
1174
+ super
1175
+ @list = UserList.new
1176
+ end
1177
+
1178
+ def set(val)
1179
+ u = @channel.server.user(val)
1180
+ @list << u unless @list.include?(u)
1181
+ end
1182
+
1183
+ def reset(val)
1184
+ u = @channel.server.user(val)
1185
+ @list.delete(u)
1186
+ end
1187
+
1188
+ end
1189
+
1190
+
1191
+ # Channel modes of type C need an argument when set,
1192
+ # but not when they get reset
1193
+ #
1194
+ # Example: l (limit)
1195
+ #
1196
+ class ModeTypeC < Mode
1197
+ def initialize(ch)
1198
+ super
1199
+ @arg = nil
1200
+ end
1201
+
1202
+ def status
1203
+ @arg
1204
+ end
1205
+ alias :value :status
1206
+
1207
+ def set(val)
1208
+ @arg = val
1209
+ end
1210
+
1211
+ def reset
1212
+ @arg = nil
1213
+ end
1214
+
1215
+ end
1216
+
1217
+
1218
+ # Channel modes of type D are basically booleans
1219
+ #
1220
+ # Example: m (moderate)
1221
+ #
1222
+ class ModeTypeD < Mode
1223
+ def initialize(ch)
1224
+ super
1225
+ @set = false
1226
+ end
1227
+
1228
+ def set?
1229
+ return @set
1230
+ end
1231
+
1232
+ def set
1233
+ @set = true
1234
+ end
1235
+
1236
+ def reset
1237
+ @set = false
1238
+ end
1239
+
1240
+ end
1241
+
1242
+
1243
+ # A Topic represents the topic of a channel. It consists of
1244
+ # the topic itself, who set it and when
1245
+ #
1246
+ class Topic
1247
+ attr_accessor :text, :set_by, :set_on
1248
+ alias :to_s :text
1249
+
1250
+ # Create a new Topic setting the text, the creator and
1251
+ # the creation time
1252
+ #
1253
+ def initialize(text="", set_by="", set_on=Time.new)
1254
+ @text = text
1255
+ @set_by = set_by.to_irc_netmask
1256
+ @set_on = set_on
1257
+ end
1258
+
1259
+ # Replace a Topic with another one
1260
+ #
1261
+ def replace(topic)
1262
+ raise TypeError, "#{topic.inspect} is not of class #{self.class}" unless topic.kind_of?(self.class)
1263
+ @text = topic.text.dup
1264
+ @set_by = topic.set_by.dup
1265
+ @set_on = topic.set_on.dup
1266
+ end
1267
+
1268
+ # Returns self
1269
+ #
1270
+ def to_irc_channel_topic
1271
+ self
1272
+ end
1273
+
1274
+ end
1275
+
1276
+ end
1277
+
1278
+ end
1279
+
1280
+
1281
+ class String
1282
+
1283
+ # Returns an Irc::Channel::Topic with self as text
1284
+ #
1285
+ def to_irc_channel_topic
1286
+ Irc::Channel::Topic.new(self)
1287
+ end
1288
+
1289
+ end
1290
+
1291
+
1292
+ module Irc
1293
+
1294
+
1295
+ # Here we start with the actual Channel class
1296
+ #
1297
+ class Channel
1298
+
1299
+ include ServerOrCasemap
1300
+ attr_reader :name, :topic, :mode, :users
1301
+ alias :to_s :name
1302
+
1303
+ def inspect
1304
+ str = self.__to_s__[0..-2]
1305
+ str << " on server #{server}" if server
1306
+ str << " @name=#{@name.inspect} @topic=#{@topic.text.inspect}"
1307
+ str << " @users=[#{user_nicks.sort.join(', ')}]"
1308
+ str << ">"
1309
+ end
1310
+
1311
+ # Returns self
1312
+ #
1313
+ def to_irc_channel
1314
+ self
1315
+ end
1316
+
1317
+ # TODO Ho
1318
+ def user_nicks
1319
+ @users.map { |u| u.downcase }
1320
+ end
1321
+
1322
+ # Checks if the receiver already has a user with the given _nick_
1323
+ #
1324
+ def has_user?(nick)
1325
+ @users.index(nick.to_irc_user(server_and_casemap))
1326
+ end
1327
+
1328
+ # Returns the user with nick _nick_, if available
1329
+ #
1330
+ def get_user(nick)
1331
+ idx = has_user?(nick)
1332
+ @users[idx] if idx
1333
+ end
1334
+
1335
+ # Adds a user to the channel
1336
+ #
1337
+ def add_user(user, opts={})
1338
+ silent = opts.fetch(:silent, false)
1339
+ if has_user?(user)
1340
+ warn "Trying to add user #{user} to channel #{self} again" unless silent
1341
+ else
1342
+ @users << user.to_irc_user(server_and_casemap)
1343
+ end
1344
+ end
1345
+
1346
+ # Creates a new channel with the given name, optionally setting the topic
1347
+ # and an initial users list.
1348
+ #
1349
+ # No additional info is created here, because the channel flags and userlists
1350
+ # allowed depend on the server.
1351
+ #
1352
+ def initialize(name, topic=nil, users=[], opts={})
1353
+ raise ArgumentError, "Channel name cannot be empty" if name.to_s.empty?
1354
+ warn "Unknown channel prefix #{name[0].chr}" if name !~ /^[&#+!]/
1355
+ raise ArgumentError, "Invalid character in #{name.inspect}" if name =~ /[ \x07,]/
1356
+
1357
+ init_server_or_casemap(opts)
1358
+
1359
+ @name = name
1360
+
1361
+ @topic = topic ? topic.to_irc_channel_topic : Channel::Topic.new
1362
+
1363
+ @users = UserList.new
1364
+
1365
+ users.each { |u|
1366
+ add_user(u)
1367
+ }
1368
+
1369
+ # Flags
1370
+ @mode = ModeHash.new
1371
+ end
1372
+
1373
+ # Removes a user from the channel
1374
+ #
1375
+ def delete_user(user)
1376
+ @mode.each { |sym, mode|
1377
+ mode.reset(user) if mode.kind_of?(UserMode)
1378
+ }
1379
+ @users.delete(user)
1380
+ end
1381
+
1382
+ # The channel prefix
1383
+ #
1384
+ def prefix
1385
+ name[0].chr
1386
+ end
1387
+
1388
+ # A channel is local to a server if it has the '&' prefix
1389
+ #
1390
+ def local?
1391
+ name[0] == 0x26
1392
+ end
1393
+
1394
+ # A channel is modeless if it has the '+' prefix
1395
+ #
1396
+ def modeless?
1397
+ name[0] == 0x2b
1398
+ end
1399
+
1400
+ # A channel is safe if it has the '!' prefix
1401
+ #
1402
+ def safe?
1403
+ name[0] == 0x21
1404
+ end
1405
+
1406
+ # A channel is normal if it has the '#' prefix
1407
+ #
1408
+ def normal?
1409
+ name[0] == 0x23
1410
+ end
1411
+
1412
+ # Create a new mode
1413
+ #
1414
+ def create_mode(sym, kl)
1415
+ @mode[sym.to_sym] = kl.new(self)
1416
+ end
1417
+
1418
+ def modes_of(user)
1419
+ l = []
1420
+ @mode.map { |s, m|
1421
+ l << s if (m.class <= UserMode and m.list[user])
1422
+ }
1423
+ l
1424
+ end
1425
+
1426
+ def has_op?(user)
1427
+ @mode.has_key?(:o) and @mode[:o].list[user]
1428
+ end
1429
+
1430
+ def has_voice?(user)
1431
+ @mode.has_key?(:v) and @mode[:v].list[user]
1432
+ end
1433
+ end
1434
+
1435
+
1436
+ # A ChannelList is an ArrayOf <code>Channel</code>s
1437
+ #
1438
+ class ChannelList < ArrayOf
1439
+
1440
+ # Create a new ChannelList, optionally filling it with the elements from
1441
+ # the Array argument fed to it.
1442
+ #
1443
+ def initialize(ar=[])
1444
+ super(Channel, ar)
1445
+ end
1446
+
1447
+ # Convenience method: convert the ChannelList to a list of channel names.
1448
+ # The indices are preserved
1449
+ #
1450
+ def names
1451
+ self.map { |chan| chan.name }
1452
+ end
1453
+
1454
+ end
1455
+
1456
+ end
1457
+
1458
+
1459
+ class String
1460
+
1461
+ # We keep extending String, this time adding a method that converts a
1462
+ # String into an Irc::Channel object
1463
+ #
1464
+ def to_irc_channel(opts={})
1465
+ Irc::Channel.new(self, opts)
1466
+ end
1467
+
1468
+ end
1469
+
1470
+
1471
+ module Irc
1472
+
1473
+
1474
+ # An IRC Server represents the Server the client is connected to.
1475
+ #
1476
+ class Server
1477
+
1478
+ attr_reader :hostname, :version, :usermodes, :chanmodes
1479
+ alias :to_s :hostname
1480
+ attr_reader :supports, :capabilities
1481
+
1482
+ attr_reader :channels, :users
1483
+
1484
+ # TODO Ho
1485
+ def channel_names
1486
+ @channels.map { |ch| ch.downcase }
1487
+ end
1488
+
1489
+ # TODO Ho
1490
+ def user_nicks
1491
+ @users.map { |u| u.downcase }
1492
+ end
1493
+
1494
+ def inspect
1495
+ chans, users = [@channels, @users].map {|d|
1496
+ d.sort { |a, b|
1497
+ a.downcase <=> b.downcase
1498
+ }.map { |x|
1499
+ x.inspect
1500
+ }
1501
+ }
1502
+
1503
+ str = self.__to_s__[0..-2]
1504
+ str << " @hostname=#{hostname}"
1505
+ str << " @channels=#{chans}"
1506
+ str << " @users=#{users}"
1507
+ str << ">"
1508
+ end
1509
+
1510
+ # Create a new Server, with all instance variables reset to nil (for
1511
+ # scalar variables), empty channel and user lists and @supports
1512
+ # initialized to the default values for all known supported features.
1513
+ #
1514
+ def initialize
1515
+ @hostname = @version = @usermodes = @chanmodes = nil
1516
+
1517
+ @channels = ChannelList.new
1518
+
1519
+ @users = UserList.new
1520
+
1521
+ reset_capabilities
1522
+ end
1523
+
1524
+ # Resets the server capabilities
1525
+ #
1526
+ def reset_capabilities
1527
+ @supports = {
1528
+ :casemapping => 'rfc1459'.to_irc_casemap,
1529
+ :chanlimit => {},
1530
+ :chanmodes => {
1531
+ :typea => nil, # Type A: address lists
1532
+ :typeb => nil, # Type B: needs a parameter
1533
+ :typec => nil, # Type C: needs a parameter when set
1534
+ :typed => nil # Type D: must not have a parameter
1535
+ },
1536
+ :channellen => 50,
1537
+ :chantypes => "#&!+",
1538
+ :excepts => nil,
1539
+ :idchan => {},
1540
+ :invex => nil,
1541
+ :kicklen => nil,
1542
+ :maxlist => {},
1543
+ :modes => 3,
1544
+ :network => nil,
1545
+ :nicklen => 9,
1546
+ :prefix => {
1547
+ :modes => [:o, :v],
1548
+ :prefixes => [:"@", :+]
1549
+ },
1550
+ :safelist => nil,
1551
+ :statusmsg => nil,
1552
+ :std => nil,
1553
+ :targmax => {},
1554
+ :topiclen => nil
1555
+ }
1556
+ @capabilities = {}
1557
+ end
1558
+
1559
+ # Convert a mode (o, v, h, ...) to the corresponding
1560
+ # prefix (@, +, %, ...). See also mode_for_prefix
1561
+ def prefix_for_mode(mode)
1562
+ return @supports[:prefix][:prefixes][
1563
+ @supports[:prefix][:modes].index(mode.to_sym)
1564
+ ]
1565
+ end
1566
+
1567
+ # Convert a prefix (@, +, %, ...) to the corresponding
1568
+ # mode (o, v, h, ...). See also prefix_for_mode
1569
+ def mode_for_prefix(pfx)
1570
+ return @supports[:prefix][:modes][
1571
+ @supports[:prefix][:prefixes].index(pfx.to_sym)
1572
+ ]
1573
+ end
1574
+
1575
+ # Resets the Channel and User list
1576
+ #
1577
+ def reset_lists
1578
+ @users.reverse_each { |u|
1579
+ delete_user(u)
1580
+ }
1581
+ @channels.reverse_each { |u|
1582
+ delete_channel(u)
1583
+ }
1584
+ end
1585
+
1586
+ # Clears the server
1587
+ #
1588
+ def clear
1589
+ reset_lists
1590
+ reset_capabilities
1591
+ @hostname = @version = @usermodes = @chanmodes = nil
1592
+ end
1593
+
1594
+ # This method is used to parse a 004 RPL_MY_INFO line
1595
+ #
1596
+ def parse_my_info(line)
1597
+ ar = line.split(' ')
1598
+ @hostname = ar[0]
1599
+ @version = ar[1]
1600
+ @usermodes = ar[2]
1601
+ @chanmodes = ar[3]
1602
+ end
1603
+
1604
+ def noval_warn(key, val, &block)
1605
+ if val
1606
+ yield if block_given?
1607
+ else
1608
+ warn "No #{key.to_s.upcase} value"
1609
+ end
1610
+ end
1611
+
1612
+ def val_warn(key, val, &block)
1613
+ if val == true or val == false or val.nil?
1614
+ yield if block_given?
1615
+ else
1616
+ warn "No #{key.to_s.upcase} value must be specified, got #{val}"
1617
+ end
1618
+ end
1619
+ private :noval_warn, :val_warn
1620
+
1621
+ # This method is used to parse a 005 RPL_ISUPPORT line
1622
+ #
1623
+ # See the RPL_ISUPPORT draft[http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt]
1624
+ #
1625
+ def parse_isupport(line)
1626
+ debug "Parsing ISUPPORT #{line.inspect}"
1627
+ ar = line.split(' ')
1628
+ reparse = ""
1629
+ ar.each { |en|
1630
+ prekey, val = en.split('=', 2)
1631
+ if prekey =~ /^-(.*)/
1632
+ key = $1.downcase.to_sym
1633
+ val = false
1634
+ else
1635
+ key = prekey.downcase.to_sym
1636
+ end
1637
+ case key
1638
+ when :casemapping
1639
+ noval_warn(key, val) {
1640
+ @supports[key] = val.to_irc_casemap
1641
+ }
1642
+ when :chanlimit, :idchan, :maxlist, :targmax
1643
+ noval_warn(key, val) {
1644
+ groups = val.split(',')
1645
+ groups.each { |g|
1646
+ k, v = g.split(':')
1647
+ @supports[key][k] = v.to_i || 0
1648
+ if @supports[key][k] == 0
1649
+ warn "Deleting #{key} limit of 0 for #{k}"
1650
+ @supports[key].delete(k)
1651
+ end
1652
+ }
1653
+ }
1654
+ when :chanmodes
1655
+ noval_warn(key, val) {
1656
+ groups = val.split(',')
1657
+ @supports[key][:typea] = groups[0].scan(/./).map { |x| x.to_sym}
1658
+ @supports[key][:typeb] = groups[1].scan(/./).map { |x| x.to_sym}
1659
+ @supports[key][:typec] = groups[2].scan(/./).map { |x| x.to_sym}
1660
+ @supports[key][:typed] = groups[3].scan(/./).map { |x| x.to_sym}
1661
+ }
1662
+ when :channellen, :kicklen, :modes, :topiclen
1663
+ if val
1664
+ @supports[key] = val.to_i
1665
+ else
1666
+ @supports[key] = nil
1667
+ end
1668
+ when :chantypes
1669
+ @supports[key] = val # can also be nil
1670
+ when :excepts
1671
+ val ||= 'e'
1672
+ @supports[key] = val
1673
+ when :invex
1674
+ val ||= 'I'
1675
+ @supports[key] = val
1676
+ when :maxchannels
1677
+ noval_warn(key, val) {
1678
+ reparse += "CHANLIMIT=(chantypes):#{val} "
1679
+ }
1680
+ when :maxtargets
1681
+ noval_warn(key, val) {
1682
+ @supports[:targmax]['PRIVMSG'] = val.to_i
1683
+ @supports[:targmax]['NOTICE'] = val.to_i
1684
+ }
1685
+ when :network
1686
+ noval_warn(key, val) {
1687
+ @supports[key] = val
1688
+ }
1689
+ when :nicklen
1690
+ noval_warn(key, val) {
1691
+ @supports[key] = val.to_i
1692
+ }
1693
+ when :prefix
1694
+ if val
1695
+ val.scan(/\((.*)\)(.*)/) { |m, p|
1696
+ @supports[key][:modes] = m.scan(/./).map { |x| x.to_sym}
1697
+ @supports[key][:prefixes] = p.scan(/./).map { |x| x.to_sym}
1698
+ }
1699
+ else
1700
+ @supports[key][:modes] = nil
1701
+ @supports[key][:prefixes] = nil
1702
+ end
1703
+ when :safelist
1704
+ val_warn(key, val) {
1705
+ @supports[key] = val.nil? ? true : val
1706
+ }
1707
+ when :statusmsg
1708
+ noval_warn(key, val) {
1709
+ @supports[key] = val.scan(/./)
1710
+ }
1711
+ when :std
1712
+ noval_warn(key, val) {
1713
+ @supports[key] = val.split(',')
1714
+ }
1715
+ else
1716
+ @supports[key] = val.nil? ? true : val
1717
+ end
1718
+ }
1719
+ reparse.gsub!("(chantypes)",@supports[:chantypes])
1720
+ parse_isupport(reparse) unless reparse.empty?
1721
+ end
1722
+
1723
+ # Returns the casemap of the server.
1724
+ #
1725
+ def casemap
1726
+ @supports[:casemapping]
1727
+ end
1728
+
1729
+ # Returns User or Channel depending on what _name_ can be
1730
+ # a name of
1731
+ #
1732
+ def user_or_channel?(name)
1733
+ if supports[:chantypes].include?(name[0])
1734
+ return Channel
1735
+ else
1736
+ return User
1737
+ end
1738
+ end
1739
+
1740
+ # Returns the actual User or Channel object matching _name_
1741
+ #
1742
+ def user_or_channel(name)
1743
+ if supports[:chantypes].include?(name[0])
1744
+ return channel(name)
1745
+ else
1746
+ return user(name)
1747
+ end
1748
+ end
1749
+
1750
+ # Checks if the receiver already has a channel with the given _name_
1751
+ #
1752
+ def has_channel?(name)
1753
+ return false if name.nil_or_empty?
1754
+ channel_names.index(name.irc_downcase(casemap))
1755
+ end
1756
+ alias :has_chan? :has_channel?
1757
+
1758
+ # Returns the channel with name _name_, if available
1759
+ #
1760
+ def get_channel(name)
1761
+ return nil if name.nil_or_empty?
1762
+ idx = has_channel?(name)
1763
+ channels[idx] if idx
1764
+ end
1765
+ alias :get_chan :get_channel
1766
+
1767
+ # Create a new Channel object bound to the receiver and add it to the
1768
+ # list of <code>Channel</code>s on the receiver, unless the channel was
1769
+ # present already. In this case, the default action is to raise an
1770
+ # exception, unless _fails_ is set to false. An exception can also be
1771
+ # raised if _str_ is nil or empty, again only if _fails_ is set to true;
1772
+ # otherwise, the method just returns nil
1773
+ #
1774
+ def new_channel(name, topic=nil, users=[], fails=true)
1775
+ if name.nil_or_empty?
1776
+ raise "Tried to look for empty or nil channel name #{name.inspect}" if fails
1777
+ return nil
1778
+ end
1779
+ ex = get_chan(name)
1780
+ if ex
1781
+ raise "Channel #{name} already exists on server #{self}" if fails
1782
+ return ex
1783
+ else
1784
+
1785
+ prefix = name[0].chr
1786
+
1787
+ # Give a warning if the new Channel goes over some server limits.
1788
+ #
1789
+ # FIXME might need to raise an exception
1790
+ #
1791
+ warn "#{self} doesn't support channel prefix #{prefix}" unless @supports[:chantypes].include?(prefix)
1792
+ warn "#{self} doesn't support channel names this long (#{name.length} > #{@supports[:channellen]})" unless name.length <= @supports[:channellen]
1793
+
1794
+ # Next, we check if we hit the limit for channels of type +prefix+
1795
+ # if the server supports +chanlimit+
1796
+ #
1797
+ @supports[:chanlimit].keys.each { |k|
1798
+ next unless k.include?(prefix)
1799
+ count = 0
1800
+ channel_names.each { |n|
1801
+ count += 1 if k.include?(n[0])
1802
+ }
1803
+ # raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]
1804
+ warn "Already joined #{count}/#{@supports[:chanlimit][k]} channels with prefix #{k}, we may be going over server limits" if count >= @supports[:chanlimit][k]
1805
+ }
1806
+
1807
+ # So far, everything is fine. Now create the actual Channel
1808
+ #
1809
+ chan = Channel.new(name, topic, users, :server => self)
1810
+
1811
+ # We wade through +prefix+ and +chanmodes+ to create appropriate
1812
+ # lists and flags for this channel
1813
+
1814
+ @supports[:prefix][:modes].each { |mode|
1815
+ chan.create_mode(mode, Channel::UserMode)
1816
+ } if @supports[:prefix][:modes]
1817
+
1818
+ @supports[:chanmodes].each { |k, val|
1819
+ if val
1820
+ case k
1821
+ when :typea
1822
+ val.each { |mode|
1823
+ chan.create_mode(mode, Channel::ModeTypeA)
1824
+ }
1825
+ when :typeb
1826
+ val.each { |mode|
1827
+ chan.create_mode(mode, Channel::ModeTypeB)
1828
+ }
1829
+ when :typec
1830
+ val.each { |mode|
1831
+ chan.create_mode(mode, Channel::ModeTypeC)
1832
+ }
1833
+ when :typed
1834
+ val.each { |mode|
1835
+ chan.create_mode(mode, Channel::ModeTypeD)
1836
+ }
1837
+ end
1838
+ end
1839
+ }
1840
+
1841
+ @channels << chan
1842
+ # debug "Created channel #{chan.inspect}"
1843
+ return chan
1844
+ end
1845
+ end
1846
+
1847
+ # Returns the Channel with the given _name_ on the server,
1848
+ # creating it if necessary. This is a short form for
1849
+ # new_channel(_str_, nil, [], +false+)
1850
+ #
1851
+ def channel(str)
1852
+ new_channel(str,nil,[],false)
1853
+ end
1854
+
1855
+ # Remove Channel _name_ from the list of <code>Channel</code>s
1856
+ #
1857
+ def delete_channel(name)
1858
+ idx = has_channel?(name)
1859
+ raise "Tried to remove unmanaged channel #{name}" unless idx
1860
+ @channels.delete_at(idx)
1861
+ end
1862
+
1863
+ # Checks if the receiver already has a user with the given _nick_
1864
+ #
1865
+ def has_user?(nick)
1866
+ return false if nick.nil_or_empty?
1867
+ user_nicks.index(nick.irc_downcase(casemap))
1868
+ end
1869
+
1870
+ # Returns the user with nick _nick_, if available
1871
+ #
1872
+ def get_user(nick)
1873
+ idx = has_user?(nick)
1874
+ @users[idx] if idx
1875
+ end
1876
+
1877
+ # Create a new User object bound to the receiver and add it to the list
1878
+ # of <code>User</code>s on the receiver, unless the User was present
1879
+ # already. In this case, the default action is to raise an exception,
1880
+ # unless _fails_ is set to false. An exception can also be raised
1881
+ # if _str_ is nil or empty, again only if _fails_ is set to true;
1882
+ # otherwise, the method just returns nil
1883
+ #
1884
+ def new_user(str, fails=true)
1885
+ if str.nil_or_empty?
1886
+ raise "Tried to look for empty or nil user name #{str.inspect}" if fails
1887
+ return nil
1888
+ end
1889
+ tmp = str.to_irc_user(:server => self)
1890
+ old = get_user(tmp.nick)
1891
+ # debug "Tmp: #{tmp.inspect}"
1892
+ # debug "Old: #{old.inspect}"
1893
+ if old
1894
+ # debug "User already existed as #{old.inspect}"
1895
+ if tmp.known?
1896
+ if old.known?
1897
+ # debug "Both were known"
1898
+ # Do not raise an error: things like Freenode change the hostname after identification
1899
+ warning "User #{tmp.nick} has inconsistent Netmasks! #{self} knows #{old.inspect} but access was tried with #{tmp.inspect}" if old != tmp
1900
+ raise "User #{tmp} already exists on server #{self}" if fails
1901
+ end
1902
+ if old.fullform.downcase != tmp.fullform.downcase
1903
+ old.replace(tmp)
1904
+ # debug "Known user now #{old.inspect}"
1905
+ end
1906
+ end
1907
+ return old
1908
+ else
1909
+ warn "#{self} doesn't support nicknames this long (#{tmp.nick.length} > #{@supports[:nicklen]})" unless tmp.nick.length <= @supports[:nicklen]
1910
+ @users << tmp
1911
+ return @users.last
1912
+ end
1913
+ end
1914
+
1915
+ # Returns the User with the given Netmask on the server,
1916
+ # creating it if necessary. This is a short form for
1917
+ # new_user(_str_, +false+)
1918
+ #
1919
+ def user(str)
1920
+ new_user(str, false)
1921
+ end
1922
+
1923
+ # Deletes User _user_ from Channel _channel_
1924
+ #
1925
+ def delete_user_from_channel(user, channel)
1926
+ channel.delete_user(user)
1927
+ end
1928
+
1929
+ # Remove User _someuser_ from the list of <code>User</code>s.
1930
+ # _someuser_ must be specified with the full Netmask.
1931
+ #
1932
+ def delete_user(someuser)
1933
+ idx = has_user?(someuser)
1934
+ raise "Tried to remove unmanaged user #{user}" unless idx
1935
+ have = self.user(someuser)
1936
+ @channels.each { |ch|
1937
+ delete_user_from_channel(have, ch)
1938
+ }
1939
+ @users.delete_at(idx)
1940
+ end
1941
+
1942
+ # Create a new Netmask object with the appropriate casemap
1943
+ #
1944
+ def new_netmask(str)
1945
+ str.to_irc_netmask(:server => self)
1946
+ end
1947
+
1948
+ # Finds all <code>User</code>s on server whose Netmask matches _mask_
1949
+ #
1950
+ def find_users(mask)
1951
+ nm = new_netmask(mask)
1952
+ @users.inject(UserList.new) {
1953
+ |list, user|
1954
+ if user.user == "*" or user.host == "*"
1955
+ list << user if user.nick.irc_downcase(casemap) =~ nm.nick.irc_downcase(casemap).to_irc_regexp
1956
+ else
1957
+ list << user if user.matches?(nm)
1958
+ end
1959
+ list
1960
+ }
1961
+ end
1962
+
1963
+ end
1964
+
1965
+ end
1966
+