diff --git a/.rubocop.yml b/.rubocop.yml index 574c2129dc91242ea4495a1ebb525a14e2efc028..4fa0adeeceaa2e351d503c9d1df1557e0fc41839 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -35,7 +35,7 @@ Metrics/ModuleLength: Max: 1000 Metrics/ParameterLists: - Max: 6 + Max: 7 Metrics/PerceivedComplexity: Max: 20 @@ -99,3 +99,9 @@ Style/MultilineBlockChain: Style/SpaceAroundEqualsInParameterDefault: EnforcedStyle: no_space + +Style/IndentArray: + EnforcedStyle: consistent + +Style/FirstParameterIndentation: + EnforcedStyle: consistent diff --git a/sgx-catapult.rb b/sgx-catapult.rb index c24f8686775db036a36e24eeb31daf1f64f3748b..a3b26f0ab398ac015878c0691929c4089162b0bc 100755 --- a/sgx-catapult.rb +++ b/sgx-catapult.rb @@ -134,34 +134,22 @@ module SGXcatapult write_to_stream rcpt end - def self.to_catapult(m, num_dest, user_id, token, secret, users_num) + def self.call_catapult(token, secret, m, pth, body, head={}, code=[200]) EM::HttpRequest.new( - "https://api.catapult.inetwork.com/"\ - "v1/users/#{user_id}/messages" - ).post( + "https://api.catapult.inetwork.com/#{pth}" + ).public_send( + m, head: { - 'Authorization' => [token, secret], - 'Content-Type' => 'application/json' - }, - body: JSON.dump( - from: users_num, - to: num_dest, - text: m.body, - tag: - # callbacks need id and resourcepart - WEBrick::HTTPUtils.escape(m.id.to_s) + - ' ' + - WEBrick::HTTPUtils.escape( - m.from.resource.to_s - ), - receiptRequested: 'all', - callbackUrl: ARGV[6] - ) + 'Authorization' => [token, secret] + }.merge(head), + body: body ).then { |http| puts "API response to send: #{http.response} with code"\ " response.code #{http.response_header.status}" - if http.response_header.status != 201 + if code.include?(http.response_header.status) + http.response + else # TODO: add text; mention code number EMPromise.reject( [:cancel, 'internal-server-error'] @@ -170,8 +158,42 @@ module SGXcatapult } end - message :chat?, :body do |m| - EMPromise.resolve(m.to.node.to_s).then { |num_dest| + def self.to_catapult(s, murl, num_dest, user_id, token, secret, usern) + extra = if murl + { + media: murl + } + else + { + receiptRequested: 'all', + callbackUrl: ARGV[6] + } + end + + call_catapult( + token, + secret, + :post, + "v1/users/#{user_id}/messages", + JSON.dump(extra.merge( + from: usern, + to: num_dest, + text: s.respond_to?(:body) ? s.body : '', + tag: + # callbacks need id and resourcepart + WEBrick::HTTPUtils.escape(s.id.to_s) + + ' ' + + WEBrick::HTTPUtils.escape( + s.from.resource.to_s + ) + )), + {'Content-Type' => 'application/json'}, + [201] + ) + end + + def self.validate_num(num) + EMPromise.resolve(num.to_s).then { |num_dest| if num_dest =~ /\A\+?[0-9]+(?:;.*)?\Z/ next num_dest if num_dest[0] == '+' shortcode = extract_shortcode(num_dest) @@ -179,29 +201,38 @@ module SGXcatapult end # TODO: text re num not (yet) supportd/implmentd EMPromise.reject([:cancel, 'item-not-found']) - }.then { |num_dest| - cred_key = "catapult_cred-#{m.from.stripped}" - REDIS.lrange(cred_key, 0, 3).then { |creds| - [num_dest] + creds - } - }.then { |(num_dest, *creds)| + } + end + + def self.fetch_catapult_cred_for(jid) + cred_key = "catapult_cred-#{jid.stripped}" + REDIS.lrange(cred_key, 0, 3).then { |creds| if creds.length < 4 # TODO: add text re credentials not registered EMPromise.reject( [:auth, 'registration-required'] ) else - jid_key = "catapult_jid-#{num_dest}" - REDIS.get(jid_key).then { |jid| - [jid, num_dest] + creds - } + creds end + } + end + + message :chat?, :body do |m| + EMPromise.all([ + validate_num(m.to.node), + fetch_catapult_cred_for(m.from) + ]).then { |(num_dest, creds)| + jid_key = "catapult_jid-#{num_dest}" + REDIS.get(jid_key).then { |jid| + [jid, num_dest] + creds + } }.then { |(jid, num_dest, *creds)| # if destination user is in the system pass on directly if jid pass_on_message(m, creds.last, jid) else - to_catapult(m, num_dest, *creds) + to_catapult(m, nil, num_dest, *creds) end }.catch { |e| if e.is_a?(Array) && e.length == 2 @@ -432,158 +463,73 @@ module SGXcatapult end iq '/iq/ns:close', ns: 'http://jabber.org/protocol/ibb' do |i, cn| - begin puts "IQc: #{i.inspect}" write_to_stream i.reply - # TODO: refactor below so that "message :chat?" uses same code - num_dest = i.to.to_s.split('@', 2)[0] - - if num_dest[0] != '+' - # check to see if a valid shortcode context is specified - num_and_context = num_dest.split(';', 2) - if num_and_context[1] and num_and_context[1] == - 'phone-context=ca-us.phone-context.soprani.ca' - - # TODO: check if num_dest is fully numeric - num_dest = num_and_context[0] - else - # TODO: text re num not (yet) supportd/implmentd - write_to_stream error_msg( - i.reply, nil, - :cancel, 'item-not-found' - ) - next + EMPromise.all([ + validate_num(i.to.node), + fetch_catapult_cred_for(i.from) + ]).then { |(num_dest, creds)| + # Gajim bug: has Jingle (not transport) sid; fix later + if not @jingle_fnames.key? cn[0]['sid'] + puts 'ERROR: Not found in filename map: ' + cn[0]['sid'] + + next EMPromise.reject(:done) + # TODO: in case only Gajim's bug fixed, add map: + #cn[0]['sid'] = @jingle_tsids[cn[0]['sid']] end - end - - bare_jid = i.from.to_s.split('/', 2)[0] - cred_key = "catapult_cred-" + bare_jid - - # TODO: connect at start of program instead - conn = Hiredis::Connection.new - conn.connect(ARGV[4], ARGV[5].to_i) - - conn.write ["EXISTS", cred_key] - if conn.read == 0 - conn.disconnect - - # TODO: add text re credentials not being registered - write_to_stream error_msg( - i.reply, nil, :auth, - 'registration-required' - ) - next - end - - conn.write ["LRANGE", cred_key, 0, 3] - user_id, api_token, api_secret, users_num = conn.read - conn.disconnect - - # Gajim bug: has Jingle (not transport) sid; fix later - if not @jingle_fnames.key? cn[0]['sid'] - puts 'ERROR: Not found in filename map: ' + cn[0]['sid'] - - next - # TODO: in case only Gajim's bug fixed, add map: - #cn[0]['sid'] = @jingle_tsids[cn[0]['sid']] - end - - # upload cached data to server (before success reply) - media_name = - "#{Time.now.utc.iso8601}_#{SecureRandom.uuid}"\ - "_#{@jingle_fnames[cn[0]['sid']]}" - puts 'name to save: ' + media_name - - uri = URI.parse('https://api.catapult.inetwork.com') - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - request = Net::HTTP::Put.new('/v1/users/' + user_id + - '/media/' + media_name) - request.basic_auth api_token, api_secret - request.body = @partial_data[cn[0]['sid']] - response = http.request(request) - - puts 'eAPI response to send: ' + response.to_s + ' with code ' + - response.code + ', body "' + response.body + '"' - - if response.code != '200' - # TODO: add text re unexpected code; mention code number - write_to_stream error_msg( - i.reply, nil, :cancel, - 'internal-server-error' - ) - next - end - uri = URI.parse('https://api.catapult.inetwork.com') - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - request = Net::HTTP::Post.new('/v1/users/' + user_id + - '/messages') - request.basic_auth api_token, api_secret - request.add_field('Content-Type', 'application/json') - request.body = JSON.dump( - 'from' => users_num, - 'to' => num_dest, - 'text' => '', - 'media' => [ - 'https://api.catapult.inetwork.com/v1/users/' + - user_id + '/media/' + media_name - ], - 'tag' => - # callbacks need both the id and resourcepart - WEBrick::HTTPUtils.escape(i.id.to_s) + ' ' + - WEBrick::HTTPUtils.escape( - i.from.to_s.split('/', 2)[1].to_s + # upload cached data to server (before success reply) + media_name = + "#{Time.now.utc.iso8601}_#{SecureRandom.uuid}"\ + "_#{@jingle_fnames[cn[0]['sid']]}" + puts 'name to save: ' + media_name + + path = "/v1/users/#{creds.first}/media/#{media_name}" + + EMPromise.all([ + call_catapult( + *creds[1..2], + :put, + path, + @partial_data[cn[0]['sid']] + ), + to_catapult( + i, + "https://api.catapult.inetwork.com/" + + path, + num_dest, + *creds ) - # TODO: add back when Bandwidth AP supports it (?); now: - # "The ''messages'' resource property - # ''receiptRequested'' is not supported for MMS" - #'receiptRequested' => 'all', - #'callbackUrl' => ARGV[6] - ) - response = http.request(request) - - puts 'mAPI response to send: ' + response.to_s + ' with code ' + - response.code + ', body "' + response.body + '"' - - if response.code != '201' - # TODO: add text re unexpected code; mention code number - write_to_stream error_msg( - i.reply, nil, :cancel, - 'internal-server-error' - ) - next - end - - @partial_data[cn[0]['sid']] = '' - - # received the complete file so now close the stream - msg = Blather::Stanza::Iq.new :set - msg.to = i.from - msg.from = i.to - - j = Nokogiri::XML::Node.new 'jingle', msg.document - j['xmlns'] = 'urn:xmpp:jingle:1' - j['action'] = 'session-terminate' - j['sid'] = @jingle_sids[cn[0]['sid']] - msg.add_child(j) - - r = Nokogiri::XML::Node.new 'reason', msg.document - s = Nokogiri::XML::Node.new 'success', msg.document - r.add_child(s) - j.add_child(r) - - puts 'RESPONSE1: ' + msg.inspect - write_to_stream msg - - rescue Exception => e - puts 'Shutting down gateway due to exception 007: ' + e.message - SGXcatapult.shutdown - puts 'Gateway has terminated.' - EM.stop - end + ]) + }.then { + @partial_data[cn[0]['sid']] = '' + + # received the complete file so now close the stream + msg = Blather::Stanza::Iq.new :set + msg.to = i.from + msg.from = i.to + + j = Nokogiri::XML::Node.new 'jingle', msg.document + j['xmlns'] = 'urn:xmpp:jingle:1' + j['action'] = 'session-terminate' + j['sid'] = @jingle_sids[cn[0]['sid']] + msg.add_child(j) + + r = Nokogiri::XML::Node.new 'reason', msg.document + s = Nokogiri::XML::Node.new 'success', msg.document + r.add_child(s) + j.add_child(r) + + puts 'RESPONSE1: ' + msg.inspect + write_to_stream msg + }.catch { |e| + if e.is_a?(Array) && e.length == 2 + write_to_stream error_msg(i.reply, nil, *e) + elsif e != :done + EMPromise.reject(e) + end + }.catch(&method(:panic)) end iq '/iq/ns:query', ns: 'http://jabber.org/protocol/disco#items' do |i|