Jabber Bot

changeset 0:26986fceeb03 tip

Added John's Code
author root@riviera.nuigalway.ie
date Fri Jun 27 22:01:59 2008 +0100 (2008-06-27)
parents
children
files .DS_Store bot.rb lib/timer.rb rjbotGeneric.rb
line diff
     1.1 Binary file .DS_Store has changed
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/bot.rb	Fri Jun 27 22:01:59 2008 +0100
     2.3 @@ -0,0 +1,62 @@
     2.4 +#/usr/bin/env ruby
     2.5 +require 'rubygems'
     2.6 +require 'jabber/bot'
     2.7 +
     2.8 +#
     2.9 +# Set the lib path
    2.10 +#
    2.11 +defaultlib = File.expand_path(File.dirname($0) + '/lib')
    2.12 +
    2.13 +if File.directory? "#{defaultlib}"
    2.14 +  unless $:.include? defaultlib
    2.15 +    $:.unshift defaultlib
    2.16 +  end
    2.17 +end
    2.18 +
    2.19 +
    2.20 +require 'timer'
    2.21 +
    2.22 +# Create a public Jabber::Bot
    2.23 +bot = Jabber::Bot.new(
    2.24 +  :jabber_id => '', # The bot's jabber ID
    2.25 +  :password  => '', # The bot's jabber ID Password
    2.26 +  :master    => '', # The owner of the bot
    2.27 +  :is_public => true
    2.28 +)
    2.29 +
    2.30 +# Give your bot a public command
    2.31 +bot.add_command(
    2.32 +  :syntax      => 'rand',
    2.33 +  :description => 'Produce a random number from 0 to 10',
    2.34 +  :regex       => /^rand$/,
    2.35 +  :is_public   => true
    2.36 +) { rand(10).to_s }
    2.37 +
    2.38 +# Give your bot a private command with an alias
    2.39 +bot.add_command(
    2.40 +  :syntax      => 'puts <string>',
    2.41 +  :description => 'Write something to $stdout',
    2.42 +  :regex       => /^puts\s+.+$/,
    2.43 +  :alias       => [ 
    2.44 +      :syntax => 'p <string>', 
    2.45 +      :regex => /^p\s+.+$/
    2.46 +  ]
    2.47 +) do |sender, message|
    2.48 +  puts message
    2.49 +  "'#{message}' written to $stdout"
    2.50 +end
    2.51 +
    2.52 +# Reminder
    2.53 +bot.add_command(
    2.54 +  :syntax      => 'remind <string>',
    2.55 +  :description => 'Remind me about something in 10seconds',
    2.56 +  :regex       => /^remind\s+.+$/
    2.57 +) do |sender, message|
    2.58 +  @timer = Timer.new
    2.59 +  @timer.add_once(10) { bot.deliver(sender, message) }
    2.60 +
    2.61 +  "You will be reminded in 10seconds"
    2.62 +end
    2.63 +
    2.64 +# Bring your new bot to life
    2.65 +bot.connect
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/lib/timer.rb	Fri Jun 27 22:01:59 2008 +0100
     3.3 @@ -0,0 +1,270 @@
     3.4 +# changes:
     3.5 +#  1. Timer::Timer ---> Timer
     3.6 +#  2. timer id is now the object_id of the action
     3.7 +#  3. Timer resolution removed, we're always arbitrary precision now
     3.8 +#  4. I don't see any obvious races [not that i did see any in old impl, though]
     3.9 +#  5. We're tickless now, so no need to jerk start/stop
    3.10 +#  6. We should be pretty fast now, wrt old impl
    3.11 +#  7. reschedule/remove/block now accept nil as an action id (meaning "current")
    3.12 +#  8. repeatability is ignored for 0-period repeatable timers
    3.13 +#  9. configure() method superceeds reschedule() [the latter stays as compat]
    3.14 +
    3.15 +require 'thread'
    3.16 +require 'monitor'
    3.17 +
    3.18 +# Timer handler, manage multiple Action objects, calling them when required.
    3.19 +# When the Timer is constructed, a new Thread is created to manage timed
    3.20 +# delays and run Actions.
    3.21 +#
    3.22 +# XXX: there is no way to stop the timer currently. I'm keeping it this way
    3.23 +# to weed out old Timer implementation legacy in rbot code. -jsn.
    3.24 +class Timer
    3.25 +
    3.26 +  # class representing individual timed action
    3.27 +  class Action
    3.28 +
    3.29 +    # Time when the Action should be called next
    3.30 +    attr_accessor :next
    3.31 +
    3.32 +    # Options are:
    3.33 +    # start::    Time when the Action should be run for the first time.
    3.34 +    #            Repeatable Actions will be repeated after that, see
    3.35 +    #            :period. One-time Actions will not (obviously)
    3.36 +    #            Default: Time.now + :period
    3.37 +    # period::   How often repeatable Action should be run, in seconds.
    3.38 +    #            Default: 1
    3.39 +    # blocked::  if true, Action starts as blocked (i.e. will stay dormant
    3.40 +    #            until unblocked)
    3.41 +    # args::     Arguments to pass to the Action callback. Default: []
    3.42 +    # repeat::   Should the Action be called repeatedly? Default: false
    3.43 +    # code::     You can specify the Action body using &block, *or* using
    3.44 +    #            this option.
    3.45 +
    3.46 +    def initialize(options = {}, &block)
    3.47 +      opts = {
    3.48 +        :period => 1,
    3.49 +        :blocked => false,
    3.50 +        :args => [],
    3.51 +        :repeat => false
    3.52 +      }.merge(options)
    3.53 +
    3.54 +      @block = nil
    3.55 +      # debug("adding timer #{self} :period => #{opts[:period]}, :repeat => #{opts[:repeat].inspect}")
    3.56 +      self.configure(opts, &block)
    3.57 +      # debug("added #{self}")
    3.58 +    end
    3.59 +
    3.60 +    # Provides for on-the-fly reconfiguration of the Actions
    3.61 +    # Accept the same arguments as the constructor
    3.62 +    def configure(opts = {}, &block)
    3.63 +      @period = opts[:period] if opts.include? :period
    3.64 +      @blocked = opts[:blocked] if opts.include? :blocked
    3.65 +      @repeat = opts[:repeat] if opts.include? :repeat
    3.66 +
    3.67 +      if block_given?
    3.68 +        @block = block 
    3.69 +      elsif opts[:code]
    3.70 +        @block = opts[:code]
    3.71 +      end
    3.72 +
    3.73 +      raise 'huh?? blockless action?' unless @block
    3.74 +      if opts.include? :args
    3.75 +        @args = Array === opts[:args] ? opts[:args] : [opts[:args]]
    3.76 +      end
    3.77 +
    3.78 +      if opts[:start] and (Time === opts[:start])
    3.79 +        self.next = opts[:start]
    3.80 +      else
    3.81 +        self.next = Time.now + (opts[:start] || @period)
    3.82 +      end
    3.83 +    end
    3.84 +
    3.85 +    # modify the Action period
    3.86 +    def reschedule(period, &block)
    3.87 +      self.configure(:period => period, &block)
    3.88 +    end
    3.89 +
    3.90 +    # blocks an Action, so it won't be run
    3.91 +    def block
    3.92 +      @blocked = true
    3.93 +    end
    3.94 +
    3.95 +    # unblocks a blocked Action
    3.96 +    def unblock
    3.97 +      @blocked = false
    3.98 +    end
    3.99 +
   3.100 +    def blocked?
   3.101 +      @blocked
   3.102 +    end
   3.103 +
   3.104 +    # calls the Action callback, resets .next to the Time of the next call,
   3.105 +    # if the Action is repeatable.
   3.106 +    def run(now = Time.now)
   3.107 +      raise 'inappropriate time to run()' unless self.next && self.next <= now
   3.108 +      self.next = nil
   3.109 +      begin
   3.110 +        @block.call(*@args)
   3.111 +      rescue Exception => e
   3.112 +        error "Timer action #{self.inspect}: block #{@block.inspect} failed!"
   3.113 +        error e.pretty_inspect
   3.114 +        # debug e.backtrace.join("\n")
   3.115 +      end
   3.116 +
   3.117 +      if @repeat && @period > 0
   3.118 +        self.next = now + @period
   3.119 +      end
   3.120 +
   3.121 +      return self.next
   3.122 +    end
   3.123 +  end
   3.124 +
   3.125 +  # creates a new Timer and starts it.
   3.126 +  def initialize
   3.127 +    self.extend(MonitorMixin)
   3.128 +    @tick = self.new_cond
   3.129 +    @thread = nil
   3.130 +    @actions = Hash.new
   3.131 +    @current = nil
   3.132 +    self.start
   3.133 +  end
   3.134 +
   3.135 +  # Creates and installs a new Action, repeatable by default.
   3.136 +  # _period_:: Action period
   3.137 +  # _opts_::   options for Action#new, see there
   3.138 +  # _block_::  Action callback code
   3.139 +  #
   3.140 +  # Returns the id of the created Action
   3.141 +  def add(period, opts = {}, &block)
   3.142 +    a = Action.new({:repeat => true, :period => period}.merge(opts), &block)
   3.143 +    self.synchronize do
   3.144 +      @actions[a.object_id] = a
   3.145 +      @tick.signal
   3.146 +    end
   3.147 +    return a.object_id
   3.148 +  end
   3.149 +
   3.150 +  # Creates and installs a new Action, one-time by default.
   3.151 +  # _period_:: Action delay
   3.152 +  # _opts_::   options for Action#new, see there
   3.153 +  # _block_::  Action callback code
   3.154 +  #
   3.155 +  # Returns the id of the created Action
   3.156 +  def add_once(period, opts = {}, &block)
   3.157 +    self.add(period, {:repeat => false}.merge(opts), &block)
   3.158 +  end
   3.159 +
   3.160 +  # blocks an existing Action
   3.161 +  # _aid_:: Action id, obtained previously from add() or add_once()
   3.162 +  def block(aid)
   3.163 +    # debug "blocking #{aid}"
   3.164 +    self.synchronize { self[aid].block }
   3.165 +  end
   3.166 +
   3.167 +  # unblocks an existing blocked Action
   3.168 +  # _aid_:: Action id, obtained previously from add() or add_once()
   3.169 +  def unblock(aid)
   3.170 +    # debug "unblocking #{aid}"
   3.171 +    self.synchronize do
   3.172 +      self[aid].unblock
   3.173 +      @tick.signal
   3.174 +    end
   3.175 +  end
   3.176 +
   3.177 +  # removes an existing blocked Action
   3.178 +  # _aid_:: Action id, obtained previously from add() or add_once()
   3.179 +  def remove(aid)
   3.180 +    self.synchronize do
   3.181 +      @actions.delete(aid) # or raise "nonexistent action #{aid}"
   3.182 +    end
   3.183 +  end
   3.184 +
   3.185 +  alias :delete :remove
   3.186 +
   3.187 +  # Provides for on-the-fly reconfiguration of Actions
   3.188 +  # _aid_::   Action id, obtained previously from add() or add_once()
   3.189 +  # _opts_::  see Action#new
   3.190 +  # _block_:: (optional) new Action callback code
   3.191 +  def configure(aid, opts = {}, &block)
   3.192 +    self.synchronize do
   3.193 +      self[aid].configure(opts, &block)
   3.194 +      @tick.signal
   3.195 +    end
   3.196 +  end
   3.197 +
   3.198 +  # changes Action period
   3.199 +  # _aid_:: Action id
   3.200 +  # _period_:: new period
   3.201 +  # _block_:: (optional) new Action callback code
   3.202 +  def reschedule(aid, period, &block)
   3.203 +    self.configure(aid, :period => period, &block)
   3.204 +  end
   3.205 +
   3.206 +  def start
   3.207 +    raise 'already started' if @thread
   3.208 +    @stopping = false
   3.209 +    # debug "starting timer #{self}"
   3.210 +    @thread = Thread.new do
   3.211 +      loop do
   3.212 +        tmout = self.run_actions
   3.213 +        break if tmout and tmout < 0
   3.214 +        self.synchronize { @tick.wait(tmout) }
   3.215 +      end
   3.216 +    end
   3.217 +  end
   3.218 +
   3.219 +  def stop
   3.220 +    raise 'already stopped' unless @thread
   3.221 +    # debug "stopping timer #{self}..."
   3.222 +    @stopping = true
   3.223 +    self.synchronize { @tick.signal }
   3.224 +    @thread.join(60) or @thread.kill
   3.225 +    # debug "timer #{self} stopped"
   3.226 +    @thread = nil
   3.227 +  end
   3.228 +
   3.229 +  protected
   3.230 +
   3.231 +  def [](aid)
   3.232 +    aid ||= @current
   3.233 +    raise "no current action" unless aid
   3.234 +    raise "nonexistent action #{aid}" unless @actions.include? aid
   3.235 +    @actions[aid]
   3.236 +  end
   3.237 +
   3.238 +  def run_actions(now = Time.now)
   3.239 +    nxt = nil
   3.240 +    @actions.keys.each do |k|
   3.241 +      return -1 if @stopping
   3.242 +      a = @actions[k]
   3.243 +      next if (!a) or a.blocked?
   3.244 +
   3.245 +      if a.next <= now
   3.246 +        begin
   3.247 +          @current = k
   3.248 +          v = a.run(now)
   3.249 +        ensure
   3.250 +          @current = nil
   3.251 +        end
   3.252 +
   3.253 +        unless v
   3.254 +          @actions.delete k
   3.255 +          next
   3.256 +        end
   3.257 +      else
   3.258 +        v = a.next
   3.259 +      end
   3.260 +
   3.261 +      nxt = v if v and ((!nxt) or (v < nxt))
   3.262 +    end
   3.263 +
   3.264 +    if nxt
   3.265 +      delta = nxt - now
   3.266 +      delta = 0 if delta < 0
   3.267 +      return delta
   3.268 +    else
   3.269 +      return nil
   3.270 +    end
   3.271 +  end
   3.272 +
   3.273 +end
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/rjbotGeneric.rb	Fri Jun 27 22:01:59 2008 +0100
     4.3 @@ -0,0 +1,62 @@
     4.4 +#/usr/bin/env ruby
     4.5 +require 'rubygems'
     4.6 +require 'jabber/bot'
     4.7 +
     4.8 +#
     4.9 +# Set the lib path
    4.10 +#
    4.11 +defaultlib = File.expand_path(File.dirname($0) + '/lib')
    4.12 +
    4.13 +if File.directory? "#{defaultlib}"
    4.14 +  unless $:.include? defaultlib
    4.15 +    $:.unshift defaultlib
    4.16 +  end
    4.17 +end
    4.18 +
    4.19 +
    4.20 +require 'timer'
    4.21 +
    4.22 +# Create a public Jabber::Bot
    4.23 +bot = Jabber::Bot.new(
    4.24 +  :jabber_id => 'johnlong14@gmail.com', # The bot's jabber ID
    4.25 +  :password  => 'xxxxxx', # The bot's jabber ID Password
    4.26 +  :master    => 'johnlong@jabber.org', # The owner of the bot
    4.27 +  :is_public => true
    4.28 +)
    4.29 +
    4.30 +# Give your bot a public command
    4.31 +bot.add_command(
    4.32 +  :syntax      => 'rand',
    4.33 +  :description => 'Produce a random number from 0 to 10',
    4.34 +  :regex       => /^rand$/,
    4.35 +  :is_public   => true
    4.36 +) { rand(10).to_s }
    4.37 +
    4.38 +# Give your bot a private command with an alias
    4.39 +bot.add_command(
    4.40 +  :syntax      => 'puts <string>',
    4.41 +  :description => 'Write something to $stdout',
    4.42 +  :regex       => /^puts\s+.+$/,
    4.43 +  :alias       => [ 
    4.44 +      :syntax => 'p <string>', 
    4.45 +      :regex => /^p\s+.+$/
    4.46 +  ]
    4.47 +) do |sender, message|
    4.48 +  puts message
    4.49 +  "'#{message}' written to $stdout"
    4.50 +end
    4.51 +
    4.52 +# Reminder
    4.53 +bot.add_command(
    4.54 +  :syntax      => 'remind <string>',
    4.55 +  :description => 'Remind me about something in 10seconds',
    4.56 +  :regex       => /^remind\s+.+$/
    4.57 +) do |sender, message|
    4.58 +  @timer = Timer.new
    4.59 +  @timer.add_once(10) { bot.deliver(sender, message) }
    4.60 +
    4.61 +  "You will be reminded in 10seconds"
    4.62 +end
    4.63 +
    4.64 +# Bring your new bot to life
    4.65 +bot.connect