require 'xmpp4r' require 'xmpp4r/muc/helper/simplemucclient' require 'xmpp4r/roster/helper/roster' require 'ostruct' if defined?(Encoding) # Monkey-patch an incompatibility between ejabberd and rexml require 'rexml_patches' end # Handles opening a connection to the HipChat server, and feeds all # messages through our Robut::Plugin list. class Robut::Connection # The configuration used by the Robut connection. # # Parameters: # # [+jid+, +password+, +nick+] The HipChat credentials given on # https://www.hipchat.com/account/xmpp # # [+rooms+] The chat room(s) to join, with each in the format jabber_name@conference_server # # [+logger+] a logger instance to use for debug output. attr_accessor :config # The Jabber::Client that's connected to the HipChat server. attr_accessor :client # The storage instance that's available to plugins attr_accessor :store # The roster of currently available people attr_accessor :roster # The rooms that robut is connected to. attr_accessor :rooms class << self # Class-level config. This is set by the +configure+ class method, # and is used if no configuration is passed to the +initialize+ # method. attr_accessor :config end # Configures the connection at the class level. When the +robut+ bin # file is loaded, it evals the file referenced by the first # command-line parameter. This file can configure the connection # instance later created by +robut+ by setting parameters in the # Robut::Connection.configure block. def self.configure self.config = OpenStruct.new yield config end # Sets the instance config to +config+, converting it into an # OpenStruct if necessary. def config=(config) @config = config.kind_of?(Hash) ? OpenStruct.new(config) : config end # Initializes the connection. If no +config+ is passed, it defaults # to the class_level +config+ instance variable. def initialize(_config = nil) self.config = _config || self.class.config self.client = Jabber::Client.new(self.config.jid) self.store = self.config.store || Robut::Storage::HashStore # default to in-memory store only self.config.rooms ||= Array(self.config.room) # legacy support? self.config.enable_private_messaging = true if self.config.enable_private_messaging.nil? if self.config.logger Jabber.logger = self.config.logger Jabber.debug = true end end # Connects to the specified room with the given credentials, and # enters an infinite loop. Any messages sent to the room will pass # through all the included plugins. def connect client.connect client.auth(config.password) client.send(Jabber::Presence.new.set_type(:available)) self.roster = Jabber::Roster::Helper.new(client) roster.wait_for_roster self.rooms = self.config.rooms.collect do |room_name| Robut::Room.new(self, room_name).tap {|r| r.join } end if self.config.enable_private_messaging Robut::PM.new(self, rooms) end trap_signals self end # Send a message to all rooms. def reply(*args, &block) self.rooms.each do |room| room.reply(*args, &block) end end private # Since we're entering an infinite loop, we have to trap TERM and # INT. If something like the Rdio plugin has started a server that # has already trapped those signals, we want to run those signal # handlers first. def trap_signals old_signal_callbacks = {} signal_callback = Proc.new do |signal| old_signal_callbacks[signal].call if old_signal_callbacks[signal] exit end [:INT, :TERM].each do |sig| old_signal_callbacks[sig] = trap(sig) { signal_callback.call(sig) } end end end