├── VERSION ├── .gitignore ├── spec ├── spec.opts ├── spec_helper.rb └── rfetion │ ├── buddy_list_spec.rb │ ├── sipc_message_spec.rb │ └── fetion_spec.rb ├── bin └── rfetion ├── lib ├── rfetion │ ├── message.rb │ ├── pic_certificate.rb │ ├── buddy_list.rb │ ├── contact.rb │ ├── command.rb │ ├── sipc_message.rb │ └── fetion.rb └── rfetion.rb ├── Rakefile ├── rfetion.gemspec └── README.textile /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.7 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/test.rb 2 | pkg/** 3 | *.swp 4 | block_list 5 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format 3 | specdoc 4 | --reverse 5 | --timeout 6 | 20 7 | --backtrace 8 | --loadby 9 | mtime 10 | -------------------------------------------------------------------------------- /bin/rfetion: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) 4 | 5 | require 'rfetion' 6 | require 'rfetion/command' 7 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'spec/autorun' 3 | require 'mocha' 4 | require 'fakeweb' 5 | 6 | $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) 7 | require 'rfetion' 8 | FakeWeb.allow_net_connect = false 9 | -------------------------------------------------------------------------------- /lib/rfetion/message.rb: -------------------------------------------------------------------------------- 1 | class Fetion 2 | class Message 3 | attr_reader :sip, :sent_at, :text 4 | 5 | def initialize(sip, sent_at, text) 6 | @sip = sip 7 | @sent_at = sent_at 8 | @text = text 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rfetion.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require File.dirname(__FILE__) + '/rfetion/buddy_list' 3 | require File.dirname(__FILE__) + '/rfetion/contact' 4 | require File.dirname(__FILE__) + '/rfetion/message' 5 | require File.dirname(__FILE__) + '/rfetion/pic_certificate' 6 | require File.dirname(__FILE__) + '/rfetion/sipc_message' 7 | require File.dirname(__FILE__) + '/rfetion/fetion' 8 | -------------------------------------------------------------------------------- /lib/rfetion/pic_certificate.rb: -------------------------------------------------------------------------------- 1 | class PicCertificate 2 | attr_reader :pid, :pic, :algorithm 3 | 4 | def initialize(pid, pic, algorithm) 5 | @pid = pid 6 | @pic = pic 7 | @algorithm = algorithm 8 | end 9 | 10 | def self.parse(c, algorithm) 11 | PicCertificate.new(c['id'], c['pic'], algorithm) 12 | end 13 | 14 | def to_json(*args) 15 | {:pid => @pid, :pic => @pic, :algorithm => @algorithm} 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rfetion/buddy_list.rb: -------------------------------------------------------------------------------- 1 | class Fetion 2 | class BuddyList 3 | attr_reader :bid, :name, :contacts 4 | 5 | def initialize(bid, name) 6 | @bid = bid 7 | @name = name 8 | @contacts = [] 9 | end 10 | 11 | def to_json(*args) 12 | {:bid => @bid, :name => @name, :contacts => @contacts, :total_contacts => total_contacts_count, :online_contacts => online_contacts_count}.to_json(*args) 13 | end 14 | 15 | def self.parse(buddy_list) 16 | self.new(buddy_list['id'], buddy_list['name']) 17 | end 18 | 19 | def add_contact(contact) 20 | @contacts << contact 21 | end 22 | 23 | def online_contacts_count 24 | @contacts.select {|contact| contact.status == "400"}.size 25 | end 26 | 27 | def total_contacts_count 28 | @contacts.size 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'spec/rake/spectask' 4 | require 'rake/rdoctask' 5 | require 'jeweler' 6 | 7 | desc 'Default: run unit tests.' 8 | task :default => :spec 9 | 10 | desc 'Generate documentation for the rfetion plugin.' 11 | Rake::RDocTask.new(:rdoc) do |rdoc| 12 | rdoc.rdoc_dir = 'rdoc' 13 | rdoc.title = 'rfetion' 14 | rdoc.options << '--line-numbers' << '--inline-source' 15 | rdoc.rdoc_files.include('README') 16 | rdoc.rdoc_files.include('lib/**/*.rb') 17 | end 18 | 19 | desc "Run all specs in spec directory" 20 | Spec::Rake::SpecTask.new(:spec) do |t| 21 | t.spec_files = FileList['spec/**/*_spec.rb'] 22 | end 23 | 24 | Jeweler::Tasks.new do |gemspec| 25 | gemspec.name = 'rfetion' 26 | gemspec.summary = 'rfetion is a ruby gem for China Mobile fetion service that you can send SMS free.' 27 | gemspec.description = 'rfetion is a ruby gem for China Mobile fetion service that you can send SMS free.' 28 | gemspec.email = 'flyerhzm@gmail.com' 29 | gemspec.homepage = 'http://github.com/flyerhzm/rfetion' 30 | gemspec.authors = ['Richard Huang'] 31 | gemspec.files.exclude '.gitignore' 32 | gemspec.add_dependency 'guid' 33 | gemspec.add_dependency 'nokogiri' 34 | gemspec.add_dependency 'json', '=1.2.3' 35 | gemspec.add_dependency 'macaddr' 36 | gemspec.executables << 'rfetion' 37 | end 38 | Jeweler::GemcutterTasks.new 39 | -------------------------------------------------------------------------------- /spec/rfetion/buddy_list_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper.rb' 2 | 3 | describe Fetion::BuddyList do 4 | before :each do 5 | @buddy_list = Fetion::BuddyList.new("1", "My friend") 6 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "226911221", :uri => "sip:572512981@fetion.com.cn;p=3544", :bid => "1", :status => "400")) 7 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "295098062", :uri => "sip:638993408@fetion.com.cn;p=2242", :bid => "1")) 8 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "579113578", :uri => "sip:838271744@fetion.com.cn;p=4805", :bid => "1")) 9 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "665046562", :uri => "sip:926157269@fetion.com.cn;p=12906", :bid => "1", :status => "400")) 10 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "687455743", :uri => "sip:881033150@fetion.com.cn;p=5493", :bid => "1")) 11 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "714355089", :uri => "sip:973921799@fetion.com.cn;p=12193", :bid => "1", :status => "400")) 12 | @buddy_list.add_contact(Fetion::Contact.new(:uid => "732743291", :uri => "sip:480867781@fetion.com.cn;p=16105", :bid => "1")) 13 | end 14 | 15 | it "should get total contacts count" do 16 | @buddy_list.total_contacts_count.should == 7 17 | end 18 | 19 | it "should get online contacts count" do 20 | @buddy_list.online_contacts_count.should == 3 21 | end 22 | 23 | it "should to_json" do 24 | buddy_list_json = @buddy_list.to_json 25 | p buddy_list_json 26 | buddy_list_json.should be_include %Q|"bid":"1"| 27 | buddy_list_json.should be_include %Q|"name":"My friend"| 28 | buddy_list_json.should be_include %Q|"total_contacts":7| 29 | buddy_list_json.should be_include %Q|"online_contacts":3| 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rfetion/contact.rb: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | class Fetion 3 | class Contact 4 | attr_accessor :uid, :sid, :bid, :uri, :mobile_no, :nickname, :impresa, :status 5 | 6 | STATUS = { 7 | "400" => "在线", 8 | "300" => "离开", 9 | "600" => "繁忙", 10 | "0" => "脱机" 11 | } 12 | 13 | def to_json(*args) 14 | {:uid => @uid, :sid => @sid, :bid => @bid, :uri => @uri, :mobile_no => @mobile_no, :nickname => @nickname, :impresa => @impresa, :status => @status, :display => display}.to_json(*args) 15 | end 16 | 17 | def self.parse_buddy(b) 18 | self.new(:uid => b['i'], :uri => b['u'], :nickname => b['n'], :bid => b['l'].empty? ? "0" : b['l']) 19 | end 20 | 21 | def self.parse(c) 22 | p = c.children.first 23 | self.new(:uid => c['id'], :sid => p['sid'], :uri => p['su'], :mobile_no => p['m'], :nickname => p['n'], :impresa => p['i'], :status => p['b'], :bid => p['l']) 24 | end 25 | 26 | def self.parse_request(c) 27 | self.new(:uri => c['uri'], :uid => c['user-id'], :sid => c['sid'], :mobile_no => c['mobile-no'], :nickname => c['nickname']) 28 | end 29 | 30 | def initialize(options={}) 31 | @status = "0" 32 | options.each do |key, value| 33 | send("#{key}=", value) 34 | end 35 | end 36 | 37 | def update(p, pr) 38 | self.sid = p["sid"] if p["sid"] and !p["sid"].empty? 39 | self.uri = p["su"] if p["su"] and !p["su"].empty? 40 | self.mobile_no = p["m"] if p["m"] and !p["m"].empty? 41 | self.nickname = p["n"] if p["n"] and !p["n"].empty? 42 | self.impresa = p["i"] if p["i"] and !p["i"].empty? 43 | self.status = pr["b"] if pr["b"] and !pr["b"].empty? 44 | end 45 | 46 | def display 47 | if self.impresa and !self.impresa.empty? 48 | "#{self.nickname}(#{self.impresa})" 49 | elsif self.nickname and !self.nickname.empty? 50 | self.nickname 51 | else 52 | if self.uri =~ /^(tel|sip):(\d+)/ 53 | $2 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /rfetion.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{rfetion} 8 | s.version = "0.5.7" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Richard Huang"] 12 | s.date = %q{2010-05-29} 13 | s.description = %q{rfetion is a ruby gem for China Mobile fetion service that you can send SMS free.} 14 | s.email = %q{flyerhzm@gmail.com} 15 | s.executables = ["rfetion", "rfetion"] 16 | s.extra_rdoc_files = [ 17 | "README.textile" 18 | ] 19 | s.files = [ 20 | "README.textile", 21 | "Rakefile", 22 | "VERSION", 23 | "bin/rfetion", 24 | "lib/rfetion.rb", 25 | "lib/rfetion/buddy_list.rb", 26 | "lib/rfetion/command.rb", 27 | "lib/rfetion/contact.rb", 28 | "lib/rfetion/fetion.rb", 29 | "lib/rfetion/message.rb", 30 | "lib/rfetion/pic_certificate.rb", 31 | "lib/rfetion/sipc_message.rb", 32 | "rfetion.gemspec", 33 | "spec/rfetion/buddy_list_spec.rb", 34 | "spec/rfetion/fetion_spec.rb", 35 | "spec/rfetion/sipc_message_spec.rb", 36 | "spec/spec.opts", 37 | "spec/spec_helper.rb" 38 | ] 39 | s.homepage = %q{http://github.com/flyerhzm/rfetion} 40 | s.rdoc_options = ["--charset=UTF-8"] 41 | s.require_paths = ["lib"] 42 | s.rubygems_version = %q{1.3.6} 43 | s.summary = %q{rfetion is a ruby gem for China Mobile fetion service that you can send SMS free.} 44 | s.test_files = [ 45 | "spec/rfetion/buddy_list_spec.rb", 46 | "spec/rfetion/fetion_spec.rb", 47 | "spec/rfetion/sipc_message_spec.rb", 48 | "spec/spec_helper.rb" 49 | ] 50 | 51 | if s.respond_to? :specification_version then 52 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 53 | s.specification_version = 3 54 | 55 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 56 | s.add_runtime_dependency(%q, [">= 0"]) 57 | s.add_runtime_dependency(%q, [">= 0"]) 58 | s.add_runtime_dependency(%q, [">= 0"]) 59 | s.add_runtime_dependency(%q, [">= 0"]) 60 | else 61 | s.add_dependency(%q, [">= 0"]) 62 | s.add_dependency(%q, [">= 0"]) 63 | s.add_dependency(%q, [">= 0"]) 64 | s.add_dependency(%q, [">= 0"]) 65 | end 66 | else 67 | s.add_dependency(%q, [">= 0"]) 68 | s.add_dependency(%q, [">= 0"]) 69 | s.add_dependency(%q, [">= 0"]) 70 | s.add_dependency(%q, [">= 0"]) 71 | end 72 | end 73 | 74 | -------------------------------------------------------------------------------- /lib/rfetion/command.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | options = {} 4 | 5 | OptionParser.new do |opts| 6 | # Set a banner, displayed at the top of the help screen. 7 | opts.banner = "Usage: rfetion [options]" 8 | 9 | opts.separator "" 10 | opts.separator <<-EOF 11 | Example: rfetion -m mobile -p password -c sms_content 12 | rfetion -s sip -p password -c sms_content 13 | rfetion -m mobile -p password -r mobile_or_fetion_numbers -c sms_content 14 | rfetion -m mobile -p password -r mobile_or_fetion_numbers -c sms_content -t time 15 | rfetion -m mobile -p password --add-buddy-with-mobile friend_mobile 16 | rfetion -s sip -p password --add-buddy-with-mobile friend_mobile 17 | rfetion -m mobile -p password --add-buddy-with-sip friend_sip 18 | 19 | EOF 20 | 21 | opts.on('-m', '--mobile MOBILE', 'Fetion mobile number') do |mobile| 22 | options[:mobile_no] = mobile 23 | end 24 | 25 | opts.on('-s', '--sip FETION_SIP', 'Fetion sid number') do |sid| 26 | options[:sid] = sid 27 | end 28 | 29 | opts.on('-p', '--password PASSWORD', 'Fetion password') do |password| 30 | options[:password] = password 31 | end 32 | 33 | opts.on('-c', '--content CONTENT', 'Fetion message content') do |content| 34 | options[:content] = content 35 | end 36 | 37 | opts.on('-r', '--receivers MOBILE,SIP', Array, "Receivers' Fetion mobile numbers or fetion sip numbers, if no recievers, send sms to yourself") do |receivers| 38 | options[:receivers] = receivers 39 | end 40 | 41 | opts.on('-t', '--time TIME', 'Schedule time to send sms, format is "2009-12-10 20:00:00"') do |time| 42 | options[:time] = time 43 | end 44 | 45 | opts.on('--add-buddy-with-mobile MOBILE', 'Add friend mobile as fetion friend') do |mobile| 46 | options[:friend_mobile] = mobile 47 | end 48 | 49 | opts.on('--add-buddy-with-sip SIP', 'Add friend fetion sip as fetion friend') do |sip| 50 | options[:friend_sip] = sip 51 | end 52 | 53 | opts.separator "" 54 | opts.separator "different mode:" 55 | 56 | opts.on('--debug', 'debug mode') do 57 | options[:logger_level] = Logger::DEBUG 58 | end 59 | 60 | opts.on('--silence', 'silence mode') do 61 | options[:logger_level] = Logger::ERROR 62 | end 63 | 64 | opts.separator "" 65 | opts.separator "Common options:" 66 | 67 | opts.on_tail('-v', '--version', 'Show this version') do 68 | puts File.read(File.dirname(__FILE__) + '/../../VERSION') 69 | exit 70 | end 71 | 72 | opts.on_tail("-h", "--help", "Show this message") do 73 | puts opts 74 | exit 75 | end 76 | 77 | opts.parse! 78 | end 79 | 80 | begin 81 | if options[:friend_mobile] or options[:friend_sip] 82 | raise FetionException.new('You must input your mobile number or fetion sid, and password') unless (options[:mobile_no] or options[:sid]) and options[:password] 83 | Fetion.add_buddy(options) 84 | exit 85 | end 86 | 87 | raise FetionException.new('You must input your mobile number or fetion sid, password and content') unless (options[:mobile_no] or options[:sid]) and options[:password] and options[:content] 88 | if options[:time] 89 | Fetion.set_schedule_sms(options) 90 | else 91 | Fetion.send_sms(options) 92 | end 93 | rescue FetionException => e 94 | puts e.message 95 | puts "Please use 'rfetion -h' to get more details" 96 | end 97 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. rfetion 2 | 3 | rfetion is a ruby gem for China Mobile fetion service that you can send SMS free. 4 | 5 | I'm still developing according to the Fetion 2010 version which adds the verification code to protect bot. You should download the version before 0.5.0 which does not trigger the verification. 6 | 7 | ************************************************************************** 8 | 9 | h2. Demo 10 | 11 | see "http://fetion.heroku.com":http://fetion.heroku.com 12 | 13 | ************************************************************************** 14 | 15 | h2. Install 16 | 17 |

 18 | gem sources -a http://gemcutter.org
 19 | gem install rfetion
 20 | 
21 | 22 | ************************************************************************** 23 | 24 | h2. Usage 25 | 26 | * send sms or msg to friends or yourself 27 | 28 |

 29 | Fetion.send_sms(options)
 30 | Fetion.send_msg(options)
 31 | 
32 | 33 | options can be: 34 | mobile_no 35 | sid 36 | password 37 | receivers, if receivers are nil, send sms to yourself 38 | content 39 | logger_level 40 | 41 | 42 | * set schedule sms to friends 43 | 44 |

 45 | Fetion.set_schedule_sms(options)
 46 | 
47 | 48 | options can be: 49 | mobile_no 50 | sid 51 | password 52 | receivers, if receivers are nil, send sms to yourself 53 | content 54 | time, time format is %Y-%m-%d %H:%M:%S 55 | logger_level 56 | 57 | 58 | * add friend 59 | 60 |

 61 | Fetion.add_buddy(options)
 62 | 
63 | 64 | options can be: 65 | mobile_no 66 | sid 67 | password 68 | friend_mobile 69 | friend_sip 70 | logger_level 71 | 72 | ************************************************************************** 73 | 74 | h2. Shell command 75 | 76 | you can use it in shell command directly 77 | 78 |

 79 | Usage: rfetion [options]
 80 | 
 81 |     Example: rfetion -m mobile -p password -c sms_content
 82 |              rfetion -s sip -p password -c sms_content
 83 |              rfetion -m mobile -p password -r mobile_or_fetion_numbers -c sms_content
 84 |              rfetion -m mobile -p password -r mobile_or_fetion_numbers -c sms_content -t time
 85 |              rfetion -m mobile -p password --add-buddy-with-mobile friend_mobile
 86 |              rfetion -s sip -p password --add-buddy-with-mobile friend_mobile
 87 |              rfetion -m mobile -p password --add-buddy-with-sip friend_sip
 88 | 
 89 | 
 90 |     -m, --mobile MOBILE              Fetion mobile number
 91 |     -s, --sip FETION_SIP             Fetion sid number
 92 |     -p, --password PASSWORD          Fetion password
 93 |     -c, --content CONTENT            Fetion message content
 94 |     -r, --receivers MOBILE,SIP       Receivers' Fetion mobile numbers or fetion sip numbers, if no recievers, send sms to yourself
 95 |     -t, --time TIME                  Schedule time to send sms, format is "2009-12-10 20:00:00"
 96 |         --add-buddy-with-mobile MOBILE
 97 |                                      Add friend mobile as fetion friend
 98 |         --add-buddy-with-sip SIP     Add friend fetion sip as fetion friend
 99 | 
100 | different mode:
101 |         --debug                      debug mode
102 |         --silence                    silence mode
103 | 
104 | Common options:
105 |     -v, --version                    Show this version
106 |     -h, --help                       Show this message
107 | 
108 | 109 | ****************************************************************************** 110 | 111 | Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license 112 | -------------------------------------------------------------------------------- /lib/rfetion/sipc_message.rb: -------------------------------------------------------------------------------- 1 | require 'guid' 2 | 3 | class SipcMessage 4 | def self.register_first(fetion) 5 | sipc_create(:command => 'R', :F => fetion.sid, :I => 1, :Q => "#{fetion.next_alive} R", :CN => ::Guid.new.hexdigest.upcase, :CL => %Q|type="pc" ,version="#{Fetion::VERSION}"|, :with_l => false) 6 | end 7 | 8 | def self.register_second(fetion) 9 | body = %Q|| 10 | if fetion.pic and !fetion.pic.empty? 11 | verify = %Q|Verify response="#{fetion.pic}",algorithm="#{fetion.algorithm}",type="GeneralPic",chid="#{fetion.pid}"| 12 | sipc_create(:command => 'R', :F => fetion.sid, :I => 1, :Q => "#{fetion.next_alive} R", :A => [%Q|Digest response="#{fetion.response}",algorithm="SHA1-sess-v4"|, verify], :AK => 'ak-value', :body => body) 13 | else 14 | sipc_create(:command => 'R', :F => fetion.sid, :I => 1, :Q => "#{fetion.next_alive} R", :A => %Q|Digest response="#{fetion.response}",algorithm="SHA1-sess-v4"|, :AK => 'ak-value', :body => body) 15 | end 16 | end 17 | 18 | def self.get_group_list(fetion) 19 | body = %Q|| 20 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => '1 S', :N => 'PGGetGroupList', :body => body) 21 | end 22 | 23 | def self.presence(fetion) 24 | body = %Q|| 25 | sipc_create(:command => 'SUB', :F => fetion.sid, :I => fetion.next_call, :Q => '1 SUB', :N => 'PresenceV4', :body => body) 26 | end 27 | 28 | def self.get_group_topic(fetion) 29 | body = %Q|| 30 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => '1 S', :N => 'PGGetGroupTopic', :body => body) 31 | end 32 | 33 | def self.get_address_list(fetion) 34 | body = %Q|| 35 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => '1 S', :N => 'GetAddressListV4', :body => body) 36 | end 37 | 38 | def self.send_cat_sms(fetion, receiver, content) 39 | sipc_create(:command => 'M', :F => fetion.sid, :I => fetion.next_call, :Q => '1 M', :T => receiver, :N => 'SendCatSMS', :body => content) 40 | end 41 | 42 | def self.send_cat_msg(fetion, receiver, content) 43 | sipc_create(:command => 'M', :F => fetion.sid, :I => fetion.next_call, :Q => '2 M', :T => receiver, :K => 'SaveHistory', :N => 'CatMsg', :body => content) 44 | end 45 | 46 | def self.set_schedule_sms(fetion, receivers, content, time) 47 | receivers_str = receivers.collect { |receiver| %Q[] }.join('') 48 | body = %Q|test#{receivers_str}| 49 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => '1 S', :N => 'SSSetScheduleCatSms', :SV => '1', :body => body) 50 | end 51 | 52 | def self.add_buddy(fetion, options) 53 | body = options[:friend_mobile] ? %Q|| : %Q|| 54 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => '1 S', :N => 'AddBuddyV4', :body => body) 55 | end 56 | 57 | def self.get_contact_info(fetion, uri) 58 | body = %Q|| 59 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => '1 S', :N => 'GetContactInfoV4', :body => body) 60 | end 61 | 62 | def self.logout(fetion) 63 | sipc_create(:command => 'R', :F => fetion.sid, :I => 1, :Q => "#{fetion.next_alive} R", :X => 0, :with_l => false) 64 | end 65 | 66 | def self.create_session(fetion, last_sipc_response) 67 | header = {} 68 | last_sipc_response.sections.each do |section| 69 | sipc_header = section.split("\r\n\r\n").first 70 | if sipc_header.split("\r\n").first =~ %r|I #{fetion.sid} SIP-C/4.0| 71 | sipc_header.split("\r\n")[1..-1].each do |line| 72 | key, value = line.split(': ', 2) 73 | if key == 'K' 74 | header['K'] ? header['K'] << value : header['K'] = [value] 75 | else 76 | header[key] = value 77 | end 78 | end 79 | body = %Q|v=0\r\no=-0 0 IN 127.0.0.1:8001\r\ns=session\r\nc=IN IP4 127.0.0.1:8001\r\nt=0 0\r\nm=message 8001 sip sip:#{fetion.uri}\r\n| 80 | return sipc_response_create(:I => header['I'], :Q => header['Q'], :F => header['F'], :K => header['K'], :body => body, :f_first => false) 81 | end 82 | end 83 | end 84 | 85 | def self.session_connected(fetion, last_sipc_response) 86 | header = {} 87 | last_sipc_response.sections.each do |section| 88 | sipc_header = section.split("\r\n\r\n").first 89 | if sipc_header.split("\r\n").first =~ %r|O #{fetion.sid} SIP-C/4.0| 90 | sipc_header.split("\r\n")[1..-1].each do |line| 91 | key, value = line.split(': ', 2) 92 | header[key] = value 93 | end 94 | header['K'] = ['text/html-fragment', 'text/plain'] 95 | return sipc_response_create(:F => header['F'], :I => header['I'], :Q => header['Q'], :K => header['K'], :with_l => false) 96 | end 97 | end 98 | end 99 | 100 | def self.msg_received(fetion, message_response) 101 | header = {} 102 | message_response.split("\r\n\r\n").first.split("\r\n")[1..-1].each do |line| 103 | key, value = line.split(': ', 2) 104 | header[key] = value 105 | end 106 | sipc_response_create(:F => header['F'], :I => header['I'], :Q => header['Q'], :with_l => false) 107 | end 108 | 109 | def self.close_session(fetion, receiver_uri) 110 | sipc_create(:command => 'B', :F => fetion.sid, :I => fetion.next_call, :Q => '2 B', :T => "sip:#{receiver_uri}", :with_l => false) 111 | end 112 | 113 | def self.keep_alive(fetion) 114 | body = %Q|| 115 | sipc_create(:command => 'R', :F => fetion.sid, :I => 1, :Q => "#{fetion.next_alive} R", :N => 'KeepAlive', :body => body) 116 | end 117 | 118 | def self.handle_contact_request(fetion, contact, options) 119 | body = %Q|| 120 | sipc_create(:command => 'S', :F => fetion.sid, :I => fetion.next_call, :Q => "1 S", :N => "HandleContactRequestV4", :body => body) 121 | end 122 | 123 | def self.sipc_response(http_response_body, fetion) 124 | return if http_response_body == Fetion::SIPP 125 | 126 | sections = [] 127 | body = http_response_body 128 | while true 129 | index = body.index(%r{(BN|M|I|O) #{fetion.sid} SIP-C/4.0}, 1) 130 | if index 131 | sections << body[0...index] 132 | body = body[index..-1] 133 | else 134 | sections << body 135 | break 136 | end 137 | end 138 | SipcMessage::Response.new(sections) 139 | rescue NoMethodError 140 | raise FetionException.new("Fetion error: No response to #{sections.first.split("\r\n").first}") 141 | end 142 | 143 | class Response 144 | attr_reader :sections 145 | 146 | def initialize(sections) 147 | @sections = sections 148 | end 149 | 150 | def class 151 | if first_line =~ %r|^SIP-C/4.0 (\d{3})| 152 | RESPONSES[$1.to_i] 153 | else 154 | SipcMessage::OK 155 | end 156 | end 157 | 158 | def first_line 159 | @sections.first.split("\r\n").first 160 | end 161 | 162 | def contain?(command) 163 | @sections.find {|section| section.split("\r\n").first.index(%r|#{command} |) } 164 | end 165 | 166 | def to_s 167 | @sections.first.split("\r\n").first 168 | end 169 | end 170 | 171 | class OK < Response; end 172 | class Send < Response; end 173 | class Bad < Response; end 174 | class Unauthoried < Response; end 175 | class NotFound < Response; end 176 | class ExtentionRequired < Response; end 177 | 178 | RESPONSES = { 179 | 200 => SipcMessage::OK, 180 | 280 => SipcMessage::Send, 181 | 400 => SipcMessage::Bad, 182 | 401 => SipcMessage::Unauthoried, 183 | 404 => SipcMessage::NotFound, 184 | 421 => SipcMessage::ExtentionRequired 185 | } 186 | 187 | private 188 | # command one of 'R', 'S' 189 | # with_l display L or not 190 | # body sipc body 191 | def self.sipc_create(options) 192 | options = {:body => '', :with_l => true}.merge(options) 193 | body = options.delete(:body) 194 | with_l = options.delete(:with_l) 195 | 196 | sorted_key = [:F, :I, :Q, :CN, :CL, :A, :AK, :A, :X, :T, :K, :N, :SV] 197 | sipc = "#{options.delete(:command)} fetion.com.cn SIP-C/4.0\r\n" 198 | sorted_key.each do |k| 199 | if options[k] 200 | if k == :A 201 | value = options[k].is_a?(Array) ? options[k].shift : options.delete(k) 202 | sipc += "#{k}: #{value}\r\n" 203 | else 204 | sipc += "#{k}: #{options[k]}\r\n" 205 | end 206 | end 207 | end 208 | sipc += "L: #{body == '' ? 4 : body.size}\r\n" if with_l 209 | sipc += "\r\n#{body}#{Fetion::SIPP}" 210 | sipc 211 | end 212 | 213 | def self.sipc_response_create(options) 214 | options = {:body => '', :with_l => true, :f_first => true}.merge(options) 215 | body = options.delete(:body) 216 | with_l = options.delete(:with_l) 217 | 218 | sorted_key = options.delete(:f_first) ? [:F, :I, :Q, :K] : [:I, :Q, :F, :K] 219 | sipc = "SIP-C/4.0 200 OK\r\n" 220 | sorted_key.each do |k| 221 | if options[k] 222 | if k == :K 223 | sipc += options[:K].collect { |v| "#{k}: #{v}\r\n" }.join("") 224 | else 225 | sipc += "#{k}: #{options[k]}\r\n" 226 | end 227 | end 228 | end 229 | sipc += "L: #{body == '' ? 4 : body.size}\r\n" if with_l 230 | sipc += "\r\n#{body}#{Fetion::SIPP}" 231 | sipc 232 | end 233 | end 234 | -------------------------------------------------------------------------------- /spec/rfetion/sipc_message_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe SipcMessage do 4 | class Fetion 5 | attr_accessor :mobile_no, :sid, :password, :status_code, :user_status, :uid, :ssic, :nonce, :key, :signature, :response, :call 6 | end 7 | 8 | before :each do 9 | @fetion = Fetion.new 10 | @fetion.sid = "730020377" 11 | @fetion.uid = "390937727" 12 | @fetion.mobile_no = "15800681509" 13 | @fetion.uri = "730020377@fetion.com.cn;p=6907" 14 | @fetion.response = "62E57A276EB9B7AAC233B8983A39941870CE74E3B2CD6480B5CA9DCF37C57DECEA250F261543CB4424EE9E72354C9F33C805EB9839BF96501D0261614E69BDF0DBDF484047750B3113DF8850FEF39428ADC17FE86E8800ED5A77AA7F6630F21AE8A24E6ECC2F003BF3B93E35051A7778D238F86D21581BC829679EBEAD36390F" 15 | end 16 | 17 | it "should get register first" do 18 | guid = hexdigest = "" 19 | Guid.stubs(:new).returns(guid) 20 | guid.stubs(:hexdigest).returns(hexdigest) 21 | hexdigest.stubs(:upcase).returns("19D28D4978125CAA4F6E54277BA7D9EF") 22 | @fetion.alive = 0 23 | sipc_message =<<-EOF 24 | R fetion.com.cn SIP-C/4.0 25 | F: 730020377 26 | I: 1 27 | Q: 1 R 28 | CN: 19D28D4978125CAA4F6E54277BA7D9EF 29 | CL: type="pc" ,version="3.6.2020" 30 | 31 | SIPP 32 | EOF 33 | sipc_message.gsub!("\n", "\r\n").chomp! 34 | SipcMessage.register_first(@fetion).should == sipc_message 35 | end 36 | 37 | it "should get register_second" do 38 | @fetion.alive = 1 39 | sipc_message =<<-EOF 40 | R fetion.com.cn SIP-C/4.0 41 | F: 730020377 42 | I: 1 43 | Q: 2 R 44 | A: Digest response="62E57A276EB9B7AAC233B8983A39941870CE74E3B2CD6480B5CA9DCF37C57DECEA250F261543CB4424EE9E72354C9F33C805EB9839BF96501D0261614E69BDF0DBDF484047750B3113DF8850FEF39428ADC17FE86E8800ED5A77AA7F6630F21AE8A24E6ECC2F003BF3B93E35051A7778D238F86D21581BC829679EBEAD36390F",algorithm="SHA1-sess-v4" 45 | AK: ak-value 46 | L: 447 47 | 48 | SIPP 49 | EOF 50 | sipc_message.gsub!("\n", "\r\n").chomp! 51 | @fetion.machine_code = "B04B5DA2F5F1B8D01A76C0EBC841414C" 52 | SipcMessage.register_second(@fetion).should == sipc_message 53 | end 54 | 55 | it "should get group list" do 56 | @fetion.call = 2 57 | sipc_message =<<-EOF 58 | S fetion.com.cn SIP-C/4.0 59 | F: 730020377 60 | I: 3 61 | Q: 1 S 62 | N: PGGetGroupList 63 | L: 54 64 | 65 | SIPP 66 | EOF 67 | sipc_message.gsub!("\n", "\r\n").chomp! 68 | SipcMessage.get_group_list(@fetion).should == sipc_message 69 | end 70 | 71 | it "should presence" do 72 | @fetion.call = 3 73 | sipc_message =<<-EOF 74 | SUB fetion.com.cn SIP-C/4.0 75 | F: 730020377 76 | I: 4 77 | Q: 1 SUB 78 | N: PresenceV4 79 | L: 87 80 | 81 | SIPP 82 | EOF 83 | sipc_message.gsub!("\n", "\r\n").chomp! 84 | SipcMessage.presence(@fetion).should == sipc_message 85 | end 86 | 87 | it "should get group topic" do 88 | @fetion.call = 4 89 | sipc_message =<<-EOF 90 | S fetion.com.cn SIP-C/4.0 91 | F: 730020377 92 | I: 5 93 | Q: 1 S 94 | N: PGGetGroupTopic 95 | L: 27 96 | 97 | SIPP 98 | EOF 99 | sipc_message.gsub!("\n", "\r\n").chomp! 100 | SipcMessage.get_group_topic(@fetion).should == sipc_message 101 | end 102 | 103 | it "should get address list" do 104 | @fetion.call = 5 105 | sipc_message =<<-EOF 106 | S fetion.com.cn SIP-C/4.0 107 | F: 730020377 108 | I: 6 109 | Q: 1 S 110 | N: GetAddressListV4 111 | L: 37 112 | 113 | SIPP 114 | EOF 115 | sipc_message.gsub!("\n", "\r\n").chomp! 116 | SipcMessage.get_address_list(@fetion).should == sipc_message 117 | end 118 | 119 | it "should send cat sms" do 120 | @fetion.call = 8 121 | sipc_message =<<-EOF 122 | M fetion.com.cn SIP-C/4.0 123 | F: 730020377 124 | I: 9 125 | Q: 1 M 126 | T: sip:730020377@fetion.com.cn;p=6907 127 | N: SendCatSMS 128 | L: 4 129 | 130 | testSIPP 131 | EOF 132 | sipc_message.gsub!("\n", "\r\n").chomp! 133 | SipcMessage.send_cat_sms(@fetion, 'sip:730020377@fetion.com.cn;p=6907', 'test').should == sipc_message 134 | end 135 | 136 | it "should send cat msg" do 137 | @fetion.call = 8 138 | sipc_message =<<-EOF 139 | M fetion.com.cn SIP-C/4.0 140 | F: 730020377 141 | I: 9 142 | Q: 2 M 143 | T: sip:638993408@fetion.com.cn;p=2242 144 | K: SaveHistory 145 | N: CatMsg 146 | L: 4 147 | 148 | testSIPP 149 | EOF 150 | sipc_message.gsub!("\n", "\r\n").chomp! 151 | SipcMessage.send_cat_msg(@fetion, 'sip:638993408@fetion.com.cn;p=2242', 'test').should == sipc_message 152 | end 153 | 154 | it "should set schedule sms" do 155 | @fetion.call = 8 156 | sipc_message =<<-EOF 157 | S fetion.com.cn SIP-C/4.0 158 | F: 730020377 159 | I: 9 160 | Q: 1 S 161 | N: SSSetScheduleCatSms 162 | SV: 1 163 | L: 182 164 | 165 | testSIPP 166 | EOF 167 | sipc_message.gsub!("\n", "\r\n").chomp! 168 | SipcMessage.set_schedule_sms(@fetion, ['sip:638993408@fetion.com.cn;p=2242'], 'test', '2010-05-08 15:50:00').should == sipc_message 169 | end 170 | 171 | it "should get contact info" do 172 | @fetion.call = 10 173 | sipc_message =<<-EOF 174 | S fetion.com.cn SIP-C/4.0 175 | F: 730020377 176 | I: 11 177 | Q: 1 S 178 | N: GetContactInfoV4 179 | L: 46 180 | 181 | SIPP 182 | EOF 183 | sipc_message.gsub!("\n", "\r\n").chomp! 184 | SipcMessage.get_contact_info(@fetion, "tel:15800681507").should == sipc_message 185 | end 186 | 187 | it "should get contact info with sip" do 188 | @fetion.call = 10 189 | sipc_message =<<-EOF 190 | S fetion.com.cn SIP-C/4.0 191 | F: 730020377 192 | I: 11 193 | Q: 1 S 194 | N: GetContactInfoV4 195 | L: 65 196 | 197 | SIPP 198 | EOF 199 | sipc_message.gsub!("\n", "\r\n").chomp! 200 | SipcMessage.get_contact_info(@fetion, "sip:638993408@fetion.com.cn;p=2242").should == sipc_message 201 | end 202 | 203 | it "should add buddy" do 204 | @fetion.call = 9 205 | @fetion.instance_variable_set(:@nickname, 'flyerhzm') 206 | sipc_message =<<-EOF 207 | S fetion.com.cn SIP-C/4.0 208 | F: 730020377 209 | I: 10 210 | Q: 1 S 211 | N: AddBuddyV4 212 | L: 175 213 | 214 | SIPP 215 | EOF 216 | sipc_message.gsub!("\n", "\r\n").chomp! 217 | SipcMessage.add_buddy(@fetion, :friend_mobile => "13634102006").should == sipc_message 218 | 219 | @fetion.call = 9 220 | sipc_message =<<-EOF 221 | S fetion.com.cn SIP-C/4.0 222 | F: 730020377 223 | I: 10 224 | Q: 1 S 225 | N: AddBuddyV4 226 | L: 136 227 | 228 | SIPP 229 | EOF 230 | sipc_message.gsub!("\n", "\r\n").chomp! 231 | SipcMessage.add_buddy(@fetion, :friend_sip => "638993408").should == sipc_message 232 | end 233 | 234 | it "should create session" do 235 | http_response_body =<<-EOF 236 | I 730020377 SIP-C/4.0 237 | F: sip:638993408@fetion.com.cn;p=2242 238 | I: -13 239 | K: text/plain 240 | K: text/html-fragment 241 | K: multiparty 242 | K: nudge 243 | Q: 14 I 244 | L: 21 245 | 246 | s=session 247 | m=message SIPP 248 | EOF 249 | http_response_body.gsub!("\n", "\r\n").chomp! 250 | last_sipc_response = SipcMessage.sipc_response(http_response_body, @fetion) 251 | sipc_message =<<-EOF 252 | SIP-C/4.0 200 OK 253 | I: -13 254 | Q: 14 I 255 | F: sip:638993408@fetion.com.cn;p=2242 256 | K: text/plain 257 | K: text/html-fragment 258 | K: multiparty 259 | K: nudge 260 | L: 129 261 | 262 | v=0 263 | o=-0 0 IN 127.0.0.1:8001 264 | s=session 265 | c=IN IP4 127.0.0.1:8001 266 | t=0 0 267 | m=message 8001 sip sip:730020377@fetion.com.cn;p=6907 268 | SIPP 269 | EOF 270 | sipc_message.gsub!("\n", "\r\n").chomp! 271 | SipcMessage.create_session(@fetion, last_sipc_response).should == sipc_message 272 | end 273 | 274 | it "shoud session connected" do 275 | http_response_body =<<-EOF 276 | O 730020377 SIP-C/4.0 277 | I: -13 278 | Q: 2 O 279 | K: text/plain 280 | K: text/html-fragment 281 | K: multiparty 282 | K: nudge 283 | F: sip:638993408@fetion.com.cn;p=2242 284 | 285 | A 730020377 SIP-C/4.0 286 | F: sip:638993408@fetion.com.cn;p=2242 287 | I: -13 288 | Q: 14 A 289 | 290 | SIPP 291 | EOF 292 | http_response_body.gsub!("\n", "\r\n").chomp! 293 | last_sipc_response = SipcMessage.sipc_response(http_response_body, @fetion) 294 | sipc_message =<<-EOF 295 | SIP-C/4.0 200 OK 296 | F: sip:638993408@fetion.com.cn;p=2242 297 | I: -13 298 | Q: 2 O 299 | K: text/html-fragment 300 | K: text/plain 301 | 302 | SIPP 303 | EOF 304 | sipc_message.gsub!("\n", "\r\n").chomp! 305 | SipcMessage.session_connected(@fetion, last_sipc_response).should == sipc_message 306 | end 307 | 308 | it "should msg_received" do 309 | http_response_body =<<-EOF 310 | M 730020377 SIP-C/4.0 311 | I: -13 312 | Q: 4 M 313 | F: sip:638993408@fetion.com.cn;p=2242 314 | C: text/html-fragment 315 | K: SaveHistory 316 | L: 4 317 | D: Sun, 16 May 2010 02:16:00 GMT 318 | XI: 0dbdc4e81bff425dbcf8b591b497fe94 319 | 320 | testSIPP 321 | EOF 322 | http_response_body.gsub!("\n", "\r\n").chomp! 323 | sipc_message =<<-EOF 324 | SIP-C/4.0 200 OK 325 | F: sip:638993408@fetion.com.cn;p=2242 326 | I: -13 327 | Q: 4 M 328 | 329 | SIPP 330 | EOF 331 | sipc_message.gsub!("\n", "\r\n").chomp! 332 | SipcMessage.msg_received(@fetion, http_response_body).should == sipc_message 333 | end 334 | 335 | it "should keep alive" do 336 | @fetion.alive = 3 337 | sipc_message =<<-EOF 338 | R fetion.com.cn SIP-C/4.0 339 | F: 730020377 340 | I: 1 341 | Q: 4 R 342 | N: KeepAlive 343 | L: 97 344 | 345 | SIPP 346 | EOF 347 | sipc_message.gsub!("\n", "\r\n").chomp! 348 | SipcMessage.keep_alive(@fetion).should == sipc_message 349 | end 350 | 351 | it "should close session" do 352 | @fetion.call = 5 353 | sipc_message =<<-EOF 354 | B fetion.com.cn SIP-C/4.0 355 | F: 730020377 356 | I: 6 357 | Q: 2 B 358 | T: sip:638993408@fetion.com.cn;p=2242 359 | 360 | SIPP 361 | EOF 362 | sipc_message.gsub!("\n", "\r\n").chomp! 363 | SipcMessage.close_session(@fetion, "638993408@fetion.com.cn;p=2242").should == sipc_message 364 | end 365 | 366 | it "should logout" do 367 | @fetion.alive = 2 368 | sipc_message =<<-EOF 369 | R fetion.com.cn SIP-C/4.0 370 | F: 730020377 371 | I: 1 372 | Q: 3 R 373 | X: 0 374 | 375 | SIPP 376 | EOF 377 | sipc_message.gsub!("\n", "\r\n").chomp! 378 | SipcMessage.logout(@fetion).should == sipc_message 379 | end 380 | 381 | it "should handle contact request" do 382 | @fetion.call = 8 383 | sipc_message =<<-EOF 384 | S fetion.com.cn SIP-C/4.0 385 | F: 730020377 386 | I: 9 387 | Q: 1 S 388 | N: HandleContactRequestV4 389 | L: 186 390 | 391 | SIPP 392 | EOF 393 | sipc_message.gsub!("\n", "\r\n").chomp! 394 | contact = Fetion::Contact.new 395 | contact.uid = '295098062' 396 | contact.uri = 'sip:638993408@fetion.com.cn;p=2242' 397 | SipcMessage.handle_contact_request(@fetion, contact, :result => "1").should == sipc_message 398 | end 399 | end 400 | -------------------------------------------------------------------------------- /lib/rfetion/fetion.rb: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | require 'guid' 3 | require 'time' 4 | require 'net/http' 5 | require 'net/https' 6 | require 'nokogiri' 7 | require 'digest/sha1' 8 | require 'digest/md5' 9 | require 'macaddr' 10 | require 'openssl' 11 | require 'logger' 12 | require 'json' 13 | 14 | class Fetion 15 | attr_accessor :mobile_no, :sid, :uid, :password, :call, :seq, :alive, :response, :ssic, :guid, :uri, :pid, :pic, :algorithm, :machine_code 16 | attr_reader :status_code, :user_status, :buddy_lists, :add_requests, :nickname, :impresa, :receives 17 | 18 | FETION_URL = 'http://221.176.31.39/ht/sd.aspx' 19 | 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%' 20 | FETION_PIC_URL = 'http://nav.fetion.com.cn/nav/GetPicCodeV4.aspx?algorithm=%algorithm' 21 | 22 | SIPP = 'SIPP' 23 | USER_AGENT = "IIC2.0/PC 3.6.2020" 24 | VERSION = "3.6.2020" 25 | DOMAIN = "fetion.com.cn" 26 | 27 | def initialize 28 | @call = @alive = @seq = 0 29 | @buddy_lists = [Fetion::BuddyList.new("0", "未分组")] 30 | @contacts = [] 31 | @receives = [] 32 | @add_requests = [] 33 | @logger = Logger.new(STDOUT) 34 | @logger.level = Logger::INFO 35 | @guid = Guid.new.to_s 36 | end 37 | 38 | def to_json(*args) 39 | {:mobile_no => @mobile_no, :sid => @sid, :uid => @uid, :uri => @uri, :status_code => @status_code, :buddy_lists => @buddy_lists, :receives => @receives, :add_requests => @add_requests, :nickname => @nickname, :impresa => @impresa, :ssic => @ssic, :guid => @guid, :call => @call, :seq => @seq, :alive => @alive}.to_json(*args) 40 | end 41 | 42 | def logger_level=(level) 43 | @logger.level = level 44 | end 45 | 46 | def Fetion.open(options, &block) 47 | fetion = Fetion.new 48 | fetion.logger_level = options.delete(:logger_level) || Logger::INFO 49 | fetion.mobile_no = options.delete(:mobile_no) 50 | fetion.sid = options.delete(:sid) 51 | fetion.password = options.delete(:password) 52 | fetion.login 53 | fetion.register 54 | 55 | fetion.instance_eval &block 56 | 57 | fetion.logout 58 | end 59 | 60 | def Fetion.keep_alive(options) 61 | Fetion.open(options) do 62 | get_contacts 63 | pulse 64 | sleep(15) 65 | pulse 66 | p receives 67 | end 68 | end 69 | 70 | # options 71 | # mobile_no 72 | # sid 73 | # password 74 | # receivers 75 | # content 76 | # logger_level 77 | def Fetion.send_sms(options) 78 | Fetion.open(options) do 79 | receivers = options.delete(:receivers) 80 | content = options.delete(:content) 81 | if receivers 82 | receivers = Array(receivers) 83 | receivers.collect! {|receiver| receiver.to_s} 84 | get_contacts 85 | contacts.flatten.each do |contact| 86 | if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) } 87 | send_sms(contact.uri, content) 88 | end 89 | end 90 | send_sms(uri, content) if receivers.any? { |receiver| self? receiver } 91 | else 92 | send_sms(uri, content) 93 | end 94 | end 95 | end 96 | 97 | # options 98 | # mobile_no 99 | # sid 100 | # password 101 | # receivers 102 | # content 103 | # logger_level 104 | def Fetion.send_msg(options) 105 | Fetion.open(options) do 106 | receivers = options.delete(:receivers) 107 | content = options.delete(:content) 108 | if receivers 109 | receivers = Array(receivers) 110 | receivers.collect! {|receiver| receiver.to_s} 111 | get_contacts 112 | contacts.flatten.each do |contact| 113 | if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) } 114 | send_msg(contact.uri, content) 115 | end 116 | end 117 | send_msg(uri, content) if receivers.any? { |receiver| self? receiver } 118 | else 119 | send_msg(uri, content) 120 | end 121 | end 122 | end 123 | 124 | # options 125 | # mobile_no 126 | # sid 127 | # password 128 | # receivers 129 | # content 130 | # time 131 | # logger_level 132 | def Fetion.set_schedule_sms(options) 133 | Fetion.open(options) do 134 | receivers = options.delete(:receivers) 135 | content = options.delete(:content) 136 | time = options.delete(:time) 137 | get_contacts 138 | if receivers 139 | receivers = Array(receivers) 140 | receivers.collect! {|receiver| receiver.to_s} 141 | new_receivers = contacts.flatten.collect do |contact| 142 | if receivers.include? contact.mobile_no.to_s or receivers.any? { |receiver| contact.uri.index(receiver) } 143 | contact.uri 144 | end 145 | end.compact! 146 | new_receivers << uri if receivers.any? { |receiver| self? receiver } 147 | set_schedule_sms(new_receivers, content, time) 148 | else 149 | set_schedule_sms([fetion.uri], content, time) 150 | end 151 | end 152 | end 153 | 154 | # options 155 | # mobile_no 156 | # sid 157 | # password 158 | # friend_mobile 159 | # friend_sip 160 | # logger_level 161 | def Fetion.add_buddy(options) 162 | Fetion.open(options) do 163 | add_buddy(options) 164 | end 165 | end 166 | 167 | def login 168 | @logger.info "fetion login" 169 | 170 | if @mobile_no 171 | url = FETION_LOGIN_URL.sub('%mobileno%', @mobile_no).sub('sid=%sid%', '') 172 | else 173 | url = FETION_LOGIN_URL.sub('%sid%', @sid).sub('mobileno=%mobileno%', '') 174 | end 175 | url.sub!('%digest%', Digest::SHA1.hexdigest("#{DOMAIN}:#{@password}")) 176 | url += "&pid=#@pid&pic=#@pic&algorithm=#@algorithm" if @pic and !@pic.empty? 177 | uri = URI.parse(url) 178 | http = Net::HTTP.new(uri.host, uri.port) 179 | http.use_ssl = true 180 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 181 | headers = {'User-Agent' => USER_AGENT} 182 | response = http.request_get(uri.request_uri, headers) 183 | parse_ssic(response) 184 | 185 | @logger.info "fetion login success" 186 | end 187 | 188 | def register 189 | @logger.info "fetion register" 190 | 191 | call = next_call 192 | register_first 193 | register_second 194 | call = next_call 195 | 196 | @logger.info "fetion register success" 197 | end 198 | 199 | def register_first 200 | @logger.debug "fetion register first" 201 | 202 | curl_exec(SIPP, next_url('i')) 203 | curl_exec(SipcMessage.register_first(self)) 204 | pulse(SipcMessage::Unauthoried) 205 | 206 | @logger.debug "fetion register first success" 207 | end 208 | 209 | def register_second 210 | @logger.debug "fetion register second" 211 | 212 | #url = @pic ? "#{next_url}&pid=#@pid&pic=#@pic&algorithm=#@algorithm" : next_url 213 | curl_exec(SipcMessage.register_second(self)) 214 | pulse 215 | 216 | @logger.debug "fetion register second success" 217 | end 218 | 219 | def get_contacts 220 | @logger.info "fetion get contacts" 221 | 222 | curl_exec(SipcMessage.get_group_list(self)) 223 | curl_exec(SipcMessage.presence(self)) 224 | curl_exec(SipcMessage.get_group_topic(self)) 225 | curl_exec(SipcMessage.get_address_list(self)) 226 | pulse 227 | 228 | @logger.info "fetion get contacts success" 229 | end 230 | 231 | def send_msg(receiver, content) 232 | @logger.info "fetion send cat msg to #{receiver}" 233 | 234 | curl_exec(SipcMessage.send_cat_msg(self, receiver, content)) 235 | pulse 236 | 237 | @logger.info "fetion send cat msg to #{receiver} success" 238 | end 239 | 240 | def send_sms(receiver, content) 241 | @logger.info "fetion send cat sms to #{receiver}" 242 | 243 | curl_exec(SipcMessage.send_cat_sms(self, receiver, content)) 244 | pulse(SipcMessage::Send) 245 | 246 | @logger.info "fetion send cat sms to #{receiver} success" 247 | end 248 | 249 | def set_schedule_sms(receivers, content, time) 250 | receivers = Array(receivers) 251 | time = time.is_a?(Time) ? time : Time.parse(time) 252 | now = Time.now 253 | one_year = Time.local(now.year + 1, now.month, now.day, now.hour, now.min, now.sec) 254 | raise SetScheduleSmsException.new("Can't schedule send sms to more than 64 receivers") if receivers.size > 64 255 | 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 256 | @logger.info "fetion schedule send sms to #{receivers.join(', ')}" 257 | 258 | curl_exec(SipcMessage.set_schedule_sms(self, receivers, content, time.strftime('%Y-%m-%d %H:%M:%S'))) 259 | pulse 260 | 261 | @logger.info "fetion schedule send sms to #{receivers.join(', ')} success" 262 | end 263 | 264 | # options 265 | # friend_mobile 266 | # friend_sip 267 | def add_buddy(options) 268 | uri = options[:friend_mobile] ? "tel:#{options[:friend_mobile]}" : "sip:#{options[:friend_sip]}" 269 | @logger.info "fetion send request to add #{uri} as friend" 270 | 271 | curl_exec(SipcMessage.add_buddy(self, options)) 272 | pulse 273 | 274 | @logger.info "fetion send request to add #{uri} as friend success" 275 | end 276 | 277 | # options 278 | # mobile_no 279 | # sip 280 | def get_contact_info(options) 281 | uri = if options[:mobile_no] 282 | "tel:#{options[:mobile_no]}" 283 | elsif options[:sip] 284 | "sip:#{options[:sip]}" 285 | else 286 | "uri:#{options[:uri]}" 287 | end 288 | @logger.info "fetion get contact info of #{uri}" 289 | 290 | curl_exec(SipcMessage.get_contact_info(self, uri)) 291 | sipc_response = pulse 292 | results = sipc_response.sections.first.scan(%r{.*?}).first 293 | doc = Nokogiri::XML(results) 294 | c = doc.root.xpath("/results/contact").first 295 | contact = Contact.parse_request(c) 296 | 297 | @logger.info "fetion get contact info of #{uri} success" 298 | contact 299 | end 300 | 301 | def keep_alive 302 | @logger.info "fetion keep alive" 303 | 304 | curl_exec(SipcMessage.keep_alive(self)) 305 | 306 | @logger.info "fetion keep alive success" 307 | end 308 | 309 | def pulse(expected=SipcMessage::OK) 310 | @logger.info "fetion pulse" 311 | 312 | sipc_response = curl_exec(SIPP, next_url, expected) 313 | 314 | @logger.info "fetion pulse success" 315 | sipc_response 316 | end 317 | 318 | def logout 319 | @logger.info "fetion logout" 320 | 321 | curl_exec(SipcMessage.logout(self)) 322 | pulse 323 | 324 | @logger.info "fetion logout success" 325 | end 326 | 327 | def create_session(sipc_response) 328 | @logger.info "fetion create session" 329 | 330 | sipc_response = curl_exec(SipcMessage.create_session(self, sipc_response)) 331 | pulse unless sipc_response 332 | 333 | @logger.info "fetion create session success" 334 | end 335 | 336 | def session_connected(sipc_response) 337 | @logger.info "fetion session connected" 338 | 339 | curl_exec(SipcMessage.session_connected(self, sipc_response)) 340 | 341 | @logger.info "fetion session connected success" 342 | end 343 | 344 | def msg_received(message_response) 345 | @logger.info "fetion msg received" 346 | 347 | curl_exec(SipcMessage.msg_received(self, message_response)) 348 | 349 | @logger.info "fetion msg received success" 350 | end 351 | 352 | def close_session 353 | @logger.info "fetion close session" 354 | 355 | curl_exec(SipcMessage.close_session(self)) 356 | 357 | @logger.info "fetion close session success" 358 | end 359 | 360 | def handle_contact_request(uid, options) 361 | @logger.info "fetion handle contact request" 362 | 363 | contact = @add_requests.find {|contact| contact.uid == uid} 364 | curl_exec(SipcMessage.handle_contact_request(self, contact, options)) 365 | pulse 366 | 367 | @logger.info "fetion handle contact request success" 368 | end 369 | 370 | def get_pic_certificate(algorithm) 371 | @logger.info "fetion get pic" 372 | 373 | uri = URI.parse(FETION_PIC_URL.sub('%algorithm', algorithm)) 374 | http = Net::HTTP.new(uri.host, uri.port) 375 | headers = {'User-Agent' => USER_AGENT} 376 | response = http.request_get(uri.request_uri, headers) 377 | pic_certificate = parse_pic_certificate(response, algorithm) 378 | 379 | @logger.info "fetion get pic success" 380 | pic_certificate 381 | end 382 | 383 | def parse_ssic(response) 384 | raise Fetion::PasswordError.new('帐号或密码不正确') if Net::HTTPUnauthorized === response 385 | raise Fetion::PasswordMaxError.new('您已连续输入错误密码,为了保障您的帐户安全,请输入图形验证码:') if Net::HTTPClientError === response 386 | raise Fetion::LoginException.new('Login failed.') unless Net::HTTPSuccess === response 387 | 388 | @logger.debug response.body 389 | doc = Nokogiri::XML(response.body) 390 | results = doc.root 391 | @status_code = results["status-code"] 392 | user = results.children.first 393 | @user_status = user['user-status'] 394 | @uri = user['uri'] 395 | @mobile_no = user['mobile-no'] 396 | @uid = user['user-id'] 397 | if @uri =~ /sip:(\d+)@(.+);/ 398 | @sid = $1 399 | end 400 | doc.root.xpath("/results/user/credentials/credential[@domain='fetion.com.cn']").each do |credential| 401 | @ssic = credential['c'] 402 | end 403 | 404 | @logger.debug "ssic: " + @ssic 405 | @logger.debug "status_code: " + @status_code 406 | @logger.debug "user_status: " + @user_status 407 | @logger.debug "uri: " + @uri 408 | @logger.debug "mobile_no: " + @mobile_no 409 | @logger.debug "uid: " + @uid 410 | @logger.debug "sid: " + @sid 411 | end 412 | 413 | def parse_pic_certificate(response, algorithm) 414 | raise FetionException.new('Get verification code failed.') unless Net::HTTPSuccess === response 415 | doc = Nokogiri::XML(response.body) 416 | certificate = doc.root.xpath('/results/pic-certificate').first 417 | PicCertificate.parse(certificate, algorithm) 418 | end 419 | 420 | def curl_exec(body='', url=next_url, expected=SipcMessage::OK) 421 | @logger.debug "fetion curl exec" 422 | @logger.debug "url: #{url}" 423 | @logger.debug "body: #{body}" 424 | 425 | uri = URI.parse(url) 426 | http = Net::HTTP.new(uri.host, uri.port) 427 | headers = {'Content-Type' => 'application/oct-stream', 'Pragma' => "xz4BBcV#{@guid}", 'User-Agent' => USER_AGENT, 'Cookie' => "ssic=#{@ssic}", 'Content-Length' => body.length.to_s} 428 | response = http.request_post(uri.request_uri, body, headers) 429 | 430 | @logger.debug "response: #{response.inspect}" 431 | @logger.debug "response body: #{response.body}" 432 | @logger.debug "fetion curl exec complete" 433 | 434 | raise FetionException.new("request_url: #{url}, request_body: #{body}, response: #{response.code}, response_body: #{response.body}") unless Net::HTTPSuccess === response 435 | sipc_response = SipcMessage.sipc_response(response.body, self) 436 | 437 | if sipc_response 438 | raise Fetion::ChangeMachineError.new('飞信发现您本次变更了登录地点。为保证您的帐号安全,需要您输入验证码,这可以防止恶意程序的自动登录。') if sipc_response.first_line =~ /421/ 439 | raise Fetion::SipcException.new(sipc_response, "request_url: #{url}, request_body: #{body}, sipc_response: #{sipc_response}") unless sipc_response.class == expected 440 | 441 | if sipc_response.first_line =~ /401/ 442 | # unauthorized, get nonce, key and signature 443 | raise Fetion::NoNonceException.new("No nonce found") unless response.body =~ /nonce="(.*?)",key="(.*?)",signature="(.*?)"/ 444 | @nonce = $1 445 | @key = $2 446 | @signature = $3 447 | @response = calc_response 448 | 449 | @logger.debug "nonce: #{@nonce}" 450 | @logger.debug "key: #{@key}" 451 | @logger.debug "signature: #{@signature}" 452 | @logger.debug "response: #{@response}" 453 | end 454 | create_session(sipc_response) if sipc_response.contain?('I') 455 | session_connected(sipc_response) if sipc_response.contain?('O') 456 | if sipc_response.contain?('M') 457 | receive_messages = sipc_response.sections.select {|section| section =~ /^M/} 458 | receive_messages.each do |message_response| 459 | message_header, message_content = message_response.split(/\r\n\r\n/) 460 | sip = sent_at = length = nil 461 | message_header.split(/\r\n/).each do |line| 462 | case line 463 | when /^F: sip:(.+)/ then sip = $1 464 | when /^D: (.+)/ then sent_at = Time.parse($1) 465 | when /^L: (\d+)/ then length = $1.to_i 466 | end 467 | end 468 | text = message_content.slice(0, length) 469 | @receives << Fetion::Message.new(sip, sent_at, text) 470 | msg_received(message_response) 471 | end 472 | end 473 | 474 | response.body.scan(%r{.*?}).each do |results| 475 | doc = Nokogiri::XML(results) 476 | doc.root.xpath("/results/user-info/personal").each do |personal_element| 477 | @nickname = personal_element['nickname'] 478 | @impresa = personal_element['impresa'] 479 | end 480 | doc.root.xpath("/results/user-info/contact-list/buddy-lists/buddy-list").each do |buddy_list| 481 | @buddy_lists << Fetion::BuddyList.parse(buddy_list) 482 | end 483 | doc.root.xpath("/results/user-info/contact-list/buddies/b").each do |buddy| 484 | contact = Fetion::Contact.parse_buddy(buddy) 485 | @buddy_lists.find {|buddy_list| buddy_list.bid == contact.bid}.add_contact(contact) 486 | end 487 | end 488 | 489 | response.body.scan(%r{.*?}).each do |events| 490 | doc = Nokogiri::XML(events) 491 | doc.root.xpath("/events/event[@type='PresenceChanged']/contacts/c").each do |c| 492 | unless self.uid == c['id'] 493 | contact = contacts.find {|contact| contact.uid == c['id']} 494 | if contact 495 | contact.update(c.children.first, c.children.last) 496 | else 497 | contact = Fetion::Contact.parse(c) 498 | if @buddy_lists.size > 1 499 | @buddy_lists.find {|buddy_list| buddy_list.bid == contact.bid}.add_contact(contact) 500 | else 501 | @contacts << contact 502 | end 503 | end 504 | end 505 | end 506 | doc.root.xpath("/events/event[@type='AddBuddyApplication']/application").each do |application| 507 | @add_requests << get_contact_info(:uri => application['uri']) 508 | end 509 | end 510 | end 511 | sipc_response 512 | end 513 | 514 | def next_url(t = 's') 515 | FETION_URL + "?t=#{t}&i=#{next_seq}" 516 | end 517 | 518 | def next_call 519 | @call += 1 520 | end 521 | 522 | def next_seq 523 | @seq += 1 524 | end 525 | 526 | def next_alive 527 | @alive += 1 528 | end 529 | 530 | def calc_response 531 | encrypted_password = Digest::SHA1.hexdigest([@uid.to_i].pack("V*") + [Digest::SHA1.hexdigest("#{DOMAIN}:#{@password}")].pack("H*")) 532 | rsa_result = "4A026855890197CFDF768597D07200B346F3D676411C6F87368B5C2276DCEDD2" 533 | str = @nonce + [encrypted_password].pack("H*") + [rsa_result].pack("H*") 534 | rsa_key = OpenSSL::PKey::RSA.new 535 | exponent = OpenSSL::BN.new @key[-6..-1].hex.to_s 536 | modulus = OpenSSL::BN.new @key[0...-6].hex.to_s 537 | rsa_key.e = exponent 538 | rsa_key.n = modulus 539 | 540 | rsa_key.public_encrypt(str).unpack("H*").first.upcase 541 | end 542 | 543 | def machine_code 544 | @machine_code ||= Digest::MD5.hexdigest(Mac.addr) 545 | end 546 | 547 | def self?(mobile_or_sid) 548 | mobile_or_sid == @mobile_no or mobile_or_sid == @sid 549 | end 550 | 551 | def contacts 552 | if @buddy_lists.size > 1 553 | @buddy_lists.collect {|buddy_list| buddy_list.contacts}.flatten 554 | else 555 | @contacts 556 | end 557 | end 558 | end 559 | 560 | class FetionException < Exception; end 561 | class Fetion::LoginException < FetionException; end 562 | class Fetion::PasswordError < Fetion::LoginException; end 563 | class Fetion::PasswordMaxError < Fetion::LoginException; end 564 | class Fetion::ChangeMachineError < FetionException; end 565 | class Fetion::NoNonceException < FetionException; end 566 | class Fetion::RegisterException < FetionException; end 567 | class Fetion::SendSmsException < FetionException; end 568 | class Fetion::SendMsgException < FetionException; end 569 | class Fetion::SetScheduleSmsException < FetionException; end 570 | class Fetion::AddBuddyException < FetionException; end 571 | class Fetion::GetContactsException < FetionException; end 572 | class Fetion::NoUserException < FetionException; end 573 | class Fetion::SipcException < FetionException 574 | attr_reader :first_line, :message 575 | 576 | def initialize(sipc_response, message) 577 | @first_line = sipc_response.first_line 578 | @message = message 579 | end 580 | end 581 | -------------------------------------------------------------------------------- /spec/rfetion/fetion_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper.rb' 2 | 3 | describe Fetion do 4 | before :each do 5 | @fetion = Fetion.new 6 | end 7 | 8 | after :each do 9 | FakeWeb.clean_registry 10 | end 11 | 12 | describe "to_json" do 13 | before :each do 14 | @fetion.mobile_no = '15800681509' 15 | @fetion.sid = "730020377" 16 | @fetion.password = "password" 17 | @fetion.uid = "390937727" 18 | @fetion.instance_variable_set(:@status_code, "200") 19 | @fetion.instance_variable_set(:@user_status, "101") 20 | @fetion.instance_variable_set(:@nickname, "flyerhzm") 21 | @fetion.instance_variable_set(:@impresa, "http://www.fetionrobot.com") 22 | @buddy_list0 = Fetion::BuddyList.new("0", "未分组") 23 | @buddy_list1 = Fetion::BuddyList.new("1", "我的好友") 24 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "226911221", :uri => "sip:572512981@fetion.com.cn;p=3544", :bid => "1")) 25 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "295098062", :uri => "sip:638993408@fetion.com.cn;p=2242", :bid => "1")) 26 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "579113578", :uri => "sip:838271744@fetion.com.cn;p=4805", :bid => "1")) 27 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "665046562", :uri => "sip:926157269@fetion.com.cn;p=12906", :bid => "1")) 28 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "687455743", :uri => "sip:881033150@fetion.com.cn;p=5493", :bid => "1")) 29 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "714355089", :uri => "sip:973921799@fetion.com.cn;p=12193", :bid => "1")) 30 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "732743291", :uri => "sip:480867781@fetion.com.cn;p=16105", :bid => "1")) 31 | @buddy_list2 = Fetion::BuddyList.new("2", "好友") 32 | @buddy_list3 = Fetion::BuddyList.new("3", "同学") 33 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "222516658", :uri => "sip:793401629@fetion.com.cn;p=1919", :bid => "3")) 34 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "227091544", :uri => "sip:669700695@fetion.com.cn;p=3546", :nickname => "郭庆", :bid => "3")) 35 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "228358286", :uri => "sip:660250260@fetion.com.cn;p=3854", :nickname => "蔡智武", :bid => "3")) 36 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "229415466", :uri => "sip:737769829@fetion.com.cn;p=4078", :nickname => "ice", :bid => "3")) 37 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "296436724", :uri => "sip:760087520@fetion.com.cn;p=2467", :bid => "3")) 38 | @buddy_lists = [@buddy_list0, @buddy_list1, @buddy_list2, @buddy_list3] 39 | @fetion.instance_variable_set(:@buddy_lists, @buddy_lists) 40 | end 41 | 42 | it "should get all user attributes" do 43 | fetion_json = @fetion.to_json 44 | fetion_json.should be_include %Q|"mobile_no":"15800681509"| 45 | fetion_json.should be_include %Q|"sid":"730020377"| 46 | fetion_json.should be_include %Q|"uid":"390937727"| 47 | fetion_json.should be_include %Q|"status_code":"200"| 48 | fetion_json.should be_include %Q|"nickname":"flyerhzm"| 49 | fetion_json.should be_include %Q|"impresa":"http://www.fetionrobot.com"| 50 | end 51 | end 52 | 53 | describe "login" do 54 | it "should login by mobile no" do 55 | @fetion.mobile_no = '15800681509' 56 | @fetion.password = 'password' 57 | FakeWeb.register_uri(:get, 'https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?mobileno=15800681509&domains=fetion.com.cn;m161.com.cn;www.ikuwa.cn&v4digest-type=1&v4digest=79cd56b93f21298dc8ae9d26de1258e3d6ce85a7', :body => %Q||, :set_cookie => %Q|ssic=DhIOAADVEY68pV4EcRHsJ/GIIeltaYJsYJR2pj7b2+hCYLtgUd2j2mFaOqoqR98S3dm5pPH9t7W1yH5Cp/lVRP6VTwpLVvwxhhvj8qDz/p8rrW/Ljor6P4ZQKUZYz80JHjMt8R4AAA==; path=/|) 58 | @fetion.login 59 | 60 | @fetion.status_code.should == "200" 61 | @fetion.user_status.should == "101" 62 | @fetion.mobile_no.should == "15800681509" 63 | @fetion.uid.should == "390937727" 64 | @fetion.sid.should == "730020377" 65 | @fetion.ssic.should == "CBIOAAAm+FiuQgpcnFi+B4PZgtvTLcLwrzk84mf5XsP9hnneRVyMvEFuPpvTyfV2FFZfhJrCoiLYptvuSd9M95fwTUj4jRE6NuiE43EPl220u/chMyebsSbsUDxSjuJh1hXV76sAAA==" 66 | end 67 | 68 | it "should login failed with wrong password" do 69 | @fetion.mobile_no = '15800681509' 70 | @fetion.password = 'password' 71 | FakeWeb.register_uri(:get, 'https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?mobileno=15800681509&domains=fetion.com.cn;m161.com.cn;www.ikuwa.cn&v4digest-type=1&v4digest=79cd56b93f21298dc8ae9d26de1258e3d6ce85a7', :body => %Q||, :status => ['401', 'password error']) 72 | lambda { @fetion.login }.should raise_exception(Fetion::PasswordError) 73 | end 74 | 75 | it "should get verification code when password error max" do 76 | @fetion.mobile_no = '15800681509' 77 | @fetion.password = 'password' 78 | FakeWeb.register_uri(:get, 'https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?mobileno=15800681509&domains=fetion.com.cn;m161.com.cn;www.ikuwa.cn&v4digest-type=1&v4digest=79cd56b93f21298dc8ae9d26de1258e3d6ce85a7', :body => %Q||, :status => ['421']) 79 | lambda { @fetion.login }.should raise_exception(Fetion::PasswordMaxError) 80 | end 81 | 82 | it "should login by sid" do 83 | @fetion.sid = "730020377" 84 | @fetion.password = 'password' 85 | FakeWeb.register_uri(:get, 'https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?sid=730020377&domains=fetion.com.cn;m161.com.cn;www.ikuwa.cn&v4digest-type=1&v4digest=79cd56b93f21298dc8ae9d26de1258e3d6ce85a7', :body => %Q||, :set_cookie => %Q|ssic=DhIOAADVEY68pV4EcRHsJ/GIIeltaYJsYJR2pj7b2+hCYLtgUd2j2mFaOqoqR98S3dm5pPH9t7W1yH5Cp/lVRP6VTwpLVvwxhhvj8qDz/p8rrW/Ljor6P4ZQKUZYz80JHjMt8R4AAA==; path=/|) 86 | @fetion.login 87 | 88 | @fetion.status_code.should == "200" 89 | @fetion.user_status.should == "101" 90 | @fetion.mobile_no.should == "15800681509" 91 | @fetion.uid.should == "390937727" 92 | @fetion.sid.should == "730020377" 93 | @fetion.ssic.should == "CBIOAAAm+FiuQgpcnFi+B4PZgtvTLcLwrzk84mf5XsP9hnneRVyMvEFuPpvTyfV2FFZfhJrCoiLYptvuSd9M95fwTUj4jRE6NuiE43EPl220u/chMyebsSbsUDxSjuJh1hXV76sAAA==" 94 | end 95 | end 96 | 97 | describe "register" do 98 | before :each do 99 | @fetion.instance_variable_set(:@uid, "390937727") 100 | @fetion.instance_variable_set(:@password, "password") 101 | end 102 | 103 | describe "register first" do 104 | it "should get nonce, key and signature" do 105 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=i&i=1", :body => "SIPP") 106 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=2", :body => "SIPP") 107 | response_body =< response_body) 118 | @fetion.register_first 119 | 120 | @fetion.instance_variable_get(:@nonce).should == "1104E253661D71141DFE3FB020143E5A" 121 | @fetion.instance_variable_get(:@key).should == "A355B99E9EA38B7306331739A8EC57586FD4E8EC6C6D295C5EED3B6C3A84CB79889E6BED455ACBEDF68270C3FB23C9E54F0626118A09F06845E79248B4F3164E623F84722D5F8B2DFA75AD9454B7E169FB23D5F626C136CBABC6C2D910FDF56917FAFD73990013332CD87795C04799B5E75E2E6BC756D473FC39BD70BEC64D0D010001" 122 | @fetion.instance_variable_get(:@signature).should == "8039306257522D5DA4D5BCD0D6B04730A35E1225E9A5C37FD13804B8DAB40F356EA159A6FB2812C74CB5BB33D8764BF77EB10057E177CD2BD83DBBFD36FD30E652BA963B687DABC2E9FD994FADED19286D12C70065CA255528CBAE5D9B4CC087717ED32631FAFB9A2666C2A7356226A27C48E85C3E3580EB7671EA035FE320E0" 123 | @fetion.response.size.should == 256 124 | end 125 | 126 | it "should raise no nonce exception" do 127 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=i&i=1", :body => "SIPP") 128 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=2", :body => "SIPP") 129 | response_body =< response_body) 140 | lambda { @fetion.register_first }.should raise_exception(Fetion::NoNonceException) 141 | end 142 | end 143 | 144 | describe "register second" do 145 | before :each do 146 | @fetion.mobile_no = "15800681509" 147 | @fetion.uid = "390937727" 148 | @fetion.response = "458F72ED91E149D28D8467772AB7AD366527B55AC1A10CD18BA1B9BD95F2E082B1594B6C9B116E0BDECC315A2ABA0F4DD20591BF305FCDCDA4CA7B6434EA7788B893E0BB26E4E02097B6707BE0BD60E704D560DDDCB539A3E6FD49B985631FCA02C44D09A6713358BF1D323BA62B5273C7096B97D6A75C6BF9708768FF0113D0" 149 | @fetion.instance_variable_set(:@seq, 3) 150 | end 151 | 152 | it "should get buddies" do 153 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=4", :body => "SIPP") 154 | response_body =<-10CN.sh.21.0H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/Ih6fVMvLvG6ytqiWr/O2LZYXR49fz6ur51mbN+0XedNkF3lz9Cwrm/zx3cg33PpNsaDPssXqlKBdf9FcHL2p19q+/93j43VbvcpX5fXpMpuU+cyA733+WLt5U73+4rVpFXzmYJ0tZ/m7ox0Pinzy+G50kF9U1ezlvM6avEnvOjAFhmRB8F/Pq+UFuhKkHt81f/PIaTxn52/y8nVbrXwqBZ8/Plk3bbX4bp6187w2KBCq0c8fPy3qfNpSF6/yZl22Bmz3Y4Wa1y/rvMmXU8Y88tkXWbH8brGcVVe2h8/rar16cv1FNcuBhv8n9d6sykz+2EWv7k8e8ZfLsljmRNM2m7b0x7U/7P6XQqWqzp/mbVaUjccZ/qc0rX00nxeX+VerGTEcmOhomb9rifzhh49f58umcsQ7vsquv1yezcpce/I+8L78oliuiY+P7vsNzIfa7vW0zvPl64yYJ4Dlf/74y/NzDFl7UFKEH4Ztvl2t6+Zor9NIPn38ZN1QB8/WZSmdaL+9j4mA4bDfFKsO8WQqXlRtcX6NSQ7+fvyi8v/+7jxfog/tbuDLxy/yKxU+BaPNux8T9OCDHvj4t4/v9kfx7PTspfnjq+XbZXX1+K7/2f8DdlG53MIEAAA=BN 730020377 SIP-C/4.0 162 | N: SyncUserInfoV4 163 | I: 1 164 | Q: 1 BN 165 | L: 125 166 | 167 | SIPP 168 | EOF 169 | response_body.gsub!("\n", "\r\n") 170 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=5", :body => response_body) 171 | @fetion.register_second 172 | 173 | @fetion.nickname.should == "flyerhzm" 174 | @fetion.impresa.should == "http://www.fetionrobot.com" 175 | @fetion.buddy_lists.collect {|buddy_list| buddy_list.name}.should == ['未分组', '我的好友', '好友', '同学'] 176 | @fetion.buddy_lists[1].contacts.collect {|contact| contact.uid}.should == ['226911221', '295098062', '579113578', '665046562', '687455743', '714355089', '732743291'] 177 | @fetion.buddy_lists.last.contacts.collect {|contact| contact.uid}.should == ['222516658', '227091544', '228358286', '229415466', '296436724'] 178 | end 179 | 180 | it "should get 421 extension required" do 181 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=4", :body => "SIPP") 182 | response_body =<SIPP 190 | EOF 191 | response_body.gsub!("\n", "\r\n") 192 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=5", :body => response_body) 193 | lambda { @fetion.register_second }.should raise_exception(Fetion::ChangeMachineError) 194 | end 195 | end 196 | end 197 | 198 | describe "get contacts" do 199 | before :each do 200 | @fetion.instance_variable_set(:@seq, 5) 201 | @fetion.instance_variable_set(:@sid, "730020377") 202 | @fetion.instance_variable_set(:@uid, "390937727") 203 | @buddy_list0 = Fetion::BuddyList.new("0", "未分组") 204 | @buddy_list1 = Fetion::BuddyList.new("1", "我的好友") 205 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "226911221", :uri => "sip:572512981@fetion.com.cn;p=3544", :bid => "1")) 206 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "295098062", :uri => "sip:638993408@fetion.com.cn;p=2242", :bid => "1")) 207 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "579113578", :uri => "sip:838271744@fetion.com.cn;p=4805", :bid => "1")) 208 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "665046562", :uri => "sip:926157269@fetion.com.cn;p=12906", :bid => "1")) 209 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "687455743", :uri => "sip:881033150@fetion.com.cn;p=5493", :bid => "1")) 210 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "714355089", :uri => "sip:973921799@fetion.com.cn;p=12193", :bid => "1")) 211 | @buddy_list1.add_contact(Fetion::Contact.new(:uid => "732743291", :uri => "sip:480867781@fetion.com.cn;p=16105", :bid => "1")) 212 | @buddy_list2 = Fetion::BuddyList.new("2", "好友") 213 | @buddy_list3 = Fetion::BuddyList.new("3", "同学") 214 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "222516658", :uri => "sip:793401629@fetion.com.cn;p=1919", :bid => "3")) 215 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "227091544", :uri => "sip:669700695@fetion.com.cn;p=3546", :nickname => "郭庆", :bid => "3")) 216 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "228358286", :uri => "sip:660250260@fetion.com.cn;p=3854", :nickname => "蔡智武", :bid => "3")) 217 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "229415466", :uri => "sip:737769829@fetion.com.cn;p=4078", :nickname => "ice", :bid => "3")) 218 | @buddy_list3.add_contact(Fetion::Contact.new(:uid => "296436724", :uri => "sip:760087520@fetion.com.cn;p=2467", :bid => "3")) 219 | @buddy_lists = [@buddy_list0, @buddy_list1, @buddy_list2, @buddy_list3] 220 | @fetion.instance_variable_set(:@buddy_lists, @buddy_lists) 221 | end 222 | 223 | it "should get all contacts" do 224 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=6", :body => "SIPP") 225 | response_body =<<-EOF 226 | SIP-C/4.0 200 OK 227 | I: 3 228 | Q: 1 S 229 | L: 59 230 | 231 | SIPP 232 | EOF 233 | response_body.gsub!("\n", "\r\n") 234 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=7", :body => "SIPP") 235 | response_body =<<-EOF 236 | SIP-C/4.0 200 OK 237 | I: 4 238 | Q: 1 SUB 239 | 240 | BN 730020377 SIP-C/4.0 241 | N: PresenceV4 242 | I: 1 243 | L: 322 244 | Q: 2 BN 245 | 246 |

BN 730020377 SIP-C/4.0 247 | N: PresenceV4 248 | I: 1 249 | L: 329 250 | Q: 3 BN 251 | 252 |

BN 730020377 SIP-C/4.0 253 | N: PresenceV4 254 | I: 1 255 | L: 303 256 | Q: 4 BN 257 | 258 |

BN 730020377 SIP-C/4.0 259 | N: PresenceV4 260 | I: 1 261 | L: 643 262 | Q: 5 BN 263 | 264 |

BN 730020377 SIP-C/4.0 265 | N: PresenceV4 266 | I: 1 267 | L: 338 268 | Q: 6 BN 269 | 270 |

BN 730020377 SIP-C/4.0 271 | N: PresenceV4 272 | I: 1 273 | L: 291 274 | Q: 7 BN 275 | 276 |

BN 730020377 SIP-C/4.0 277 | N: PresenceV4 278 | I: 1 279 | L: 369 280 | Q: 8 BN 281 | 282 |

BN 730020377 SIP-C/4.0 283 | N: PresenceV4 284 | I: 1 285 | L: 300 286 | Q: 9 BN 287 | 288 |

BN 730020377 SIP-C/4.0 289 | N: SystemNotifyV4 290 | L: 84 291 | I: 1 292 | Q: 10 BN 293 | 294 | SIPP 295 | EOF 296 | response_body.gsub!("\n", "\r\n") 297 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=8", :body => response_body) 298 | response_body =<<-EOF 299 | SIP-C/4.0 200 OK 300 | I: 5 301 | Q: 1 S 302 | L: 835 303 | 304 | SIPP 305 | EOF 306 | response_body.gsub!("\n", "\r\n") 307 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=9", :body => response_body) 308 | response_body =<<-EOF 309 | SIP-C/4.0 200 OK 310 | I: 6 311 | Q: 1 S 312 | L: 61 313 | 314 | SIPP 315 | EOF 316 | response_body.gsub!("\n", "\r\n") 317 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=10", :body => response_body) 318 | @fetion.get_contacts 319 | @fetion.contacts.collect {|contact| contact.sid}.should == ["572512981", "638993408", nil, "926157269", nil, nil, "480867781", "793401629", "669700695", "660250260", "737769829", "760087520"] 320 | @fetion.contacts.collect {|contact| contact.status}.should == ["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "400", "0"] 321 | end 322 | 323 | it "should get received msg while get contacts" do 324 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=6", :body => "SIPP") 325 | response_body =<<-EOF 326 | SIP-C/4.0 200 OK 327 | I: 3 328 | Q: 1 S 329 | L: 59 330 | 331 | SIPP 332 | EOF 333 | response_body.gsub!("\n", "\r\n") 334 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=7", :body => "SIPP") 335 | response_body =<<-EOF 336 | SIP-C/4.0 200 OK 337 | I: 4 338 | Q: 1 SUB 339 | 340 | M 730020377 SIP-C/4.0 341 | F: sip:480867781@fetion.com.cn;p=16105 342 | I: -1 343 | C: text/plain 344 | Q: 2 M 345 | D: Mon, 10 May 2010 14:26:17 GMT 346 | L: 12 347 | 348 | testtesttestBN 730020377 SIP-C/4.0 349 | N: SystemNotifyV4 350 | L: 84 351 | I: 1 352 | Q: 3 BN 353 | 354 | BN 730020377 SIP-C/4.0 355 | N: PresenceV4 356 | I: 1 357 | L: 322 358 | Q: 4 BN 359 | 360 |

BN 730020377 SIP-C/4.0 361 | N: PresenceV4 362 | I: 1 363 | L: 329 364 | Q: 5 BN 365 | 366 |

BN 730020377 SIP-C/4.0 367 | N: PresenceV4 368 | I: 1 369 | L: 303 370 | Q: 6 BN 371 | 372 |

BN 730020377 SIP-C/4.0 373 | N: PresenceV4 374 | I: 1 375 | L: 643 376 | Q: 7 BN 377 | 378 |

BN 730020377 SIP-C/4.0 379 | N: PresenceV4 380 | I: 1 381 | L: 338 382 | Q: 8 BN 383 | 384 |

BN 730020377 SIP-C/4.0 385 | N: PresenceV4 386 | I: 1 387 | L: 291 388 | Q: 9 BN 389 | 390 |

BN 730020377 SIP-C/4.0 391 | N: PresenceV4 392 | I: 1 393 | L: 369 394 | Q: 10 BN 395 | 396 |

BN 730020377 SIP-C/4.0 397 | N: PresenceV4 398 | I: 1 399 | L: 300 400 | Q: 11 BN 401 | 402 |

SIPP 403 | EOF 404 | response_body.gsub!("\n", "\r\n") 405 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=8", :body => response_body) 406 | response_body =<<-EOF 407 | SIP-C/4.0 200 OK 408 | I: 5 409 | Q: 1 S 410 | L: 835 411 | 412 | SIPP 413 | EOF 414 | response_body.gsub!("\n", "\r\n") 415 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=9", :body => response_body) 416 | response_body =<<-EOF 417 | SIP-C/4.0 200 OK 418 | I: 6 419 | Q: 1 S 420 | L: 61 421 | 422 | SIPP 423 | EOF 424 | response_body.gsub!("\n", "\r\n") 425 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=10", :body => response_body) 426 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => "SIPP") 427 | @fetion.get_contacts 428 | @fetion.contacts.collect {|c| c.sid}.should == ["572512981", "638993408", nil, "926157269", nil, nil, "480867781", "793401629", "669700695", "660250260", "737769829", "760087520"] 429 | @fetion.receives.collect {|r| r.sip}.should == ["480867781@fetion.com.cn;p=16105"] 430 | @fetion.receives.collect {|r| r.sent_at}.should == [Time.parse("Mon, 10 May 2010 14:26:17 GMT")] 431 | @fetion.receives.collect {|r| r.text}.should == ["testtesttest"] 432 | end 433 | end 434 | 435 | describe "send msg" do 436 | before :each do 437 | @fetion.instance_variable_set(:@seq, 10) 438 | end 439 | 440 | it "should send msg to receiver" do 441 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => 'SIPP') 442 | response_body =<<-EOF 443 | SIP-C/4.0 200 OK 444 | I: 7 445 | Q: 3 M 446 | D: Sat, 08 May 2010 14:51:55 GMT 447 | XI: 925d6e3837b7410f9187ae66853e9a25 448 | T: sip:638993408@fetion.com.cn;p=2242 449 | 450 | SIPP 451 | EOF 452 | response_body.gsub!("\n", "\r\n") 453 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => response_body) 454 | @fetion.send_msg('sip:638993408@fetion.com.cn;p=2242', 'test') 455 | end 456 | end 457 | 458 | describe "send sms" do 459 | before :each do 460 | @fetion.instance_variable_set(:@seq, 10) 461 | end 462 | 463 | it "should send sms to receiver" do 464 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => 'SIPP') 465 | response_body =<<-EOF 466 | SIP-C/4.0 280 Send SMS OK 467 | T: sip:730020377@fetion.com.cn;p=6907 468 | I: 9 469 | Q: 1 M 470 | 471 | SIPP 472 | EOF 473 | response_body.gsub!("\n", "\r\n") 474 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => response_body) 475 | @fetion.send_sms('sip:638993408@fetion.com.cn;p=2242', 'test') 476 | end 477 | end 478 | 479 | describe "set schedule sms" do 480 | before :each do 481 | @fetion.instance_variable_set(:@seq, 10) 482 | end 483 | 484 | it "should set schedule sms to receiver" do 485 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => 'SIPP') 486 | response_body =<<-EOF 487 | SIP-C/4.0 200 OK 488 | I: 11 489 | Q: 1 S 490 | L: 92 491 | 492 | SIPP 493 | EOF 494 | response_body.gsub!("\n", "\r\n") 495 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => response_body) 496 | @fetion.set_schedule_sms('sip:638993408@fetion.com.cn;p=2242', 'test', Time.at(Time.now + 24*60*60)) 497 | end 498 | end 499 | 500 | describe "add buddy" do 501 | before :each do 502 | @fetion.instance_variable_set(:@seq, 11) 503 | @fetion.instance_variable_set(:@nickname, 'flyerhzm') 504 | end 505 | 506 | it "should add buddy" do 507 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => 'SIPP') 508 | response_body =<<-EOF 509 | SIP-C/4.0 200 OK 510 | I: 16 511 | Q: 1 S 512 | L: 320 513 | 514 | BN 730020377 SIP-C/4.0 515 | N: SyncUserInfoV4 516 | I: 1 517 | Q: 17 BN 518 | L: 248 519 | 520 | BN 730020377 SIP-C/4.0 521 | N: PresenceV4 522 | I: 1 523 | L: 329 524 | Q: 18 BN 525 | 526 |

SIPP 527 | EOF 528 | response_body.gsub!("\n", "\r\n") 529 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => response_body) 530 | @fetion.add_buddy(:friend_mobile => '13634102006') 531 | end 532 | end 533 | 534 | describe "get contact info" do 535 | before :each do 536 | @fetion.instance_variable_set(:@seq, 11) 537 | end 538 | 539 | it "should get contact info" do 540 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => 'SIPP') 541 | response_body =<<-EOF 542 | SIP-C/4.0 200 OK 543 | I: 11 544 | Q: 1 S 545 | L: 166 546 | 547 | SIPP 548 | EOF 549 | response_body.gsub!("\n", "\r\n") 550 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => response_body) 551 | @fetion.get_contact_info(:friend_mobile => '15800681507') 552 | end 553 | 554 | it "should get exception when no such user" do 555 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => 'SIPP') 556 | response_body =<<-EOF 557 | SIP-C/4.0 404 Not Found 558 | I: 35 559 | Q: 1 S 560 | 561 | SIPP 562 | EOF 563 | response_body.gsub!("\n", "\r\n") 564 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => response_body) 565 | lambda {@fetion.get_contact_info(:friend_mobile => '15800681505')}.should raise_exception(Fetion::SipcException) 566 | end 567 | end 568 | 569 | describe "logout" do 570 | before :each do 571 | @fetion.instance_variable_set(:@seq, 12) 572 | end 573 | 574 | it "should logout" do 575 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => "SIPP") 576 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=14", :body => "SIPP") 577 | end 578 | end 579 | 580 | describe "keep alive" do 581 | before :each do 582 | @fetion.instance_variable_set(:@seq, 10) 583 | @fetion.instance_variable_set(:@alive, 1) 584 | @fetion.instance_variable_set(:@sid, "730020377") 585 | end 586 | 587 | it "should get credential" do 588 | response_body =<<-EOF 589 | SIP-C/4.0 200 OK 590 | I: 1 591 | Q: 3 R 592 | X: 600 593 | L: 1143 594 | 595 | SIPP 596 | EOF 597 | response_body.gsub!("\n", "\r\n") 598 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 599 | @fetion.keep_alive 600 | end 601 | end 602 | 603 | describe "pulse" do 604 | before :each do 605 | @fetion.instance_variable_set(:@seq, 10) 606 | @fetion.instance_variable_set(:@sid, "730020377") 607 | contact = Fetion::Contact.new(:uid => '295098062') 608 | @fetion.instance_variable_set(:@contacts, [contact]) 609 | end 610 | 611 | it "should get presence with online" do 612 | buddy_list = Fetion::BuddyList.new(1, 'friends') 613 | contact = Fetion::Contact.new(:uid => "295098062") 614 | buddy_list.add_contact(contact) 615 | @fetion.instance_variable_set(:@buddy_lists, [buddy_list]) 616 | response_body =<<-EOF 617 | BN 730020377 SIP-C/4.0 618 | N: PresenceV4 619 | I: 1 620 | L: 154 621 | Q: 11 BN 622 | 623 | SIPP 624 | EOF 625 | response_body.gsub!("\n", "\r\n") 626 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 627 | @fetion.pulse 628 | @fetion.contacts.find {|contact| contact.uid == '295098062'}.status.should == "400" 629 | end 630 | 631 | 632 | it "should get receive msg for first session" do 633 | response_body =<<-EOF 634 | I 730020377 SIP-C/4.0 635 | F: sip:638993408@fetion.com.cn;p=2242 636 | I: -13 637 | K: text/plain 638 | K: text/html-fragment 639 | K: multiparty 640 | K: nudge 641 | Q: 14 I 642 | L: 21 643 | 644 | s=session 645 | m=message SIPP 646 | EOF 647 | response_body.gsub!("\n", "\r\n") 648 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 649 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => "SIPP") 650 | response_body =<<-EOF 651 | O 730020377 SIP-C/4.0 652 | I: -13 653 | Q: 2 O 654 | K: text/plain 655 | K: text/html-fragment 656 | K: multiparty 657 | K: nudge 658 | F: sip:638993408@fetion.com.cn;p=2242 659 | 660 | A 730020377 SIP-C/4.0 661 | F: sip:638993408@fetion.com.cn;p=2242 662 | I: -13 663 | Q: 14 A 664 | 665 | SIPP 666 | EOF 667 | response_body.gsub!("\n", "\r\n") 668 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => response_body) 669 | response_body =<<-EOF 670 | M 730020377 SIP-C/4.0 671 | I: -13 672 | Q: 4 M 673 | F: sip:638993408@fetion.com.cn;p=2242 674 | C: text/html-fragment 675 | K: SaveHistory 676 | L: 4 677 | D: Sun, 16 May 2010 02:16:00 GMT 678 | XI: 0dbdc4e81bff425dbcf8b591b497fe94 679 | 680 | testSIPP 681 | EOF 682 | response_body.gsub!("\n", "\r\n") 683 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=14", :body => response_body) 684 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=15", :body => "SIPP") 685 | @fetion.pulse 686 | @fetion.receives.collect {|r| r.sip}.should == ["638993408@fetion.com.cn;p=2242"] 687 | @fetion.receives.collect {|r| r.sent_at}.should == [Time.parse("Sun, 16 May 2010 02:16:00 GMT")] 688 | @fetion.receives.collect {|r| r.text}.should == ["test"] 689 | end 690 | 691 | it "should get receive msg without pulse for first session" do 692 | response_body =<<-EOF 693 | I 730020377 SIP-C/4.0 694 | F: sip:638993408@fetion.com.cn;p=2242 695 | I: -13 696 | K: text/plain 697 | K: text/html-fragment 698 | K: multiparty 699 | K: nudge 700 | Q: 14 I 701 | L: 21 702 | 703 | s=session 704 | m=message SIPP 705 | EOF 706 | response_body.gsub!("\n", "\r\n") 707 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 708 | response_body =<<-EOF 709 | O 730020377 SIP-C/4.0 710 | I: -13 711 | Q: 2 O 712 | K: text/plain 713 | K: text/html-fragment 714 | K: multiparty 715 | K: nudge 716 | F: sip:638993408@fetion.com.cn;p=2242 717 | 718 | A 730020377 SIP-C/4.0 719 | F: sip:638993408@fetion.com.cn;p=2242 720 | I: -13 721 | Q: 14 A 722 | 723 | SIPP 724 | EOF 725 | response_body.gsub!("\n", "\r\n") 726 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => response_body) 727 | response_body =<<-EOF 728 | M 730020377 SIP-C/4.0 729 | I: -13 730 | Q: 4 M 731 | F: sip:638993408@fetion.com.cn;p=2242 732 | C: text/html-fragment 733 | K: SaveHistory 734 | L: 4 735 | D: Sun, 16 May 2010 02:16:00 GMT 736 | XI: 0dbdc4e81bff425dbcf8b591b497fe94 737 | 738 | testSIPP 739 | EOF 740 | response_body.gsub!("\n", "\r\n") 741 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => response_body) 742 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=14", :body => "SIPP") 743 | @fetion.pulse 744 | @fetion.receives.collect {|r| r.sip}.should == ["638993408@fetion.com.cn;p=2242"] 745 | @fetion.receives.collect {|r| r.sent_at}.should == [Time.parse("Sun, 16 May 2010 02:16:00 GMT")] 746 | @fetion.receives.collect {|r| r.text}.should == ["test"] 747 | end 748 | 749 | it "should get receive msg" do 750 | response_body =<<-EOF 751 | M 730020377 SIP-C/4.0 752 | I: -17 753 | Q: 4 M 754 | F: sip:638993408@fetion.com.cn;p=2242 755 | C: text/html-fragment 756 | K: SaveHistory 757 | L: 12 758 | D: Tue, 11 May 2010 15:18:56 GMT 759 | XI: 7eb8bc4e9df742b2aa557f9e85c8d8af 760 | 761 | testtesttestSIPP 762 | EOF 763 | response_body.gsub!("\n", "\r\n") 764 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 765 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => "SIPP") 766 | @fetion.pulse 767 | @fetion.receives.collect {|r| r.sip}.should == ["638993408@fetion.com.cn;p=2242"] 768 | @fetion.receives.collect {|r| r.sent_at}.should == [Time.parse("Tue, 11 May 2010 15:18:56 GMT")] 769 | @fetion.receives.collect {|r| r.text}.should == ["testtesttest"] 770 | end 771 | 772 | it "should get multiple receive msgs" do 773 | response_body =<<-EOF 774 | M 730020377 SIP-C/4.0 775 | I: -11 776 | Q: 4 M 777 | F: sip:638993408@fetion.com.cn;p=2242 778 | C: text/html-fragment 779 | K: SaveHistory 780 | L: 8 781 | D: Sat, 22 May 2010 15:17:26 GMT 782 | XI: b1344eca984c418ba4b72a6fed5011e5 783 | 784 | testtestM 730020377 SIP-C/4.0 785 | I: -11 786 | Q: 5 M 787 | F: sip:638993408@fetion.com.cn;p=2242 788 | C: text/html-fragment 789 | K: SaveHistory 790 | L: 4 791 | D: Sat, 22 May 2010 15:17:26 GMT 792 | XI: 9b9eba984eb4468babf70f5dcceca39d 793 | 794 | testSIPP 795 | EOF 796 | response_body.gsub!("\n", "\r\n") 797 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 798 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => "SIPP") 799 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => "SIPP") 800 | @fetion.pulse 801 | @fetion.receives.collect {|r| r.sip}.should == ["638993408@fetion.com.cn;p=2242", "638993408@fetion.com.cn;p=2242"] 802 | @fetion.receives.collect {|r| r.sent_at}.should == [Time.parse("Sat, 22 May 2010 15:17:26 GMT"), Time.parse("Sat, 22 May 2010 15:17:26 GMT")] 803 | @fetion.receives.collect {|r| r.text}.should == ["testtest", "test"] 804 | end 805 | 806 | it "should get add buddy message" do 807 | response_body =<<-EOF 808 | BN 480867781 SIP-C/4.0 809 | N: contact 810 | I: 1 811 | Q: 5 BN 812 | L: 207 813 | 814 | SIPP 815 | EOF 816 | response_body.gsub!("\n", "\r\n") 817 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => response_body) 818 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => "SIPP") 819 | response_body =<<-EOF 820 | SIP-C/4.0 200 OK 821 | I: 7 822 | Q: 1 S 823 | L: 368 824 | 825 | EOF 826 | EOF 827 | response_body.gsub!("\n", "\r\n") 828 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=13", :body => response_body) 829 | @fetion.pulse 830 | end 831 | 832 | it "should handle contact request" do 833 | contact = Fetion::Contact.new(:uid => '295098062', :uri => 'sip:638993408@fetion.com.cn;p=2242') 834 | @fetion.instance_variable_set(:@add_requests, [contact]) 835 | buddy_list = Fetion::BuddyList.new("1", "friends") 836 | @fetion.instance_variable_set(:@buddy_lists, [buddy_list]) 837 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=11", :body => "SIPP") 838 | response_body =<<-EOF 839 | SIP-C/4.0 200 OK 840 | I: 9 841 | Q: 1 S 842 | L: 349 843 | 844 | BN 480867781 SIP-C/4.0 845 | N: PresenceV4 846 | I: 1 847 | L: 329 848 | Q: 6 BN 849 | 850 |

SIPP 851 | EOF 852 | FakeWeb.register_uri(:post, "http://221.176.31.39/ht/sd.aspx?t=s&i=12", :body => response_body) 853 | @fetion.handle_contact_request('295098062', :result => "1") 854 | end 855 | end 856 | 857 | describe "pic" do 858 | it "should get pic when password error max" do 859 | response_body =<<-EOF 860 | 861 | EOF 862 | FakeWeb.register_uri(:get, "http://nav.fetion.com.cn/nav/GetPicCodeV4.aspx?algorithm=picc-PasswordErrorMax", :body => response_body) 863 | actual_pic = @fetion.get_pic_certificate("picc-PasswordErrorMax") 864 | expected_pic = PicCertificate.parse(Nokogiri::XML(response_body).root.xpath('/results/pic-certificate').first, 'picc-PasswordErrorMax') 865 | actual_pic.pid.should == expected_pic.pid 866 | actual_pic.pic.should == expected_pic.pic 867 | actual_pic.algorithm.should == expected_pic.algorithm 868 | end 869 | end 870 | end 871 | --------------------------------------------------------------------------------