aboutsummaryrefslogtreecommitdiff
path: root/lib/mauve/notifiers/xmpp-smack.rb
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2011-04-13 17:03:16 +0100
committerPatrick J Cherry <patrick@bytemark.co.uk>2011-04-13 17:03:16 +0100
commit89a67770e66d11740948e90a41db6cee0482cf8e (patch)
treebe858515fb789a89d68f94975690ab019813726c /lib/mauve/notifiers/xmpp-smack.rb
new version.
Diffstat (limited to 'lib/mauve/notifiers/xmpp-smack.rb')
-rw-r--r--lib/mauve/notifiers/xmpp-smack.rb395
1 files changed, 395 insertions, 0 deletions
diff --git a/lib/mauve/notifiers/xmpp-smack.rb b/lib/mauve/notifiers/xmpp-smack.rb
new file mode 100644
index 0000000..a160a35
--- /dev/null
+++ b/lib/mauve/notifiers/xmpp-smack.rb
@@ -0,0 +1,395 @@
+# encoding: utf-8
+
+# Ruby.
+require 'pp'
+require 'log4r'
+require 'monitor'
+
+# Java. Note that paths are mangeled in jmauve_starter.
+require 'java'
+require 'smack.jar'
+require 'smackx.jar'
+include_class "org.jivesoftware.smack.XMPPConnection"
+include_class "org.jivesoftware.smackx.muc.MultiUserChat"
+include_class "org.jivesoftware.smack.RosterListener"
+
+module Mauve
+
+ module Notifiers
+
+ module Xmpp
+
+ class XMPPSmackException < StandardError
+ end
+
+ ## Main wrapper to smack java library.
+ #
+ # @author Yann Golanski
+ # @see http://www.igniterealtime.org/builds/smack/docs/3.1.0/javadoc/
+ #
+ # This is a singleton which is not idea but works well for mauve's
+ # configuration file set up.
+ #
+ # In general, this class is meant to be intialized then the method
+ # create_slave_thread must be called. The latter will spawn a new
+ # thread that will do the connecting and sending of messages to
+ # the XMPP server. Once this is done, messages can be send via the
+ # send_msg() method. Those will be queued and depending on the load,
+ # should be send quickly enough. This is done so that the main thread
+ # can not worry about sending messages and can do important work.
+ #
+ # @example
+ # bot = Mauve::Notifiers::Xmpp::XMPPSmack.new()
+ # bot.run_slave_thread("chat.bytemark.co.uk", 'mauvealert', 'TopSecret')
+ # msg = "What fresh hell is this? -- Dorothy Parker."
+ # bot.send_msg("yann@chat.bytemark.co.uk", msg)
+ # bot.send_msg("muc:test@conference.chat.bytemark.co.uk", msg)
+ #
+ # @FIXME This won't quiet work with how mauve is set up.
+ #
+ class XMPPSmack
+
+ # Globals are evil.
+ @@instance = nil
+
+ # Default constructor.
+ #
+ # A queue (@queue) is used to pass information between master/slave.
+ def initialize ()
+ extend(MonitorMixin)
+ @logger = Log4r::Logger.new "mauve::XMPP_smack<#{Process.pid}>"
+ @queue = Queue.new
+ @xmpp = nil
+ @name = "mauve alert"
+ @slave_thread = nil
+ @regexp_muc = Regexp.compile(/^muc\:/)
+ @regexp_tail = Regexp.compile(/\/.*$/)
+ @jid_created_chat = Hash.new()
+ @separator = '<->'
+ @logger.info("Created XMPPSmack singleton")
+ end
+
+ # Returns the instance of the XMPPSmack singleton.
+ #
+ # @param [String] login The JID as a full address.
+ # @param [String] pwd The password corresponding to the JID.
+ # @return [XMPPSmack] The singleton instance.
+ def self.instance (login, pwd)
+ if true == @@instance.nil?
+ @@instance = XMPPSmack.new
+ jid, tmp = login.split(/@/)
+ srv, name = tmp.split(/\//)
+ name = "Mauve Alert Bot" if true == name.nil?
+ @@instance.run_slave_thread(srv, jid, pwd, name)
+ sleep 5 # FIXME: This really should be synced... But how?
+ end
+ return @@instance
+ end
+
+ # Create the thread that sends messages to the server.
+ #
+ # @param [String] srv The server address.
+ # @param [String] jid The JID.
+ # @param [String] pwd The password corresponding to the JID.
+ # @param [String] name The bot name.
+ # @return [NULL] nada
+ def run_slave_thread (srv, jid, pwd, name)
+ @srv = srv
+ @jid = jid
+ @pwd = pwd
+ @name = name
+ @logger.info("Creating slave thread on #{@jid}@#{@srv}/#{@name}.")
+ @slave_thread = Thread.new do
+ self.create_slave_thread()
+ end
+ return nil
+ end
+
+ # Returns whether instance is connected and authenticated.
+ #
+ # @return [Boolean] True or false.
+ def is_connected_and_authenticated? ()
+ return false if true == @xmpp.nil?
+ return (@xmpp.isConnected() and @xmpp.isAuthenticated())
+ end
+
+ # Creates the thread that does the actual sending to XMPP.
+ # @return [NULL] nada
+ def create_slave_thread ()
+ begin
+ @logger.info("Slave thread is now alive.")
+ self.open()
+ loop do
+ rcp, msg = @queue.deq().split(@separator, 2)
+ @logger.debug("New message for '#{rcp}' saying '#{msg}'.")
+ if rcp.match(@regexp_muc)
+ room = rcp.gsub(@regexp_muc, '').gsub(@regexp_tail, '')
+ self.send_to_muc(room, msg)
+ else
+ self.send_to_jid(rcp, msg)
+ end
+ end
+ rescue XMPPSmackException
+ @logger.fatal("Something is wrong")
+ ensure
+ @logger.info("XMPP bot disconnect.")
+ @xmpp.disconnect()
+ end
+ return nil
+ end
+
+ # Send a message to the recipient.
+ #
+ # @param [String] rcp The recipent MUC or JID.
+ # @param [String] msg The message.
+ # @return [NULL] nada
+ def send_msg(rcp, msg)
+ #if @slave_thread.nil? or not self.is_connected_and_authenticated?()
+ # str = "There is either no slave thread running or a disconnect..."
+ # @logger.warn(str)
+ # self.reconnect()
+ #end
+ @queue.enq(rcp + @separator + msg)
+ return nil
+ end
+
+ # Sends a message to a room.
+ #
+ # @param [String] room The name of the room.
+ # @param [String] mgs The message to send.
+ # @return [NULL] nada
+ def send_to_muc (room, msg)
+ if not @jid_created_chat.has_key?(room)
+ @jid_created_chat[room] = MultiUserChat.new(@xmpp, room)
+ @jid_created_chat[room].join(@name)
+ end
+ @logger.debug("Sending to MUC '#{room}' message '#{msg}'.")
+ @jid_created_chat[room].sendMessage(msg)
+ return nil
+ end
+
+ # Sends a message to a jid.
+ #
+ # Do not destroy the chat, we can reuse it when the user log back in again.
+ # Maybe?
+ #
+ # @param [String] jid The JID of the recipient.
+ # @param [String] mgs The message to send.
+ # @return [NULL] nada
+ def send_to_jid (jid, msg)
+ if true == jid_is_available?(jid)
+ if not @jid_created_chat.has_key?(jid)
+ @jid_created_chat[jid] = @xmpp.getChatManager.createChat(jid, nil)
+ end
+ @logger.debug("Sending to JID '#{jid}' message '#{msg}'.")
+ @jid_created_chat[jid].sendMessage(msg)
+ end
+ return nil
+ end
+
+ # Check to see if the jid is available or not.
+ #
+ # @param [String] jid The JID of the recipient.
+ # @return [Boolean] Whether we can send a message or not.
+ def jid_is_available?(jid)
+ if true == @xmpp.getRoster().getPresence(jid).isAvailable()
+ @logger.debug("#{jid} is available. Status is " +
+ "#{@xmpp.getRoster().getPresence(jid).getStatus()}")
+ return true
+ else
+ @logger.warn("#{jid} is not available. Status is " +
+ "#{@xmpp.getRoster().getPresence(jid).getStatus()}")
+ return false
+ end
+ end
+
+ # Opens a connection to the xmpp server at given port.
+ #
+ # @return [NULL] nada
+ def open()
+ @logger.info("XMPP bot is being created.")
+ self.open_connection()
+ self.open_authentication()
+ self.create_roster()
+ sleep 5
+ return nil
+ end
+
+ # Connect to server.
+ #
+ # @return [NULL] nada
+ def open_connection()
+ @xmpp = XMPPConnection.new(@srv)
+ if false == self.connect()
+ str = "Connection refused"
+ @logger.error(str)
+ raise XMPPSmackException.new(str)
+ end
+ @logger.debug("XMPP bot connected successfully.")
+ return nil
+ end
+
+ # Authenticat connection.
+ #
+ # @return [NULL] nada
+ def open_authentication()
+ if false == self.login(@jid, @pwd)
+ str = "Authentication failed"
+ @logger.error(str)
+ raise XMPPSmackException.new(str)
+ end
+ @logger.debug("XMPP bot authenticated successfully.")
+ return nil
+ end
+
+ # Create a new roster and listener.
+ #
+ # @return [NULL] nada
+ def create_roster
+ @xmpp.getRoster().addRosterListener(RosterListener.new())
+ @xmpp.getRoster().reload()
+ @xmpp.getRoster().getPresence(@xmpp.getUser).setStatus(
+ "Purple alert! Purple alert!")
+ @logger.debug("XMPP bot roster aquired successfully.")
+ return nil
+ end
+
+ # Connects to the server.
+ #
+ # @return [Boolean] true (aka sucess) or false (aka failure).
+ def connect ()
+ @xmpp.connect()
+ return @xmpp.isConnected()
+ end
+
+ # Login onto the server.
+ #
+ # @param [String] jid The JID.
+ # @param [String] pwd The password corresponding to the JID.
+ # @return [Boolean] true (aka sucess) or false (aka failure).
+ def login (jid, pwd)
+ @xmpp.login(jid, pwd, @name)
+ return @xmpp.isAuthenticated()
+ end
+
+ # Reconnects in case of errors.
+ #
+ # @return [NULL] nada
+ def reconnect()
+ @xmpp.disconnect
+ @slave_thread = Thread.new do
+ self.create_slave_thread()
+ end
+ return nil
+ end
+
+ def presenceChanged ()
+ end
+
+ end # XMPPSmack
+
+
+ ## This is the class that gets called in person.rb.
+ #
+ # This class is a wrapper to XMPPSmack which does the hard work. It is
+ # done this way to conform to the mauve configuration file way of
+ # defining notifications.
+ #
+ # @author Yann Golanski
+ class Default
+
+ # Name of the class.
+ attr_reader :name
+
+ # Atrtribute.
+ attr_accessor :jid
+
+ # Atrtribute.
+ attr_accessor :password
+
+ # Atrtribute.
+ attr_accessor :initial_jid
+
+ # Atrtribute.
+ attr_accessor :initial_messages
+
+ # Default constructor.
+ #
+ # @param [String] name The name of the notifier.
+ def initialize (name)
+ extend(MonitorMixin)
+ @name = name
+ @logger = Log4r::Logger.new "mauve::XMPP_default<#{Process.pid}>"
+ end
+
+ # Sends a message to the relevant jid or muc.
+ #
+ # We have no way to know if a messages was recieved, only that
+ # we send it.
+ #
+ # @param [String] destionation
+ # @param [Alert] alert A mauve alert class
+ # @param [Array] all_alerts subset of current alerts
+ # @param [Hash] conditions Supported conditions, see above.
+ # @return [Boolean] Whether a message can be send or not.
+ def send_alert(destination, alert, all_alerts, conditions = nil)
+ synchronize {
+ client = XMPPSmack.instance(@jid, @password)
+ if not destination.match(/^muc:/)
+ if false == client.jid_is_available?(destination.gsub(/^muc:/, ''))
+ return false
+ end
+ end
+ client.send_msg(destination, convert_alert_to_message(alert))
+ return true
+ }
+ end
+
+ # Takes an alert and converts it into a message.
+ #
+ # @param [Alert] alert The alert to convert.
+ # @return [String] The message, either as HTML.
+ def convert_alert_to_message(alert)
+ arr = alert.summary_three_lines
+ str = arr[0] + ": " + arr[1]
+ str += " -- " + arr[2] if false == arr[2].nil?
+ str += "."
+ return str
+ #return alert.summary_two_lines.join(" -- ")
+ #return "<p>" + alert.summary_two_lines.join("<br />") + "</p>"
+ end
+
+ # This is so unit tests can run fine.
+ include Debug
+
+ end # Default
+
+ end
+ end
+end
+
+# This is a simple example of usage. Run with:
+# ../../../jmauve_starter.rb xmpp-smack.rb
+# Clearly, the mauve jabber password is not correct.
+#
+# /!\ WARNING: DO NOT COMMIT THE REAL PASSWORD TO MERCURIAL!!!
+#
+def send_msg()
+ bot = Mauve::Notifiers::Xmpp::XMPPSmack.instance(
+ "mauvealert@chat.bytemark.co.uk/testing1234", '')
+ msg = "What fresh hell is this? -- Dorothy Parker."
+ bot.send_msg("yann@chat.bytemark.co.uk", msg)
+ bot.send_msg("muc:test@conference.chat.bytemark.co.uk", msg)
+ sleep 2
+end
+
+if __FILE__ == './'+$0
+ Thread.abort_on_exception = true
+ logger = Log4r::Logger.new('mauve')
+ logger.level = Log4r::DEBUG
+ logger.add Log4r::Outputter.stdout
+ send_msg()
+ send_msg()
+ logger.info("START")
+ logger.info("END")
+end