diff options
-rwxr-xr-x | bin/rbot | 3 | ||||
-rw-r--r-- | data/rbot/languages/russian.lang | 75 | ||||
-rw-r--r-- | data/rbot/plugins/tinyurl.rb | 39 | ||||
-rw-r--r-- | lib/rbot/auth.rb | 263 | ||||
-rw-r--r-- | lib/rbot/ircbot.rb | 4 | ||||
-rw-r--r-- | lib/rbot/plugins.rb | 12 |
6 files changed, 295 insertions, 101 deletions
@@ -56,7 +56,8 @@ end begin require 'rbot/ircbot' rescue LoadError => e - puts "Error: couldn't find the rbot/ircbot module for loading\n - did you install rbot using setup.rb?" + puts "Error: couldn't find the rbot/ircbot module (or one of its dependencies)\n - did you install rbot using setup.rb?" + puts e exit 2 end diff --git a/data/rbot/languages/russian.lang b/data/rbot/languages/russian.lang new file mode 100644 index 00000000..bd01f0dd --- /dev/null +++ b/data/rbot/languages/russian.lang @@ -0,0 +1,75 @@ +okay: + хорошо + понятно + да конечно + раз плюнуть + зделано + можно + секундочку + да да + секунду + без проблем +dunno: + незнаю + понятия не имею + без понятий + я не могу вам помочь + что? + если бы я знал + не спрашивай + а кто его знает + а вы уверены? + да нет! +dunno_about_X: + незнаю об %s + никогда не слышал %s :( + %s? а что это такое ? + но что значит %s? +insult: + %s: я вас ненавижу! + %s: :( + %s: у вас нет совести :( + %s: умри! + %s: я буду жаловатся + %s: а вы меня обидели +hello: + привет :) + как дела :) + драсти + эй там! + будь здоров + доброе утро + добрый день + как дела? +hello_X: + привет %s :) + %s: драсти + %s: будь здоров + %s: доброе утро + hey %s :) + %s: эй там! + %s: как ты там + рад встреч %s! + %s: хей! + приветствую %s? +welcome: + без проблем + всегда рад + мне было приятно + рад помочи + :) +thanks: + пасиба :) + ой как хорошо! + спасибо :) + =D + а мы друзья! +thanks_X: + %s: спасибо :) + %s: ой как хорошо!! + %s: =D + %s: пасиба :) + %s: а мы друзья +quit: + до свиданья + ну я пошел diff --git a/data/rbot/plugins/tinyurl.rb b/data/rbot/plugins/tinyurl.rb new file mode 100644 index 00000000..b74969d9 --- /dev/null +++ b/data/rbot/plugins/tinyurl.rb @@ -0,0 +1,39 @@ +require "rubygems" +require "shorturl" + +class TinyURL < Plugin + include WWW + + # return a help string when the bot is asked for help on this plugin + def help(plugin, topic="") + return "tinyurl <your long url>" + end + + # reply to a private message that we've registered for + def privmsg(m) + + # m.params contains the rest of the message, m.plugin contains the first + # word (useful because it's possible to register for multiple commands) + unless(m.params) + m.reply "incorrect usage. " + help(m.plugin) + end + + # TODO: might want to add a check here to validate the url + # if they call 'rubyurl help' backwards, don't return a lame link + + if (m.params == "help") + m.reply "Try again. Correct usage is: " + help(m.plugin) + return false + end + + # call the ShortURL library with the value of the url + url = ShortURL.shorten(m.params, :tinyurl) + + m.reply "tinyurl: #{url}" + + end +end + +# create an instance of the RubyURL class and register it as a plugin +tinyurl = TinyURL.new +tinyurl.register("tinyurl") diff --git a/lib/rbot/auth.rb b/lib/rbot/auth.rb index 16f092a5..9868e6ea 100644 --- a/lib/rbot/auth.rb +++ b/lib/rbot/auth.rb @@ -4,39 +4,45 @@ module Irc # netmask:: netmask to test against # Compare a netmask with a standard IRC glob, e.g foo!bar@baz.com would # match *!*@baz.com, foo!*@*, *!bar@*, etc. - def Irc.netmaskmatch(globmask, netmask) - regmask = globmask.gsub(/\*/, ".*?") + def Irc.netmaskmatch( globmask, netmask ) + regmask = Regexp.escape( globmask ) + regmask.gsub!( /\\\*/, '.*' ) return true if(netmask =~ /#{regmask}/i) return false end # check if a string is an actual IRC hostmask - def Irc.ismask(mask) + def Irc.ismask?(mask) mask =~ /^.+!.+@.+$/ end - + Struct.new( 'UserData', :level, :password, :hostmasks ) + # User-level authentication to allow/disallow access to bot commands based # on hostmask and userlevel. class IrcAuth - BotConfig.register BotConfigStringValue.new('auth.password', - :default => "rbotauth", :wizard => true, - :desc => "Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)") - + BotConfig.register BotConfigStringValue.new( 'auth.password', + :default => 'rbotauth', :wizard => true, + :desc => 'Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)' ) + BotConfig.register BotConfigIntegerValue.new( 'auth.default_level', + :default => 10, :wizard => true, + :desc => 'The default level for new/unknown users' ) + # create a new IrcAuth instance. # bot:: associated bot class def initialize(bot) @bot = bot - @users = Hash.new(0) + @users = Hash.new do + Struct::UserData.new(@bot.config['auth.default_level'], '', []) + end @levels = Hash.new(0) - if(File.exist?("#{@bot.botclass}/users.rbot")) - IO.foreach("#{@bot.botclass}/users.rbot") do |line| - if(line =~ /\s*(\d+)\s*(\S+)/) - level = $1.to_i - mask = $2 - @users[mask] = level - end - end + @currentUsers = Hash.new( nil ) + if( File.exist?( "#{@bot.botclass}/users.yaml" ) ) + File.open( "#{@bot.botclass}/users.yaml" ) { |file| + # work around YAML not maintaining the default proc + @loadedusers = YAML::parse(file).transform + @users.merge(@loadedusers) + } end if(File.exist?("#{@bot.botclass}/levels.rbot")) IO.foreach("#{@bot.botclass}/levels.rbot") do |line| @@ -51,15 +57,13 @@ module Irc # save current users and levels to files. # levels are written to #{botclass}/levels.rbot - # users are written to #{botclass}/users.rbot + # users are written to #{botclass}/users.yaml def save Dir.mkdir("#{@bot.botclass}") if(!File.exist?("#{@bot.botclass}")) - File.open("#{@bot.botclass}/users.rbot", "w") do |file| - @users.each do |key, value| - file.puts "#{value} #{key}" - end + File.open("#{@bot.botclass}/users.yaml", 'w') do |file| + file.puts @users.to_yaml end - File.open("#{@bot.botclass}/levels.rbot", "w") do |file| + File.open("#{@bot.botclass}/levels.rbot", 'w') do |file| @levels.each do |key, value| file.puts "#{value} #{key}" end @@ -73,30 +77,58 @@ module Irc # returns true if user with hostmask +mask+ is permitted to perform # +command+ optionally pass tell as the target for the "insufficient auth" # message, if the user is not authorised - def allow?(command, mask, tell=nil) - auth = userlevel(mask) - if(auth >= @levels[command]) - return true - else - debug "#{mask} is not allowed to perform #{command}" - @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell - return false - end + def allow?( command, mask, tell=nil ) + auth = @users[matchingUser(mask)].level # Directly using @users[] is possible, because UserData has a default setting + if( auth >= @levels[command] ) + return true + else + debug "#{mask} is not allowed to perform #{command}" + @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell + return false + end end # add user with hostmask matching +mask+ with initial auth level +level+ - def useradd(mask, level) - if(Irc.ismask(mask)) - @users[mask] = level - end + def useradd( username, level=@bot.config['auth.default_level'], password='', hostmask='*!*@*' ) + @users[username] = Struct::UserData.new( level, password, [hostmask] ) if ! @users.has_key? username end - + # mask:: mask of user to remove # remove user with mask +mask+ - def userdel(mask) - if(Irc.ismask(mask)) - @users.delete(mask) + def userdel( username ) + @users.delete( username ) if @users.has_key? username + end + + def usermod( username, item, value=nil ) + if @users.has_key?( username ) + case item + when 'hostmask' + if Irc.ismask?( value ) + @users[username].hostmasks = [ value ] + return true + end + when '+hostmask' + if Irc.ismask?( value ) + @users[username].hostmasks += [ value ] + return true + end + when '-hostmask' + if Irc.ismask?( value ) + @users[username].hostmasks -= [ value ] + return true + end + when 'password' + @users[username].password = value + return true + when 'level' + @users[username].level = value.to_i + return true + else + debug "usermod: Tried to modify unknown item #{item}" + @bot.say tell, "Unknown item #{item}" if tell + end end + return false end # command:: command to adjust @@ -106,30 +138,32 @@ module Irc @levels[command] = level end - # specific users. - # mask:: mask of user - # returns the authlevel of user with mask +mask+ - # finds the matching user which has the highest authlevel (so you can have - # a default level of 5 for *!*@*, and yet still give higher levels to - def userlevel(mask) - # go through hostmask list, find match with _highest_ level (all users - # will match *!*@*) - level = 0 - @users.each {|user,userlevel| - if(Irc.netmaskmatch(user, mask)) - level = userlevel if userlevel > level + def matchingUser( mask ) + currentUser = nil + currentLevel = 0 + @users.each { |user, data| # TODO Will get easier if YPaths are used... + if data.level > currentLevel + data.hostmasks.each { |hostmask| + if Irc.netmaskmatch( hostmask, mask ) + currentUser = user + currentLevel = data.level + end + } end } - level + currentUser + end + + def identify( mask, username, password ) + usermod( username, '+hostmask', mask ) if @users.has_key? username && @users[username].password == password + debug "User identified: #{username}" end # return all currently defined commands (for which auth is required) and # their required authlevels def showlevels - reply = "Current levels are:" - @levels.sort.each {|a| - key = a[0] - value = a[1] + reply = 'Current levels are:' + @levels.sort.each { |key, value| reply += " #{key}(#{value})" } reply @@ -137,32 +171,46 @@ module Irc # return all currently defined users and their authlevels def showusers - reply = "Current users are:" - @users.sort.each {|a| - key = a[0] - value = a[1] - reply += " #{key}(#{value})" + reply = 'Current users are:' + @users.sort.each { |key, value| + reply += " #{key}(#{value.level})" } reply end - + + def showdetails( username ) + if @users.has_key? username + reply = "#{username}(#{@users[username].level}):" + @users[username].hostmasks.each { |hostmask| + reply += " #{hostmask}" + } + end + reply + end + # module help - def help(topic="") + def help(topic='') case topic - when "setlevel" - return "setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)" - when "useradd" - return "useradd <mask> <level> => Add user <mask> at level <level> (private addressing only)" - when "userdel" - return "userdel <mask> => Remove user <mask> (private addressing only)" - when "auth" - return "auth <masterpw> => Recognise your hostmask as bot master (private addressing only)" - when "levels" - return "levels => list commands and their required levels (private addressing only)" - when "users" - return "users => list users and their levels (private addressing only)" + when 'setlevel' + return 'setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)' + when 'useradd' + return 'useradd <username> => Add user <mask>, you still need to set him up correctly (private addressing only)' + when 'userdel' + return 'userdel <username> => Remove user <username> (private addressing only)' + when 'usermod' + return 'usermod <username> <item> <value> => Modify <username>s settings. Valid <item>s are: hostmask, (+|-)hostmask, password, level (private addressing only)' + when 'auth' + return 'auth <masterpw> => Create a user with your hostmask and master password as bot master (private addressing only)' + when 'levels' + return 'levels => list commands and their required levels (private addressing only)' + when 'users' + return 'users [<username>]=> list users and their levels or details about <username> (private addressing only)' + when 'whoami' + return 'whoami => Show as whom you are recognized (private addressing only)' + when 'identify' + return 'identify <username> <password> => Identify your hostmask as belonging to <username> (private addressing only)' else - return "Auth module (User authentication) topics: setlevel, useradd, userdel, auth, levels, users" + return 'Auth module (User authentication) topics: setlevel, useradd, userdel, usermod, auth, levels, users, whoami, identify' end end @@ -171,31 +219,58 @@ module Irc if(m.address? && m.private?) case m.message when (/^setlevel\s+(\S+)\s+(\d+)$/) - if(@bot.auth.allow?("auth", m.source, m.replyto)) - @bot.auth.setlevel($1, $2.to_i) + if( @bot.auth.allow?( 'auth', m.source, m.replyto ) ) + @bot.auth.setlevel( $1, $2.to_i ) m.reply "level for #$1 set to #$2" end - when (/^useradd\s+(\S+)\s+(\d+)/) - if(@bot.auth.allow?("auth", m.source, m.replyto)) - @bot.auth.useradd($1, $2.to_i) - m.reply "added user #$1 at level #$2" + when( /^useradd\s+(\S+)/ ) # FIXME Needs review!!! (\s+(\S+)(\s+(\S+)(\s+(\S+))?)?)? Should this part be added to make complete useradds possible? + if( @bot.auth.allow?( 'auth', m.source, m.replyto ) ) + @bot.auth.useradd( $1 ) + m.reply "added user #$1, please set him up correctly" end - when (/^userdel\s+(\S+)/) - if(@bot.auth.allow?("auth", m.source, m.replyto)) - @bot.auth.userdel($1) + when( /^userdel\s+(\S+)/ ) + if( @bot.auth.allow?( 'auth', m.source, m.replyto ) ) + @bot.auth.userdel( $1 ) m.reply "user #$1 is gone" end + when( /^usermod\s+(\S+)\s+(\S+)\s+(\S+)/ ) + if( @bot.auth.allow?('auth', m.source, m.replyto ) ) + if( @bot.auth.usermod( $1, $2, $3 ) ) + m.reply "Set #$2 of #$1 to #$3" + else + m.reply "Failed to set #$2 of #$1 to #$3" + end + end when (/^auth\s+(\S+)/) - if($1 == @bot.config["auth.password"]) - @bot.auth.useradd(Regexp.escape(m.source), 1000) - m.reply "Identified, security level maxed out" + if( $1 == @bot.config['auth.password'] ) + if ! @users.has_key? 'master' + @bot.auth.useradd( 'master', 1000, @bot.config['auth.password'], m.source ) + else + @bot.usermod( 'master', '+hostmask', m.source ) + end + m.reply 'Identified, security level maxed out' + else + m.reply 'Incorrect password' + end + when( /^identify\s+(\S+)\s+(\S+)/ ) + if( @bot.auth.identify( m.source, $1, $2 ) ) + m.reply "Identified as #$1(#{@users[$1].level($1)}" + else + m.reply 'Incorrect username/password' + end + when( 'whoami' ) + user = @bot.auth.matchingUser( m.source ) + if user + m.reply "I recognize you as #{user}(#{@users[user].level})" else - m.reply "incorrect password" + m.reply 'You don\'t belong to any user.' end - when ("levels") - m.reply @bot.auth.showlevels if(@bot.auth.allow?("config", m.source, m.replyto)) - when ("users") - m.reply @bot.auth.showusers if(@bot.auth.allow?("config", m.source, m.replyto)) + when( /^users\s+(\S+)/ ) + m.reply @bot.auth.showdetails( $1 ) if( @bot.auth.allow?( 'auth', m.source, m.replyto ) ) + when ( 'levels' ) + m.reply @bot.auth.showlevels if( @bot.auth.allow?( 'config', m.source, m.replyto ) ) + when ( 'users' ) + m.reply @bot.auth.showusers if( @bot.auth.allow?( 'users', m.source, m.replyto ) ) end end end diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb index 271d0d70..435c6284 100644 --- a/lib/rbot/ircbot.rb +++ b/lib/rbot/ircbot.rb @@ -329,8 +329,8 @@ class IrcBot def mainloop while true begin - connect - @timer.start + connect + @timer.start while true if @socket.select diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb index 893bb414..f45e44f4 100644 --- a/lib/rbot/plugins.rb +++ b/lib/rbot/plugins.rb @@ -200,7 +200,8 @@ module Plugins debug "loading plugin #{tmpfilename}" plugin_module.module_eval(plugin_string) processed << file - rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err + rescue Exception => err + # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err puts "warning: plugin #{tmpfilename} load failed: " + err puts err.backtrace.join("\n") end @@ -250,7 +251,8 @@ module Plugins if(@@plugins.has_key?(key)) begin return @@plugins[key].help(key, params) - rescue TimeoutError, StandardError, NameError, SyntaxError => err + rescue Exception => err + #rescue TimeoutError, StandardError, NameError, SyntaxError => err puts "plugin #{@@plugins[key].name} help() failed: " + err puts err.backtrace.join("\n") end @@ -267,7 +269,8 @@ module Plugins if(p.respond_to? method) begin p.send method, *args - rescue TimeoutError, StandardError, NameError, SyntaxError => err + rescue Exception => err + #rescue TimeoutError, StandardError, NameError, SyntaxError => err puts "plugin #{p.name} #{method}() failed: " + err puts err.backtrace.join("\n") end @@ -284,7 +287,8 @@ module Plugins @@bot.auth.allow?(m.plugin, m.source, m.replyto)) begin @@plugins[m.plugin].privmsg(m) - rescue TimeoutError, StandardError, NameError, SyntaxError => err + rescue Exception => err + #rescue TimeoutError, StandardError, NameError, SyntaxError => err puts "plugin #{@@plugins[m.plugin].name} privmsg() failed: " + err puts err.backtrace.join("\n") end |