rbot 0.9.10 → 0.9.12
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +22 -4
- data/COPYING +5 -18
- data/ChangeLog +396 -0
- data/README +23 -25
- data/REQUIREMENTS +58 -12
- data/Rakefile +215 -0
- data/bin/rbot +47 -25
- data/bin/rbot-remote +76 -0
- data/data/rbot/languages/french.lang +5 -5
- data/data/rbot/languages/german.lang +4 -4
- data/data/rbot/languages/italian.lang +53 -0
- data/data/rbot/languages/japanese.lang +43 -0
- data/data/rbot/languages/russian.lang +60 -60
- data/data/rbot/languages/traditional_chinese.lang +75 -0
- data/data/rbot/plugins/alias.rb +192 -0
- data/data/rbot/plugins/autoop.rb +74 -37
- data/data/rbot/plugins/autorejoin.rb +15 -5
- data/data/rbot/plugins/bans.rb +454 -150
- data/data/rbot/plugins/bash.rb +173 -31
- data/data/rbot/plugins/botsnack.rb +37 -0
- data/data/rbot/plugins/cal.rb +11 -2
- data/data/rbot/plugins/chanserv.rb +132 -0
- data/data/rbot/plugins/chucknorris.rb +2 -2
- data/data/rbot/plugins/chucknorris.yml.gz +0 -0
- data/data/rbot/plugins/debugger.rb +136 -0
- data/data/rbot/plugins/deepthoughts.rb +89 -95
- data/data/rbot/plugins/delicious.rb +109 -0
- data/data/rbot/plugins/dice.rb +77 -25
- data/data/rbot/plugins/dict.rb +243 -0
- data/data/rbot/plugins/dictclient.rb +211 -0
- data/data/rbot/plugins/digg.rb +2 -2
- data/data/rbot/plugins/excuse.rb +4 -4
- data/data/rbot/plugins/factoids.rb +506 -0
- data/data/rbot/plugins/figlet.rb +127 -18
- data/data/rbot/plugins/fish.rb +93 -33
- data/data/rbot/plugins/forecast.rb +40 -27
- data/data/rbot/plugins/fortune.rb +86 -11
- data/data/rbot/plugins/freshmeat.rb +46 -12
- data/data/rbot/plugins/games/azgame.rb +565 -0
- data/data/rbot/plugins/games/quiz.rb +961 -0
- data/data/rbot/plugins/games/roshambo.rb +62 -0
- data/data/rbot/plugins/{roulette.rb → games/roulette.rb} +58 -8
- data/data/rbot/plugins/games/shiritori.rb +485 -0
- data/data/rbot/plugins/games/uno.rb +1205 -0
- data/data/rbot/plugins/games/wheelfortune.rb +615 -0
- data/data/rbot/plugins/grouphug.rb +76 -22
- data/data/rbot/plugins/hl2.rb +97 -0
- data/data/rbot/plugins/host.rb +10 -1
- data/data/rbot/plugins/httpd.rb.disabled +1 -0
- data/data/rbot/plugins/imdb.rb +511 -47
- data/data/rbot/plugins/insult.rb +32 -37
- data/data/rbot/plugins/iplookup.rb +224 -224
- data/data/rbot/plugins/karma.rb +45 -21
- data/data/rbot/plugins/keywords.rb +258 -160
- data/data/rbot/plugins/lart.rb +126 -95
- data/data/rbot/plugins/lastfm.rb +421 -12
- data/data/rbot/plugins/linkbot.rb +75 -0
- data/data/rbot/plugins/markov.rb +103 -46
- data/data/rbot/plugins/math.rb +6 -5
- data/data/rbot/plugins/modes.rb +114 -0
- data/data/rbot/plugins/nickrecover.rb +97 -0
- data/data/rbot/plugins/nickserv.rb +111 -48
- data/data/rbot/plugins/nslookup.rb +15 -19
- data/data/rbot/plugins/plugin.header +11 -0
- data/data/rbot/plugins/quakeauth.rb +74 -11
- data/data/rbot/plugins/quotes.rb +189 -233
- data/data/rbot/plugins/reaction.rb +415 -0
- data/data/rbot/plugins/remind.rb +11 -15
- data/data/rbot/plugins/remotectl.rb +29 -0
- data/data/rbot/plugins/ri.rb +74 -0
- data/data/rbot/plugins/rot13.rb +16 -1
- data/data/rbot/plugins/rss.rb +1219 -585
- data/data/rbot/plugins/salut.rb +226 -0
- data/data/rbot/plugins/script.rb +184 -0
- data/data/rbot/plugins/search.rb +233 -0
- data/data/rbot/plugins/seen.rb +44 -40
- data/data/rbot/plugins/shortenurls.rb +93 -0
- data/data/rbot/plugins/slashdot.rb +49 -11
- data/data/rbot/plugins/spell.rb +37 -27
- data/data/rbot/plugins/theyfightcrime.rb +36 -39
- data/data/rbot/plugins/threat.rb +25 -22
- data/data/rbot/plugins/time.rb +135 -0
- data/data/rbot/plugins/topic.rb +107 -50
- data/data/rbot/plugins/translator.rb +360 -0
- data/data/rbot/plugins/tube.rb +28 -40
- data/data/rbot/plugins/twitter.rb +187 -0
- data/data/rbot/plugins/urban.rb +62 -53
- data/data/rbot/plugins/url.rb +193 -409
- data/data/rbot/plugins/usermodes.rb +26 -0
- data/data/rbot/plugins/wall.rb +60 -0
- data/data/rbot/plugins/weather.rb +254 -598
- data/data/rbot/plugins/wow.rb +41 -37
- data/data/rbot/plugins/wserver.rb +17 -25
- data/data/rbot/plugins/youtube.rb +237 -0
- data/data/rbot/templates/lart/{larts → larts-english} +0 -0
- data/data/rbot/templates/lart/larts-italian +25 -0
- data/data/rbot/templates/lart/larts-japanese +10 -0
- data/data/rbot/templates/lart/{praises → praises-english} +0 -0
- data/data/rbot/templates/lart/praises-italian +4 -0
- data/data/rbot/templates/lart/praises-japanese +3 -0
- data/data/rbot/templates/quiz/win_messages +11 -0
- data/data/rbot/templates/salut/salut-english +40 -0
- data/data/rbot/templates/salut/salut-french +29 -0
- data/data/rbot/templates/salut/salut-italian +51 -0
- data/data/rbot/templates/salut/salut-japanese +31 -0
- data/lib/rbot/botuser.rb +940 -0
- data/lib/rbot/config-compat.rb +23 -0
- data/lib/rbot/config.rb +211 -276
- data/lib/rbot/core/auth.rb +1063 -0
- data/lib/rbot/core/basics.rb +214 -0
- data/lib/rbot/core/config.rb +330 -0
- data/lib/rbot/core/filters_ui.rb +64 -0
- data/lib/rbot/core/irclog.rb +292 -0
- data/lib/rbot/core/remote.rb +401 -0
- data/lib/rbot/core/unicode.rb +91 -0
- data/lib/rbot/core/userdata.rb +200 -0
- data/lib/rbot/core/utils/extends.rb +444 -0
- data/lib/rbot/core/utils/filters.rb +154 -0
- data/lib/rbot/core/utils/httputil.rb +689 -0
- data/lib/rbot/core/utils/utils.rb +717 -0
- data/lib/rbot/dbhash.rb +30 -9
- data/lib/rbot/irc.rb +1966 -0
- data/lib/rbot/ircbot.rb +816 -675
- data/lib/rbot/ircsocket.rb +246 -232
- data/lib/rbot/language.rb +86 -8
- data/lib/rbot/load-gettext.rb +154 -0
- data/lib/rbot/maskdb.rb +162 -0
- data/lib/rbot/message.rb +392 -65
- data/lib/rbot/messagemapper.rb +438 -94
- data/lib/rbot/pkgconfig.rb +6 -0
- data/lib/rbot/plugins.rb +713 -172
- data/lib/rbot/post-config.rb +1 -0
- data/lib/rbot/rbotconfig.rb +31 -12
- data/lib/rbot/registry.rb +79 -48
- data/lib/rbot/rfc2812.rb +927 -519
- data/lib/rbot/timer.rb +232 -167
- data/po/en_US/rbot-alias.po +82 -0
- data/po/en_US/rbot-autoop.po +0 -0
- data/po/en_US/rbot-autorejoin.po +0 -0
- data/po/en_US/rbot-azgame.po +200 -0
- data/po/en_US/rbot-bans.po +0 -0
- data/po/en_US/rbot-bash.po +0 -0
- data/po/en_US/rbot-botsnack.po +0 -0
- data/po/en_US/rbot-cal.po +3 -0
- data/po/en_US/rbot-chanserv.po +0 -0
- data/po/en_US/rbot-chucknorris.po +0 -0
- data/po/en_US/rbot-debugger.po +0 -0
- data/po/en_US/rbot-deepthoughts.po +0 -0
- data/po/en_US/rbot-delicious.po +0 -0
- data/po/en_US/rbot-dice.po +0 -0
- data/po/en_US/rbot-dict.po +0 -0
- data/po/en_US/rbot-dictclient.po +124 -0
- data/po/en_US/rbot-digg.po +0 -0
- data/po/en_US/rbot-eightball.po +0 -0
- data/po/en_US/rbot-excuse.po +0 -0
- data/po/en_US/rbot-factoids.po +107 -0
- data/po/en_US/rbot-figlet.po +36 -0
- data/po/en_US/rbot-fish.po +0 -0
- data/po/en_US/rbot-forecast.po +0 -0
- data/po/en_US/rbot-fortune.po +0 -0
- data/po/en_US/rbot-freshmeat.po +0 -0
- data/po/en_US/rbot-grouphug.po +18 -0
- data/po/en_US/rbot-hl2.po +0 -0
- data/po/en_US/rbot-host.po +3 -0
- data/po/en_US/rbot-imdb.po +0 -0
- data/po/en_US/rbot-insult.po +0 -0
- data/po/en_US/rbot-iplookup.po +0 -0
- data/po/en_US/rbot-karma.po +0 -0
- data/po/en_US/rbot-keywords.po +24 -0
- data/po/en_US/rbot-lart.po +0 -0
- data/po/en_US/rbot-lastfm.po +172 -0
- data/po/en_US/rbot-linkbot.po +0 -0
- data/po/en_US/rbot-markov.po +20 -0
- data/po/en_US/rbot-math.po +0 -0
- data/po/en_US/rbot-modes.po +0 -0
- data/po/en_US/rbot-nickrecover.po +40 -0
- data/po/en_US/rbot-nickserv.po +104 -0
- data/po/en_US/rbot-nslookup.po +0 -0
- data/po/en_US/rbot-quakeauth.po +0 -0
- data/po/en_US/rbot-quiz.po +0 -0
- data/po/en_US/rbot-quotes.po +108 -0
- data/po/en_US/rbot-reaction.po +0 -0
- data/po/en_US/rbot-remind.po +0 -0
- data/po/en_US/rbot-remotectl.po +0 -0
- data/po/en_US/rbot-ri.po +0 -0
- data/po/en_US/rbot-roshambo.po +0 -0
- data/po/en_US/rbot-rot13.po +0 -0
- data/po/en_US/rbot-roulette.po +0 -0
- data/po/en_US/rbot-rss.po +20 -0
- data/po/en_US/rbot-salut.po +0 -0
- data/po/en_US/rbot-script.po +0 -0
- data/po/en_US/rbot-search.po +0 -0
- data/po/en_US/rbot-seen.po +0 -0
- data/po/en_US/rbot-shiritori.po +113 -0
- data/po/en_US/rbot-shortenurls.po +0 -0
- data/po/en_US/rbot-slashdot.po +0 -0
- data/po/en_US/rbot-spell.po +54 -0
- data/po/en_US/rbot-theyfightcrime.po +0 -0
- data/po/en_US/rbot-threat.po +0 -0
- data/po/en_US/rbot-time.po +0 -0
- data/po/en_US/rbot-topic.po +0 -0
- data/po/en_US/rbot-translator.po +77 -0
- data/po/en_US/rbot-tube.po +0 -0
- data/po/en_US/rbot-twitter.po +24 -0
- data/po/en_US/rbot-uno.po +512 -0
- data/po/en_US/rbot-urban.po +0 -0
- data/po/en_US/rbot-url.po +0 -0
- data/po/en_US/rbot-usermodes.po +0 -0
- data/po/en_US/rbot-wall.po +33 -0
- data/po/en_US/rbot-weather.po +0 -0
- data/po/en_US/rbot-wheelfortune.po +205 -0
- data/po/en_US/rbot-wow.po +0 -0
- data/po/en_US/rbot-wserver.po +0 -0
- data/po/en_US/rbot-youtube.po +58 -0
- data/po/en_US/rbot.po +1097 -0
- data/po/fr/rbot-alias.po +97 -0
- data/po/fr/rbot-autoop.po +0 -0
- data/po/fr/rbot-autorejoin.po +0 -0
- data/po/fr/rbot-azgame.po +204 -0
- data/po/fr/rbot-bans.po +0 -0
- data/po/fr/rbot-bash.po +0 -0
- data/po/fr/rbot-botsnack.po +0 -0
- data/po/fr/rbot-cal.po +3 -0
- data/po/fr/rbot-chanserv.po +0 -0
- data/po/fr/rbot-chucknorris.po +0 -0
- data/po/fr/rbot-debugger.po +0 -0
- data/po/fr/rbot-deepthoughts.po +0 -0
- data/po/fr/rbot-delicious.po +0 -0
- data/po/fr/rbot-dice.po +0 -0
- data/po/fr/rbot-dict.po +0 -0
- data/po/fr/rbot-dictclient.po +134 -0
- data/po/fr/rbot-digg.po +0 -0
- data/po/fr/rbot-eightball.po +0 -0
- data/po/fr/rbot-excuse.po +0 -0
- data/po/fr/rbot-factoids.po +111 -0
- data/po/fr/rbot-figlet.po +37 -0
- data/po/fr/rbot-fish.po +0 -0
- data/po/fr/rbot-forecast.po +0 -0
- data/po/fr/rbot-fortune.po +0 -0
- data/po/fr/rbot-freshmeat.po +0 -0
- data/po/fr/rbot-grouphug.po +18 -0
- data/po/fr/rbot-hl2.po +0 -0
- data/po/fr/rbot-host.po +3 -0
- data/po/fr/rbot-imdb.po +0 -0
- data/po/fr/rbot-insult.po +0 -0
- data/po/fr/rbot-iplookup.po +0 -0
- data/po/fr/rbot-karma.po +0 -0
- data/po/fr/rbot-keywords.po +24 -0
- data/po/fr/rbot-lart.po +0 -0
- data/po/fr/rbot-lastfm.po +172 -0
- data/po/fr/rbot-linkbot.po +0 -0
- data/po/fr/rbot-markov.po +20 -0
- data/po/fr/rbot-math.po +0 -0
- data/po/fr/rbot-modes.po +0 -0
- data/po/fr/rbot-nickrecover.po +36 -0
- data/po/fr/rbot-nickserv.po +116 -0
- data/po/fr/rbot-nslookup.po +0 -0
- data/po/fr/rbot-quakeauth.po +0 -0
- data/po/fr/rbot-quiz.po +0 -0
- data/po/fr/rbot-quotes.po +138 -0
- data/po/fr/rbot-reaction.po +0 -0
- data/po/fr/rbot-remind.po +0 -0
- data/po/fr/rbot-remotectl.po +0 -0
- data/po/fr/rbot-ri.po +0 -0
- data/po/fr/rbot-roshambo.po +0 -0
- data/po/fr/rbot-rot13.po +0 -0
- data/po/fr/rbot-roulette.po +0 -0
- data/po/fr/rbot-rss.po +20 -0
- data/po/fr/rbot-salut.po +0 -0
- data/po/fr/rbot-script.po +0 -0
- data/po/fr/rbot-search.po +0 -0
- data/po/fr/rbot-seen.po +0 -0
- data/po/fr/rbot-shiritori.po +119 -0
- data/po/fr/rbot-shortenurls.po +0 -0
- data/po/fr/rbot-slashdot.po +0 -0
- data/po/fr/rbot-spell.po +56 -0
- data/po/fr/rbot-theyfightcrime.po +0 -0
- data/po/fr/rbot-threat.po +0 -0
- data/po/fr/rbot-time.po +0 -0
- data/po/fr/rbot-topic.po +0 -0
- data/po/fr/rbot-translator.po +90 -0
- data/po/fr/rbot-tube.po +0 -0
- data/po/fr/rbot-twitter.po +24 -0
- data/po/fr/rbot-uno.po +521 -0
- data/po/fr/rbot-urban.po +0 -0
- data/po/fr/rbot-url.po +0 -0
- data/po/fr/rbot-usermodes.po +0 -0
- data/po/fr/rbot-wall.po +33 -0
- data/po/fr/rbot-weather.po +0 -0
- data/po/fr/rbot-wheelfortune.po +255 -0
- data/po/fr/rbot-wow.po +0 -0
- data/po/fr/rbot-wserver.po +0 -0
- data/po/fr/rbot-youtube.po +64 -0
- data/po/fr/rbot.po +1133 -0
- data/po/it/rbot-alias.po +82 -0
- data/po/it/rbot-autoop.po +0 -0
- data/po/it/rbot-autorejoin.po +0 -0
- data/po/it/rbot-azgame.po +186 -0
- data/po/it/rbot-bans.po +0 -0
- data/po/it/rbot-bash.po +0 -0
- data/po/it/rbot-botsnack.po +0 -0
- data/po/it/rbot-cal.po +3 -0
- data/po/it/rbot-chanserv.po +0 -0
- data/po/it/rbot-chucknorris.po +0 -0
- data/po/it/rbot-debugger.po +0 -0
- data/po/it/rbot-deepthoughts.po +0 -0
- data/po/it/rbot-delicious.po +0 -0
- data/po/it/rbot-dice.po +0 -0
- data/po/it/rbot-dict.po +0 -0
- data/po/it/rbot-dictclient.po +111 -0
- data/po/it/rbot-digg.po +0 -0
- data/po/it/rbot-eightball.po +0 -0
- data/po/it/rbot-excuse.po +0 -0
- data/po/it/rbot-factoids.po +110 -0
- data/po/it/rbot-figlet.po +36 -0
- data/po/it/rbot-fish.po +0 -0
- data/po/it/rbot-forecast.po +0 -0
- data/po/it/rbot-fortune.po +0 -0
- data/po/it/rbot-freshmeat.po +0 -0
- data/po/it/rbot-grouphug.po +18 -0
- data/po/it/rbot-hl2.po +0 -0
- data/po/it/rbot-host.po +3 -0
- data/po/it/rbot-imdb.po +0 -0
- data/po/it/rbot-insult.po +0 -0
- data/po/it/rbot-iplookup.po +0 -0
- data/po/it/rbot-karma.po +0 -0
- data/po/it/rbot-keywords.po +24 -0
- data/po/it/rbot-lart.po +0 -0
- data/po/it/rbot-lastfm.po +172 -0
- data/po/it/rbot-linkbot.po +0 -0
- data/po/it/rbot-markov.po +20 -0
- data/po/it/rbot-math.po +0 -0
- data/po/it/rbot-modes.po +0 -0
- data/po/it/rbot-nickrecover.po +36 -0
- data/po/it/rbot-nickserv.po +104 -0
- data/po/it/rbot-nslookup.po +0 -0
- data/po/it/rbot-quakeauth.po +0 -0
- data/po/it/rbot-quiz.po +0 -0
- data/po/it/rbot-quotes.po +108 -0
- data/po/it/rbot-reaction.po +0 -0
- data/po/it/rbot-remind.po +0 -0
- data/po/it/rbot-remotectl.po +0 -0
- data/po/it/rbot-ri.po +0 -0
- data/po/it/rbot-roshambo.po +0 -0
- data/po/it/rbot-rot13.po +0 -0
- data/po/it/rbot-roulette.po +0 -0
- data/po/it/rbot-rss.po +20 -0
- data/po/it/rbot-salut.po +0 -0
- data/po/it/rbot-script.po +0 -0
- data/po/it/rbot-search.po +0 -0
- data/po/it/rbot-seen.po +0 -0
- data/po/it/rbot-shiritori.po +102 -0
- data/po/it/rbot-shortenurls.po +0 -0
- data/po/it/rbot-slashdot.po +0 -0
- data/po/it/rbot-spell.po +54 -0
- data/po/it/rbot-theyfightcrime.po +0 -0
- data/po/it/rbot-threat.po +0 -0
- data/po/it/rbot-time.po +0 -0
- data/po/it/rbot-topic.po +0 -0
- data/po/it/rbot-translator.po +77 -0
- data/po/it/rbot-tube.po +0 -0
- data/po/it/rbot-twitter.po +24 -0
- data/po/it/rbot-uno.po +564 -0
- data/po/it/rbot-urban.po +0 -0
- data/po/it/rbot-url.po +0 -0
- data/po/it/rbot-usermodes.po +0 -0
- data/po/it/rbot-wall.po +33 -0
- data/po/it/rbot-weather.po +0 -0
- data/po/it/rbot-wheelfortune.po +251 -0
- data/po/it/rbot-wow.po +0 -0
- data/po/it/rbot-wserver.po +0 -0
- data/po/it/rbot-youtube.po +64 -0
- data/po/it/rbot.po +1127 -0
- data/po/ja/rbot-alias.po +82 -0
- data/po/ja/rbot-autoop.po +0 -0
- data/po/ja/rbot-autorejoin.po +0 -0
- data/po/ja/rbot-azgame.po +194 -0
- data/po/ja/rbot-bans.po +0 -0
- data/po/ja/rbot-bash.po +0 -0
- data/po/ja/rbot-botsnack.po +0 -0
- data/po/ja/rbot-cal.po +3 -0
- data/po/ja/rbot-chanserv.po +0 -0
- data/po/ja/rbot-chucknorris.po +0 -0
- data/po/ja/rbot-debugger.po +0 -0
- data/po/ja/rbot-deepthoughts.po +0 -0
- data/po/ja/rbot-delicious.po +0 -0
- data/po/ja/rbot-dice.po +0 -0
- data/po/ja/rbot-dict.po +0 -0
- data/po/ja/rbot-dictclient.po +111 -0
- data/po/ja/rbot-digg.po +0 -0
- data/po/ja/rbot-eightball.po +0 -0
- data/po/ja/rbot-excuse.po +0 -0
- data/po/ja/rbot-factoids.po +107 -0
- data/po/ja/rbot-figlet.po +36 -0
- data/po/ja/rbot-fish.po +0 -0
- data/po/ja/rbot-forecast.po +0 -0
- data/po/ja/rbot-fortune.po +0 -0
- data/po/ja/rbot-freshmeat.po +0 -0
- data/po/ja/rbot-grouphug.po +18 -0
- data/po/ja/rbot-hl2.po +0 -0
- data/po/ja/rbot-host.po +3 -0
- data/po/ja/rbot-imdb.po +0 -0
- data/po/ja/rbot-insult.po +0 -0
- data/po/ja/rbot-iplookup.po +0 -0
- data/po/ja/rbot-karma.po +0 -0
- data/po/ja/rbot-keywords.po +24 -0
- data/po/ja/rbot-lart.po +0 -0
- data/po/ja/rbot-lastfm.po +172 -0
- data/po/ja/rbot-linkbot.po +0 -0
- data/po/ja/rbot-markov.po +20 -0
- data/po/ja/rbot-math.po +0 -0
- data/po/ja/rbot-modes.po +0 -0
- data/po/ja/rbot-nickrecover.po +36 -0
- data/po/ja/rbot-nickserv.po +104 -0
- data/po/ja/rbot-nslookup.po +0 -0
- data/po/ja/rbot-quakeauth.po +0 -0
- data/po/ja/rbot-quiz.po +0 -0
- data/po/ja/rbot-quotes.po +108 -0
- data/po/ja/rbot-reaction.po +0 -0
- data/po/ja/rbot-remind.po +0 -0
- data/po/ja/rbot-remotectl.po +0 -0
- data/po/ja/rbot-ri.po +0 -0
- data/po/ja/rbot-roshambo.po +0 -0
- data/po/ja/rbot-rot13.po +0 -0
- data/po/ja/rbot-roulette.po +0 -0
- data/po/ja/rbot-rss.po +20 -0
- data/po/ja/rbot-salut.po +0 -0
- data/po/ja/rbot-script.po +0 -0
- data/po/ja/rbot-search.po +0 -0
- data/po/ja/rbot-seen.po +0 -0
- data/po/ja/rbot-shiritori.po +115 -0
- data/po/ja/rbot-shortenurls.po +0 -0
- data/po/ja/rbot-slashdot.po +0 -0
- data/po/ja/rbot-spell.po +54 -0
- data/po/ja/rbot-theyfightcrime.po +0 -0
- data/po/ja/rbot-threat.po +0 -0
- data/po/ja/rbot-time.po +0 -0
- data/po/ja/rbot-topic.po +0 -0
- data/po/ja/rbot-translator.po +77 -0
- data/po/ja/rbot-tube.po +0 -0
- data/po/ja/rbot-twitter.po +24 -0
- data/po/ja/rbot-uno.po +512 -0
- data/po/ja/rbot-urban.po +0 -0
- data/po/ja/rbot-url.po +0 -0
- data/po/ja/rbot-usermodes.po +0 -0
- data/po/ja/rbot-wall.po +33 -0
- data/po/ja/rbot-weather.po +0 -0
- data/po/ja/rbot-wheelfortune.po +205 -0
- data/po/ja/rbot-wow.po +0 -0
- data/po/ja/rbot-wserver.po +0 -0
- data/po/ja/rbot-youtube.po +58 -0
- data/po/ja/rbot.po +999 -0
- data/po/rbot-alias.pot +83 -0
- data/po/rbot-autoop.pot +0 -0
- data/po/rbot-autorejoin.pot +0 -0
- data/po/rbot-azgame.pot +187 -0
- data/po/rbot-bans.pot +0 -0
- data/po/rbot-bash.pot +0 -0
- data/po/rbot-botsnack.pot +0 -0
- data/po/rbot-cal.pot +21 -0
- data/po/rbot-chanserv.pot +0 -0
- data/po/rbot-chucknorris.pot +0 -0
- data/po/rbot-debugger.pot +0 -0
- data/po/rbot-deepthoughts.pot +0 -0
- data/po/rbot-delicious.pot +0 -0
- data/po/rbot-dice.pot +0 -0
- data/po/rbot-dict.pot +0 -0
- data/po/rbot-dictclient.pot +112 -0
- data/po/rbot-digg.pot +0 -0
- data/po/rbot-eightball.pot +0 -0
- data/po/rbot-excuse.pot +0 -0
- data/po/rbot-factoids.pot +108 -0
- data/po/rbot-figlet.pot +37 -0
- data/po/rbot-fish.pot +0 -0
- data/po/rbot-forecast.pot +0 -0
- data/po/rbot-fortune.pot +0 -0
- data/po/rbot-freshmeat.pot +0 -0
- data/po/rbot-grouphug.pot +36 -0
- data/po/rbot-hl2.pot +0 -0
- data/po/rbot-host.pot +21 -0
- data/po/rbot-imdb.pot +0 -0
- data/po/rbot-insult.pot +0 -0
- data/po/rbot-iplookup.pot +0 -0
- data/po/rbot-karma.pot +0 -0
- data/po/rbot-keywords.pot +25 -0
- data/po/rbot-lart.pot +0 -0
- data/po/rbot-lastfm.pot +190 -0
- data/po/rbot-linkbot.pot +0 -0
- data/po/rbot-markov.pot +21 -0
- data/po/rbot-math.pot +0 -0
- data/po/rbot-modes.pot +0 -0
- data/po/rbot-nickrecover.pot +37 -0
- data/po/rbot-nickserv.pot +105 -0
- data/po/rbot-nslookup.pot +0 -0
- data/po/rbot-quakeauth.pot +0 -0
- data/po/rbot-quiz.pot +0 -0
- data/po/rbot-quotes.pot +109 -0
- data/po/rbot-reaction.pot +0 -0
- data/po/rbot-remind.pot +0 -0
- data/po/rbot-remotectl.pot +0 -0
- data/po/rbot-ri.pot +0 -0
- data/po/rbot-roshambo.pot +0 -0
- data/po/rbot-rot13.pot +0 -0
- data/po/rbot-roulette.pot +0 -0
- data/po/rbot-rss.pot +21 -0
- data/po/rbot-salut.pot +0 -0
- data/po/rbot-script.pot +0 -0
- data/po/rbot-search.pot +0 -0
- data/po/rbot-seen.pot +0 -0
- data/po/rbot-shiritori.pot +103 -0
- data/po/rbot-shortenurls.pot +0 -0
- data/po/rbot-slashdot.pot +0 -0
- data/po/rbot-spell.pot +55 -0
- data/po/rbot-theyfightcrime.pot +0 -0
- data/po/rbot-threat.pot +0 -0
- data/po/rbot-time.pot +0 -0
- data/po/rbot-topic.pot +0 -0
- data/po/rbot-translator.pot +78 -0
- data/po/rbot-tube.pot +0 -0
- data/po/rbot-twitter.pot +25 -0
- data/po/rbot-uno.pot +513 -0
- data/po/rbot-urban.pot +0 -0
- data/po/rbot-url.pot +0 -0
- data/po/rbot-usermodes.pot +0 -0
- data/po/rbot-wall.pot +33 -0
- data/po/rbot-weather.pot +0 -0
- data/po/rbot-wheelfortune.pot +206 -0
- data/po/rbot-wow.pot +0 -0
- data/po/rbot-wserver.pot +0 -0
- data/po/rbot-youtube.pot +59 -0
- data/po/rbot.pot +1004 -0
- data/po/zh_CN/rbot-alias.po +82 -0
- data/po/zh_CN/rbot-autoop.po +0 -0
- data/po/zh_CN/rbot-autorejoin.po +0 -0
- data/po/zh_CN/rbot-azgame.po +186 -0
- data/po/zh_CN/rbot-bans.po +0 -0
- data/po/zh_CN/rbot-bash.po +0 -0
- data/po/zh_CN/rbot-botsnack.po +0 -0
- data/po/zh_CN/rbot-cal.po +3 -0
- data/po/zh_CN/rbot-chanserv.po +0 -0
- data/po/zh_CN/rbot-chucknorris.po +0 -0
- data/po/zh_CN/rbot-debugger.po +0 -0
- data/po/zh_CN/rbot-deepthoughts.po +0 -0
- data/po/zh_CN/rbot-delicious.po +0 -0
- data/po/zh_CN/rbot-dice.po +0 -0
- data/po/zh_CN/rbot-dict.po +0 -0
- data/po/zh_CN/rbot-dictclient.po +111 -0
- data/po/zh_CN/rbot-digg.po +0 -0
- data/po/zh_CN/rbot-eightball.po +0 -0
- data/po/zh_CN/rbot-excuse.po +0 -0
- data/po/zh_CN/rbot-factoids.po +107 -0
- data/po/zh_CN/rbot-figlet.po +36 -0
- data/po/zh_CN/rbot-fish.po +0 -0
- data/po/zh_CN/rbot-forecast.po +0 -0
- data/po/zh_CN/rbot-fortune.po +0 -0
- data/po/zh_CN/rbot-freshmeat.po +0 -0
- data/po/zh_CN/rbot-grouphug.po +18 -0
- data/po/zh_CN/rbot-hl2.po +0 -0
- data/po/zh_CN/rbot-host.po +3 -0
- data/po/zh_CN/rbot-imdb.po +0 -0
- data/po/zh_CN/rbot-insult.po +0 -0
- data/po/zh_CN/rbot-iplookup.po +0 -0
- data/po/zh_CN/rbot-karma.po +0 -0
- data/po/zh_CN/rbot-keywords.po +24 -0
- data/po/zh_CN/rbot-lart.po +0 -0
- data/po/zh_CN/rbot-lastfm.po +172 -0
- data/po/zh_CN/rbot-linkbot.po +0 -0
- data/po/zh_CN/rbot-markov.po +20 -0
- data/po/zh_CN/rbot-math.po +0 -0
- data/po/zh_CN/rbot-modes.po +0 -0
- data/po/zh_CN/rbot-nickrecover.po +36 -0
- data/po/zh_CN/rbot-nickserv.po +104 -0
- data/po/zh_CN/rbot-nslookup.po +0 -0
- data/po/zh_CN/rbot-quakeauth.po +0 -0
- data/po/zh_CN/rbot-quiz.po +0 -0
- data/po/zh_CN/rbot-quotes.po +108 -0
- data/po/zh_CN/rbot-reaction.po +0 -0
- data/po/zh_CN/rbot-remind.po +0 -0
- data/po/zh_CN/rbot-remotectl.po +0 -0
- data/po/zh_CN/rbot-ri.po +0 -0
- data/po/zh_CN/rbot-roshambo.po +0 -0
- data/po/zh_CN/rbot-rot13.po +0 -0
- data/po/zh_CN/rbot-roulette.po +0 -0
- data/po/zh_CN/rbot-rss.po +20 -0
- data/po/zh_CN/rbot-salut.po +0 -0
- data/po/zh_CN/rbot-script.po +0 -0
- data/po/zh_CN/rbot-search.po +0 -0
- data/po/zh_CN/rbot-seen.po +0 -0
- data/po/zh_CN/rbot-shiritori.po +102 -0
- data/po/zh_CN/rbot-shortenurls.po +0 -0
- data/po/zh_CN/rbot-slashdot.po +0 -0
- data/po/zh_CN/rbot-spell.po +54 -0
- data/po/zh_CN/rbot-theyfightcrime.po +0 -0
- data/po/zh_CN/rbot-threat.po +0 -0
- data/po/zh_CN/rbot-time.po +0 -0
- data/po/zh_CN/rbot-topic.po +0 -0
- data/po/zh_CN/rbot-translator.po +77 -0
- data/po/zh_CN/rbot-tube.po +0 -0
- data/po/zh_CN/rbot-twitter.po +24 -0
- data/po/zh_CN/rbot-uno.po +512 -0
- data/po/zh_CN/rbot-urban.po +0 -0
- data/po/zh_CN/rbot-url.po +0 -0
- data/po/zh_CN/rbot-usermodes.po +0 -0
- data/po/zh_CN/rbot-wall.po +33 -0
- data/po/zh_CN/rbot-weather.po +0 -0
- data/po/zh_CN/rbot-wheelfortune.po +205 -0
- data/po/zh_CN/rbot-wow.po +0 -0
- data/po/zh_CN/rbot-wserver.po +0 -0
- data/po/zh_CN/rbot-youtube.po +58 -0
- data/po/zh_CN/rbot.po +1006 -0
- data/po/zh_TW/rbot-alias.po +82 -0
- data/po/zh_TW/rbot-autoop.po +0 -0
- data/po/zh_TW/rbot-autorejoin.po +0 -0
- data/po/zh_TW/rbot-azgame.po +186 -0
- data/po/zh_TW/rbot-bans.po +0 -0
- data/po/zh_TW/rbot-bash.po +0 -0
- data/po/zh_TW/rbot-botsnack.po +0 -0
- data/po/zh_TW/rbot-cal.po +3 -0
- data/po/zh_TW/rbot-chanserv.po +0 -0
- data/po/zh_TW/rbot-chucknorris.po +0 -0
- data/po/zh_TW/rbot-debugger.po +0 -0
- data/po/zh_TW/rbot-deepthoughts.po +0 -0
- data/po/zh_TW/rbot-delicious.po +0 -0
- data/po/zh_TW/rbot-dice.po +0 -0
- data/po/zh_TW/rbot-dict.po +0 -0
- data/po/zh_TW/rbot-dictclient.po +111 -0
- data/po/zh_TW/rbot-digg.po +0 -0
- data/po/zh_TW/rbot-eightball.po +0 -0
- data/po/zh_TW/rbot-excuse.po +0 -0
- data/po/zh_TW/rbot-factoids.po +107 -0
- data/po/zh_TW/rbot-figlet.po +36 -0
- data/po/zh_TW/rbot-fish.po +0 -0
- data/po/zh_TW/rbot-forecast.po +0 -0
- data/po/zh_TW/rbot-fortune.po +0 -0
- data/po/zh_TW/rbot-freshmeat.po +0 -0
- data/po/zh_TW/rbot-grouphug.po +18 -0
- data/po/zh_TW/rbot-hl2.po +0 -0
- data/po/zh_TW/rbot-host.po +3 -0
- data/po/zh_TW/rbot-imdb.po +0 -0
- data/po/zh_TW/rbot-insult.po +0 -0
- data/po/zh_TW/rbot-iplookup.po +0 -0
- data/po/zh_TW/rbot-karma.po +0 -0
- data/po/zh_TW/rbot-keywords.po +24 -0
- data/po/zh_TW/rbot-lart.po +0 -0
- data/po/zh_TW/rbot-lastfm.po +172 -0
- data/po/zh_TW/rbot-linkbot.po +0 -0
- data/po/zh_TW/rbot-markov.po +20 -0
- data/po/zh_TW/rbot-math.po +0 -0
- data/po/zh_TW/rbot-modes.po +0 -0
- data/po/zh_TW/rbot-nickrecover.po +36 -0
- data/po/zh_TW/rbot-nickserv.po +104 -0
- data/po/zh_TW/rbot-nslookup.po +0 -0
- data/po/zh_TW/rbot-quakeauth.po +0 -0
- data/po/zh_TW/rbot-quiz.po +0 -0
- data/po/zh_TW/rbot-quotes.po +108 -0
- data/po/zh_TW/rbot-reaction.po +0 -0
- data/po/zh_TW/rbot-remind.po +0 -0
- data/po/zh_TW/rbot-remotectl.po +0 -0
- data/po/zh_TW/rbot-ri.po +0 -0
- data/po/zh_TW/rbot-roshambo.po +0 -0
- data/po/zh_TW/rbot-rot13.po +0 -0
- data/po/zh_TW/rbot-roulette.po +0 -0
- data/po/zh_TW/rbot-rss.po +20 -0
- data/po/zh_TW/rbot-salut.po +0 -0
- data/po/zh_TW/rbot-script.po +0 -0
- data/po/zh_TW/rbot-search.po +0 -0
- data/po/zh_TW/rbot-seen.po +0 -0
- data/po/zh_TW/rbot-shiritori.po +102 -0
- data/po/zh_TW/rbot-shortenurls.po +0 -0
- data/po/zh_TW/rbot-slashdot.po +0 -0
- data/po/zh_TW/rbot-spell.po +54 -0
- data/po/zh_TW/rbot-theyfightcrime.po +0 -0
- data/po/zh_TW/rbot-threat.po +0 -0
- data/po/zh_TW/rbot-time.po +0 -0
- data/po/zh_TW/rbot-topic.po +0 -0
- data/po/zh_TW/rbot-translator.po +77 -0
- data/po/zh_TW/rbot-tube.po +0 -0
- data/po/zh_TW/rbot-twitter.po +24 -0
- data/po/zh_TW/rbot-uno.po +512 -0
- data/po/zh_TW/rbot-urban.po +0 -0
- data/po/zh_TW/rbot-url.po +0 -0
- data/po/zh_TW/rbot-usermodes.po +0 -0
- data/po/zh_TW/rbot-wall.po +33 -0
- data/po/zh_TW/rbot-weather.po +0 -0
- data/po/zh_TW/rbot-wheelfortune.po +205 -0
- data/po/zh_TW/rbot-wow.po +0 -0
- data/po/zh_TW/rbot-wserver.po +0 -0
- data/po/zh_TW/rbot-youtube.po +58 -0
- data/po/zh_TW/rbot.po +1034 -0
- data/setup.rb +800 -574
- metadata +723 -108
- data/data/rbot/contrib/plugins/figlet.rb +0 -20
- data/data/rbot/contrib/plugins/ri.rb +0 -83
- data/data/rbot/plugins/demauro.rb +0 -95
- data/data/rbot/plugins/google.rb +0 -53
- data/data/rbot/plugins/opme.rb +0 -19
- data/data/rbot/plugins/roshambo.rb +0 -54
- data/data/rbot/plugins/rubyurl.rb +0 -39
- data/data/rbot/plugins/tinyurl.rb +0 -39
- data/data/rbot/plugins/xmlrpc.rb.disabled +0 -52
- data/data/rbot/templates/levels.rbot +0 -32
- data/data/rbot/templates/users.rbot +0 -1
- data/lib/rbot/auth.rb +0 -314
- data/lib/rbot/channel.rb +0 -54
- data/lib/rbot/httputil.rb +0 -309
- data/lib/rbot/utils.rb +0 -83
data/lib/rbot/dbhash.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
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.
|
192
|
+
rescue Exception => e
|
193
|
+
error "failed to clean up environment: #{e.pretty_inspect}"
|
173
194
|
end
|
174
195
|
end
|
175
196
|
|
data/lib/rbot/irc.rb
ADDED
@@ -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
|
+
|