# require 'xmpp4r/client' # require 'xmpp4r/reliable' # require 'xmpp4r/roster' # require 'xmpp4r/muc' # require 'xmpp4r/bytestreams' # new xmpp client: # require "blather/client/dsl" # require "blather/client/dsl/pubsub" Thread.new { ::EM.run } # ### MUC extentions for Blather: # See: https://github.com/maccman/blather and https://github.com/adhearsion/blather/issues/81 # module Blather # class Roster # def push(elem, send = true, groups = ["Buddies"]) # jid = elem.respond_to?(:jid) && elem.jid ? elem.jid : JID.new(elem) # @items[key(jid)] = node = RosterItem.new(elem) # @items[key(jid)].subscription = :both # @items[key(jid)].groups = groups # @stream.write(node.to_stanza(:set)) if send # end # alias_method :add, :push # end # class Client # def unbind # # Fix bug where closing connection stops EM for ALL Client Instances. # # call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop) # call_handler_for(:disconnected, nil) # true # end # end # # # module DSL # class MultiUserChat # def initialize(client, jid, nickname = nil, password = nil) # @client = client # @room = ::Blather::JID.new(jid) # @nickname = nickname || @room.resource # @password = password # @room.strip! # end # def join(reason = nil) # join = ::Blather::Stanza::Presence::MUCJoin.new # join.to = "#{@room}/#{@nickname}" # join.password = @password # write join # end # # # # available # # # def status=(state) # status = ::Blather::Stanza::Presence::Status.new # status.state = state # status.to = @room # write status # end # # # # # # # # comment # # # # # # # def invite(jid, reason = nil) # message = ::Blather::Stanza::Message.new(@room, nil, nil) # message << ::Blather::MUC::Invite.new(jid, reason, @password) # write message # end # # # # foo # # # def say(msg, xhtml = nil) # message = ::Blather::Stanza::Message.new(@room, msg, :groupchat) # message.xhtml = xhtml if xhtml # write message # end # # # # foo # # # def subject=(body) # message = Blather::Stanza::Message.new(@room, nil, :groupchat) # message.subject = body # write message # end # def leave # self.status = :unavailable # end # # # # # # # # Macbeth doth come. # # # # # # # def destroy(reason = nil) # destroy = ::Blather::MUC::Owner::Destroy.new(@room) # destroy.reason = reason # write destroy # end # def get_configuration(&block) # get_configure = ::Blather::MUC::Owner::Configure.new(:get, @room) # write_with_handler(get_configure) do |stana| # yield stana # end # end # alias_method :configuration, :get_configuration # def set_configuration(values, &block) # set_configure = ::Blather::MUC::Owner::Configure.new(:set, @room) # set_configure.data = values # write_with_handler(set_configure, &block) # end # alias_method :configuration=, :set_configuration # # # # # # # # # def set_default_configuration(&block) # set_configuration(:default) do # yield if block_given? # end # end # alias_method :unlock, :set_default_configuration # # # # # # # # # # # # # # # # # # # # # # # def members(&block) # members = ::Blather::MUC::Admin::Members.new(@room) # write_with_handler(members, &block) # end # def write(stanza) # @client.write(stanza) # end # def write_with_handler(stanza, &block) # @client.write_with_handler(stanza, &block) # end # end # end Class Blather::DSL::MultiUserChat # end # end Module Blather::DSL # # ### Support for MUC Presense # module MUC # # # class Join < ::Blather::XMPPNode # register :x, "http://jabber.org/protocol/muc" # def self.new(password = nil) # join = super :x # join.password = password # join # end # def password=(password) # return if password.blank? # create_password.content = password # end # protected # def create_password # unless create_password = find_first('ns:password', :ns => self.class.registered_ns) # self << (create_password = XMPPNode.new('password', self.document)) # end # create_password # end # end #Join # # # # # # comment # # # # # class Invite < ::Blather::Stanza # register :muc_invite, :x, "http://jabber.org/protocol/muc#user" # def self.import(node) # self.new(node.element_name).inherit(node) # end # def inherit(node) # create_invite.remove # self.from = node[:from] # invite = node.find_first('ns:x/ns:invite', :ns => self.class.registered_ns) # self << invite # self # end # def self.new(jid = nil, reason = nil, password = nil) # invite = super :x # invite.invite_to = jid # invite.reason = reason # invite.password = password # invite # end # def invite_to=(jid) # create_invite[:to] = JID.new(jid) # end # def invite_to # ::Blather::JID.new(create_invite[:to]) # end # def invite_from # create_invite[:from] # end # def reason=(reason) # return if reason.blank? # create_reason.content = reason # end # def reason # create_reason.content # end # def password=(password) # return if password.blank? # create_password.content = password # end # def password # create_password.content # end # protected # def create_password # unless create_password = find_first('ns:password', :ns => self.class.registered_ns) # self << (create_password = XMPPNode.new('password', self.document)) # end # create_password # end # def create_invite # unless create_invite = find_first('ns:invite', :ns => self.class.registered_ns) # self << (create_invite = XMPPNode.new('invite', self.document)) # end # create_invite # end # def create_reason # unless create_reason = create_invite.find_first('ns:reason', :ns => self.class.registered_ns) # self.create_invite << (create_reason = XMPPNode.new('reason', self.document)) # end # create_reason # end # def create_password # unless create_password = find_first('ns:password', :ns => self.class.registered_ns) # self << (create_password = XMPPNode.new('password', self.document)) # end # create_password # end # end #Invite # class Owner < Blather::Stanza::Iq::Query # register :owner, :owner, 'http://jabber.org/protocol/muc#owner' # def self.new(type = nil, to = nil) # node = super(type) # node.to = to # node # end # class Configure < Owner # DATA_NAMESPACE = 'jabber:x:data' # def data=(data = :default) # create_data[:type] = 'submit' # unless data.blank? || data == :default # raise "Invalid data format" unless data.is_a?(Hash) # data.each {|key, value| # create_field = XMPPNode.new('field', self.document) # create_field[:var] = key # create_field_value = XMPPNode.new('value', self.document) # if [TrueClass, FalseClass].include?(value.class) # value = value ? 1 : 0 # end # create_field_value.content = value.to_s # create_field << create_field_value # create_data << create_field # } # end # end # def data # items = create_data.find('//ns:field', :ns => self.class.registered_ns) # items.inject({}) do |hash, item| # key = item[:var] # value = item.find_first('ns:value', :ns => self.class.registered_ns) # value = value.content # hash[key] = value # hash # end # end # protected # def create_data # unless create_data = query.find_first('ns:x', :ns => DATA_NAMESPACE) # query << (create_data = XMPPNode.new('x', self.document)) # create_data.namespace = DATA_NAMESPACE # end # create_data # end # end # # # # # # # # Macbeth doth come. # # # # # # # class Destroy < Owner # def self.new(*args) # query = super(:set, *args) # query.create_destroy # query # end # def reason=(reason) # return if reason.blank? # create_reason.content = reason # end # def create_reason # @private # unless create_reason = create_destroy.find_first('ns:reason', :ns => self.class.registered_ns) # create_destroy << (create_reason = XMPPNode.new('reason', self.document)) # end # create_reason # end # def create_destroy # @private # unless create_destroy = query.find_first('ns:destroy', :ns => self.class.registered_ns) # query << (create_destroy = XMPPNode.new('destroy', self.document)) # end # create_destroy # end # end # end # class Admin < Blather::Stanza::Iq::Query # register :admin, :admin, 'http://jabber.org/protocol/muc#admin' # def self.new(type = nil, to = nil) # node = super(type) # node.to = to # node # end # # # # # # # # # # # class Members < Admin # def self.new(*args) # query = super(:get, *args) # query.create_item # query # end # def create_item # @private # unless create_item = query.find_first('ns:item', :ns => self.class.registered_ns) # query << (create_item = XMPPNode.new('item', self.document)) # create_item[:affiliation] = 'member' # end # create_item # end # end # end # Admin # end # end module MUC # # # class Stanza # class Presence # class MUCJoin < ::Blather::Stanza::Presence # MUC_NS = "http://jabber.org/protocol/muc" # register :muc_join, :muc_join # def password # create_muc.content_from :password # create_password.content # end # def password=(value) # create_muc.set_content_for :password # end # private # def create_muc # unless create_muc = find_first('ns:x', :ns => MUC_NS) # self << (create_muc = ::Blather::XMPPNode.new('x', self.document)) # create_muc.namespace = MUC_NS # end # create_muc # end # end # # # # class MUCUser < Presence # class Status < ::Blather::XMPPNode # def self.new(code) # new_node = super :status # new_node.code = code # new_node # end # def code # read_attr :code # end # def code=(var) # write_attr :code, var # end # end # MUC_NS = "http://jabber.org/protocol/muc#user" # register :muc_user, :muc_user # def affiliation # create_ia[:affiliation] # end # def affiliation=(val) # create_ia[:affiliation] = val # end # def role # create_ia[:role] # end # def role=(val) # create_ia[:role] = val # end # def jid # create_ia[:jid] # end # def jid=(val) # create_ia[:jid] = val # end # private # def create_muc # unless create_muc = find_first('ns:x', :ns => MUC_NS) # self << (create_muc = ::Blather::XMPPNode.new('x', self.document)) # create_muc.namespace = MUC_NS # end # create_muc # end # def create_ia # unless create_ia = create_muc.find_first('ns:item', :ns => self.class.registered_ns) # create_muc << (create_ia = XMPPNode.new('item', self.document)) # end # create_ia # end # end # end # end # # # end # end Module Blather # XMPP Classes: # Review : Blather switchover: # require ::File.expand_path("./xmpp_client.rb",::File.dirname(__FILE__)) # # ### Invitations # class XmppAgentInvitation # def initialize(parent=nil, invitation=nil) # @uuid = ::GxG::uuid_generate.to_sym # @pending = true # @parent = parent # @invitation = invitation # # # if @invitation.is_a?(::Blather::Stanza::Presence) # @details = {:sender => @invitation.from.to_s, :type => :association} # else # @details = {:sender => @invitation.from.to_s, :type => :unkown} # end # # # self # end # # Review : debug only # def invitation() # @invitation # end # # # def uuid() # @uuid # end # # # def inspect() # @details.inspect # end # # # def pending?() # @pending # end # # # def keys() # @details.keys() # end # # # def [](the_key) # @details[(the_key)] # end # # # def accept() # if @invitation # @parent.write(@invitation.approve!) # @pending = false # @parent.refresh_invitations() # true # else # false # end # end # # # def decline() # if @invitation # @parent.write(@invitation.cancel!) # @pending = false # @parent.refresh_invitations() # true # else # false # end # end # end # # ### File Transfer # class XmppAgentFileTransfer # # Review : totally rewrite this # def initialize(manager, direction, details, parent) # @reference = ::GxG::uuid_generate.to_sym # @session_id = nil # @pending = true # @inprogress = false # @percent_completed = 0 # @status_thread_safety = ::Mutex.new # @manager = manager # @direction = direction # @info = {:reference => @reference} # @bytestream = nil # @file_details = nil # @cancel = false # the_value = ::GxG::SYSTEM.maximum_buffer_size.get_at_path("/:ipv4/:tcp/:write/:valid") # if the_value.is_a?(::Range) # @valid_write_buffer_sizes = the_value # else # @valid_write_buffer_sizes = (4096..65536) # end # @buffer_size = 4096 # @client = parent # if @direction == :download # @invitation = details[:iq] # @sender = details[:iq].from # @info[:sender] = @sender.to_s # @info[:recipient] = @client.jid.to_s # @file_details = {:filename => details[:file_info].fname, :path => (details[:download_directory] + "/" + details[:file_info].fname), :size => details[:file_info].size, :md5 => details[:file_info].hash, :description => details[:file_info].description} # @download_directory = details[:download_directory] # @info[:details] = @file_details # @recipient = nil # # # # # end # if @direction == :upload # @info[:sender] = @client.jid.to_s # @info[:recipient] = details[:jid].to_s # @recipient = ::Jabber::JID.new(details[:jid]) # @file_details = {:filename => ::File.basename(details[:path]), :path => details[:path], :size => ::File.size(details[:path]), :md5 => ::Digest::MD5.file(details[:path]), :description => details[:description]} # @info[:details] = @file_details # @invitation = nil # @download_directory = nil # end # self # end # # # def inspect() # "" # end # # # def info() # @info.clone # end # # # def reference() # @reference.clone # end # # # def session_id() # @session_id.clone # end # # # def pending?() # @status_thread_safety.synchronize { @pending } # end # # # def buffer_size() # @status_thread_safety.synchronize { @buffer_size } # end # # # def buffer_size=(the_size=4096) # begin # unless the_size.is_a?(::Integer) # raise ArgumentError, "You MUST provide an Integer to set buffer size." # end # if @status_thread_safety.synchronize { @direction == :upload } # unless @valid_write_buffer_sizes.include?(the_size) # raise ArgumentError, "You MUST provide an Integer between #{@valid_read_buffer_sizes.first} and #{@valid_read_buffer_sizes.last}." # @status_thread_safety.synchronize { @buffer_size = the_size } # end # else # log_warn("Buffer size is only set on upload file transfers.") # end # the_size # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # nil # end # end # # # def cancelled?() # @status_thread_safety.synchronize { @cancel } # end # # # def cancel() # if @client # begin # if @status_thread_safety.synchronize { @pending == true } # @status_thread_safety.synchronize { @pending = false } # end # if @status_thread_safety.synchronize { @inprogress == true } # @status_thread_safety.synchronize { @inprogress = false } # end # @status_thread_safety.synchronize { @cancel = true } # @client.refresh_file_transfers # true # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # else # false # end # end # # # def accept() # if @client # if @status_thread_safety.synchronize { @pending == true && @direction == :download } # Thread.new { # begin # bytes_received = 0 # total_size = @file_details[:size] # @bytestream = @manager.accept(@invitation) # if @bytestream # @bytestream.accept() # @status_thread_safety.synchronize { # @pending = false # @inprogress = true # @session_id = @bytestream.session_id.to_s # } # # # cancel_callback = Proc.new do |the_iq| # if the_iq.type == :error # the_error = ::Jabber::ServerError.new(the_iq.error) # if @invitation.id.to_s == the_iq.id.to_s # the_transfer = @client.find_file_transfer(@reference) # if the_transfer # the_transfer.cancel # end # end # log_error({:error => the_error, :parameters => {:iq => the_iq}}) # end # end # @client.set_xfr_callback(@session_id,&cancel_callback) # # # the_file = ::File.open(@file_details[:path], "w+b",0664) # the_file.pos = 0 # buffer = @bytestream.read() # while buffer && @status_thread_safety.synchronize { @cancel == false } do # the_file.write(buffer.to_s) # bytes_received += buffer.to_s.size # @status_thread_safety.synchronize { @percent_completed = ((bytes_received.to_f / total_size.to_f) * 100.0).to_i } # @client.notify({:event => :file_transfer_progress, :transfer => @reference, :progress => @status_thread_safety.synchronize { @percent_completed }}) # buffer = @bytestream.read() # end # the_file.close # @bytestream.close # @status_thread_safety.synchronize { # @inprogress = false # @pending = false # } # the_path = @file_details[:path] # if @status_thread_safety.synchronize { @cancel == true } # ::File.delete(the_path) # @client.notify({:event => :file_transfer_cancelled, :transfer => @reference}) # else # if @file_details[:md5] # unless @file_details[:md5] == ::Digest::MD5.file(the_path) # raise Exception, "MD5 Checksum Failure - file corruption on file: #{the_path}" # end # @client.notify({:event => :file_transfer_complete, :transfer => @reference}) # end # end # else # @status_thread_safety.synchronize { # @inprogress = false # @pending = false # } # the_file = nil # raise Exception, "Failed to negotiate a transfer method." # end # rescue Exception => the_error # @status_thread_safety.synchronize { # @inprogress = false # @pending = false # } # if the_file # the_file.close # end # if @bytestream # @bytestream.close # end # log_error({:error => the_error, :parameters => {}}) # @client.notify({:event => :file_transfer_error, :transfer => @reference}) # end # if @session_id # @client.clear_xfr_callback(@session_id) # end # @client.refresh_file_transfers # } # end # true # else # false # end # end # # # def decline() # if @client # if @status_thread_safety.synchronize { @pending == true && @direction == :download } # begin # @manager.decline(@invitation) # @status_thread_safety.synchronize { @pending = false } # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # end # true # else # false # end # end # # # def in_progress?() # @status_thread_safety.synchronize { @inprogress } # end # # # def percent_completed() # @status_thread_safety.synchronize { @percent_completed.clone } # end # # # def file_details() # @status_thread_safety.synchronize { @file_details.clone } # end # # # def download_directory() # @status_thread_safety.synchronize { @download_directory.clone } # end # # # def download_directory=(the_path=nil) # begin # unless the_path.is_a?(::String) # raise ArgumentError, "You MUST supply a String as valid directory path." # end # the_path = ::File.expand_path(the_path) # unless the_path.valid_path?() # raise ArgumentError, "You MUST supply a valid directory path." # end # unless ::File.exist?(the_path) # raise ArgumentError, "You MUST supply a valid directory path - Directory does not exist." # end # @status_thread_safety.synchronize { # unless @inprogress == true # if @direction == :download # @download_directory = ::File.expand_path(the_path) # @file_details[:path] = (@download_directory + "/" + @file_details[:filename]) # end # end # } # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # end # # # def upload_file(proxy=nil) # if @client # if @direction == :upload # unless @status_thread_safety.synchronize { @inprogress == true } # Thread.new { # begin # bytes_sent = 0 # buffer_size = @status_thread_safety.synchronize { @buffer_size } # the_file_source = ::Jabber::FileTransfer::FileSource.new(@file_details[:path]) # the_file_source.length = buffer_size # total_size = the_file_source.size # @bytestream = nil # response = @manager.offer(@recipient,the_file_source,@file_details[:description],nil,@reference.to_s) # if response.is_a?(::Hash) # @bytestream = response[:bytestream] # the_iq_id = response[:iq_id] # if @bytestream # if @bytestream.is_a?(::Jabber::Bytestreams::SOCKS5BytestreamsInitiator) # if proxy.is_any?(::String, ::Jabber::JID) # @bytestream.add_streamhost(proxy) # end # end # if @bytestream.is_a?(::Jabber::Bytestreams::IBBInitiator) # @bytestream.block_size = buffer_size # end # # # @status_thread_safety.synchronize { # @pending = false # @inprogress = true # } # open_response = @bytestream.open() # if open_response # puts "Got: (open_response) - #{open_response.inspect}" # if open_response.type == :result # # # @session_id = @bytestream.session_id.to_s # # # cancel_callback = Proc.new do |the_iq| # if the_iq.type == :error # the_error = ::Jabber::ServerError.new(the_iq.error) # if the_iq_id.to_s == the_iq.id.to_s # the_transfer = @client.find_file_transfer(@reference) # if the_transfer # the_transfer.cancel # end # end # log_error({:error => the_error, :parameters => {:iq => the_iq}}) # end # end # @client.set_xfr_callback(@session_id,&cancel_callback) # # # ::GxG.apportioned_ranges(total_size,buffer_size).each do |the_range| # if @status_thread_safety.synchronize { @cancel == true } # the_file_source.close # break # end # the_file_source.seek(the_range.first) # bytes_sent += @bytestream.write(the_file_source.read(the_range.size)).to_i # @status_thread_safety.synchronize { @percent_completed = ((bytes_sent.to_f / total_size.to_f) * 100.0).to_i } # # # end # @bytestream.flush # @bytestream.close # @status_thread_safety.synchronize { @inprogress = false } # else # # Error? # @status_thread_safety.synchronize { # @pending = false # @inprogress = false # } # end # else # raise Exception, "Failed to open the stream." # end # end # else # @status_thread_safety.synchronize { # @pending = false # @inprogress = false # } # end # rescue Exception => the_error # the_file_source.close # @bytestream.close # @status_thread_safety.synchronize { @inprogress = false } # log_error({:error => the_error, :parameters => {:proxy => proxy}}) # end # @client.clear_xfr_callback(@reference.to_s) # if @session_id # @client.clear_xfr_callback(@session_id) # end # @client.refresh_file_transfers # } # end # end # end # end # # # end # # # # ### Conversation Channel # class XmppAgentConversation # # # def default_room_configuration(options={}) # unless options.is_a?(::Hash) # options = {} # end # default_config = { # 'FORM_TYPE' => 'http://jabber.org/protocol/muc#roomconfig', # 'form' => 'config', # 'muc#roomconfig_roomname' => nil, # 'muc#roomconfig_roomdesc' => nil, # 'leave' => 'has left', # 'join' => 'has become available', # 'rename' => 'is now known as', # 'muc#roomconfig_changesubject' => 0, # 'muc#roomconfig_maxusers' => 0, # 'privacy' => 0, # 'muc#roomconfig_publicroom' => 1, # 'muc#roomconfig_persistentroom' => 1, # 'legacy' => 0, # 'muc#roomconfig_moderatedroom' => 0, # 'defaulttype' => 0, # 'privmsg' => 0, # 'muc#roomconfig_membersonly' => 0, # 'muc#roomconfig_allowinvites' => 1, # 'muc#roomconfig_passwordprotectedroom' => 0, # 'muc#roomconfig_roomsecret' => nil, # 'muc#roomconfig_whois' => 'moderators', # 'muc#roomconfig_enablelogging' => 0, # 'logformat' => 'xml' # } # default_config.merge(options) # end # # # def initialize(parent=nil, details={}) # if ::GxG::valid_uuid?(details[:context]) # @uuid = details[:context].to_s.to_sym # else # @uuid = ::GxG::uuid_generate.to_sym # end # @parent = parent # # @details should have a JID for the sender-jid/room-jid # # @details should indicate whether a private direct message or MUC ROOM message (:chat? or :groupchat?) # @details = details # # # @received = [] # @received_thread_safety = ::Mutex.new # # Add call backs # # @parent.notify({:event => :new_message, :conversation => @uuid, :message => message.id}) # # @parent.notify({:event => :new_private_message, :conversation => @uuid, :message => message.id}) # self # end # # # def uuid() # @uuid # end # # # def jid() # @details[:sender].to_s # end # # # def my_jid() # result = nil # if @parent # result = @parent.jid().to_s # end # result # end # # # def [](the_key=nil) # result = nil # if the_key.is_a?(::Symbol) # result = @details[(the_key)] # end # result # end # # # def title() # result = nil # # Review : TODO - return ROOM title # result # end # # # def inspect() # {:jid => this().jid, :title => this().title}.inspect # end # # # def closed?() # # Review : integrate with new system # false # end # # # def process_received(&block) # begin # if block.respond_to?(:call) # count = 0 # @received_thread_safety.synchronize { # count = @received.size # } # record = nil # count.times do # @received_thread_safety.synchronize { # record = @received.shift # } # if record # block.call(record) # end # end # true # else # false # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # false # end # end # # # def add_message(the_message=nil) # if the_message # @received_thread_safety.synchronize { @received << the_message } # true # else # false # end # end # # # def join(the_jid=nil, password=nil, options={}) # result = false # begin # # Review : TODO - MUC join # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:the_jid => the_jid, :options => options}}) # end # result # end # # # def leave(exit_string=nil) # result = false # begin # # Review : TODO - MUC leave # # @parent.refresh_conversations # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:exit_string => exit_string}}) # end # result # end # # # def chat_buddies() # result = [] # begin # # Review : TODO - like XmppAgent.buddies but for this ROOM only. # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def say_something(the_message=nil, to_jid=nil) # result = false # begin # if @details[:type] == :chat # recipient = (to_jid || the_message[:to]).to_s # @parent.say(::Blather::JID.new(recipient), @parent.gxg_message_to_xmpp(the_message)) # else # # Review : TODO - send chat message to ROOM # end # result = true # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:message => the_message, :to => to_jid}}) # end # result # end # # # def send_invitations(the_recipients=nil, reason=nil) # result = false # begin # # Review : TODO - ROOM invitation list processing list.each { |i| send i } # # "You MUST provide an Array of JID Strings." # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:recipients => the_recipients, :message => reason}}) # end # result # end # # # def configuration() # result = nil # begin # # Review : TODO - get ROOM configuration # # if @handler # # if @handler.owner? # # current_configuration = {} # # iq = ::Jabber::Iq.new(:get, @handler.jid.to_s) # # iq.to = @handler.jid.to_s # # iq.from = @handler.my_jid.to_s # # iq.add(::Jabber::MUC::IqQueryMUCOwner.new) # # field_keys = this().default_room_configuration.keys # # response = @stream.send_with_id(iq) # # if response # # if (response.query && response.query.x(::Jabber::Dataforms::XData)) # # field_keys.each do |the_key| # # field = response.query.x(::Jabber::Dataforms::XData).field(the_key.to_s) # # if field.is_a?(::Jabber::Dataforms::XDataField) # # current_configuration[(the_key.to_s)] = field.value # # end # # end # # end # # end # # result = current_configuration # # else # # raise Exception, "You are not the owner of this conversation room." # # end # # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def change_configuration(the_configuration=nil) # result = false # begin # # Review : TODO - alter ROOM configuration if admin/owner # # if @handler # # if @handler.owner? # # unless the_configuration.is_a?(::Hash) # # raise ArgumentError, "You MUST provide a configuration Hash." # # end # # the_configuration = this().default_room_configuration(the_configuration) # # @handler.submit_room_configuration(the_configuration) # # result = true # # else # # raise Exception, "You are not the owner of this conversation room." # # end # # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:configuration => the_configuration}}) # end # result # end # # # end # # ### XMPP Client # class XmppAgent # # ### # def self.online_availability_values() # {:online => :chat, :busy => :dnd, :away => :away, :extended_away => :xa, :offline => :unavailable, :error => :error} # end # # # def self.registration_fields() # # XEP-0077 Support # {:username => 'Account name associated with the user', :nick => 'Familiar name of the user', :password => 'Password or secret for the user', :name => 'Full name of the user', :first => 'First name or given name of the user', :last => 'Last name, surname, or family name of the user', :email => 'Email address of the user', :address => 'Street portion of a physical or mailing address', :city => 'Locality portion of a physical or mailing address', :state => 'Region portion of a physical or mailing address', :zip => 'Postal code portion of a physical or mailing address', :phone => 'Telephone number of the user', :url => 'URL to web page describing the user', :date => 'Some date (e.g., birth date, hire date, sign-up date)'}.clone # end # # # def self.registration_info(the_url=nil) # result = nil # begin # if the_url.is_a?(::URI::Generic) # the_jid = "anonymous" # if the_url.hostname() # the_jid << ("@" + the_url.hostname.to_s) # else # raise ArgumentError, "You MUST provide a vailid host or IP Address in a valid URI." # end # # Review : convert this code to Blather compatible # # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas. # # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas. # result = [] # # client = ::Jabber::Client.new(the_jid) # # client.connect(the_url.hostname.to_s, (the_url.port || 5222)) # # info = client.register_info() # # if client.status == 2 # # client.close # # end # # if info.is_a?(::Array) # # fields = ::GxG::Networking::XmppClient::registration_fields() # # result = {:instructions => info[0], :registration_fields => {}} # # info[1].each do |the_raw_key| # # result[:registration_fields][(the_raw_key.to_sym)] = fields[(the_raw_key.to_sym)] # # end # # end # else # raise ArgumentError, "You MUST provide a vailid URL." # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:url => the_url}}) # end # result # end # # # def self.remove_registration(the_url=nil) # # Review : convert this code to Blather compatible # # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas. # # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas. # result = false # begin # if the_url.is_a?(::URI::Generic) # if the_url.password() == nil # raise ArgumentError, "You MUST provide a vailid password." # end # the_jid = "" # if the_url.user() # the_jid << the_url.user.to_s # else # raise ArgumentError, "You MUST provide a vailid user name." # end # if the_url.hostname() # the_jid << ("@" + the_url.hostname.to_s) # else # raise ArgumentError, "You MUST provide a vailid host or IP Address." # end # if the_url.path() # the_jid << ("/" + File.basename(the_url.path.to_s)) # end # # Here ??? # # Connect # client = ::Blather::Client::setup(the_jid, the_url.password.to_s, the_url.hostname.to_s, (the_url.port || 5222)) # client.run # # xxx # # client.remove_registration # # xxx # client.close # result = true # else # raise ArgumentError, "You MUST provide a vailid URL." # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:url => the_url, :fields => fields}}) # end # result # end # # # def self.register_with_server(the_url=nil, fields={}) # # Review : convert this code to Blather compatible # # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas. # # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas. # result = false # begin # if the_url.is_a?(::URI::Generic) # if the_url.password() == nil # raise ArgumentError, "You MUST provide a vailid password." # else # fields['password'] = the_url.password.to_s # end # the_jid = "" # if the_url.user() # the_jid << the_url.user.to_s # fields['username'] = the_url.user.to_s # else # raise ArgumentError, "You MUST provide a vailid user name." # end # if the_url.hostname() # the_jid << ("@" + the_url.hostname.to_s) # else # raise ArgumentError, "You MUST provide a vailid host or IP Address." # end # if the_url.path() # the_jid << ("/" + File.basename(the_url.path.to_s)) # end # # xxx # # client = ::Jabber::Client.new(the_jid) # # client.connect(the_url.hostname.to_s, (the_url.port || 5222)) # # valid_fields = ::GxG::Networking::XmppClient::registration_fields().keys # # the_fields = {} # # fields.each_pair do |key, entry| # # if valid_fields.include?(key.to_sym) # # the_fields[(key.to_s)] = entry # # end # # end # # if the_fields.keys.size > 0 # # client.register(the_url.password.to_s, the_fields) # # else # # client.register(the_url.password.to_s) # # end # # if client.status == 2 # # client.close # # end # result = true # else # raise ArgumentError, "You MUST provide a vailid URL." # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:url => the_url, :fields => fields}}) # end # result # end # # # # ### Support Methods # def write(stanza) # if @client # @client.write stanza # end # true # end # # # def <<(stanza) # if @client # @client.write stanza # end # self # end # # # def publish_and_subscribe # @pubsub # end # # ### Setup a before filter # # # # @param [Symbol] handler (optional) the stanza handler the filter should # # run before # # @param [guards] guards (optional) a set of guards to check the stanza # # against # # @yield [Blather::Stanza] stanza # def before(handler = nil, *guards, &block) # if @client # @client.register_filter :before, handler, *guards, &block # end # end # # ### Setup an after filter # # # # @param [Symbol] handler (optional) the stanza handler the filter should # # run after # # @param [guards] guards (optional) a set of guards to check the stanza # # against # # @yield [Blather::Stanza] stanza # def after(handler = nil, *guards, &block) # if @client # @client.register_filter :after, handler, *guards, &block # end # end # # ### Set handler for a stanza type # # # # @param [Symbol] handler the stanza type it should handle # # @param [guards] guards (optional) a set of guards to check the stanza # # against # # @yield [Blather::Stanza] stanza # def handle(handler, *guards, &block) # if @client # @client.register_handler handler, *guards, &block # end # end # # ### Wrapper for "handle :ready" (just a bit of syntactic sugar) # # # # This is run after the connection has been completely setup # def when_ready(&block) # self.handle :ready, &block # end # # ### Wrapper for "handle :disconnected" # # # # This is run after the connection has been shut down. # # # # @example Reconnect after a disconnection # # disconnected { client.run } # def disconnected(&block) # self.handle :disconnected, &block # end # # ### Set current status # # # # @param [Blather::Stanza::Presence::State::VALID_STATES] state the current # # state # # @param [#to_s] msg the status message to use # def set_status(state = nil, msg = nil) # if @client # @client.status = state, msg # end # end # # # def get_status() # if @client # @client.status # else # :unavailable # end # end # # ### Direct access to the roster # # # # @return [Blather::Roster] # def my_roster # if @client # @client.roster # end # end # # ### Write data to the stream # # # # @param [#to_xml, #to_s] stanza the data to send down the wire. # def write_to_stream(stanza) # self.write stanza # end # # ### Helper method to join a MUC room # # # # @overload join(room_jid, nickname) # # @param [Blather::JID, #to_s] room the JID of the room to join # # @param [#to_s] nickname the nickname to join the room as # # @overload join(room_jid, nickname) # # @param [#to_s] room the name of the room to join # # @param [Blather::JID, #to_s] service the service domain the room is hosted at # # @param [#to_s] nickname the nickname to join the room as # def join(room, service, nickname = nil) # join = Blather::Stanza::Presence::MUC.new # if nickname # join.to = "#{room}@#{service}/#{nickname}" # else # join.to = "#{room}/#{service}" # end # self.write join # end # # ### Helper method to make sending basic messages easier # # # # @param [Blather::JID, #to_s] to the JID of the message recipient # # @param [#to_s] msg the message to send # # @param [#to_sym] the stanza method to use # def say(to, msg, using = :chat) # self.write Blather::Stanza::Message.new(to, msg, using) # end # # ### The JID according to the server # # # # @return [Blather::JID] # def jid # if @client # @client.jid # else # nil # end # end # # other supporting JID methods # def jid_node() # jid = self.jid # if jid # jid.node() # else # nil # end # end # def jid_domain() # jid = self.jid # if jid # jid.domain() # else # nil # end # end # def jid_resource() # jid = self.jid # if jid # jid.resource() # else # nil # end # end # # ### Halt the handler chain # # # # Use this to stop the propogation of the stanza though the handler chain. # # # # @example Ignore all IQ stanzas # # # # before(:iq) { halt } # def halt # # Review : how to actually use this? I see no way to call 'throw' yet. (research) # # throw :halt # end # # ### Pass responsibility to the next handler # # # # Use this to jump out of the current handler and let the next registered # # handler take care of the stanza # # # # @example Pass a message to the next handler # # # # This is contrive and should be handled with guards, but pass a message # # to the next handler based on the content # # # # message { |s| puts "message caught" } # # message { |s| pass if s.body =~ /pass along/ } # def pass # # Review : how to actually use this? I see no way to call 'throw' yet. (research) # # throw :pass # end # # ### Request items or info from an entity # # discover (items|info), [jid], [node] do |response| # # end # def discover(what, who, where, &callback) # if @client # stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new # stanza.to = who # stanza.node = where # @client.register_tmp_handler stanza.id, &callback # @client.write stanza # end # end # # ### Set the capabilities of the client # # # # @param [String] node the URI # # @param [Array] identities an array of identities # # @param [Array] features an array of features # def set_caps(node, identities, features) # if @client # @client.caps.node = node # @client.caps.identities = identities # @client.caps.features = features # end # end # # Send capabilities to the server # def send_caps # if @client # @client.register_handler :disco_info, :type => :get, :node => @client.caps.node do |s| # r = @client.caps.dup # r.to = s.from # r.id = s.id # @client.write r # end # @client.write client.caps.c # end # end # # ### Conversion Tools # def xmpp_message_to_gxg(the_message=nil) # # need: :to, :body, :state, :subject, :context, :type # # ::Celluloid::UUID::random_generate().to_s # unless the_message.is_a?(Blather::Stanza::Message) # raise ArgumentError, "You MUST provide a valid message object: Blather::Stanza::Message." # end # gxg_message = ::GxG::Events::Message.new({:sender => :unknown}) # gxg_message[:id] = (the_message.id || ::GxG::uuid_generate.to_sym) # gxg_message[:sender] = the_message.from.to_s # gxg_message[:to] = the_message.to.to_s # gxg_message[:type] = the_message.type # if the_message.thread.is_a?(::Hash) # gxg_message[:context] = the_message.thread.values[0] # else # gxg_message[:context] = the_message.thread # end # gxg_message[:subject] = the_message.subject # gxg_message[:body] = the_message.body # gxg_message # end # # # def gxg_message_to_xmpp(the_message=nil) # # need: :to, :body, :state, :subject, :context, :type # # ::Celluloid::UUID::random_generate().to_s # unless the_message.is_a?(::GxG::Events::Message) # raise ArgumentError, "You MUST provide a valid message object: GxG::Events::Message or new_message Hash." # end # xmpp_message = Blather::Stanza::Message.new(the_message[:to], the_message[:body]) # # chat states: active composing gone inactive paused # # xmpp_message.set_chat_state(the_message[:state].to_s.to_sym) # xmpp_message.id = (the_message[:id].to_s) # xmpp_message.from = (the_message[:sender].to_s) # xmpp_message.to = (the_message[:to].to_s) # if the_message[:context].to_s.size > 0 # xmpp_message.thread = (the_message[:context].to_s) # end # xmpp_message.subject = (the_message[:subject].to_s) # # # # The following Symbols are allowed: # # * :chat # # * :error # # * :groupchat # # * :headline # # * :normal # xmpp_message.type = (the_message[:type] || :chat) # xmpp_message.body = the_message[:body] # xmpp_message # end # # # # ### Initialization # def initialize(options={}) # @client = nil # @pubsub = nil # @roster = nil # @browser = nil # @transfer_manager = nil # @xfr_callbacks = nil # @conversations = [] # @conversation_thread_safety = ::Mutex.new # @invitations = [] # @invitation_thread_safety = ::Mutex.new # @download_directory = ::File.expand_path(".") # @file_transfers = [] # @file_transfers_thread_safety = ::Mutex.new # @notification_object = options[:notify] # self # end # # Review : debug only method # def client() # @client # end # # # def notify(notification_type=nil) # if notification_type # if @notification_object # if @notification_object.respond_to?(:notify) # @notification_object.notify(notification_type) # end # end # end # end # # # def login(the_url=nil, options={}) # result = false # begin # if the_url.is_a?(::URI::Generic) # if the_url.password() == nil && the_url.user != "anonymous" # raise ArgumentError, "You MUST provide a vailid password." # end # the_jid = "" # if the_url.user() # the_jid << the_url.user.to_s # else # raise ArgumentError, "You MUST provide a vailid user name." # end # if the_url.hostname() # the_jid << ("@" + the_url.hostname.to_s) # else # raise ArgumentError, "You MUST provide a vailid host or IP Address." # end # if the_url.path() # the_jid << ("/" + File.basename(the_url.path.to_s)) # end # # Connect # @client = ::Blather::Client::setup(the_jid, the_url.password.to_s, the_url.hostname.to_s, (the_url.port || 5222)) # # @client.run # # Initialize Supports # @pubsub = ::Blather::DSL::PubSub.new(@client, self.jid_domain) # # Auto-reconnect if disconnected # self.handle :disconnected do # @client.run # end # # ### Setup Message Reception: (skipping compose etc messages for now.) # # Handlers Map : https://github.com/adhearsion/blather#handlers-hierarchy # # Private Message (type :normal) # self.handle :message, :normal?, :body do |message| # the_message = self.xmpp_message_to_gxg(message) # the_channel = self.conversations(the_message[:sender].to_s)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # the_channel.add_message(the_message) # self.notify({:event => :new_private_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) # true # end # # Private Message (type :chat) # self.handle :message, :chat?, :body do |message| # the_message = self.xmpp_message_to_gxg(message) # the_channel = self.conversations(the_message[:sender].to_s)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # the_channel.add_message(the_message) # self.notify({:event => :new_private_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) # true # end # # Group Message # self.handle :message, :groupchat?, :body do |message| # the_message = self.xmpp_message_to_gxg(message) # the_channel = self.conversations(the_message[:sender].to_s)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :groupchat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # the_channel.add_message(the_message) # self.notify({:event => :new_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) # true # end # # Announcement Message # self.handle :message, :headline?, :body do |message| # the_message = self.xmpp_message_to_gxg(message) # the_channel = self.conversations(the_message[:sender].to_s)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # the_channel.add_message(the_message) # self.notify({:event => :new_announcement, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) # true # end # # Error Message # self.handle :message, :error?, :body do |message| # the_message = self.xmpp_message_to_gxg(message) # the_channel = self.conversations(the_message[:sender].to_s)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # the_channel.add_message(the_message) # self.notify({:event => :new_error, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) # true # end # # Presence # self.handle :iq do |update| # if update.respond_to?(:type) # if update.is_a?(::Blather::Stanza::Iq::Ping) # # Auto-reply to Pings: # reply = update.reply # reply.type = :result # # Review : how to add a data payload to the reply? # self.write_to_stream(reply) # else # if update.is_a?(::Blather::Stanza::Iq) # # reply = update.reply # # reply.type = :result # # # Review : how to add a data payload to the reply? # # self.write_to_stream(reply) # # puts "Got: #{update.class.inspect}\n-----------------------------------------\n#{update.inspect}\n Sent: #{reply.inspect}\n-----------------------------------------\n" # end # end # end # # Note : returning 'false' signals that subsequent process of this stanza is desired. # false # end # # Invitation # self.handle :subscription do |invitation| # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } # self.notify({:event => :new_invitation, :invitation => gxg_invitation.uuid, :sender => gxg_invitation.from.to_s}) # true # end # # Presence # self.handle :presence do |presence_update| # the_sender = "#{presence_update.from.node.to_s}@#{presence_update.from.domain.to_s}" # self.my_roster.each do |roster_item| # if roster_item.jid.to_s == the_sender # roster_item.status = presence_update # break # end # end # false # end # # File Transfer # self.handle :file_transfer do |invitation| # # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) # # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } # puts "Got (file transfer) : #{invitation.inspect}" # false # end # # Multi-User Chat (MUC) Invitation # self.handle :muc_join do |invitation| # # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) # # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } # puts "Got (MUC join) : #{invitation.inspect}" # true # end # self.handle :muc_user do |invitation| # # try also: :muc_user, :invite? # # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) # # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } # puts "Got (MUC user) : #{invitation.inspect}" # true # end # # Connect # @client.run # result = true # timeout = Time.now.to_f + 30.0 # until @client.connected? do # if Time.now.to_f >= timeout # result = false # break # end # end # end # rescue Exception => the_error # puts the_error.to_s # end # # # result # end # # # def logout # if @client # @client.clear_handlers :disconnected # @client.close # @client = nil # end # # # true # end # # # def buddies() # result = [] # begin # if @client # @client.roster.items.each_pair do |key_object, data| # new_record = {:jid => "", :title => "Untitled", :groups => [], :status => :offline} # new_record[:jid] = key_object.to_s # new_record[:title] = data.name.to_s # new_record[:groups] = data.groups # if data.status() # new_record[:status] = data.status().state() # end # result << new_record # end # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def add_buddy(the_jid=nil) # result = false # begin # if @client # if the_jid.is_a?(::String) # unless the_jid.valid_jid? # raise ArgumentError, "You MUST provide a valid JID string." # end # @client.write(Blather::Stanza::Presence::Subscription.new(::Blather::JID.new(the_jid), "subscribe")) # result = true # end # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:the_jid => the_jid}}) # end # result # end # # # def remove_buddy(the_jid=nil) # result = false # begin # if @client # if the_jid.is_a?(::String) # unless the_jid.valid_jid? # raise ArgumentError, "You MUST provide a valid JID string." # end # @client.write(Blather::Stanza::Presence::Subscription.new(::Blather::JID.new(the_jid), "unsubscribe")) # result = true # end # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:the_jid => the_jid}}) # end # result # end # # # def invitations() # result = [] # begin # if @client # @invitation_thread_safety.synchronize { # @invitations.each do |entry| # if entry.pending? # result << entry # end # end # } # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def refresh_invitations() # result = false # begin # if @client # deadlist = [] # @invitation_thread_safety.synchronize { # @invitations.each_index do |index| # unless @invitations[(index)].pending? # deadlist << index # end # end # if deadlist.size > 0 # deadlist.reverse.each do |the_index| # @invitations.delete_at(the_index) # end # end # result = true # } # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def refresh_conversations() # result = false # begin # if @client # deadlist = [] # @conversation_thread_safety.synchronize { # @conversations.each_index do |index| # if @conversations[(index)].closed? # deadlist << index # end # end # if deadlist.size > 0 # deadlist.reverse.each do |the_index| # @conversations.delete_at(the_index) # end # end # result = true # } # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def conversations(select_jid=nil) # result = [] # begin # if @client # @conversation_thread_safety.synchronize { # @conversations.each do |entry| # if select_jid # if select_jid.to_s == entry.jid.to_s # unless entry.closed? # result << entry # break # end # end # else # unless entry.closed? # result << entry # end # end # end # } # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def create_conversation(the_jid=nil, password=nil, configuration={}) # result = false # begin # if @client # unless the_jid.is_a?(::String) # raise ArgumentError, "You MUST provide a valid JID String." # end # unless the_jid.valid_jid?() # raise ArgumentError, "You MUST provide a valid JID String. The one you provided is malformed." # end # if (self.buddies.collect {|entry| entry[:jid]}).include?(the_jid) # # private channel # the_channel = self.conversations(the_jid)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :chat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # else # # groupchat channel # the_channel = self.conversations(the_jid)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :groupchat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # end # if the_channel # the_channel.join(the_jid) # if configuration.is_any?(::Hash, ::GxG::Database::PersistedHash) # if configuration.keys.size > 0 # the_channel.change_configuration(configuration) # end # end # end # end # result = true # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:jid => the_jid}}) # end # result # end # # # def join_conversation(the_jid=nil, password=nil, options={}) # result = false # begin # if @client # unless the_jid.is_a?(::String) # raise ArgumentError, "You MUST provide a valid JID String." # end # unless the_jid.valid_jid?() # raise ArgumentError, "You MUST provide a valid JID String. The one you provided is malformed." # end # the_channel = self.conversations(the_jid)[0] # unless the_channel # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :groupchat}) # @conversation_thread_safety.synchronize { @conversations << the_channel } # the_channel.join(the_jid) # end # result = true # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:jid => the_jid}}) # end # result # end # # # def download_directory() # @file_transfers_thread_safety.synchronize { @download_directory } # end # # # def download_directory=(the_path=nil) # unless the_path.is_a?(::String) # raise ArgumentError, "You MUST supply a String as valid file path." # end # the_path = ::File.expand_path(the_path) # unless the_path.valid_path?() # raise ArgumentError, "You MUST supply a valid file path." # end # unless ::File.exist?(the_path) # raise ArgumentError, "You MUST supply a valid file path - File does not exist." # end # @file_transfers_thread_safety.synchronize { @download_directory = the_path } # end # # # def send_file(the_jid=nil,the_path=nil, description=nil, buffer_size=4096, proxy=nil) # if @client # begin # unless the_jid.is_a?(::String) # raise ArgumentError, "You MUST supply a valid JID String to specify the recipient of the file." # end # unless the_path.is_a?(::String) # raise ArgumentError, "You MUST supply a String as valid file path." # end # the_path = ::File.expand_path(the_path) # unless the_path.valid_path?() # raise ArgumentError, "You MUST supply a valid file path." # end # unless ::File.exist?(the_path) # raise ArgumentError, "You MUST supply a valid file path - File does not exist." # end # unless description.is_a?(::String) # description = "File Transfer: #{::File.basename(the_path)}" # end # unless buffer_size.is_a?(::Integer) # raise ArgumentError, "You MUST provide an Integer to set buffer size." # end # valid_buffer_size_range = ::GxG::SYSTEM.maximum_buffer_size.get_at_path("/:ipv4/:tcp/:write/:valid") # unless valid_buffer_size_range.is_a?(::Range) # valid_buffer_size_range = (4096..65536) # end # unless valid_buffer_size_range.include?(buffer_size) # raise ArgumentError, "You MUST provide an Integer between #{valid_buffer_size_range.first} and #{valid_buffer_size_range.last}." # end # # Review : rebuild/rewrite this section and XmppAgentFileTransfer # # the_transfer = ::GxG::Networking::XmppAgentFileTransfer.new(@transfer_manager, :upload, {:jid => the_jid, :path => the_path, :description => description}, this()) # # @file_transfers_thread_safety.synchronize { # # @file_transfers << the_transfer # # } # # the_transfer.buffer_size = buffer_size # # the_transfer.upload_file(proxy) # # the_transfer.reference # rescue Exception => the_error # log_error({:error => the_error, :parameters => {:jid => the_jid, :path => the_path, :description => description, :buffer_size => buffer_size}}) # end # else # nil # end # end # # # def file_transfers() # result = [] # if @client # begin # @file_transfers.each do |entry| # if (entry.pending? || entry.in_progress? || entry.percent_completed < 100) # result << entry # end # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # end # result # end # # # def refresh_file_transfers() # result = false # begin # if @client # manifest = [] # deadlist = [] # @file_transfers_thread_safety.synchronize { # @file_transfers.each do |entry| # manifest << entry # end # } # manifest.each_with_index do |entry, index| # unless (entry.pending?() || entry.in_progress?()) # deadlist << index # end # end # @file_transfers_thread_safety.synchronize { # if deadlist.size > 0 # deadlist.reverse.each do |the_index| # @file_transfers.delete_at(the_index) # end # end # } # result = true # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # def find_file_transfer(reference=nil, use_session = false) # result = nil # begin # if @client # manifest = [] # @file_transfers_thread_safety.synchronize { # @file_transfers.each do |entry| # manifest << entry # end # } # manifest.each do |entry| # if use_session == true # if (reference == entry.session_id()) # result = entry # break # end # else # if (reference == entry.reference()) # result = entry # break # end # end # end # end # rescue Exception => the_error # log_error({:error => the_error, :parameters => {}}) # end # result # end # # # # # end # ### XMPP # class BridgeAdapterXMPP < ::GxG::Communications::BridgeAdapter # def self.protocol() # :xmpp # end # # # def self.abilities() # [:message, :file_transfer, :request] # end # # # def self.limits() # {:message_size => 1048576, :file_transfer_size => nil, :request_size => 65536} # end # # # def self.templates() # {} # end # # # private # def decode_message(the_message=nil) # result = message # if message.is_a?(::GxG::Events::Message) # fallback_key = nil # key = nil # message_sender = message[:sender].to_s # @thread_safety.synchronize { # if @keychain[(message_sender.to_s)] # fallback_key = @keychain[(message_sender.to_s)][:original] # key = @keychain[(message_sender.to_s)][:current] # end # } # new_body = nil # if key && fallback_key # if message[:body].is_a?(::String) # begin # if message[:body].base64? # new_body = message[:body].decode64.decrypt(key) # else # new_body = message[:body].decrypt(key) # end # new_body = ::Hash::gxg_import(::JSON::parse(new_body, {:symbolize_names => true})) # rescue Exception => the_error # begin # if message[:body].base64? # new_body = message[:body].decode64.decrypt(fallback_key) # else # new_body = message[:body].decrypt(fallback_key) # end # new_body = ::Hash::gxg_import(::JSON::parse(new_body, {:symbolize_names => true})) # rescue Exception => the_error # message[:bridge_unintelligible] = true # log_error({:error => the_error}) # new_body = nil # end # end # end # else # if message[:body].is_a?(::String) # if message[:body].base64? # new_body = message[:body].decode64 # else # new_body = message[:body] # end # begin # new_body = ::Hash::gxg_import(::JSON::parse(new_body, {:symbolize_names => true})) # rescue Exception => the_error # log_error({:error => the_error}) # new_body = nil # end # end # end # if new_body.is_a?(::Hash) # message[:body] = new_body # end # end # result # end # # # public # # # def initialize(the_process=nil, the_url=nil, options={:sasl => :digest, :digest => true}) # super(the_process, the_url, options) # unless @options[:sasl] # @options = {:sasl => :digest, :digest => true} # end # # # # Channels format: { => {:channel => , :entities => [, ...]}} # @channels = {} # # Keychain format: {:original => "", :current => ""} # @keychain = {} # # Messages format: { => [<::GxG::Events::Message>, ...]} # @messages = {} # @messages_busy_semaphore = false # @invitations = {} # @file_transfers = {} # # # @connector = ::GxG::Networking::XmppClient.new({:notify => self}) # self.open # # # self # end # # # def my_id() # if @connector # @connector.my_jid.to_s # else # nil # end # end # # ### Information Refresh # def notify(details=nil) # if details.is_a?(::Hash) # if @process # if @process.respond_to?(:dispatcher) && @process.respond_to?(:respond_to_event?) # case details[:event] # when :new_invitation # active_list = {} # @connector.invitations.each do |the_invitation| # active_list[(the_invitation.uuid)] = {:invitation => the_invitation, :title => the_invitation[:title].to_s, :sender => the_invitation[:sender].to_s, :type => the_invitation[:type].to_s} # end # @thread_safety.synchronize { @invitations = active_list } # # # if @process.respond_to_event?(:new_invitation) # @process.dispatcher.post_event(:communications) do # @process.call_event({:new_invitation => {:invitation => details[:invitation], :sender => details[:sender], :at => ::DateTime.now}}) # end # end # when :new_file_transfer # # First, update Bridge Manifest # active_list = {} # @connector.file_transfers.each do |the_transfer| # reference = the_transfer.reference # file_name = the_transfer.file_details[:filename] # file_size = the_transfer.file_details[:size] # sender = the_transfer.info[:sender] # # set download path # unless ::File.exists?(GxG::SERVER_PATHS[:temporary] + "/" + reference.to_s) # ::FileUtils.mkpath(GxG::SERVER_PATHS[:temporary] + "/" + reference.to_s) # end # the_transfer.download_directory = (GxG::SERVER_PATHS[:temporary] + "/" + reference.to_s) # # store entry # active_list[(reference)] = {:transfer => the_transfer, :sender => sender, :file => file_name, :size => file_size, :path => ("/System/Temporary/" + reference.to_s + "/" + file_name)} # end # @thread_safety.synchronize { @file_transfers = active_list } # if @process.respond_to_event?(:new_file_transfer) # @process.dispatcher.post_event(:communications) do # @process.call_event({:new_file_transfer => {:transfer => details[:transfer], :sender => details[:sender], :at => ::DateTime.now}}) # end # end # when :file_transfer_complete, :file_transfer_cancelled, :file_transfer_error # notification = {} # notification[(details[:event])] = {:transfer => details[:transfer], :at => ::DateTime.now} # # # active_list = {} # @connector.file_transfers.each do |the_transfer| # reference = the_transfer.reference # file_name = the_transfer.file_details[:filename] # file_size = the_transfer.file_details[:size] # sender = the_transfer.info[:sender] # # store entry # active_list[(reference)] = {:transfer => the_transfer, :sender => sender, :file => file_name, :size => file_size, :path => ("/System/Temporary/" + reference.to_s + "/" + file_name)} # end # @thread_safety.synchronize { @file_transfers = active_list } # # # if @process.respond_to_event?(details[:event]) # @process.call_event(notification) # end # when :file_transfer_progress # if @process.respond_to_event?(:file_transfer_progress) # @process.call_event({:file_transfer_progress => {:transfer => details[:transfer], :progress => details[:progress], :at => ::DateTime.now}}) # end # when :new_message, :new_private_message, :new_announcement, :new_error # # ### unless channel exists - add it # the_channel = @thread_safety.synchronize { @channels[(details[:conversation].to_sym)] } # unless the_channel # found = nil # @connector.conversations.each do |channel_object| # if details[:conversation].to_sym == channel_object.uuid # found = channel_object # break # end # end # if found # @thread_safety.synchronize { @channels[(details[:conversation].to_sym)] = {:channel => found, :title => found.title()}} # the_channel = found # end # end # # ### get messages # if the_channel # unless @thread_safety.synchronize { @messages[(the_channel.uuid)].is_a?(::Array) } # @thread_safety.synchronize { @messages[(the_channel.uuid)] = [] } # end # the_channel.process_received() do |the_message| # # Note: this will pick up other messages from the adapter now, but it is ok - a notification will be generated on EACH message. # the_message[:channel] = the_channel.uuid # @thread_safety.synchronize { @messages[(the_channel.uuid)] << the_message } # end # end # # Review : differentiate beteween private and open messages? How to do? # if @process.respond_to_event?(:new_message) # notification = {} # notification[:new_message] = {:channel => details[:conversation], :message => details[:message], :sender => details[:sender].to_s, :at => ::DateTime.now} # # # @process.dispatcher.post_event(:communications) do # @process.call_event(notification) # end # end # # # end # end # end # end # end # # ### Keychain # def set_key(recipient=nil, password=nil) # result = false # if recipient.to_s.valid_jid? && password.is_a?(::String) # @thread_safety.synchronize { # if @keychain[(recipient.to_s)] # @keychain[(recipient.to_s)][:current] = password # else # @keychain[(recipient.to_s)] = {:original => password, :current => password} # end # result = true # } # end # result # end # # ### Invitations # def accept_invitation(the_invitation_uuid=nil) # result = false # if ::GxG::valid_uuid?(the_invitation_uuid) # found = nil # @thread_safety.synchronize { # found = @invitations[(the_invitation_uuid.to_sym)] # } # if found # found[:invitation].accept() # result = true # end # end # result # end # # # def decline_invitation(the_invitation_uuid=nil) # result = false # if ::GxG::valid_uuid?(the_invitation_uuid) # found = nil # @thread_safety.synchronize { # found = @invitations[(the_invitation_uuid.to_sym)] # } # if found # found[:invitation].decline() # result = true # end # end # result # end # # ### File Transfers # def file_transfers() # result = {} # @thread_safety.synchronize { # @file_transfers.each_pair do |the_uuid, the_record| # result[(the_uuid)] = {:sender => the_record[:sender], :file => the_record[:file], :size => the_record[:size], :path => the_record[:path]} # end # } # result # end # # # def accept_file_transfer(the_transfer_uuid=nil) # result = false # if ::GxG::valid_uuid?(the_transfer_uuid) # found = nil # @thread_safety.synchronize { # found = @file_transfers[(the_transfer_uuid.to_sym)] # } # if found # found[:transfer].accept() # result = true # end # end # result # end # # # def decline_file_transfer(the_transfer_uuid=nil) # result = false # if ::GxG::valid_uuid?(the_transfer_uuid) # found = nil # @thread_safety.synchronize { # found = @file_transfers[(the_transfer_uuid.to_sym)] # } # if found # found[:transfer].decline() # if ::File.exists?(GxG::SERVER_PATHS[:temporary] + "/" + found[:transfer].reference.to_s) # ::GxG::VFS.rmdir("/System/Temporary/" + found[:transfer].reference.to_s) # end # result = true # end # end # result # end # # # def cancel_file_transfer(the_transfer_uuid=nil) # result = false # if ::GxG::valid_uuid?(the_transfer_uuid) # found = nil # @thread_safety.synchronize { # found = @file_transfers[(the_transfer_uuid.to_sym)] # } # if found # found[:transfer].cancel() # if ::File.exists?(GxG::SERVER_PATHS[:temporary] + "/" + found[:transfer].reference.to_s) # ::GxG::VFS.rmdir("/System/Temporary/" + found[:transfer].reference.to_s) # end # result = true # end # end # result # end # # ### Entities # def entities() # result = [] # @connector.buddies().each do |record| # new_record = {:id => "", :title => "Untitled", :groups => [], :status => :offline} # new_record[:id] = record[:jid] # new_record[:title] = record[:title] # new_record[:groups] = record[:groups] # case record[:status] # when :chat, :normal, :available # new_record[:status] = :online # when :dnd # new_record[:status] = :busy # when :away # new_record[:status] = :away # when :xa # new_record[:status] = :extended_away # when :unavailable, :offline # new_record[:status] = :offline # when :error # new_record[:status] = :error # end # result << new_record # end # result # end # # # def entity_status(the_address=nil) # result = :invalid_address # if the_address.valid_jid?() # result = :unknown_address # self.entities.each do |the_record| # if the_record[:id] == the_address # result = the_record[:status] # break # end # end # end # result # end # # # def status() # result = :offline # case @adapter.get_status # when :chat, :normal, :available # result = :online # when :dnd # result = :busy # when :away # result = :away # when :xa # result = :extended_away # when :unavailable, :offline # result = :offline # when :error # result = :error # end # result # end # # # def status=(the_status=nil, message=nil) # result = :available # case the_status # when :online # result = :available # when :busy # result = :dnd # when :away # result = :away # when :extended_away # result = :xa # when :offline # result = :unavailable # when :error # result = :error # end # @adapter.set_status(result, message) # end # # ### Basic Channel Support # def channels() # result = {} # @thread_safety.synchronize { # @channels.each_pair do |the_uuid, the_record| # result[(the_uuid)] = the_record[:title] # end # } # result # end # # # def associate(recipient=nil) # result = false # if recipient.is_a?(::String) # if recipient.valid_jid? # @adapter.add_buddy(recipient) # result = true # end # end # result # end # # # def disassociate(recipient=nil) # result = false # if recipient.is_a?(::String) # if recipient.valid_jid? # @adapter.remove_buddy(recipient) # result = true # end # end # result # end # # # def open_channel(with_jid=nil, password=nil, configuration={}) # result = nil # if with_jid.is_a?(::String) # if with_jid.valid_jid? && configuration.is_any?(::Hash, ::GxG::Database::PersistedHash) # # If channel already exists and they attempt a repeat opening action: # @thread_safety.synchronize { # @channels.each_pair do |the_uuid, the_record| # if with_jid.to_s == the_record.jid.to_s # result = the_record.uuid # break # end # end # } # unless result # # Channel needs to be constructed: # if @connector.create_conversation(with_jid, password, configuration) == true # existing_channels = self.channels() # found = nil # @thread_safety.synchronize { # @connector.conversations.each do |channel_object| # if existing_channels.keys.include?(channel_object.uuid) # next # else # found = channel_object # @channels[(channel_object.uuid)] = {:channel => found, :title => found.title()} # break # end # end # } # if found # result = channel_object.uuid # if password # if channel_object[:type] == :chat # # on :groupchat password is only used for access, not encryption. # self.set_key(with_jid, password) # end # end # end # # # end # end # end # end # result # end # # # def join_channel(channel_jid=nil, password=nil, options={}) # result = nil # if channel_jid.is_a?(::String) # if channel_jid.valid_jid? && options.is_any?(::Hash, ::GxG::Database::PersistedHash) # if @connector.join_conversation(channel_jid, password, options) == true # existing_channels = self.channels() # found = nil # @thread_safety.synchronize { # @connector.conversations.each do |channel_object| # if existing_channels.keys.include?(channel_object.uuid) # next # else # found = channel_object # @channels[(channel_object.uuid)] = {:channel => found, :title => found.title()} # break # end # end # } # if found # result = channel_object.uuid # end # # # end # end # end # result # end # # # def close_channel(the_channel_uuid=nil) # if ::GxG::valid_uuid?(the_channel_uuid) # the_channel = nil # @thread_safety.synchronize { # if @channels[(the_channel_uuid.to_sym)].is_a?(::Hash) # the_channel = @channels.delete(the_channel_uuid.to_sym)[:channel] # end # } # if the_channel # the_channel.leave("{ \"exit_channel\":\"#{the_channel.my_jid.to_s}\" }") # true # else # false # end # else # false # end # end # # ### Messages # def send_message(the_channel_uuid=nil, message=nil, recipient=nil, use_fallback_key=false) # result = false # # # if ::GxG::valid_uuid?(the_channel_uuid) # the_channel = nil # @thread_safety.synchronize { # if @channels[(the_channel_uuid.to_sym)].is_a?(::Hash) # the_channel = @channels[(the_channel_uuid.to_sym)][:channel] # end # } # if the_channel # if message.is_any?(::String, ::Hash, ::GxG::Database::PersistedHash, ::GxG::Events::Message) # if message.is_a?(::String) # message = new_message({:sender => @connector.my_jid.to_s, :body => {:message => message}.gxg_export.to_json}) # end # if message.is_a?(::Hash) # message = new_message({:sender => @connector.my_jid.to_s, :body => message.gxg_export.to_json}) # end # if message.is_a?(::GxG::Database::PersistedHash) # message = new_message({:sender => @connector.my_jid.to_s, :body => message.sync_export.gxg_export.to_json}) # end # if message.is_a?(::GxG::Events::Message) # if message[:body].is_a?(::Hash) # message[:body] = message[:body].gxg_export.to_json # end # if message[:body].is_a?(::GxG::Database::PersistedHash) # message[:body] = message[:body].sync_export.gxg_export.to_json # end # end # # # if message.is_a?(::GxG::Events::Message) # # Reset :sender if a ruby object is the current setting. (bridge addressing translation) # unless message[:sender].is_a?(::String) # message[:sender] = @connector.my_jid.to_s # end # # encrypt body? # key = nil # if recipient.is_a?(::String) # intended_recipient = recipient # else # intended_recipient = the_channel.jid() # end # @thread_safety.synchronize { # if @keychain[(intended_recipient.to_s)] # if use_fallback_key == true # key = @keychain[(intended_recipient.to_s)][:original] # else # key = @keychain[(intended_recipient.to_s)][:current] # end # end # } # if key.is_a?(::String) # message[:body] = message[:body].encrypt(key).encode64 # else # message[:body] = message[:body].encode64 # end # # send message # result = the_channel.say_something(message, recipient) # # Review : use this next little bit of code in the request/reply code: # # if result == true # # message.succeed(message) # # else # # message.fail(message) # # end # end # else # raise ArgumentError, "You MUST provide the message as a String, Hash, GxG::Database::PersistedHash, or GxG::Events::Message; NOT #{message.class} ." # end # else # raise ArgumentError, "Invalid Channel selector, the Channel was not found: #{the_channel.inspect} . Try opening the channel first." # end # else # raise ArgumentError, "Invalid Channel selector: #{the_channel.inspect} . You MUST provide a valid UUID." # end # result # end # # # def next_message(the_channel_uuid=nil) # result = nil # if ::GxG::valid_uuid?(the_channel_uuid) # @thread_safety.synchronize { # if @messages[(the_channel_uuid.to_sym)].is_a?(::Array) # result = @messages[(the_channel_uuid.to_sym)].shift # end # } # if result # result = decode_message(result) # end # end # result # end # # # def all_messages(the_channel_uuid=nil) # result = [] # if ::GxG::valid_uuid?(the_channel_uuid) # @thread_safety.synchronize { # if @messages[(the_channel_uuid.to_sym)].is_a?(::Array) # @messages[(the_channel_uuid.to_sym)].size.times do # result << decode_message(@messages[(the_channel_uuid.to_sym)].shift) # end # end # } # end # result # end # # # def get_message(the_channel_uuid=nil, the_message_uuid=nil) # result = nil # if ::GxG::valid_uuid?(the_channel_uuid) && the_message_uuid # @thread_safety.synchronize { # if @messages[(the_channel_uuid.to_sym)].is_a?(::Array) # @messages[(the_channel_uuid.to_sym)].each_with_index do |the_message, the_index| # if the_message_uuid == the_message.id() # result = @messages[(the_channel_uuid.to_sym)].delete_at(the_index) # break # end # end # end # } # if result # result = decode_message(result) # end # end # result # end # # # def get_messages_by_context(the_channel_uuid=nil, the_context_uuid=nil) # result = [] # if ::GxG::valid_uuid?(the_channel_uuid) && the_context_uuid # @thread_safety.synchronize { # if @messages[(the_channel_uuid.to_sym)].is_a?(::Array) # @messages[(the_channel_uuid.to_sym)].each_with_index do |the_message, the_index| # if the_context_uuid.to_s.to_sym == the_message[:context].to_s.to_sym # result << @messages[(the_channel_uuid.to_sym)].delete_at(the_index) # end # end # end # } # if result # result = decode_message(result) # end # end # result # end # # # def get_messages_by_sender(the_channel_uuid=nil, the_sender=nil) # result = [] # if ::GxG::valid_uuid?(the_channel_uuid) && the_sender # @thread_safety.synchronize { # if @messages[(the_channel_uuid.to_sym)].is_a?(::Array) # @messages[(the_channel_uuid.to_sym)].each_with_index do |the_message, the_index| # if the_sender.to_s == the_message[:sender].to_s # result << @messages[(the_channel_uuid.to_sym)].delete_at(the_index) # end # end # end # } # if result # result = decode_message(result) # end # end # result # end # # # end #