X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=data%2Frbot%2Fplugins%2Freaction.rb;h=9150d23ec2459829ad54c8693b0b6836eb89c3ce;hb=6ef6b436240072f775f87687e38b880672262657;hp=bce50c36f85827cfa56b7947e6e6505c62b69ea3;hpb=3623ada6d5b3bb0e0f986ab7d6811d5b0ff70e62;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/data/rbot/plugins/reaction.rb b/data/rbot/plugins/reaction.rb index bce50c36..9150d23e 100644 --- a/data/rbot/plugins/reaction.rb +++ b/data/rbot/plugins/reaction.rb @@ -12,9 +12,49 @@ # Very alpha stage, so beware of sudden reaction syntax changes class ::Reaction - attr_reader :trigger, :reply - attr_reader :raw_trigger, :raw_reply - attr_accessor :author, :date, :channel + attr_reader :trigger, :replies + attr_reader :raw_trigger, :raw_replies + + class ::Reply + attr_reader :act, :reply, :pct, :range + attr_reader :author, :date, :channel + attr_writer :date + + def pct=(val) + @pct = val + @reaction.make_ranges + end + + def author=(name) + @author = name.to_s + end + + def channel=(name) + @channel = name.to_s + end + + def initialize(reaction, act, expr, pct, author, date, channel) + @reaction = reaction + @act = act + @reply = expr + self.pct = pct + self.author = author + @date = date + self.channel = channel + end + + def to_s + [ + "#{act} #{reply} (#{pct} chance)", + @range ? "(#{@range})" : "", + "(#{author}, #{channel}, #{date})" + ].join(" ") + end + + def apply(subs={}) + [act, reply % subs] + end + end def trigger=(expr) @raw_trigger = expr.dup @@ -25,22 +65,55 @@ class ::Reaction end @trigger = [act] if rex.sub!(%r@^([/!])(.*)\1$@, '\2') - debug rex @trigger << Regexp.new(rex) else @trigger << Regexp.new(/\b#{Regexp.escape(rex)}\b/u) end end - def reply=(expr) - @raw_reply = expr.dup + def add_reply(expr, *args) + @raw_replies << expr.dup act = false rex = expr.dup if rex.sub!(/^act:/,'') act = true end - @reply = [act ? :act : :reply] - @reply << rex + @replies << Reply.new(self, act ? :act : :reply, rex, *args) + make_ranges + return @replies.last + end + + def find_reply(expr) + @replies[@raw_replies.index(expr)] rescue nil + end + + def make_ranges + totals = 0 + pcts = @replies.map { |rep| + totals += rep.pct + rep.pct + } + pcts.map! { |p| + p/totals + } if totals > 1 + debug "percentages: #{pcts.inspect}" + + last = 0 + @replies.each_with_index { |r, i| + p = pcts[i] + r.range = last..(last+p) + last+=p + } + debug "ranges: #{@replies.map { |r| r.range}.inspect}" + end + + def pick_reply + pick = rand() + debug "#{pick} in #{@replies.map { |r| r.range}.inspect}" + @replies.each { |r| + return r if r.range and r.range === pick + } + return nil end def ===(message) @@ -48,23 +121,21 @@ class ::Reaction return message.message.match(@trigger.last) end - def initialize(trig, msg , auth, dt, chan) + def initialize(trig) self.trigger=trig - self.reply=msg - self.author=auth.to_s - self.date=dt - self.channel=chan.to_s + @raw_replies = [] + @replies = [] end def to_s - "trigger #{raw_trigger} (#{author}, #{channel}, #{date})" + raw_trigger end end class ReactionPlugin < Plugin - ADD_SYNTAX = 'react to *trigger with *reply' + ADD_SYNTAX = 'react to *trigger with *reply at :chance chance' def add_syntax return ADD_SYNTAX @@ -99,11 +170,19 @@ class ReactionPlugin < Plugin def help(plugin, topic="") if plugin.to_sym == :react - return "react to with => create a new reaction to expression to which the bot will reply , seek help for reaction trigger and reaction reply for more details" + return "react to with [at chance] => " + + "create a new reaction to expression to which the bot will reply , optionally at chance , " + + "seek help for reaction trigger, reaction reply and reaction chance for more details" end case (topic.to_sym rescue nil) + when :add + help(:react) when :remove, :delete, :rm, :del "reaction #{topic} => removes the reaction to expression " + when :chance, :chances + "reaction chances are expressed either in terms of percentage (like 30%) or in terms of floating point numbers (like 0.3), and are clipped to be " + + "between 0 and 1 (i.e. 0% and 100%). A reaction can have multiple replies, each with a different chance; if the total of the chances is less than one, " + + "there is a chance that the trigger will not actually cause a reply. Otherwise, the chances express the relative frequency of the replies." when :trigger, :triggers "reaction triggers can have one of the format: single_word 'multiple words' \"multiple words \" /regular_expression/ !regular_expression!. " + "If prefixed by 'act:' (e.g. act:/(order|command)s/) the bot will only respond if a CTCP ACTION matches the trigger" @@ -116,8 +195,10 @@ class ReactionPlugin < Plugin "stuff (everything that follows the trigger), match (the actual matched text)" when :list "reaction list [n]: lists the n-the page of programmed reactions (30 reactions are listed per page)" + when :show + "reaction show : list the programmed replies to trigger " else - "reaction topics: add, remove, delete, rm, del, triggers, replies, list" + "reaction topics: add, remove, delete, rm, del, triggers, replies, chance, list, show" end end @@ -141,8 +222,10 @@ class ReactionPlugin < Plugin :stuff => stuff } subs = @subs.dup.merge extra - args = [wanted.reply.first] - args << wanted.reply.last % extra + reply = wanted.pick_reply + debug "picked #{reply}" + return unless reply + args = reply.apply(subs) m.__send__(*args) end @@ -155,14 +238,30 @@ class ReactionPlugin < Plugin def handle_add(m, params) trigger = params[:trigger].to_s reply = params[:reply].to_s - if find_reaction(trigger) - m.reply "there's already a reaction to #{trigger}" - return + + pct = params[:chance] || "1" + if pct.sub!(/%$/,'') + pct = (pct.to_f/100).clip(0,1) + else + pct = pct.to_f.clip(0,1) end - reaction = Reaction.new(trigger, reply, m.sourcenick, Time.now, m.channel) - @reactions << reaction - m.reply "added reaction to #{reaction.trigger.last} with #{reaction.reply.last}" + reaction = find_reaction(trigger) + if not reaction + reaction = Reaction.new(trigger) + @reactions << reaction + m.reply "Ok, I'll start reacting to #{reaction.raw_trigger}" + end + found = reaction.find_reply(reply) + if found + found.pct = pct + found.author = m.sourcenick + found.date = Time.now + found.channel = m.channel + else + found = reaction.add_reply(reply, pct, m.sourcenick, Time.now, m.channel) + end + m.reply "I'll react to #{reaction.raw_trigger} with #{reaction.raw_replies.last} (#{(reaction.replies.last.pct * 100).to_i}%)" end def handle_rm(m, params) @@ -171,7 +270,7 @@ class ReactionPlugin < Plugin found = find_reaction(trigger) if found @reactions.delete(found) - m.reply "removed reaction to #{found.trigger.last} with #{found.reply.last}" + m.reply "I won't react to #{found.raw_trigger} anymore" else m.reply "no reaction programmed for #{trigger}" end @@ -192,6 +291,24 @@ class ReactionPlugin < Plugin m.reply "Programmed reactions (page #{page}/#{pages}): #{str}" end + def handle_show(m, params) + if @reactions.empty? + m.reply "no reactions programmed" + return + end + + trigger = params[:trigger].to_s + + found = find_reaction(trigger) + + unless found + m.reply "I'm not reacting to #{trigger}" + return + end + + m.reply found.replies.join(", ") + end + end plugin = ReactionPlugin.new @@ -209,6 +326,8 @@ plugin.map plugin.add_syntax.sub('*', ':'), :action => 'handle_add' plugin.map 'reaction list [:page]', :action => 'handle_list', :requirements => { :page => /^\d+$/ } +plugin.map 'reaction show *trigger', :action => 'handle_show' + plugin.map 'reaction del[ete] *trigger', :action => 'handle_rm' plugin.map 'reaction delete *trigger', :action => 'handle_rm' plugin.map 'reaction remove *trigger', :action => 'handle_rm'