class WsRosterController < WsController require 'xmpp4r/roster' require 'xmpp4r/vcard' require 'RMagick' def initialize super @storages_arr = [:clients, :rosters] @storages_hash = [:link_roster_client, :my_presences, :presences] end def initialize_storage @storages_arr.each do |storage| connection_store[storage] = [] end @storages_hash.each do |storage| connection_store[storage] = {} end end ## # Pripoj sa na jabber ucty. def connect initialize_storage() cookies = env['rack.request.cookie_hash'] # TODO: nahlasit bug na websocket-rails, lebo sa neda pristupit ku `cookies' connection_store[:cipher_key] = cipher_key = cookies['key'] connection_store[:cipher_iv] = cipher_iv = cookies['iv'] credentials = User.crendentials_for_token(session[:token]) clients = credentials.map do |tuple| decrypted_pass = Security::decrypt(tuple['pass'], cipher_key, cipher_iv) {jid: tuple['jid'], pass: decrypted_pass} end clients.each do |client| Thread.new do begin client = Signin.try_login(client[:jid], client[:pass]) connection_store[:clients] << client send_message 'app.client.connected', client.jid.strip.to_s #Rails.logger.debug '!!!!! finished for ' + client_id.to_s rescue Signin::LoginError #Rails.logger.debug '!!!!! finished FAILED for ' + client_id.to_s send_message 'app.client.cannot_connect', client.jid.strip.to_s end end end end ## # Inicializuj roster so zoznamom ludi v nom. # Vrat zoznam ludi (ich JID). def init_roster all_jids = [] connection_store[:clients].each do |client| roster = Jabber::Roster::Helper.new(client) connection_store[:rosters] << roster connection_store[:link_roster_client][roster] = client roster.get_roster() roster.wait_for_roster() roster.items.each do |jid, contact| all_jids << { jid: jid.to_s, belongsTo: client.jid.strip.to_s } end end trigger_success contacts: all_jids end ## # Stiahni vcard ludi v rosteri def start_fetching_vcards connection_store[:rosters].each do |roster| client = connection_store[:link_roster_client][roster] roster.items.each do |jid, contact| Thread.new do vcard = get_vcard_info(client, jid.to_s) send_message 'app.roster.vcard', jid: jid.to_s, vcard: vcard end end end end ## # Zacni pocuvat zmeny stavov v rosteri def start_polling_contacts_state start_polling_control_answer() connection_store[:rosters].each do |roster| start_polling_subscription(roster) start_polling_presence(roster) start_polling_friend_requests(roster) end end ## # Nastav ma ako online # # Musi sa zavolat az po start_polling_contacts_state, inak sa nemusia # zachytit stavy ostatnych v rosteri. def set_presence connection_store[:clients].each do |client| presence = Jabber::Presence.new.set_type(:available) client.send(presence) connection_store[:my_presences][client] = presence end end def remove_contact jid = message[:jid] client_jid = message[:client] found_roster_items = [] connection_store[:rosters].each do |roster| roster_item = roster.find(jid) contact_obj = roster_item.first[1] if contact_obj if client_jid && connection_store[:link_roster_client][roster].jid.strip.to_s == client_jid found_roster_items = [ contact_obj ] break else found_roster_items.push(contact_obj) end end end found_roster_items.each do |contact| contact.remove() end end ## # Ziskaj informacie o mne (meno, stav, status...) def myself # TODO: v pripade viacerych uctov zjednotit meno a stav vcard = {} jid = presence = '' connection_store[:clients].each do |client| vcard = get_vcard_info(client) jid = client.jid.strip.to_s presence = uniform_presence(connection_store[:my_presences][client].show) end trigger_success jid: jid, vcard: vcard, status: presence end def me_update_status status_message = message[:message] state = message[:state] xmpp_state = case state when 'away' then :away when 'dnd' then :dnd else nil end presence = Jabber::Presence.new presence.show = xmpp_state presence.status = status_message connection_store[:clients].each do |client| client.send(presence) end end def me_update_vcard connection_store[:clients].each do |client| Thread.new do my_vcard = Jabber::Vcard::Helper.get(client) if message[:name] my_vcard['FN'] = my_vcard['NICKNAME'] = message[:name] end if message[:avatar] begin im = Magick::Image::read_inline(message[:avatar]).first type = im.format.downcase allowed_types = %w(png jpeg pjpeg gif webp) return unless allowed_types.include? type im.resize_to_fit! 128,128 base64 = Base64.encode64(im.to_blob) my_vcard['PHOTO/TYPE'] = 'image/' + type my_vcard['PHOTO/BINVAL'] = base64 rescue # ignored end end Jabber::Vcard::Helper.set(client, my_vcard) end end end def disconnect connection_store[:clients] && connection_store[:clients].each do |client| client.close() end @storages_arr.each do |storage| connection_store.delete(storage) end @storages_hash.each do |storage| connection_store.delete(storage) end end def add_contact(jid = nil) new_jid = message[:jid] || jid if is_valid_jid? new_jid connection_store[:rosters].each do |roster| Thread.new do roster.add(new_jid, nil, true) end end end end def answer_friend_request jid = message[:jid] answer = message[:answer] connection_store[:rosters].each do |roster| if answer roster.accept_subscription(jid) add_contact(jid) else roster.decline_subscription(jid) end end end private def select_most_online_status(jid_stripped) if connection_store[:presences][jid_stripped].nil? || connection_store[:presences][jid_stripped].empty? return uniform_presence(:offline) end statuses = { offline: 0, dnd: 1, away: 2, online: 3 } Hash[connection_store[:presences][jid_stripped].map do |jid, data| [ statuses[data[:status]], data[:status] ] end].max.second end def get_vcard_info(me, contact_jid = nil) vcard = Jabber::Vcard::Helper.get(me, contact_jid) { name: pull_name_from_vcard(vcard) || contact_jid, avatar: vcard['PHOTO/TYPE'] && ('data:' + vcard['PHOTO/TYPE'] + ';base64,' + vcard['PHOTO/BINVAL']) || '' } end def pull_name_from_vcard(vcard) vcard && (vcard['FN'] || vcard['NICKNAME']) end def uniform_presence(xmpp_presence) case xmpp_presence when :offline then :offline when :away, :xa then :away when :dnd then :dnd else :online end end def start_polling_control_answer connection_store[:clients].each do |client| client.add_message_callback do |message| if message.attribute('i_am_using_same_app') connection_store[:presences][message.from.strip.to_s][message.from.to_s.to_sym][:multichat] = true send_message 'app.roster.using_this_app', jid: message.from.strip.to_s elsif message.attribute('are_you_using_my_app') client.send(MessageBuilder::control_answer(client.jid.to_s, message.from.to_s)) end end end end def start_polling_presence(roster) roster.add_presence_callback do |roster_item, old_presence, new_presence| if new_presence.type == :unavailable result = {message: ''} unless connection_store[:presences][roster_item.jid.strip.to_s].nil? connection_store[:presences][roster_item.jid.strip.to_s.to_sym].delete(new_presence.from.to_s) end client = connection_store[:link_roster_client][roster] # mozno treba vyhodit cloveka z multichatu, ak som jeho owner kick_from_all_multichats(client, new_presence.from) # mozno treba sa odpojit z multichatu, ak sa odpojil jeho owner kick_myself_from_multichat(client, new_presence.from) else status = uniform_presence(new_presence.show) result = {message: new_presence.status.to_s} if connection_store[:presences][roster_item.jid.strip.to_s.to_sym].nil? connection_store[:presences][roster_item.jid.strip.to_s.to_sym] = Hash.new() end connection_store[:presences][roster_item.jid.strip.to_s][new_presence.from.to_s.to_sym] = { status: status, multichat: false, priority: new_presence.priority } ask_if_using_this_app(connection_store[:link_roster_client][roster], new_presence.from.to_s) end result[:status] = select_most_online_status(roster_item.jid.strip.to_s) send_message 'app.roster.statusChanged', jid: roster_item.jid.strip.to_s, status: result end end def start_polling_subscription(roster) roster.add_subscription_callback do |roster_item, stanza| # stan unsubscribe = niekto uz ma nesubscribuje # stan unsubscribed = niekto ma uz nesubscribuje a aj si ma vymazal # stan subscribed = niekto moze vidiet moj stav client = connection_store[:link_roster_client][roster] send_message 'app.roster.subscriptionChanged', action: stanza.type, jid: roster_item.jid.strip.to_s, belongsTo: client.jid.strip.to_s end end def start_polling_friend_requests(roster) client = connection_store[:link_roster_client][roster] roster.add_subscription_request_callback do |roster_item, stanza| jid = stanza.from.to_s send_message 'app.roster.friendRequest', jid: jid, name: get_vcard_info(client, jid)[:name] end end def ask_if_using_this_app(client, contact) client.send(MessageBuilder::control_question(client.jid.to_s, contact)) end def is_valid_jid?(jid) !! jid.scan(/^\w+@\w+\.\w+$/).first end end