diff options
Diffstat (limited to 'lib/mauve/notifiers/xmpp-smack.rb')
-rw-r--r-- | lib/mauve/notifiers/xmpp-smack.rb | 395 |
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 |