Class: Fetion

Inherits:
Object
  • Object
show all
Defined in:
lib/rfetion/fetion.rb,
lib/rfetion/contact.rb,
lib/rfetion/message.rb,
lib/rfetion/buddy_list.rb

Defined Under Namespace

Classes: AddBuddyException, BuddyList, Contact, GetContactsException, LoginException, Message, NoNonceException, NoUserException, RegisterException, SendMsgException, SendSmsException, SetScheduleSmsException, SipcException

Constant Summary collapse

FETION_URL =
'http://221.176.31.39/ht/sd.aspx'
FETION_LOGIN_URL =
'https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?mobileno=%mobileno%sid=%sid%&domains=fetion.com.cn;m161.com.cn;www.ikuwa.cn&v4digest-type=1&v4digest=%digest%'
SIPP =
'SIPP'
USER_AGENT =
"IIC2.0/PC 3.6.2020"
VERSION =
"3.6.2020"
DOMAIN =
"fetion.com.cn"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeFetion

Returns a new instance of Fetion.



22
23
24
25
26
27
28
29
30
31
# File 'lib/rfetion/fetion.rb', line 22

def initialize
  @call = @alive = @seq = 0
  @buddy_lists = []
  @buddies = []
  @contacts = []
  @receives = []
  @logger = Logger.new(STDOUT)
  @logger.level = Logger::INFO
  @guid = Guid.new.to_s
end

Instance Attribute Details

#buddy_listsObject (readonly)

Returns the value of attribute buddy_lists.



12
13
14
# File 'lib/rfetion/fetion.rb', line 12

def buddy_lists
  @buddy_lists
end

#contactsObject (readonly)

Returns the value of attribute contacts.



12
13
14
# File 'lib/rfetion/fetion.rb', line 12

def contacts
  @contacts
end

#guidObject

Returns the value of attribute guid.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def guid
  @guid
end

#mobile_noObject

Returns the value of attribute mobile_no.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def mobile_no
  @mobile_no
end

#nicknameObject (readonly)

Returns the value of attribute nickname.



12
13
14
# File 'lib/rfetion/fetion.rb', line 12

def nickname
  @nickname
end

#passwordObject

Returns the value of attribute password.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def password
  @password
end

#receivesObject (readonly)

Returns the value of attribute receives.



12
13
14
# File 'lib/rfetion/fetion.rb', line 12

def receives
  @receives
end

#responseObject (readonly)

Returns the value of attribute response.



12
13
14
# File 'lib/rfetion/fetion.rb', line 12

def response
  @response
end

#seqObject

Returns the value of attribute seq.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def seq
  @seq
end

#sidObject

Returns the value of attribute sid.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def sid
  @sid
end

#ssicObject

Returns the value of attribute ssic.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def ssic
  @ssic
end

#uriObject

Returns the value of attribute uri.



11
12
13
# File 'lib/rfetion/fetion.rb', line 11

def uri
  @uri
end

#user_idObject (readonly)

Returns the value of attribute user_id.



12
13
14
# File 'lib/rfetion/fetion.rb', line 12

def user_id
  @user_id
end

Class Method Details

.add_buddy(options) ⇒ Object

options

mobile_no
sid
password
friend_mobile
friend_sip
logger_level


152
153
154
155
156
# File 'lib/rfetion/fetion.rb', line 152

def Fetion.add_buddy(options)
  Fetion.open(options) do
    add_buddy(options)
  end
end

.keep_alive(options) ⇒ Object



51
52
53
54
55
56
57
58
59
# File 'lib/rfetion/fetion.rb', line 51

def Fetion.keep_alive(options)
  Fetion.open(options) do
    get_contacts
    keep_alive
    sleep(15)
    keep_alive
    p receives
  end
end

.open(options, &block) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rfetion/fetion.rb', line 37

def Fetion.open(options, &block)
  fetion = Fetion.new
  fetion.logger_level = options.delete(:logger_level) || Logger::INFO
  fetion.mobile_no = options.delete(:mobile_no)
  fetion.sid = options.delete(:sid)
  fetion.password = options.delete(:password)
  fetion.
  fetion.register

  fetion.instance_eval &block

  fetion.logout
end

.send_msg(options) ⇒ Object

options

mobile_no
sid
password
receivers
content
logger_level


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/rfetion/fetion.rb', line 95

def Fetion.send_msg(options)
  Fetion.open(options) do
    receivers = options.delete(:receivers)
    content = options.delete(:content)
    if receivers
      receivers = Array(receivers)
      receivers.collect! {|receiver| receiver.to_s}
      get_contacts
      contacts.each do |contact|
        if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
          send_msg(contact.uri, content)
        end
      end
      send_msg(uri, content) if  receivers.any? { |receiver| self? receiver }
    else
      send_msg(uri, content)
    end
  end
end

.send_sms(options) ⇒ Object

options

mobile_no
sid
password
receivers
content
logger_level


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rfetion/fetion.rb', line 68

def Fetion.send_sms(options)
  Fetion.open(options) do
    receivers = options.delete(:receivers)
    content = options.delete(:content)
    if receivers
      receivers = Array(receivers)
      receivers.collect! {|receiver| receiver.to_s}
      get_contacts
      contacts.each do |contact|
        if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
          send_sms(contact.uri, content)
        end
      end
      send_sms(uri, content) if  receivers.any? { |receiver| self? receiver }
    else
      send_sms(uri, content)
    end
  end
end

.set_schedule_sms(options) ⇒ Object

options

mobile_no
sid
password
receivers
content
time
logger_level


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/rfetion/fetion.rb', line 123

def Fetion.set_schedule_sms(options)
  Fetion.open(options) do
    receivers = options.delete(:receivers)
    content = options.delete(:content)
    time = options.delete(:time)
    get_contacts
    if receivers
      receivers = Array(receivers)
      receivers.collect! {|receiver| receiver.to_s}
      new_receivers = contacts.collect do |contact|
        if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
          contact.uri
        end
      end.compact!
      new_receivers << uri if receivers.any? { |receiver| self? receiver }
      set_schedule_sms(new_receivers, content, time)
    else  
      set_schedule_sms([fetion.uri], content, time)
    end
  end
end

Instance Method Details

#add_buddy(options) ⇒ Object

options

friend_mobile
friend_sip


256
257
258
259
260
261
262
263
264
# File 'lib/rfetion/fetion.rb', line 256

def add_buddy(options)
  uri = options[:friend_mobile] ? "tel:#{options[:friend_mobile]}" : "sip:#{options[:friend_sip]}"
  @logger.info "fetion send request to add #{uri} as friend"
  
  curl_exec(SipcMessage.add_buddy(self, options))
  pulse

  @logger.info "fetion send request to add #{uri} as friend success"
end

#calc_responseObject



452
453
454
455
456
457
458
459
460
461
462
463
# File 'lib/rfetion/fetion.rb', line 452

def calc_response
  encrypted_password = Digest::SHA1.hexdigest([@user_id.to_i].pack("V*") + [Digest::SHA1.hexdigest("#{DOMAIN}:#{@password}")].pack("H*"))
  rsa_result = "4A026855890197CFDF768597D07200B346F3D676411C6F87368B5C2276DCEDD2"
  str = @nonce + [encrypted_password].pack("H*") + [rsa_result].pack("H*")
  rsa_key = OpenSSL::PKey::RSA.new
  exponent = OpenSSL::BN.new @key[-6..-1].hex.to_s
  modulus = OpenSSL::BN.new @key[0...-6].hex.to_s
  rsa_key.e = exponent
  rsa_key.n = modulus

  rsa_key.public_encrypt(str).unpack("H*").first.upcase
end

#create_session(sipc_response) ⇒ Object



296
297
298
299
300
301
302
303
# File 'lib/rfetion/fetion.rb', line 296

def create_session(sipc_response)
  @logger.info "fetion create session"

  sipc_response = curl_exec(SipcMessage.create_session(self, sipc_response))
  pulse unless sipc_response

  @logger.info "fetion create session success"
end

#curl_exec(body = '', url = next_url, expected = SipcMessage::OK) ⇒ Object

Raises:



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/rfetion/fetion.rb', line 352

def curl_exec(body='', url=next_url, expected=SipcMessage::OK)
  @logger.debug "fetion curl exec"
  @logger.debug "url: #{url}"
  @logger.debug "body: #{body}"
  
  uri = URI.parse(url)
  http = Net::HTTP.new(uri.host, uri.port)
  headers = {'Content-Type' => 'application/oct-stream', 'Pragma' => "xz4BBcV#{@guid}", 'User-Agent' => USER_AGENT, 'Cookie' => "ssic=#{@ssic}", 'Content-Length' => body.length.to_s}
  response = http.request_post(uri.request_uri, body, headers)

  @logger.debug "response: #{response.inspect}"
  @logger.debug "response body: #{response.body}"
  @logger.debug "fetion curl exec complete"
  
  raise FetionException.new("request_url: #{url}, request_body: #{body}, response: #{response.code}, response_body: #{response.body}") unless Net::HTTPSuccess === response
  sipc_response = SipcMessage.sipc_response(response.body, self)
  
  if sipc_response
    raise Fetion::SipcException.new(sipc_response, "request_url: #{url}, request_body: #{body}, sipc_response: #{sipc_response}") unless sipc_response.class == expected
  
    if sipc_response.first_line =~ /401/
      # unauthorized, get nonce, key and signature
      raise Fetion::NoNonceException.new("Fetion Error: No nonce found") unless response.body =~ /nonce="(.*?)",key="(.*?)",signature="(.*?)"/
      @nonce = $1
      @key = $2
      @signature = $3
      @response = calc_response

      @logger.debug "nonce: #{@nonce}"
      @logger.debug "key: #{@key}"
      @logger.debug "signature: #{@signature}"
      @logger.debug "response: #{@response}"
    elsif sipc_response.contain?('I')
      create_session(sipc_response)
    elsif sipc_response.contain?('O')
      session_connected(sipc_response)
    else
      response.body.scan(%r{<results>.*?</results>}).each do |results|
        doc = Nokogiri::XML(results)
        doc.root.xpath("/results/user-info/personal").each do |personal_element|
          @nickname = personal_element['nickname']
          @logger.debug "nickname: #@nickname"
        end
        doc.root.xpath("/results/user-info/contact-list/buddy-lists/buddy-list").each do |buddy_list|
          @buddy_lists << Fetion::BuddyList.parse(buddy_list)
        end
        doc.root.xpath("/results/user-info/contact-list/buddies/b").each do |buddy|
          @buddies << {:uri => buddy["u"]}
        end
      end
      
      response.body.scan(%r{<events>.*?</events>}).each do |events|
        doc = Nokogiri::XML(events)
        doc.root.xpath("/events/event[@type='PresenceChanged']/contacts/c").each do |c|
          contact = contacts.find {|contact| contact.id == c['id']}
          if contact
            contact.status = c.children.first['b']
          else
            @contacts << Fetion::Contact.parse(c) unless c['id'] == @user_id
          end
        end
      end
      
      receive_messages = response.body.scan(%r{M #{@sid} SIP-C/4.0.*?BN}m)
      receive_messages = response.body.scan(%r{M #{@sid} SIP-C/4.0.*?SIPP\r?\n?$}m) if receive_messages.empty?
      receive_messages.each do |message_response|
        message_header, message_content = message_response.split(/\r\n\r\n/)
        sip = sent_at = length = nil
        message_header.split(/\r\n/).each do |line|
          case line
          when /^F: sip:(.+)/ then sip = $1
          when /^D: (.+)/ then sent_at = Time.parse($1)
          when /^L: (\d+)/ then length = $1.to_i
          end
        end
        text = message_content.slice(0, length)
        @receives << Fetion::Message.new(sip, sent_at, text)
        msg_received(message_response)
      end
    end
  end
  sipc_response
end

#get_contact_info(options) ⇒ Object

options

mobile_no
sip


269
270
271
272
273
274
275
276
277
# File 'lib/rfetion/fetion.rb', line 269

def get_contact_info(options)
  uri = options[:mobile_no] ? "tel:#{options[:mobile_no]}" : "sip:#{options[:sip]}"
  @logger.info "fetion get contact info of #{uri}"
  
  curl_exec(SipcMessage.get_contact_info(self, uri))
  pulse

  @logger.info "fetion get contact info of #{uri} success"
end

#get_contactsObject



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/rfetion/fetion.rb', line 208

def get_contacts
  @logger.info "fetion get contacts"

  curl_exec(SipcMessage.get_group_list(self))
  curl_exec(SipcMessage.presence(self))
  curl_exec(SipcMessage.get_group_topic(self))
  curl_exec(SipcMessage.get_address_list(self))
  pulse

  @logger.info "fetion get contacts success"
end

#keep_aliveObject



279
280
281
282
283
284
285
# File 'lib/rfetion/fetion.rb', line 279

def keep_alive
  @logger.info "fetion keep alive"
  
  pulse

  @logger.info "fetion keep alive success"
end

#logger_level=(level) ⇒ Object



33
34
35
# File 'lib/rfetion/fetion.rb', line 33

def logger_level=(level)
  @logger.level = level
end

#loginObject



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rfetion/fetion.rb', line 158

def 
  @logger.info "fetion login"

  if @mobile_no
    url = FETION_LOGIN_URL.sub('%mobileno%', @mobile_no).sub('sid=%sid%', '')
  else
    url = FETION_LOGIN_URL.sub('%sid%', @sid).sub('mobileno=%mobileno%', '')
  end
  uri = URI.parse(url.sub('%digest%', Digest::SHA1.hexdigest("#{DOMAIN}:#{@password}")))
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  headers = {'User-Agent' => USER_AGENT}
  response = http.request_get(uri.request_uri, headers)
  parse_ssic(response)

  @logger.info "fetion login success"
end

#logoutObject



287
288
289
290
291
292
293
294
# File 'lib/rfetion/fetion.rb', line 287

def logout
  @logger.info "fetion logout"

  curl_exec(SipcMessage.logout(self))
  pulse

  @logger.info "fetion logout success"
end

#msg_received(message_response) ⇒ Object



313
314
315
316
317
318
319
# File 'lib/rfetion/fetion.rb', line 313

def msg_received(message_response)
  @logger.info "fetion msg received"

  curl_exec(SipcMessage.msg_received(self, message_response))

  @logger.info "fetion msg received success"
end

#next_aliveObject



448
449
450
# File 'lib/rfetion/fetion.rb', line 448

def next_alive
  @alive += 1
end

#next_callObject



440
441
442
# File 'lib/rfetion/fetion.rb', line 440

def next_call
  @call += 1
end

#next_seqObject



444
445
446
# File 'lib/rfetion/fetion.rb', line 444

def next_seq
  @seq += 1
end

#next_url(t = 's') ⇒ Object



436
437
438
# File 'lib/rfetion/fetion.rb', line 436

def next_url(t = 's')
  FETION_URL + "?t=#{t}&i=#{next_seq}"
end

#parse_ssic(response) ⇒ Object



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/rfetion/fetion.rb', line 321

def parse_ssic(response)
  raise Fetion::LoginException.new('Fetion Error: Login failed.') unless Net::HTTPSuccess === response
  raise Fetion::LoginException.new('Fetion Error: No ssic found in cookie.') unless response['set-cookie'] =~ /ssic=(.*);/

  @ssic = $1
  @logger.debug response.body
  doc = Nokogiri::XML(response.body)
  results = doc.root
  @status_code = results["status-code"]
  user = results.children.first
  @user_status = user['user-status']
  @uri = user['uri']
  @mobile_no = user['mobile-no']
  @user_id = user['user-id']
  if @uri =~ /sip:(\d+)@(.+);/
    @sid = $1
  end

  @logger.debug "ssic: " + @ssic
  @logger.debug "status_code: " + @status_code
  @logger.debug "user_status: " + @user_status
  @logger.debug "uri: " + @uri
  @logger.debug "mobile_no: " + @mobile_no
  @logger.debug "user_id: " + @user_id
  @logger.debug "sid: " + @sid
end

#pulse(expected = SipcMessage::OK) ⇒ Object



348
349
350
# File 'lib/rfetion/fetion.rb', line 348

def pulse(expected=SipcMessage::OK)
  curl_exec(SIPP, next_url, expected)
end

#registerObject



177
178
179
180
181
182
183
184
185
186
# File 'lib/rfetion/fetion.rb', line 177

def register
  @logger.info "fetion register"

  call = next_call
  register_first
  register_second
  call = next_call

  @logger.info "fetion register success"
end

#register_firstObject



188
189
190
191
192
193
194
195
196
# File 'lib/rfetion/fetion.rb', line 188

def register_first
  @logger.debug "fetion register first"

  curl_exec(SIPP, next_url('i'))
  curl_exec(SipcMessage.register_first(self))
  pulse(SipcMessage::Unauthoried)

  @logger.debug "fetion register first success"
end

#register_secondObject



198
199
200
201
202
203
204
205
206
# File 'lib/rfetion/fetion.rb', line 198

def register_second
  @logger.debug "fetion register second"

  body = %Q|<args><device machine-code="B04B5DA2F5F1B8D01A76C0EBC841414C" /><caps value="ff" /><events value="7f" /><user-info mobile-no="#{@mobile_no}" user-id="#{@user_id}"><personal version="0" attributes="v4default" /><custom-config version="0" /><contact-list version="0"   buddy-attributes="v4default" /></user-info><credentials domains="fetion.com.cn;m161.com.cn;www.ikuwa.cn;games.fetion.com.cn" /><presence><basic value="400" desc="" /></presence></args>|
  curl_exec(SipcMessage.register_second(self))
  pulse

  @logger.debug "fetion register second success"
end

#self?(mobile_or_sid) ⇒ Boolean

Returns:

  • (Boolean)


465
466
467
# File 'lib/rfetion/fetion.rb', line 465

def self?(mobile_or_sid)
  mobile_or_sid == @mobile_no or mobile_or_sid == @sid
end

#send_msg(receiver, content) ⇒ Object



220
221
222
223
224
225
226
227
# File 'lib/rfetion/fetion.rb', line 220

def send_msg(receiver, content)
  @logger.info "fetion send cat msg to #{receiver}"

  curl_exec(SipcMessage.send_cat_msg(self, receiver, content))
  pulse

  @logger.info "fetion send cat msg to #{receiver} success"
end

#send_sms(receiver, content) ⇒ Object



229
230
231
232
233
234
235
236
# File 'lib/rfetion/fetion.rb', line 229

def send_sms(receiver, content)
  @logger.info "fetion send cat sms to #{receiver}"

  curl_exec(SipcMessage.send_cat_sms(self, receiver, content))
  pulse(SipcMessage::Send)

  @logger.info "fetion send cat sms to #{receiver} success"
end

#session_connected(sipc_response) ⇒ Object



305
306
307
308
309
310
311
# File 'lib/rfetion/fetion.rb', line 305

def session_connected(sipc_response)
  @logger.info "fetion session connected"

  curl_exec(SipcMessage.session_connected(self, sipc_response))

  @logger.info "fetion session connected success"
end

#set_schedule_sms(receivers, content, time) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/rfetion/fetion.rb', line 238

def set_schedule_sms(receivers, content, time)
  receivers = Array(receivers)
  time = time.is_a?(Time) ? time : Time.parse(time)
  now = Time.now
  one_year = Time.local(now.year + 1, now.month, now.day, now.hour, now.min, now.sec)
  raise SetScheduleSmsException.new("Can't schedule send sms to more than 64 receivers") if receivers.size > 64
  raise SetScheduleSmsException.new("Schedule time must between #{(now + 600).strftime('%Y-%m-%d %H:%M:%S')} and #{one_year.strftime('%Y-%m-%d %H:%M:%S')}") if time < (now + 600) or time > one_year
  @logger.info "fetion schedule send sms to #{receivers.join(', ')}"
  
  curl_exec(SipcMessage.set_schedule_sms(self, receivers, content, time.strftime('%Y-%m-%d %H:%M:%S')))
  pulse

  @logger.info "fetion schedule send sms to #{receivers.join(', ')} success"
end