X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=data%2Frbot%2Fplugins%2Fkeywords.rb;h=5ed5256544a7e4e32a30e4901fccdb4b04d0c66b;hb=71f81ee086db58b94b69d3e4082cbadec1b7c245;hp=dc8d4ac65c9733eb4ec0f5caed7aeffbd81a8dc8;hpb=474629cc9bf83c8d1de713a85c5ff36a7dcd2bf3;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/data/rbot/plugins/keywords.rb b/data/rbot/plugins/keywords.rb index dc8d4ac6..5ed52565 100644 --- a/data/rbot/plugins/keywords.rb +++ b/data/rbot/plugins/keywords.rb @@ -12,7 +12,7 @@ class Keyword # type:: type of keyword (e.g "is" or "are") # values:: array of values - # + # # create a keyword of type +type+ with values +values+ def initialize(type, values) @type = type.downcase @@ -28,6 +28,19 @@ class Keyword end end + # return an array of all the possible values + def to_factoids(key) + ar = Array.new + @values.each { |val| + debug "key #{key}, value #{val}" + vals = val.split(" or ") + vals.each { |v| + ar << "%s %s %s" % [key, @type, v] + } + } + return ar + end + # describe the keyword (show all values without interpolation) def desc @values.join(" | ") @@ -71,7 +84,7 @@ class Keyword end end -# keywords class. +# keywords class. # # Handles all that stuff like "bot: foo is bar", "bot: foo?" # @@ -79,13 +92,16 @@ end # handle it, checks for a keyword command or lookup, otherwise the message # is delegated to plugins class Keywords < Plugin - BotConfig.register BotConfigBooleanValue.new('keyword.listen', + Config.register Config::BooleanValue.new('keyword.listen', :default => false, :desc => "Should the bot listen to all chat and attempt to automatically detect keywords? (e.g. by spotting someone say 'foo is bar')") - BotConfig.register BotConfigBooleanValue.new('keyword.address', + Config.register Config::BooleanValue.new('keyword.address', :default => true, :desc => "Should the bot require that keyword lookups are addressed to it? If not, the bot will attempt to lookup foo if someone says 'foo?' in channel") - + Config.register Config::IntegerValue.new('keyword.search_results', + :default => 3, + :desc => "How many search results to display at a time") + # create a new KeywordPlugin instance, associated to bot +bot+ def initialize super @@ -95,7 +111,7 @@ class Keywords < Plugin upgrade_data scan - + # import old format keywords into DBHash if(File.exist?("#{@bot.botclass}/keywords.rbot")) log "auto importing old keywords.rbot" @@ -113,13 +129,6 @@ class Keywords < Plugin File.rename("#{@bot.botclass}/keywords.rbot", "#{@bot.botclass}/keywords.rbot.old") end end - - # drop static keywords and reload them from files, picking up any new - # keyword files that have been added - def rescan - @statickeywords = Hash.new - scan - end # load static keywords from files, picking up any new keyword files that # have been added @@ -129,9 +138,9 @@ class Keywords < Plugin next unless f =~ /\.db$/ log "upgrading keyword db #{f} (rbot 0.9.5 or prior) database format" newname = f.gsub(/\.db$/, ".kdb") - old = BDB::Hash.open f, nil, + old = BDB::Hash.open f, nil, "r+", 0600 - new = BDB::CIBtree.open(newname, nil, + new = BDB::CIBtree.open(newname, nil, BDB::CREATE | BDB::EXCL, 0600) old.each {|k,v| @@ -141,7 +150,7 @@ class Keywords < Plugin new.close File.delete(f) } - + # then scan for current DBTree files, and load them Dir["#{@bot.botclass}/keywords/*"].each {|f| next unless f =~ /\.kdb$/ @@ -150,7 +159,7 @@ class Keywords < Plugin debug "keywords module: loading DBTree file #{f}, key #{key}" @statickeywords[key] = hsh } - + # then scan for non DB files, and convert/import them and delete Dir["#{@bot.botclass}/keywords/*"].each {|f| next if f =~ /\.kdb$/ @@ -182,7 +191,7 @@ class Keywords < Plugin def upgrade_data if File.exist?("#{@bot.botclass}/keywords.db") log "upgrading old keywords (rbot 0.9.5 or prior) database format" - old = BDB::Hash.open "#{@bot.botclass}/keywords.db", nil, + old = BDB::Hash.open "#{@bot.botclass}/keywords.db", nil, "r+", 0600 old.each {|k,v| @keywords[k] = v @@ -191,10 +200,10 @@ class Keywords < Plugin @keywords.flush File.rename("#{@bot.botclass}/keywords.db", "#{@bot.botclass}/keywords.db.old") end - + if File.exist?("#{@bot.botclass}/keyword.db") log "upgrading old keywords (rbot 0.9.9 or prior) database format" - old = BDB::CIBtree.open "#{@bot.botclass}/keyword.db", nil, + old = BDB::CIBtree.open "#{@bot.botclass}/keyword.db", nil, "r+", 0600 old.each {|k,v| @keywords[k] = v @@ -217,7 +226,7 @@ class Keywords < Plugin end end end - + # lookup keyword +key+, return it or nil def [](key) return nil if key.nil? @@ -252,7 +261,7 @@ class Keywords < Plugin # m:: PrivMessage containing message info # key:: key being queried # quiet:: optional, if false, complain if +key+ is not found - # + # # handle a message asking about a keyword def keyword_lookup(m, key, quiet = false) return if key.nil? @@ -260,10 +269,10 @@ class Keywords < Plugin m.reply "sorry, I don't know about \"#{key}\"" unless quiet return end - + response = kw.to_s response.gsub!(//, m.sourcenick) - + if(response =~ /^\s*(.*)/) m.reply $1 elsif(response =~ /^\s*(.*)/) @@ -275,26 +284,34 @@ class Keywords < Plugin end end - + # handle a message which alters a keyword # like "foo is bar" or "foo is also qux" def keyword_command(m, lhs, mhs, rhs, quiet = false) debug "got keyword command #{lhs}, #{mhs}, #{rhs}" - + return if lhs.strip.empty? + + overwrite = false + overwrite = true if(lhs.gsub!(/^no,\s*/, "")) + also = false also = true if(rhs.gsub!(/^also\s+/, "")) - + values = rhs.split(/\s+\|\s+/) lhs = Keyword.unescape lhs - - if(also && has_key?(lhs)) + + if(overwrite || also || !has_key?(lhs)) + if(also && has_key?(lhs)) + kw = self[lhs] + kw << values + @keywords[lhs] = kw.dump + else + @keywords[lhs] = Keyword.new(mhs, values).dump + end + m.okay if !quiet + elsif(has_key?(lhs)) kw = self[lhs] - kw << values - @keywords[lhs] = kw.dump - else - @keywords[lhs] = Keyword.new(mhs, values).dump + m.reply "but #{lhs} #{kw.type} #{kw.desc}" if kw && !quiet end - - @bot.okay m.target if !quiet end # return help string for Keywords with option topic +topic+ @@ -331,6 +348,8 @@ class Keywords < Plugin 'forget => forget a keyword' when "tell" 'tell about => tell somebody about a keyword' + when "learn" + 'learn that is/are => define a keyword, definition can contain "|" to separate multiple randomly chosen replies' else 'keyword module (fact learning and regurgitation) topics: lookup, set, forget, tell, search, listen, address, , , , ' end @@ -371,14 +390,14 @@ class Keywords < Plugin end # search for keywords, optionally also the definition and the static keywords - def keyword_search(m, key, full = false, all = false) + def keyword_search(m, key, full = false, all = false, from = 1) begin if key =~ /^\/(.+)\/$/ re = Regexp.new($1, Regexp::IGNORECASE) else re = Regexp.new(Regexp.escape(key), Regexp::IGNORECASE) end - + matches = Array.new @keywords.each {|k,v| kw = Keyword.restore(v) @@ -396,16 +415,20 @@ class Keywords < Plugin } } end - + if matches.length == 1 rkw = matches[0] m.reply "#{rkw[0]} #{rkw[1].type} #{rkw[1].desc}" elsif matches.length > 0 - i = 0 + if from > matches.length + m.reply "#{matches.length} found, can't tell you about #{from}" + return + end + i = 1 matches.each {|rkw| - m.reply "[#{i+1}/#{matches.length}] #{rkw[0]} #{rkw[1].type} #{rkw[1].desc}" + m.reply "[#{i}/#{matches.length}] #{rkw[0]} #{rkw[1].type} #{rkw[1].desc}" if i >= from i += 1 - break if i == 4 + break if i == from+@bot.config['keyword.search_results'] } else m.reply "no keywords match #{key}" @@ -420,9 +443,45 @@ class Keywords < Plugin # forget one of the dynamic keywords def keyword_forget(m, key) - if(@keywords.has_key?(key)) - @keywords.delete(key) - @bot.okay m.replyto + if @keywords.delete(key) + m.okay + else + m.reply _("couldn't find keyword %{key}" % { :key => key }) + end + end + + # low-level keyword wipe command for when forget doesn't work + def keyword_wipe(m, key) + reg = @keywords.registry + reg.env.begin(reg) { |t, b| + b.delete_if { |k, v| + (k == key) && (m.reply "wiping keyword #{key} with stored value #{Marshal.restore(v)}") + } + t.commit + } + m.reply "done" + end + + # export keywords to factoids file + def keyword_factoids_export + ar = Array.new + + debug @keywords.keys + + @keywords.each { |k, val| + next unless val + kw = Keyword.restore(val) + ar |= kw.to_factoids(k) + } + + # TODO check factoids config + # also TODO: runtime export + dir = File.join(@bot.botclass,"factoids") + fname = File.join(dir,"keyword_factoids.rbot") + + Dir.mkdir(dir) unless FileTest.directory?(dir) + Utils.safe_save(fname) do |file| + file.puts ar end end @@ -431,10 +490,19 @@ class Keywords < Plugin case m.plugin when "keyword" case m.params + when /^export$/ + begin + keyword_factoids_export + m.okay + rescue + m.reply _("failed to export keywords as factoids (%{err})" % {:err => $!}) + end when /^set\s+(.+?)\s+(is|are)\s+(.+)$/ keyword_command(m, $1, $2, $3) if @bot.auth.allow?('keycmd', m.source, m.replyto) when /^forget\s+(.+)$/ keyword_forget(m, $1) if @bot.auth.allow?('keycmd', m.source, m.replyto) + when /^wipe\s(.+)$/ # note that only one space is stripped, allowing removal of space-prefixed keywords + keyword_wipe(m, $1) if @bot.auth.allow?('keycmd', m.source, m.replyto) when /^lookup\s+(.+)$/ keyword_lookup(m, $1) if @bot.auth.allow?('keyword', m.source, m.replyto) when /^stats\s*$/ @@ -443,7 +511,13 @@ class Keywords < Plugin key = $1 full = key.sub!('--full ', '') all = key.sub!('--all ', '') - keyword_search(m, key, full, all) if @bot.auth.allow?('keyword', m.source, m.replyto) + if key.sub!(/--from (\d+) /, '') + from = $1.to_i + else + from = 1 + end + from = 1 unless from > 0 + keyword_search(m, key, full, all, from) if @bot.auth.allow?('keyword', m.source, m.replyto) when /^tell\s+(\S+)\s+about\s+(.+)$/ keyword_tell(m, $1, $2) if @bot.auth.allow?('keyword', m.source, m.replyto) else @@ -457,15 +531,19 @@ class Keywords < Plugin else m.reply "wrong 'tell' syntax" end + when "learn" + if m.params =~ /^that\s+(.+?)\s+(is|are)\s+(.+)$/ + keyword_command(m, $1, $2, $3) if @bot.auth.allow?('keycmd', m.source, m.replyto) + else + m.reply "wrong 'learn' syntax" + end end end - def listen(m) - return if m.address? - # in channel message, not to me + def unreplied(m) # TODO option to do if(m.message =~ /^(.*)$/, ie try any line as a # keyword lookup. - if !@bot.config["keyword.address"] && m.message =~ /^(.*\S)\s*\?\s*$/ + if m.message =~ /^(.*\S)\s*\?\s*$/ and (m.address? or not @bot.config["keyword.address"]) keyword_lookup m, $1, true if @bot.auth.allow?("keyword", m.source) elsif @bot.config["keyword.listen"] && (m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/) # TODO MUCH more selective on what's allowed here @@ -476,6 +554,7 @@ end plugin = Keywords.new plugin.register 'keyword' -plugin.register 'forget' -plugin.register 'tell' +plugin.register 'forget' rescue nil +plugin.register 'tell' rescue nil +plugin.register 'learn' rescue nil