kennethkalmer/daemon-kit

View on GitHub
lib/daemon_kit/jabber.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'yaml'

module DaemonKit
  # Thin wrapper around xmpp4r-simple, specifically designed to ease
  # configuration of a jabber daemon and provide some added simplicity.
  class Jabber

    # Jabber connection
    attr_reader :connection

    @@instance = nil
    @@message_handler = nil
    @@presence_handler = nil
    @@subscription_handler = nil

    class << self

      # Deliver a message to the specified jid.
      def deliver( jid, message )
        instance.connection.deliver( jid, message )
      end

      # Use this instead of initializing, keeps it singleton
      def instance
        @instance ||= new
        @instance.startup!
      end
      private :new

      def run
        DaemonKit.logger.warn "Please use the new XMPP daemons, this class is deprecated"
        DaemonKit.logger.info "Starting jabber loop"

        loop do
          process_messages
          process_updates
          process_subscriptions

          begin
            sleep 1
          rescue Interrupt
            DaemonKit.logger.warn "Jabber loop interrupted"
            break
          end
        end
      end

      def process_messages
        @message_handler ||= Proc.new { |m| DaemonKit.logger.info "Received message from #{m.from}: #{m.body}" }

        instance.valid_messages { |m| @message_handler.call(m) }
      end

      def process_updates
        @presence_handler ||= Proc.new { |friend, old_presence, new_presence|
          DaemonKit.logger.debug "Received presence update: #{friend} went from #{old_presence} to #{new_presence}"
        }

        instance.connection.presence_updates { |friend, old_presence, new_presence|
          @presence_handler.call(friend, old_presence, new_presence)
        }

      end

      def process_subscriptions
        @subscription_handler ||= Proc.new { |friend,presence| DaemonKit.logger.debug "Received presence update from #{friend}: #{presence}" }

        instance.connection.subscription_requests { |friend,presence| @subscription_handler.call(friend,presence) }
      end

      def received_messages(&block)
        @message_handler = block
      end

      def presence_updates(&block)
        @presence_handler = block
      end

      def subscription_requests(&block)
        @subscription_handler = block
      end

    end

    def initialize
      options = DaemonKit::Config.load( 'jabber' )

      @jabber_id  = options.delete("jabber_id")
      @password   = options.delete("password")
      @resource   = options.delete("resource") || 'daemon_kit'
      @masters    = options.delete("masters") || []
      @supporters = options.delete("supporters") || []

      raise ArgumentError if [ @jabber_id, @password ].any? { |a| a.nil? }
    end

    def startup!
      return self if @booted

      connect!
      setup_roster!

      DaemonKit.trap( 'INT', Proc.new { self.shutdown! } )
      DaemonKit.trap( 'TERM', Proc.new { self.shutdown! } )

      @booted = true

      self
    end

    def shutdown!
      DaemonKit.logger.warn "Disconnecting jabber connection"
      self.connection.disconnect
    end

    def contacts
      @masters + @supporters
    end

    def valid_messages(&block)
      self.connection.received_messages.each do |message|
        next unless valid_master?( message.from )

        busy do
          block.call message
        end
      end
    end

    def valid_master?( jid )
      @masters.include?( jid.strip.to_s )
    end

    def busy(&block)
      self.connection.status(:dnd, "Working...")
      yield
      self.connection.status(:chat, self.status_line )
    end

    def status_line
      "#{DaemonKit.configuration.daemon_name} ready for instructions"
    end

    private

    def connect!
      jid = @jabber_id + '/' + @resource

      @connection = ::Jabber::Simple.new( jid, @password, nil, self.status_line )
    end

    def setup_roster!
      # cleanup the roster
      self.connection.roster.items.each_pair do |jid, roster_item|
        jid = jid.strip.to_s
        unless self.contacts.include?( jid )
          self.connection.remove( jid )
        end
      end

      # add missing contacts
      self.contacts.each do |jid|
        unless self.connection.subscribed_to?( jid )
          self.connection.add( jid )
          #self.connection.accept_subscription( jid )
        end
      end
    end

  end
end