rfetion 0.4.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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