rfetion 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,41 +1,53 @@
1
- require 'rubygems'
2
1
  require 'guid'
3
2
  require 'time'
4
3
  require 'net/http'
5
4
  require 'net/https'
6
5
  require 'nokogiri'
7
6
  require 'digest/sha1'
8
- require 'digest/md5'
7
+ require 'openssl'
9
8
  require 'logger'
10
9
 
11
- class FetionException < Exception
12
- end
13
-
14
10
  class Fetion
15
11
  attr_accessor :mobile_no, :sid, :password
16
- attr_reader :uri, :contacts
12
+ attr_reader :user_id, :uri, :contacts, :response, :nickname
13
+
14
+ FETION_URL = 'http://221.176.31.39/ht/sd.aspx'
15
+ 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%'
17
16
 
18
- FETION_URL = 'http://221.130.44.194/ht/sd.aspx'
19
- FETION_LOGIN_URL = 'https://uid.fetion.com.cn/ssiportal/SSIAppSignIn.aspx'
20
- FETION_CONFIG_URL = 'http://nav.fetion.com.cn/nav/getsystemconfig.aspx'
21
- FETION_SIPP = 'SIPP'
22
- @nonce = nil
17
+ SIPP = 'SIPP'
18
+ USER_AGENT = "IIC2.0/PC 3.6.2020"
19
+ VERSION = "3.6.2020"
20
+ DOMAIN = "fetion.com.cn"
23
21
 
24
22
  def initialize
25
- @next_call = 0
23
+ @call = 0
24
+ @alive = 0
26
25
  @seq = 0
27
26
  @buddies = []
28
27
  @contacts = []
29
28
  @logger = Logger.new(STDOUT)
30
29
  @logger.level = Logger::INFO
31
- @cat = true
32
- @guid = ::Guid.new.to_s
30
+ @guid = Guid.new.to_s
33
31
  end
34
32
 
35
33
  def logger_level=(level)
36
34
  @logger.level = level
37
35
  end
38
36
 
37
+ def Fetion.open(options, &block)
38
+ fetion = Fetion.new
39
+ fetion.logger_level = options.delete(:logger_level) || Logger::INFO
40
+ fetion.mobile_no = options.delete(:mobile_no)
41
+ fetion.sid = options.delete(:sid)
42
+ fetion.password = options.delete(:password)
43
+ fetion.login
44
+ fetion.register
45
+
46
+ fetion.instance_eval &block
47
+
48
+ fetion.logout
49
+ end
50
+
39
51
  # options
40
52
  # mobile_no
41
53
  # sid
@@ -44,30 +56,23 @@ class Fetion
44
56
  # content
45
57
  # logger_level
46
58
  def Fetion.send_sms(options)
47
- fetion = Fetion.new
48
- fetion.logger_level = options[:logger_level] || Logger::INFO
49
- fetion.mobile_no = options[:mobile_no]
50
- fetion.sid = options[:sid]
51
- fetion.password = options[:password]
52
- fetion.login
53
- fetion.register
54
- receivers = options[:receivers]
55
- content = options[:content]
56
- if receivers
57
- receivers = Array(receivers)
58
- receivers.collect! {|receiver| receiver.to_s}
59
- fetion.get_buddy_list
60
- fetion.get_contacts_info
61
- fetion.contacts.each do |contact|
62
- if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
63
- fetion.send_sms(contact.uri, content)
59
+ Fetion.open(options) do
60
+ receivers = options.delete(:receivers)
61
+ content = options.delete(:content)
62
+ if receivers
63
+ receivers = Array(receivers)
64
+ receivers.collect! {|receiver| receiver.to_s}
65
+ get_contacts
66
+ contacts.each do |contact|
67
+ if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
68
+ send_sms(contact.uri, content)
69
+ end
64
70
  end
71
+ send_sms(uri, content) if receivers.any? { |receiver| self? receiver }
72
+ else
73
+ send_sms(uri, content)
65
74
  end
66
- fetion.send_sms(fetion.uri, content) if receivers.any? { |receiver| fetion.self? receiver }
67
- else
68
- fetion.send_sms(fetion.uri, content)
69
75
  end
70
- fetion.logout
71
76
  end
72
77
 
73
78
  # options
@@ -78,30 +83,23 @@ class Fetion
78
83
  # content
79
84
  # logger_level
80
85
  def Fetion.send_msg(options)
81
- fetion = Fetion.new
82
- fetion.logger_level = options[:logger_level] || Logger::INFO
83
- fetion.mobile_no = options[:mobile_no]
84
- fetion.sid = options[:sid]
85
- fetion.password = options[:password]
86
- fetion.login
87
- fetion.register
88
- receivers = options[:receivers]
89
- content = options[:content]
90
- if receivers
91
- receivers = Array(receivers)
92
- receivers.collect! {|receiver| receiver.to_s}
93
- fetion.get_buddy_list
94
- fetion.get_contacts_info
95
- fetion.contacts.each do |contact|
96
- if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
97
- fetion.send_msg(contact.uri, content)
86
+ Fetion.open(options) do
87
+ receivers = options.delete(:receivers)
88
+ content = options.delete(:content)
89
+ if receivers
90
+ receivers = Array(receivers)
91
+ receivers.collect! {|receiver| receiver.to_s}
92
+ get_contacts
93
+ contacts.each do |contact|
94
+ if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
95
+ send_msg(contact.uri, content)
96
+ end
98
97
  end
98
+ send_msg(uri, content) if receivers.any? { |receiver| self? receiver }
99
+ else
100
+ send_msg(uri, content)
99
101
  end
100
- fetion.send_msg(fetion.uri, content) if receivers.any? { |receiver| fetion.self? receiver }
101
- else
102
- fetion.send_msg(fetion.uri, content)
103
102
  end
104
- fetion.logout
105
103
  end
106
104
 
107
105
  # options
@@ -112,33 +110,26 @@ class Fetion
112
110
  # content
113
111
  # time
114
112
  # logger_level
115
- def Fetion.schedule_sms(options)
116
- fetion = Fetion.new
117
- fetion.logger_level = options[:logger_level] || Logger::INFO
118
- fetion.mobile_no = options[:mobile_no]
119
- fetion.sid = options[:sid]
120
- fetion.password = options[:password]
121
- fetion.login
122
- fetion.register
123
- receivers = options[:receivers]
124
- content = options[:content]
125
- time = options[:time]
126
- fetion.get_buddy_list
127
- fetion.get_contacts_info
128
- if receivers
129
- receivers = Array(receivers)
130
- receivers.collect! {|receiver| receiver.to_s}
131
- new_receivers = fetion.contacts.collect do |contact|
132
- if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
133
- contact.uri
134
- end
135
- end.compact!
136
- new_receivers << fetion.uri if receivers.any? { |receiver| fetion.self? receiver }
137
- fetion.schedule_sms(new_receivers, content, time)
138
- else
139
- fetion.schedule_sms([fetion.uri], content, time)
113
+ def Fetion.set_schedule_sms(options)
114
+ Fetion.open(options) do
115
+ receivers = options.delete(:receivers)
116
+ content = options.delete(:content)
117
+ time = options.delete(:time)
118
+ get_contacts
119
+ if receivers
120
+ receivers = Array(receivers)
121
+ receivers.collect! {|receiver| receiver.to_s}
122
+ new_receivers = contacts.collect do |contact|
123
+ if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) }
124
+ contact.uri
125
+ end
126
+ end.compact!
127
+ new_receivers << uri if receivers.any? { |receiver| self? receiver }
128
+ set_schedule_sms(new_receivers, content, time)
129
+ else
130
+ set_schedule_sms([fetion.uri], content, time)
131
+ end
140
132
  end
141
- fetion.logout
142
133
  end
143
134
 
144
135
  # options
@@ -149,213 +140,110 @@ class Fetion
149
140
  # friend_sip
150
141
  # logger_level
151
142
  def Fetion.add_buddy(options)
152
- fetion = Fetion.new
153
- fetion.logger_level = options[:logger_level] || Logger::INFO
154
- fetion.mobile_no = options[:mobile_no]
155
- fetion.sid = options[:sid]
156
- fetion.password = options[:password]
157
- fetion.login
158
- fetion.register
159
- fetion.get_personal_info
160
- fetion.add_buddy(options)
161
- fetion.logout
143
+ Fetion.open(options) do
144
+ add_buddy(options)
145
+ end
162
146
  end
163
147
 
164
148
  def login
165
- @logger.info "fetion login"
166
149
  if @mobile_no
167
- uri = URI.parse(FETION_LOGIN_URL + "?mobileno=#{@mobile_no}&pwd=#{@password}")
150
+ url = FETION_LOGIN_URL.sub('%mobileno%', @mobile_no).sub('sid=%sid%', '')
168
151
  else
169
- uri = URI.parse(FETION_LOGIN_URL + "?sid=#{@sid}&pwd=#{@password}")
152
+ url = FETION_LOGIN_URL.sub('%sid%', @sid).sub('mobileno=%mobileno%', '')
170
153
  end
154
+ uri = URI.parse(url.sub('%digest%', Digest::SHA1.hexdigest("#{DOMAIN}:#{@password}")))
171
155
  http = Net::HTTP.new(uri.host, uri.port)
172
156
  http.use_ssl = true
173
157
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
174
- headers = {'Content-Type' => 'application/oct-stream', 'Pragma' => "xz4BBcV#{@guid}", 'User-Agent' => 'IIC2.0/PC 3.2.0540'}
158
+ headers = {'User-Agent' => USER_AGENT}
175
159
  response = http.request_get(uri.request_uri, headers)
176
160
 
177
161
  raise FetionException.new('Fetion Error: Login failed.') unless response.is_a? Net::HTTPSuccess
178
- raise FetionException.new('Fetion Error: No ssic found in cookie.') unless response['set-cookie'] =~ /ssic=(.*);/
179
162
 
180
- @ssic = $1
181
- @logger.debug response.body
182
- doc = Nokogiri::XML(response.body)
183
- results = doc.root
184
- @status_code = results["status-code"]
185
- user = results.children.first
186
- @user_status = user['user-status']
187
- @uri = user['uri']
188
- @mobile_no = user['mobile-no']
189
- @user_id = user['user-id']
190
- if @uri =~ /sip:(\d+)@(.+);/
191
- @sid = $1
192
- @domain = $2
193
- end
194
- @logger.debug "ssic: " + @ssic
195
- @logger.debug "status_code: " + @status_code
196
- @logger.debug "user_status: " + @user_status
197
- @logger.debug "uri: " + @uri
198
- @logger.debug "mobile_no: " + @mobile_no
199
- @logger.debug "user_id: " + @user_id
200
- @logger.debug "sid: " + @sid
201
- @logger.debug "domain: " + @domain
202
- @logger.info "fetion login success"
163
+ parse_login_response(response)
203
164
  end
204
165
 
205
166
  def register
206
- @logger.info "fetion http register"
207
167
  call = next_call
208
- arg = '<args><device type="PC" version="284571220" client-version="3.3.0370" /><caps value="simple-im;im-session;temp-group;personal-group" /><events value="contact;permission;system-message;personal-group" /><user-info attributes="all" /><presence><basic value="400" desc="" /></presence></args>'
209
-
210
- # get nonce, it failed, try again 16s later
211
- begin
212
- register_first(call, arg)
213
- rescue FetionException
214
- sleep 16
215
- register_first(call, arg)
216
- end
217
-
218
- begin
219
- register_second(call, arg)
220
- rescue FetionException
221
- sleep 16
222
- register_second(call, arg)
223
- end
224
- @logger.info "fetion http register success"
168
+ register_first
169
+ register_second
170
+ call = next_call
225
171
  end
226
172
 
227
- def register_first(call, arg)
228
- @logger.debug "fetion http register first"
229
-
230
- curl_exec(next_url, @ssic, FETION_SIPP)
231
-
232
- msg = sip_create("R fetion.com.cn SIP-C/2.0", {'F' => @sid, 'I' => call, 'Q' => '1 R'}, arg) + FETION_SIPP
233
- curl_exec(next_url('i'), @ssic, msg)
234
-
235
- response = curl_exec(next_url, @ssic, FETION_SIPP)
236
- raise FetionException.new("Fetion Error: no nonce found") unless response.body =~ /nonce="(\w+)"/
173
+ def register_first
174
+ curl_exec(SIPP, next_url('i'))
175
+ curl_exec(SipcMessage.register_first(self))
176
+ response = pulse
177
+ raise Fetion::NoNonceException.new("Fetion Error: no nonce found") unless response.body =~ /nonce="(.*?)",key="(.*?)",signature="(.*?)"/
237
178
 
238
179
  @nonce = $1
239
- @salt = "777A6D03"
240
- @cnonce = calc_cnonce
180
+ @key = $2
181
+ @signature = $3
241
182
  @response = calc_response
242
183
 
243
184
  @logger.debug "nonce: #{@nonce}"
244
- @logger.debug "salt: #{@salt}"
245
- @logger.debug "cnonce: #{@cnonce}"
185
+ @logger.debug "key: #{@key}"
186
+ @logger.debug "signature: #{@signature}"
246
187
  @logger.debug "response: #{@response}"
247
- @logger.debug "fetion http register first success"
248
188
  end
249
189
 
250
- def register_second(call, arg)
251
- @logger.debug "fetion http register second"
252
-
253
- msg = sip_create('R fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => call, 'Q' => '2 R', 'A' => "Digest algorithm=\"SHA1-sess\",response=\"#{@response}\",cnonce=\"#{@cnonce}\",salt=\"#{@salt}\""}, arg) + FETION_SIPP
254
- curl_exec(next_url, @ssic, msg)
255
- response = curl_exec(next_url, @ssic, FETION_SIPP)
190
+ def register_second
191
+ 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>|
192
+ curl_exec(SipcMessage.register_second(self))
193
+ response = pulse
256
194
 
257
195
  raise FetionException.new('Fetion Error: Register failed.') unless response.is_a? Net::HTTPSuccess
258
- @logger.debug "fetion http register second success"
259
- end
260
196
 
261
- def get_buddy_list
262
- @logger.info "fetion get buddy list"
263
- arg = '<args><contacts><buddy-lists /><buddies attributes="all" /><mobile-buddies attributes="all" /><chat-friends /><blacklist /><allow-list /></contacts></args>'
264
- msg = sip_create('S fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 S', 'N' => 'GetContactList'}, arg) + FETION_SIPP
265
- curl_exec(next_url, @ssic, msg)
266
- response = curl_exec(next_url, @ssic, FETION_SIPP)
267
- raise FetionException.new("Fetion Error: Get buddy list error") unless response.is_a? Net::HTTPSuccess
268
-
269
- response.body.scan(%r{<results>.*?</results>}).each do |results|
270
- doc = Nokogiri::XML(results)
271
- doc.root.xpath("/results/contacts/allow-list/contact").each do |contact|
272
- @buddies << {:uri => contact["uri"]}
273
- end
274
- end
275
- @logger.debug "buddies: #{@buddies.inspect}"
276
- @logger.info "fetion get buddy list success"
197
+ parse_info(response.body)
198
+ parse_buddies(response.body)
277
199
  end
278
200
 
279
- def get_contacts_info
280
- @logger.info "fetion get contacts info"
281
- arg = '<args><contacts attributes="provisioning;impresa;mobile-no;nickname;name;gender;portrait-crc;ivr-enabled" extended-attributes="score-level">'
282
- @buddies.each do |buddy|
283
- arg += "<contact uri=\"#{buddy[:uri]}\" />"
284
- end
285
- arg += '</contacts></args>'
286
-
287
- msg = sip_create('S fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 S', 'N' => 'GetContactsInfo'}, arg) + FETION_SIPP
288
- curl_exec(next_url, @ssic, msg)
289
- while true do
290
- sleep 1
291
- response = curl_exec(next_url, @ssic, FETION_SIPP)
292
- raise FetionException.new("Fetion Error: Get contacts info error") unless response.is_a? Net::HTTPSuccess
293
- break if response.body.size > FETION_SIPP.size
294
- end
201
+ def get_contacts
202
+ curl_exec(SipcMessage.get_group_list(self))
295
203
 
296
- response.body.scan(%r{<results>.*?</results>}).each do |results|
297
- doc = Nokogiri::XML(results)
298
- doc.root.xpath("/results/contacts/contact").each do |contact|
299
- attrs = contact.children.size == 0 ? {} : contact.children.first
300
- @contacts << Contact.new(contact["uri"], attrs)
301
- end
302
- end
303
- @logger.debug @contacts.inspect
304
- @logger.info "fetion get contacts info success"
204
+ response = curl_exec(SipcMessage.presence(self))
205
+ response = curl_exec(SipcMessage.get_group_topic(self))
206
+ raise FetionException.new('Fetion Error: get contacts failed.') unless response.is_a? Net::HTTPSuccess
207
+ parse_contacts(response.body)
208
+
209
+ curl_exec(SipcMessage.get_address_list(self))
210
+ pulse
305
211
  end
306
212
 
307
213
  def send_msg(receiver, content)
308
- @logger.info "fetion SendMsg to #{receiver}"
309
- msg = sip_create('M fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '3 M', 'T' => receiver, 'C' => 'text/html-fragment', 'K' => 'SaveHistory'}, content) + FETION_SIPP
310
- curl_exec(next_url, @ssic, msg)
311
- response = curl_exec(next_url, @ssic, FETION_SIPP)
214
+ @logger.info "fetion send msg to #{receiver}"
215
+ curl_exec(SipcMessage.send_msg(self, receiver, content))
216
+ response = pulse
312
217
 
313
- raise FetionException.new("Fetion Error: Send sms error") unless response.is_a? Net::HTTPSuccess
314
- @logger.info "fetion SendMsg to #{receiver} success"
218
+ raise SendMsgException.new("Fetion Error: Send sms error") unless response.is_a? Net::HTTPSuccess
219
+ @logger.info "fetion send msg to #{receiver} success"
315
220
  end
316
221
 
317
222
  def send_sms(receiver, content)
318
- @logger.info "fetion #{send_command} to #{receiver}"
319
- msg = sip_create('M fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 M', 'T' => receiver, 'N' => send_command}, content) + FETION_SIPP
320
- curl_exec(next_url, @ssic, msg)
321
- response = curl_exec(next_url, @ssic, FETION_SIPP)
223
+ @logger.info "fetion send cat sms to #{receiver}"
224
+ curl_exec(SipcMessage.send_cat_sms(self, receiver, content))
225
+ response = pulse
322
226
 
323
- raise FetionException.new("Fetion Error: Send sms error") unless response.is_a? Net::HTTPSuccess
324
- @logger.info "fetion #{send_command} to #{receiver} success"
227
+ raise SendSmsException.new("Fetion Error: Send cat sms error") unless response.is_a? Net::HTTPSuccess
228
+ @logger.info "fetion send cat sms to #{receiver} success"
325
229
  end
326
230
 
327
- def schedule_sms(receivers, content, time)
231
+ def set_schedule_sms(receivers, content, time)
328
232
  receivers = Array(receivers)
329
233
  time = time.is_a?(Time) ? time : Time.parse(time)
330
234
  now = Time.now
331
235
  one_year = Time.local(now.year + 1, now.month, now.day, now.hour, now.min, now.sec)
332
- raise FetionException.new("Can't schedule send sms to more than 32 receivers") if receivers.size > 32
333
- raise FetionException.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
236
+ raise SetScheduleSmsException.new("Can't schedule send sms to more than 64 receivers") if receivers.size > 64
237
+ 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
334
238
  @logger.info "fetion schedule send sms to #{receivers.join(', ')}"
335
239
 
336
- receivers_str = receivers.collect { |receiver| %Q[<receiver uri="#{receiver}" />] }.join('')
337
- arg = %Q{<args><schedule-sms send-time="#{time.getutc.strftime('%Y-%m-%d %H:%M:%S')}"><message>#{content}</message><receivers>#{receivers_str}</receivers></schedule-sms></args>}
338
- msg = sip_create('S fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 S', 'N' => 'SSSetScheduleCatSms'}, arg) + FETION_SIPP
339
- curl_exec(next_url, @ssic, msg)
340
- response = curl_exec(next_url, @ssic, FETION_SIPP)
240
+ curl_exec(SipcMessage.set_schedule_sms(self, receivers, content, time.strftime('%Y-%m-%d %H:%M:%S')))
241
+ response = pulse
341
242
 
342
- raise FetionException.new("Fetion Error: Schedule sms error") unless response.is_a? Net::HTTPSuccess
243
+ raise SetScheduleSmsException.new("Fetion Error: Set schedule sms error") unless response.is_a? Net::HTTPSuccess
343
244
  @logger.info "fetion schedule send sms to #{receivers.join(', ')} success"
344
245
  end
345
246
 
346
- def get_personal_info
347
- @logger.info "fetion get personal info"
348
- arg = %Q{<args><personal attributes="all" /><services version="" attributes="all" /><config version="96" attributes="all" /><mobile-device attributes="all" /></args>}
349
- msg = sip_create('S fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 S', 'N' => 'GetPersonalInfo'}, arg) + FETION_SIPP
350
- curl_exec(next_url, @ssic, msg)
351
- response = curl_exec(next_url, @ssic, FETION_SIPP)
352
- raise FetionException.new("Fetion Error: Get personal info error") unless response.is_a? Net::HTTPSuccess
353
-
354
- doc = Nokogiri::XML(response.body.chomp(FETION_SIPP))
355
- @person = doc.root.xpath('/results/personal').first
356
- @logger.info "fetion get personal info success"
357
- end
358
-
359
247
  # options
360
248
  # friend_mobile
361
249
  # friend_sip
@@ -363,44 +251,103 @@ class Fetion
363
251
  uri = options[:friend_mobile] ? "tel:#{options[:friend_mobile]}" : "sip:#{options[:friend_sip]}"
364
252
 
365
253
  @logger.info "fetion send request to add #{uri} as friend"
366
- arg = %Q{<args><contacts><buddies><buddy uri="#{uri}" local-name="" buddy-lists="1" desc="#{@person['nickname']}" expose-mobile-no="1" expose-name="1" /></buddies></contacts></args>}
367
- msg = sip_create('S fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 S', 'N' => 'AddBuddy'}, arg) + FETION_SIPP
368
- curl_exec(next_url, @ssic, msg)
369
- response = curl_exec(next_url, @ssic, FETION_SIPP)
370
- raise FetionException.new("Fetion Error: Add buddy error") unless response.is_a? Net::HTTPSuccess
371
-
372
- if response.body =~ /No Subscription/
373
- raise FetionException.new("Fetion Error: No #{uri}") if options[:friend_sip]
374
-
375
- arg = %Q{<args><contacts><mobile-buddies><mobile-buddy uri="#{uri}" local-name="" buddy-lists="1" desc="#{@person['nickname']}" expose-mobile-no="1" expose-name="1" /></mobile-buddies></contacts></args>}
376
- msg = sip_create('S fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => next_call, 'Q' => '1 S', 'N' => 'AddMobileBuddy'}, arg) + FETION_SIPP
377
- curl_exec(next_url, @ssic, msg)
378
- response = curl_exec(next_url, @ssic, FETION_SIPP)
379
- raise FetionException.new("Fetion Error: Add buddy error") unless response.is_a? Net::HTTPSuccess
380
-
381
- raise FetionException.new("Fetion Error: No #{uri}") if response.body =~ /Not Found/
382
- end
254
+ curl_exec(SipcMessage.add_buddy(self, options))
255
+ response = pulse
256
+ raise AddBuddyException.new("Fetion Error: Add buddy error") unless response.is_a? Net::HTTPSuccess
257
+
383
258
  @logger.info "fetion send request to add #{uri} as friend success"
384
259
  end
385
260
 
261
+ # options
262
+ # mobile_no
263
+ # sip
264
+ def get_contact_info(options)
265
+ uri = options[:mobile_no] ? "tel:#{options[:mobile_no]}" : "sip:#{options[:sip]}"
266
+
267
+ @logger.info "fetion get contact info of #{uri}"
268
+ curl_exec(SipcMessage.get_contact_info(self, options))
269
+ response = pulse
270
+
271
+ sipc_response = SipcMessage.sipc_response(response.body)
272
+ raise Fetion::NoUserException.new("Fetion Error: get contact info #{uri} with #{sipc_response.to_s}") unless SipcMessage::OK === sipc_response
273
+
274
+ @logger.info "fetion get contact info of #{uri} success"
275
+ end
276
+
386
277
  def logout
387
- @logger.info "fetion logout"
388
- msg = sip_create('R fetion.com.cn SIP-C/2.0', {'F' => @sid, 'I' => 1, 'Q' => '3 R', 'X' => 0}, '') + FETION_SIPP
389
- curl_exec(next_url, @ssic, msg)
390
- response = curl_exec(next_url, @ssic, FETION_SIPP)
278
+ curl_exec(SipcMessage.logout(self))
279
+ response = pulse
391
280
 
392
281
  # raise FetionException.new("Fetion Error: Logout error") unless response.is_a? Net::HTTPSuccess
393
- @logger.info "fetion logout success"
394
282
  end
395
283
 
396
- def curl_exec(url, ssic, body)
284
+ def parse_login_response(response)
285
+ raise Fetion::LoginException.new('Fetion Error: No ssic found in cookie.') unless response['set-cookie'] =~ /ssic=(.*);/
286
+
287
+ @ssic = $1
288
+ @logger.debug response.body
289
+ doc = Nokogiri::XML(response.body)
290
+ results = doc.root
291
+ @status_code = results["status-code"]
292
+ user = results.children.first
293
+ @user_status = user['user-status']
294
+ @uri = user['uri']
295
+ @mobile_no = user['mobile-no']
296
+ @user_id = user['user-id']
297
+ if @uri =~ /sip:(\d+)@(.+);/
298
+ @sid = $1
299
+ end
300
+
301
+ @logger.debug "ssic: " + @ssic
302
+ @logger.debug "status_code: " + @status_code
303
+ @logger.debug "user_status: " + @user_status
304
+ @logger.debug "uri: " + @uri
305
+ @logger.debug "mobile_no: " + @mobile_no
306
+ @logger.debug "user_id: " + @user_id
307
+ @logger.debug "sid: " + @sid
308
+ end
309
+
310
+ def parse_info(response_body)
311
+ response_body.scan(%r{<results>.*?</results>}).each do |results|
312
+ doc = Nokogiri::XML(results)
313
+ personal = doc.root.xpath("/results/user-info/personal").first
314
+ @nickname = personal['nickname']
315
+ end
316
+
317
+ @logger.debug "nickname: #@nickname"
318
+ end
319
+
320
+ def parse_buddies(response_body)
321
+ response_body.scan(%r{<results>.*?</results>}).each do |results|
322
+ doc = Nokogiri::XML(results)
323
+ doc.root.xpath("/results//buddies/b").each do |buddy|
324
+ @buddies << {:uri => buddy["u"]}
325
+ end
326
+ end
327
+ @logger.debug "buddies: #{@buddies.inspect}"
328
+ end
329
+
330
+ def parse_contacts(response_body)
331
+ response_body.scan(%r{<events>.*?</events>}).each do |results|
332
+ doc = Nokogiri::XML(results)
333
+ doc.root.xpath("/events//c/p").each do |person|
334
+ @contacts << Contact.new(person) if person['sid']
335
+ end
336
+ end
337
+ @logger.debug "contacts: #{@contacts.inspect}"
338
+ end
339
+
340
+ def pulse
341
+ curl_exec(SIPP)
342
+ end
343
+
344
+ def curl_exec(body='', url=next_url)
397
345
  @logger.debug "fetion curl exec"
398
346
  @logger.debug "url: #{url}"
399
- @logger.debug "ssic: #{ssic}"
400
347
  @logger.debug "body: #{body}"
401
348
  uri = URI.parse(url)
402
349
  http = Net::HTTP.new(uri.host, uri.port)
403
- headers = {'Content-Type' => 'application/oct-stream', 'Pragma' => "xz4BBcV#{@guid}", 'User-Agent' => 'IIC2.0/PC 3.2.0540', 'Cookie' => "ssic=#{@ssic}"}
350
+ headers = {'Content-Type' => 'application/oct-stream', 'Pragma' => "xz4BBcV#{@guid}", 'User-Agent' => USER_AGENT, 'Cookie' => "ssic=#{@ssic}", 'Content-Length' => body.length.to_s}
404
351
  response = http.request_post(uri.request_uri, body, headers)
405
352
 
406
353
  @logger.debug "response: #{response.inspect}"
@@ -409,49 +356,70 @@ class Fetion
409
356
  response
410
357
  end
411
358
 
412
- def sip_create(invite, fields, arg)
413
- sip = invite + "\r\n"
414
- fields.each {|k, v| sip += "#{k}: #{v}\r\n"}
415
- sip += "L: #{arg.size}\r\n\r\n#{arg}"
416
- @logger.debug "sip message: #{sip}"
417
- sip
418
- end
419
-
420
- def calc_response
421
- str = [hash_password[8..-1]].pack("H*")
422
- key = Digest::SHA1.digest("#{@sid}:#{@domain}:#{str}")
423
-
424
- h1 = Digest::MD5.hexdigest("#{key}:#{@nonce}:#{@cnonce}").upcase
425
- h2 = Digest::MD5.hexdigest("REGISTER:#{@sid}").upcase
426
-
427
- Digest::MD5.hexdigest("#{h1}:#{@nonce}:#{h2}").upcase
359
+ def next_url(t = 's')
360
+ FETION_URL + "?t=#{t}&i=#{next_seq}"
428
361
  end
429
362
 
430
- def calc_cnonce
431
- Digest::MD5.hexdigest(@guid).upcase
363
+ def next_call
364
+ @call += 1
432
365
  end
433
366
 
434
- def hash_password
435
- salt = "#{0x77.chr}#{0x7A.chr}#{0x6D.chr}#{0x03.chr}"
436
- src = salt + Digest::SHA1.digest(@password)
437
- '777A6D03' + Digest::SHA1.hexdigest(src).upcase
367
+ def next_seq
368
+ @seq += 1
438
369
  end
439
370
 
440
- def next_url(t = 's')
441
- @seq += 1
442
- FETION_URL + "?t=#{t}&i=#{@seq}"
371
+ def next_alive
372
+ @alive += 1
443
373
  end
444
374
 
445
- def next_call
446
- @next_call += 1
375
+ def calc_response
376
+ encrypted_password = Digest::SHA1.hexdigest([@user_id.to_i].pack("V*") + [Digest::SHA1.hexdigest("#{DOMAIN}:#{@password}")].pack("H*"))
377
+ rsa_result = "4A026855890197CFDF768597D07200B346F3D676411C6F87368B5C2276DCEDD2"
378
+ str = @nonce + [encrypted_password].pack("H*") + [rsa_result].pack("H*")
379
+ rsa_key = OpenSSL::PKey::RSA.new
380
+ exponent = OpenSSL::BN.new @key[-6..-1].hex.to_s
381
+ modulus = OpenSSL::BN.new @key[0...-6].hex.to_s
382
+ rsa_key.e = exponent
383
+ rsa_key.n = modulus
384
+
385
+ rsa_key.public_encrypt(str).unpack("H*").first.upcase
447
386
  end
448
387
 
449
- def send_command
450
- @cat ? 'SendCatSMS' : 'SendSMS'
451
- end
452
-
453
388
  def self?(mobile_or_sid)
454
389
  mobile_or_sid == @mobile_no or mobile_or_sid == @sid
455
390
  end
391
+
392
+ [:login, :register, :get_contacts, :logout].each do |method|
393
+ class_eval <<-EOF
394
+ alias_method :origin_#{method}, :#{method}
395
+
396
+ def #{method}
397
+ @logger.info "fetion #{method.to_s.gsub(/_/, ' ')}"
398
+ origin_#{method}
399
+ @logger.info "fetion #{method.to_s.gsub(/_/, ' ')} success"
400
+ end
401
+ EOF
402
+ end
403
+
404
+ [:register_first, :register_second].each do |method|
405
+ class_eval <<-EOF
406
+ alias_method :origin_#{method}, :#{method}
407
+
408
+ def #{method}
409
+ @logger.debug "fetion #{method.to_s.gsub(/_/, ' ')}"
410
+ origin_#{method}
411
+ @logger.debug "fetion #{method.to_s.gsub(/_/, ' ')} success"
412
+ end
413
+ EOF
414
+ end
456
415
  end
457
416
 
417
+ class FetionException < Exception; end
418
+ class Fetion::LoginException < FetionException; end
419
+ class Fetion::NoNonceException < FetionException; end
420
+ class Fetion::RegisterException < FetionException; end
421
+ class Fetion::SendSmsException < FetionException; end
422
+ class Fetion::SendMsgException < FetionException; end
423
+ class Fetion::SetScheduleSmsException < FetionException; end
424
+ class Fetion::AddBuddyException < FetionException; end
425
+ class Fetion::NoUserException < FetionException; end