app/controllers/ws_roster_controller.rb
2a61cdcc
 class WsRosterController < WsController
b27f285a
     require 'xmpp4r/roster'
     require 'xmpp4r/vcard'
6fb9497e
     require 'RMagick'
ff94bdbf
 
b27f285a
     def initialize
         super
d4872cda
 
         @storages_arr = [:clients, :rosters]
dbe4bff6
         @storages_hash = [:link_roster_client, :my_presences, :presences]
b27f285a
     end
 
     def initialize_storage
d4872cda
         @storages_arr.each do |storage|
93083165
             connection_store[storage] = []
b27f285a
         end
52ef7688
 
d4872cda
         @storages_hash.each do |storage|
             connection_store[storage] = {}
         end
ff94bdbf
     end
 
52ef7688
     ##
     # Pripoj sa na jabber ucty.
70fc03c1
     def connect
b27f285a
         initialize_storage()
 
a615e89f
         # TODO: Pouzit najprv:
         # clients = Token.fing_user_accounts_having_to_token(session[:token])
         # ale toto, az ked budem mat dokonceny multiaccount (settings a popup)
5011e360
         # TODO: skusit zrychlit
f3618169
         cookies = env['rack.request.cookie_hash'] # TODO: nahlasit bug na websocket-rails, lebo sa neda pristupit ku `cookies'
882090fc
         cipher_key = cookies['key']
         cipher_iv = cookies['iv']
b27f285a
 
882090fc
         clients = session[:users].map do |jid, encrypted_pass|
             decrypted_pass = Security::decrypt(encrypted_pass, cipher_key, cipher_iv)
             {jid: jid, pass: decrypted_pass}
         end
b27f285a
 
882090fc
         clients.each do |client|
9234acc2
             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
b27f285a
             end
         end
70fc03c1
     end
 
ff94bdbf
     ##
52ef7688
     # 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
b27f285a
 
             roster.get_roster()
52ef7688
             roster.wait_for_roster()
b27f285a
 
             roster.items.each do |jid, contact|
6a6ccd01
                 all_jids << {
                     jid: jid.to_s,
                     belongsTo: client.jid.strip.to_s
                 }
b27f285a
             end
         end
 
52ef7688
         trigger_success contacts: all_jids
ff94bdbf
     end
 
52ef7688
     ##
     # 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
910e0f23
                     vcard = get_vcard_info(client, jid.to_s)
52ef7688
                     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
2fe6ba99
         start_polling_control_answer()
dbe4bff6
 
2fe6ba99
         connection_store[:rosters].each do |roster|
             start_polling_subscription(roster)
             start_polling_presence(roster)
910e0f23
             start_polling_friend_requests(roster)
43ad79fa
         end
     end
52ef7688
 
43ad79fa
     ##
     # 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|
d4872cda
             presence = Jabber::Presence.new.set_type(:available)
             client.send(presence)
dbe4bff6
             connection_store[:my_presences][client] = presence
d4872cda
         end
     end
 
635339a3
     def remove_contact
         jid = message[:jid]
         client_jid = message[:client]
 
0ec320b3
         found_roster_items = []
635339a3
         connection_store[:rosters].each do |roster|
             roster_item = roster.find(jid)
0ec320b3
             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
635339a3
             end
         end
 
0ec320b3
         found_roster_items.each do |contact|
             contact.remove()
         end
635339a3
     end
 
d4872cda
     ##
     # 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|
910e0f23
             vcard = get_vcard_info(client)
d4872cda
             jid = client.jid.strip.to_s
dbe4bff6
             presence = uniform_presence(connection_store[:my_presences][client].show)
52ef7688
         end
d4872cda
 
         trigger_success jid: jid, vcard: vcard, status: presence
52ef7688
     end
 
c4a1e746
     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
6347ea28
         connection_store[:clients].each do |client|
6fb9497e
             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
6347ea28
         end
c4a1e746
     end
 
52ef7688
     def disconnect
         connection_store[:clients] && connection_store[:clients].each do |client|
             client.close()
b27f285a
         end
ff94bdbf
 
4b5bb9c5
         @storages_arr.each do |storage|
             connection_store.delete(storage)
         end
 
         @storages_hash.each do |storage|
b27f285a
             connection_store.delete(storage)
         end
ff94bdbf
     end
70fc03c1
 
910e0f23
     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]
70fc03c1
 
910e0f23
         connection_store[:rosters].each do |roster|
             if answer
                 roster.accept_subscription(jid)
                 add_contact(jid)
             else
                 roster.decline_subscription(jid)
             end
         end
23f7b7b2
     end
52ef7688
 
     private
 
dbe4bff6
     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
 
910e0f23
     def get_vcard_info(me, contact_jid = nil)
d4872cda
         vcard = Jabber::Vcard::Helper.get(me, contact_jid)
52ef7688
 
d4872cda
         { name: pull_name_from_vcard(vcard) || contact_jid,
f99e8d29
           avatar: vcard['PHOTO/TYPE'] && ('data:' + vcard['PHOTO/TYPE'] + ';base64,' + vcard['PHOTO/BINVAL']) || ''
52ef7688
         }
     end
d4872cda
 
     def pull_name_from_vcard(vcard)
f99e8d29
         vcard && (vcard['FN'] || vcard['NICKNAME'])
d4872cda
     end
 
     def uniform_presence(xmpp_presence)
         case xmpp_presence
dbe4bff6
             when :offline   then :offline
d4872cda
             when :away, :xa then :away
             when :dnd       then :dnd
             else                 :online
         end
     end
7a817e66
 
     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')
e513299a
                     connection_store[:presences][message.from.strip.to_s][message.from.to_s.to_sym][:multichat] = true
79fff9ad
 
7a817e66
                     send_message 'app.roster.using_this_app',
79fff9ad
                                  jid: message.from.strip.to_s
7a817e66
                 elsif message.attribute('are_you_using_my_app')
79fff9ad
                     client.send(MessageBuilder::control_answer(client.jid.to_s, message.from.to_s))
7a817e66
                 end
             end
         end
     end
79fff9ad
 
2fe6ba99
     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
 
bc02cb76
                 client = connection_store[:link_roster_client][roster]
2fe6ba99
                 # mozno treba vyhodit cloveka z multichatu, ak som jeho owner
bc02cb76
                 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)
2fe6ba99
             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,
9ba34cc7
                     multichat: false,
                     priority: new_presence.priority
2fe6ba99
                 }
 
e513299a
                 ask_if_using_this_app(connection_store[:link_roster_client][roster], new_presence.from.to_s)
2fe6ba99
             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
 
910e0f23
     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
 
79fff9ad
     def ask_if_using_this_app(client, contact)
e513299a
         client.send(MessageBuilder::control_question(client.jid.to_s, contact))
79fff9ad
     end
910e0f23
 
     def is_valid_jid?(jid)
         !! jid.scan(/^\w+@\w+\.\w+$/).first
     end
ff94bdbf
 end