├── Rakefile
├── lib
├── china_sms
│ ├── version.rb
│ └── service
│ │ ├── chanyoo.rb
│ │ ├── tui3.rb
│ │ ├── luosimao.rb
│ │ ├── emay.rb
│ │ ├── smsbao.rb
│ │ ├── yunpian.rb
│ │ └── sendcloud.rb
└── china_sms.rb
├── Gemfile
├── .travis.yml
├── spec
├── spec_helper.rb
├── service
│ ├── emay_spec.rb
│ ├── luosimao_spec.rb
│ ├── chanyoo_spec.rb
│ ├── sendcloud_spec.rb
│ ├── tui3_spec.rb
│ ├── smsbao_spec.rb
│ └── yunpian_spec.rb
└── china_sms_spec.rb
├── bin
└── console
├── .gitignore
├── china_sms.gemspec
├── LICENSE.txt
└── README.md
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
--------------------------------------------------------------------------------
/lib/china_sms/version.rb:
--------------------------------------------------------------------------------
1 | module ChinaSMS
2 | VERSION = "0.0.7"
3 | end
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in china_sms.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | rvm:
4 | - 1.9.3
5 | - 2.0.0
6 |
7 | script:
8 | - bundle exec rspec spec
9 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'rubygems'
3 | require 'bundler/setup'
4 |
5 | require 'webmock/rspec'
6 | require 'china_sms'
7 | require 'rspec/its'
8 |
9 | #WebMock.allow_net_connect!
10 | RSpec.configure do |config|
11 | end
12 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "china_sms"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | require 'irb'
10 |
11 | IRB.start
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 |
19 | *.swo
20 | *.swp
21 |
22 | # Ignore IDE
23 | .idea
24 |
25 | # Ignore bundler config
26 | .bundle
27 |
28 | #mac os
29 | .DS_Store
30 | ._.DS_Store
31 |
32 | .project
33 |
--------------------------------------------------------------------------------
/lib/china_sms/service/chanyoo.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module ChinaSMS
3 | module Service
4 | module Chanyoo
5 | extend self
6 |
7 | URL = "http://api.chanyoo.cn/utf8/interface/send_sms.aspx"
8 |
9 | def to(phone, content, options)
10 | phones = Array(phone).join(',')
11 | res = Net::HTTP.post_form(URI.parse(URL), username: options[:username], password: options[:password], receiver: phones, content: content)
12 | result res.body
13 | end
14 |
15 | def result(body)
16 | code = body.match(/.+result>(.+)\<\/result/)[1]
17 | message = body.match(/.+message>(.+)\<\/message/)[1]
18 | {
19 | success: (code.to_i >= 0),
20 | code: code,
21 | message: message.force_encoding("UTF-8")
22 | }
23 | end
24 |
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/china_sms/service/tui3.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'json'
3 |
4 | module ChinaSMS
5 | module Service
6 | module Tui3 # http://tui3.com/
7 | extend self
8 |
9 | URL = "http://tui3.com/api/send/"
10 |
11 | def to(phone, content, options)
12 | phones = Array(phone).join(',')
13 | res = Net::HTTP.post_form(URI.parse(URL), k: options[:password], t: phones, c: content, p: 1, r: 'json')
14 | body = res.body
15 | body = "{\"err_code\": 2, \"err_msg\":\"非法apikey:#{options[:password]}\"}" if body == 'invalid parameters'
16 | result JSON[body]
17 | end
18 |
19 | def result(body)
20 | {
21 | success: body['err_code'] == 0,
22 | code: body['err_code'],
23 | message: body['err_msg']
24 | }
25 | end
26 |
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/china_sms/service/luosimao.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | module ChinaSMS
4 | module Service
5 | module Luosimao
6 | extend self
7 |
8 | URL = "https://sms-api.luosimao.com/v1/send.json"
9 |
10 | def to(phone, content, options)
11 | url = URI.parse(URL)
12 | post = Net::HTTP::Post.new(url.path)
13 | post.basic_auth(options[:username], options[:password])
14 | post.set_form_data({mobile: phone, message: content})
15 |
16 | socket = Net::HTTP.new(url.host, url.port)
17 | socket.use_ssl = true
18 | response = socket.start {|http| http.request(post) }
19 | result JSON.parse(response.body)
20 | end
21 |
22 | def result(body)
23 | {
24 | success: body['error'] == 0,
25 | code: body['error'],
26 | message: body['msg']
27 | }
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/service/emay_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Emay" do
5 |
6 | describe "#to" do
7 | let(:username) { 'YOUR_CDKEY' }
8 | let(:password) { 'YOUR_PASSWORD' }
9 | let(:url) { "http://sdkhttp.eucp.b2m.cn/sdkproxy/sendsms.action" }
10 | let(:content) { '测试内容' }
11 | subject { ChinaSMS::Service::Emay.to phone, content, username: username, password: password }
12 |
13 | describe 'single phone' do
14 | let(:phone) { '13552241639' }
15 |
16 | before do
17 | stub_request(:post, url).
18 | with(body: {cdkey: username, password: password, phone: phone, message: content}).
19 | to_return(body: '0')
20 | end
21 |
22 | its([:success]) { should eql true }
23 | its([:code]) { should eql '0' }
24 | end
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/spec/service/luosimao_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Luosimao" do
5 |
6 | describe "luosimao#to" do
7 | let(:username) { 'api' }
8 | let(:password) { 'password' }
9 | let(:url) { "https://sms-api.luosimao.com/v1/send.json" }
10 | let(:content) { '【畅友短信测试】深圳 Rubyist 活动时间变更到周六下午 3:00,请留意。【19屋】' }
11 | subject { ChinaSMS::Service::Luosimao.to phone, content, username: username, password: password }
12 |
13 | describe 'single phone' do
14 | let(:phone) { '13928935535' }
15 |
16 | before do
17 | stub_request(:post, "https://#{username}:#{password}@sms-api.luosimao.com/v1/send.json").
18 | with(:body => {"message"=> content, "mobile"=> phone}).to_return(body: '{"error":0,"msg":"ok"}')
19 | end
20 |
21 | its([:success]) { should eql true }
22 | its([:code]) { should eql 0 }
23 | its([:message]) { should eql "ok" }
24 | end
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/lib/china_sms/service/emay.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module ChinaSMS
3 | module Service
4 | module Emay
5 | extend self
6 | URL = "http://sdkhttp.eucp.b2m.cn/sdkproxy"
7 |
8 | def to(phone, content, options)
9 | phones = Array(phone).join(',')
10 | res = Net::HTTP.post_form(URI.parse("#{URL}/sendsms.action"), cdkey: options[:username], password: options[:password], phone: phones, message: content)
11 | result res.body
12 | end
13 |
14 | def get(options)
15 | # res = Net::HTTP.post_form(URI.parse(GET_URL), cdkey: options[:username], password: options[:password])
16 | url = "#{URL}/getmo.action?cdkey=#{options[:username]}&password=#{options[:password]}"
17 | res = Net::HTTP.get(URI.parse(url))
18 | res.body
19 | end
20 |
21 | def result(body)
22 | code = body.match(/.+error>(.+)\<\/error/)[1]
23 | {
24 | success: (code.to_i >= 0),
25 | code: code
26 | }
27 | end
28 |
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/china_sms_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "ChinaSMS" do
5 | let(:service) { :tui3 }
6 | let(:username) { 'saberma' }
7 | let(:password) { '666666' }
8 | let(:phone) { '13928452841' }
9 | let(:content) { '活动通知:深圳 Rubyist 活动时间变更到明天下午 7:00,请留意。' }
10 |
11 | context 'with service' do
12 | before { ChinaSMS.use service, username: username, password: password }
13 |
14 | describe "#use" do
15 | subject { ChinaSMS }
16 | its(:username) { should eql "saberma"}
17 | end
18 |
19 | describe "#to" do
20 | subject { ChinaSMS.to(phone, content) }
21 | before { ChinaSMS::Service::Tui3.stub(:to).with(phone, content, username: username, password: password).and_return(success: true, code: 0) }
22 | its([:success]) { should eql true }
23 | its([:code]) { should eql 0 }
24 | end
25 | end
26 |
27 | context 'without service' do
28 | before { ChinaSMS.clear }
29 |
30 | describe '#to' do
31 | it 'should be ignore' do
32 | ChinaSMS.to(phone, content)
33 | end
34 | end
35 |
36 | end
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/china_sms.gemspec:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | # coding: utf-8
4 | lib = File.expand_path('../lib', __FILE__)
5 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6 | require 'china_sms/version'
7 |
8 | Gem::Specification.new do |spec|
9 | spec.name = "china_sms"
10 | spec.version = ChinaSMS::VERSION
11 | spec.authors = ["saberma"]
12 | spec.email = ["mahb45@gmail.com"]
13 | spec.description = %q{a gem for chinese people to send sms}
14 | spec.summary = %q{a gem for chinese people to send sms}
15 | spec.homepage = "https://github.com/saberma/china_sms"
16 | spec.license = "MIT"
17 |
18 | spec.files = `git ls-files`.split($/)
19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21 | spec.require_paths = ["lib"]
22 |
23 | spec.add_development_dependency "bundler", "~> 1.2"
24 | spec.add_development_dependency "rake"
25 | spec.add_development_dependency "rspec"
26 | spec.add_development_dependency "rspec-its"
27 | spec.add_development_dependency "webmock"
28 | end
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 saberma
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/lib/china_sms.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require "china_sms/version"
3 | require 'net/http'
4 | Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/china_sms/service/*.rb").sort.each do |f|
5 | require f.match(/(china_sms\/service\/.*)\.rb$/)[0]
6 | end
7 |
8 | module ChinaSMS
9 | extend self
10 |
11 | attr_reader :username, :password
12 |
13 | def use(service, options)
14 | @service = ChinaSMS::Service.const_get("#{service.to_s.capitalize}")
15 | @service.const_set("URL", options[:base_uri]) if options[:base_uri]
16 | @username = options[:username]
17 | @password = options[:password]
18 | end
19 |
20 | def to(receiver, content, options = {})
21 | options = default_options.merge options
22 | @service.to receiver, content, options if @service
23 | end
24 |
25 | def get(options = {})
26 | options = default_options.merge options
27 | @service.get options if @service
28 | end
29 |
30 | def clear
31 | @service = @username = @password = nil
32 | end
33 |
34 | private
35 |
36 | def default_options
37 | {
38 | username: @username,
39 | password: @password
40 | }
41 | end
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/lib/china_sms/service/smsbao.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module ChinaSMS
3 | module Service
4 | module Smsbao
5 | extend self
6 |
7 | URL = "http://api.smsbao.com/sms"
8 | REMAIN_URL = "http://www.smsbao.com/query"
9 |
10 | MESSAGES = {
11 | '0' => '短信发送成功',
12 | '30' => '密码错误',
13 | '40' => '账号不存在',
14 | '41' => '余额不足',
15 | '42' => '帐号过期',
16 | '43' => 'IP地址限制',
17 | '50' => '内容含有敏感词',
18 | '51' => '手机号码不正确'
19 | }
20 |
21 | def to(phone, content, options)
22 | phones = Array(phone).join(',')
23 | res = Net::HTTP.post_form(URI.parse(URL), u: options[:username], p: Digest::MD5.hexdigest(options[:password]), m: phones, c: content)
24 | result res.body
25 | end
26 |
27 | def get options
28 | res = Net::HTTP.post_form(URI.parse(REMAIN_URL), u: options[:username], p: Digest::MD5.hexdigest(options[:password]))
29 | r = res.body.match(/(\d+)\n(\d+),(\d+)/)
30 | {
31 | success: (r[1] == '0'),
32 | code: r[1],
33 | message: MESSAGES[r[1]],
34 | send: r[2].to_i,
35 | remain: r[3].to_i
36 | }
37 | end
38 |
39 | def result(code)
40 | {
41 | success: (code == '0'),
42 | code: code,
43 | message: MESSAGES[code]
44 | }
45 | end
46 |
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/spec/service/chanyoo_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Chanyoo" do
5 |
6 | describe "#to" do
7 | let(:username) { 'saberma' }
8 | let(:password) { '666666' }
9 | let(:url) { "http://api.chanyoo.cn/utf8/interface/send_sms.aspx" }
10 | let(:content) { '【畅友短信测试】深圳 Rubyist 活动时间变更到周六下午 3:00,请留意。【19屋】' }
11 | subject { ChinaSMS::Service::Chanyoo.to phone, content, username: username, password: password }
12 |
13 | describe 'single phone' do
14 | let(:phone) { '13928452841' }
15 |
16 | before do
17 | stub_request(:post, url).
18 | with(body: {username: username, password: password, receiver: phone, content: content}).
19 | to_return(body: '50短信发送成功')
20 | end
21 |
22 | its([:success]) { should eql true }
23 | its([:code]) { should eql '50' }
24 | its([:message]) { should eql "短信发送成功" }
25 | end
26 |
27 | describe 'multiple phones' do
28 | let(:phone) { ['13928452841', '13590142385'] }
29 |
30 | before do
31 | stub_request(:post, url).
32 | with(body: {username: username, password: password, receiver: phone.join(','), content: content}).
33 | to_return(body: '50短信发送成功')
34 | end
35 |
36 | its([:success]) { should eql true }
37 | its([:code]) { should eql '50' }
38 | its([:message]) { should eql "短信发送成功" }
39 | end
40 |
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/service/sendcloud_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Sendcloud" do
5 | let(:username) { 'user' }
6 | let(:password) { 'xxxxtemrejmijfeijixwewe' }
7 |
8 | describe "#to" do
9 | let(:url) { 'http://www.sendcloud.net/smsapi/send' }
10 | let(:template_id) { 3126 }
11 | let(:content) { {} }
12 | subject { ChinaSMS::Service::Sendcloud.to phone, '', {content: content, template_id: template_id , username: username, password: password} }
13 |
14 | describe 'single phone' do
15 | let(:phone) { '13601647905' }
16 |
17 | before do
18 | body = {"phone"=> phone, "vars"=> content.to_json, "msgType"=> '0', "smsUser"=> username, "templateId"=>template_id, "signature"=>"16f795fd3514ed89bdd9fe577fb2601a" }
19 | stub_request(:post, url).
20 | with(body: URI.encode_www_form(body)).
21 | to_return(body: "{\"info\":{\"successCount\":1,\"smsIds\":[\"1478579653941_49236_566_3126_clrsg5$13601647905\"]},\"statusCode\":200,\"message\":\"请求成功\",\"result\":true}")
22 | end
23 |
24 | its([:success]) { should eql true }
25 | its([:code]) { should eql 200 }
26 | its([:message]) { should eql "请求成功" }
27 | end
28 | end
29 |
30 | describe "#get_signature" do
31 | let(:phone) { "13601647905" }
32 | let(:content) { {}.to_json }
33 | let(:params) { {"phone"=> phone, "vars"=> content, "msgType"=>0, "smsUser"=> username, "templateId"=>3126, 'password'=> password} }
34 | subject { ChinaSMS::Service::Sendcloud.get_signature params }
35 |
36 | it { should eql "16f795fd3514ed89bdd9fe577fb2601a" }
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/service/tui3_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Tui3" do
5 | describe "#to" do
6 | let(:password) { '50e7a3smdl521942ea38dnf4b58c5e6b' }
7 | let(:url) { "http://tui3.com/api/send/" }
8 | let(:content) { '推立方测试:深圳 Rubyist 活动时间变更到明天下午 7:00,请留意。' }
9 | subject { ChinaSMS::Service::Tui3.to phone, content, password: password }
10 |
11 | describe 'single phone' do
12 | let(:phone) { '13928452888' }
13 |
14 | before do
15 | stub_request(:post, url).
16 | with(body: {k: password, t: phone, c: content, p: '1', r: 'json'}).
17 | to_return(body: '{"err_code":0,"err_msg":"操作成功!","server_time":"2013-07-01 21:42:37"}' )
18 | end
19 |
20 | its([:success]) { should eql true }
21 | its([:code]) { should eql 0 }
22 | its([:message]) { should eql "操作成功!" }
23 | end
24 |
25 | describe 'multiple phones' do
26 | let(:phone) { ['13928452888', '13590142385'] }
27 |
28 | before do
29 | stub_request(:post, url).
30 | with(body: {k: password, t: phone.join(','), c: content, p: '1', r: 'json'}).
31 | to_return(body: '{"err_code":0,"err_msg":"操作成功!","server_time":"2013-07-01 21:42:37"}' )
32 | end
33 |
34 | its([:success]) { should eql true }
35 | its([:code]) { should eql 0 }
36 | its([:message]) { should eql "操作成功!" }
37 | end
38 |
39 | context 'invalid key' do
40 | let(:phone) { '13928452888' }
41 | let(:password) { '666666' }
42 |
43 | before do
44 | stub_request(:post, url).
45 | with(body: {k: password, t: phone, c: content, p: '1', r: 'json'}).
46 | to_return(body: 'invalid parameters' )
47 | end
48 |
49 | its([:success]) { should eql false }
50 | its([:message]) { should eql "非法apikey:#{password}" }
51 | end
52 |
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/china_sms/service/yunpian.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module ChinaSMS
3 | module Service
4 | module Yunpian
5 | extend self
6 |
7 | GET_URL = "http://yunpian.com/v1/user/get.json"
8 | SEND_URL = 'http://yunpian.com/v1/sms/send.json'
9 | TPL_SEND_URL = 'http://yunpian.com/v1/sms/tpl_send.json'
10 |
11 | def to phone, content, options = {}
12 | options[:tpl_id] ||= 2
13 | options[:apikey] ||= options[:password]
14 | except! options, :username, :password
15 |
16 | res = if content.is_a? Hash
17 | message = parse_content content
18 | options.merge!({ mobile: phone, tpl_value: message })
19 | Net::HTTP.post_form(URI.parse(TPL_SEND_URL), options)
20 | else
21 | except! options, :tpl_id
22 | message = content
23 | options.merge!({ mobile: phone, text: message })
24 | Net::HTTP.post_form(URI.parse(SEND_URL), options)
25 | end
26 |
27 | result res.body
28 | end
29 |
30 | def get options = {}
31 | options[:apikey] ||= options[:password]
32 | except! options, :username, :password
33 | res = Net::HTTP.post_form(URI.parse(GET_URL), options)
34 | result res.body
35 | end
36 |
37 | def result body
38 | begin
39 | JSON.parse body
40 | rescue => e
41 | {
42 | code: 502,
43 | msg: "内容解析错误",
44 | detail: e.to_s
45 | }
46 | end
47 | end
48 |
49 | private
50 |
51 | def except! options = {}, *keys
52 | keys.each {|key| options.delete(key)}
53 | options
54 | end
55 |
56 | def parse_content content
57 | content.map { |k, v| "##{k}#=#{v}" }.join('&')
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/service/smsbao_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Smsbao" do
5 | let(:username) { 'saberma' }
6 | let(:password) { '666666' }
7 | describe "#to" do
8 | let(:url) { "http://api.smsbao.com/sms" }
9 | let(:content) { '【短信宝测试】深圳 Rubyist 活动时间变更到明天下午 7:00,请留意。【19屋】' }
10 | subject { ChinaSMS::Service::Smsbao.to phone, content, username: username, password: password }
11 |
12 | describe 'single phone' do
13 | let(:phone) { '13928452841' }
14 |
15 | before do
16 | stub_request(:post, url).
17 | with(body: {u: username, p: Digest::MD5.hexdigest(password), m: phone, c: content}).
18 | to_return(body: '0')
19 | end
20 |
21 | its([:success]) { should eql true }
22 | its([:code]) { should eql '0' }
23 | its([:message]) { should eql "短信发送成功" }
24 | end
25 |
26 | describe 'multiple phones' do
27 | let(:phone) { ['13928452841', '13590142385'] }
28 |
29 | before do
30 | stub_request(:post, url).
31 | with(body: {u: username, p: Digest::MD5.hexdigest(password), m: phone.join(','), c: content}).
32 | to_return(body: '0')
33 | end
34 |
35 | its([:success]) { should eql true }
36 | its([:code]) { should eql '0' }
37 | its([:message]) { should eql "短信发送成功" }
38 | end
39 |
40 | end
41 |
42 | describe "get" do
43 | let(:remain_url) { "http://www.smsbao.com/query" }
44 | subject { ChinaSMS::Service::Smsbao.get username: username, password: password }
45 |
46 | describe "remain send count" do
47 | before do
48 | stub_request(:post, remain_url).
49 | with(body: {u: username, p: Digest::MD5.hexdigest(password)}).
50 | to_return(body: "0\n100,200")
51 | end
52 |
53 | its([:success]) { should eql true }
54 | its([:code]) { should eql '0' }
55 | its([:message]) { should eql '短信发送成功' }
56 | its([:send]) { should eql 100 }
57 | its([:remain]) { should eql 200 }
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChinaSMS 短信平台 Ruby 接口
2 |
3 | [](https://travis-ci.org/saberma/china_sms)
4 |
5 | ## 支持以下短信平台
6 |
7 | 所有短信平台都禁止发送私人短信,并要求在短信内容末尾加上签名后缀,如【19屋】
8 |
9 | * [云片网](http://www.yunpian.com/) 专注于帮助企业与客户更好的沟通, 提供短信服务, 功能强大, 可以配置多种[模板](http://www.yunpian.com/api/tpl.html), 实时性高, 定制型强, 价格优惠.
10 | * [推立方](http://tui3.com/) 专注于注册校验码等实时应用,需要[配置内容格式和签名](http://www.tui3.com/Members/smsconfigv2/),会自动在短信加上签名后缀。短信计数:移动/联通每条短信的最大长度为64字符,电信每条最大长度60字符(半角、全角各算一个)。超过该长度后,短信后面自动增加分页信息(x/y),此时,每条短信最大长度需要再减3(不超过10页)
11 | * [短信宝](http://www.smsbao.com/)
12 | * [畅友网络](http://www.chanyoo.cn/) 群发短信需要半小时左右的时间审核,星期五等繁忙时段会有几个小时的延时,不适合发送注册校验码等实时短信,单次最多发送500个号码
13 | * [亿美软通](http://www.emay.cn/)
14 | * [螺丝帽](http://luosimao.com/)
15 |
16 | 感谢 [云片网](http://yunpian.com/?ref=china_sms) 为 [19屋活动平台](http://19wu.com) 提供短信赞助。
17 |
18 | ## 安装
19 |
20 | 加入以下代码到 Gemfile:
21 |
22 | gem 'china_sms'
23 |
24 | 然后执行:
25 |
26 | $ bundle
27 |
28 | 或者直接安装:
29 |
30 | $ gem install china_sms
31 |
32 | ## 使用
33 |
34 | ```ruby
35 | # 支持 :tui3, :yunpian, :smsbao, :chanyoo, :emay, luosimao 短信接口
36 | ChinaSMS.use :tui3, username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD'
37 | ChinaSMS.to '13912345678', '[Test]China SMS gem has been released.'
38 |
39 |
40 | # :yunpian
41 | # 如果content(第二个参数) 是字符串
42 | # 调用 通用接口 发送短信
43 | # 如果是 Hash
44 | # 调用 模板接口 发送短信
45 | # 可选参数:
46 | # :tpl_id 默认是 2
47 |
48 | ChinaSMS.use :yunpian, password: 'YOUR_API_KEY'
49 |
50 | # 通用接口
51 | ChinaSMS.to '13912345678', '[Test]China SMS gem has been released.'
52 | ChinaSMS.to '13912345678', 'China SMS gem has been released.【Test】' # luosimao 的签名要放在后面
53 |
54 | # 模板接口
55 | # 模板是 "您的验证码是#code#【#company#】”
56 | tpl_params = { code: 123, company: '19wu' }
57 | ChinaSMS.to '13912345678', tpl_params, tpl_id: 1
58 |
59 | ```
60 |
61 | ## 贡献
62 |
63 | ```bash
64 | git clone git@github.com:saberma/china_sms.git
65 | cd china_sms
66 | bundle console # 请不要使用 irb,可能会有依赖问题
67 | ```
68 |
69 | ## TODO
70 |
71 | * 签名作为参数,根据各个服务提供商的不同来确定放在内容前面还是后面,例如 luosimao 要求放在内容后面
72 |
73 | ## 安全性
74 |
75 | 在安全性方面,很多接口都是使用用户登录明文密码,而云片网、螺丝帽、推立方 和 短信宝 要好一些。
76 |
77 | * **云片网**,使用HTTPS,API使用独立的apikey,不使用登录名和密码,支持 IP 白名单
78 | * **推立方**,不使用登录密码,而是由系统自动生成一长串 API_KEY,专用于接口调用
79 | * **短信宝**,使用登录密码,但在调用时要先转成 MD5
80 | * **螺丝帽**,使用HTTPS,登录密码
81 |
--------------------------------------------------------------------------------
/lib/china_sms/service/sendcloud.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module ChinaSMS
3 | module Service
4 | module Sendcloud
5 | extend self
6 |
7 | URL = 'http://www.sendcloud.net/smsapi/send'.freeze
8 |
9 | MESSAGES = {
10 | '500' => '发送失败, 手机空号',
11 | '510' => '发送失败, 手机停机',
12 | '550' => '发送失败, 该模板内容被投诉',
13 | '580' => '发送失败, 手机关机',
14 | '590' => '发送失败, 其他原因',
15 | '200' => '请求成功',
16 | '311' => '部分号码请求成功',
17 | '312' => '全部请求失败',
18 | '411' => '手机号不能为空',
19 | '412' => '手机号格式错误',
20 | '413' => '有重复的手机号',
21 | '421' => '签名参数错误',
22 | '422' => '签名错误',
23 | '431' => '模板不存在',
24 | '432' => '模板未提交审核或者未审核通过',
25 | '433' => '模板ID不能为空',
26 | '441' => '替换变量格式错误',
27 | '451' => '定时发送时间的格式错误',
28 | '452' => '定时发送时间早于服务器时间, 时间已过去',
29 | '461' => '时间戳无效, 与服务器时间间隔大于6秒',
30 | '471' => 'smsUser不存在',
31 | '472' => 'smsUser不能为空',
32 | '473' => '没有权限, 免费用户不能发送短信',
33 | '474' => '用户不存在',
34 | '481' => '手机号和替换变量不能为空',
35 | '482' => '手机号和替换变量格式错误',
36 | '483' => '替换变量长度不能超过32个字符',
37 | '496' => '一分钟内给当前手机号发信太多',
38 | '498' => '一天内给当前手机号发信太多',
39 | '499' => '账户额度不够',
40 | '501' => '服务器异常',
41 | '601' => '你没有权限访问'
42 | }
43 |
44 | module MSG_TYPE
45 | SMS = 0
46 |
47 | MMS = 1
48 | end
49 |
50 | def get_signature(params)
51 | param_str = []
52 | password = params.delete 'password'
53 | params.keys.sort.each do |key|
54 | param_str << "#{key}=#{params[key]}"
55 | end
56 |
57 | sign_str = "#{password}{param_str.join('&')}{password}"
58 |
59 | Digest::MD5.hexdigest sign_str
60 | end
61 |
62 | def to(phone, content = '', options = {})
63 | options[:type] ||= MSG_TYPE::SMS
64 |
65 | params = {
66 | 'phone' => phone,
67 | 'vars' => options[:content].to_json,
68 | 'msgType' => options[:type],
69 | 'smsUser' => options[:username],
70 | 'templateId' => options[:template_id],
71 | 'password' => options[:password]
72 | }
73 |
74 | params.merge! 'signature' => get_signature(params)
75 |
76 | puts "Request params: #{params}"
77 |
78 | res = Net::HTTP.post_form(URI.parse(URL), params)
79 |
80 | puts "Response Body: #{res.body}"
81 |
82 | result JSON.parse(res.body)
83 | end
84 |
85 | def result(response)
86 | code = response['statusCode']
87 |
88 | {
89 | success: (code == 200),
90 | code: code,
91 | message: response['message']
92 | }
93 | end
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/spec/service/yunpian_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | describe "Yunpian" do
5 | describe "#to" do
6 | let(:apikey) { '2022b1599967a8cb788c05ddd9fc339e' }
7 | let(:send_url) { "http://yunpian.com/v1/sms/send.json" }
8 | let(:tpl_send_url) { "http://yunpian.com/v1/sms/tpl_send.json" }
9 | let(:content) { '云片测试:验证码 1234。' }
10 | let(:tpl_content) { Hash[:code, 123, :company, '云片网'] }
11 |
12 | describe 'single phone' do
13 | let(:phone) { '13928452841' }
14 |
15 | before do
16 | stub_request(:post, tpl_send_url).
17 | with(
18 | body: {
19 | "apikey" => apikey,
20 | "mobile" => phone,
21 | "tpl_id" => "2",
22 | "tpl_value" => "#code#=123company#=云片网" },
23 | headers: {
24 | 'Content-Type' => 'application/x-www-form-urlencoded'
25 | }).
26 | to_return(
27 | body: {
28 | 'code' => 0,
29 | 'msg' => 'OK',
30 | 'result' => {
31 | 'count' => '1',
32 | 'fee' => '1',
33 | 'sid' => '592762800'
34 | }
35 | }.to_json
36 | )
37 |
38 | stub_request(:post, send_url).
39 | with(
40 | body: {
41 | "apikey" => apikey,
42 | "mobile" => phone,
43 | "text" => content },
44 | headers: {
45 | 'Content-Type' => 'application/x-www-form-urlencoded'
46 | }).
47 | to_return(
48 | body: {
49 | 'code' => 0,
50 | 'msg' => 'OK',
51 | 'result' => {
52 | 'count' => '1',
53 | 'fee' => '1',
54 | 'sid' => '592762800'
55 | }
56 | }.to_json
57 | )
58 | end
59 |
60 | context 'string content' do
61 | subject { ChinaSMS::Service::Yunpian.to phone, content, password: apikey }
62 |
63 | its(["code"]) { should eql 0 }
64 | its(["msg"]) { should eql "OK" }
65 | end
66 |
67 | context 'tpl content' do
68 | subject { ChinaSMS::Service::Yunpian.to phone, tpl_content, password: apikey }
69 |
70 | its(["code"]) { should eql 0 }
71 | its(["msg"]) { should eql "OK" }
72 | end
73 | end
74 |
75 | describe 'invalid key' do
76 | let(:phone) { '13928452841' }
77 | let(:apikey) { '666666' }
78 |
79 | before do
80 | stub_request(:post, tpl_send_url).
81 | with(
82 | body: {
83 | "apikey" => apikey,
84 | "mobile" => phone,
85 | "tpl_id" => "2",
86 | "tpl_value" => "#code#=123company#=云片网" },
87 | headers: {
88 | 'Content-Type' => 'application/x-www-form-urlencoded'
89 | }).
90 | to_return(
91 | body: {
92 | "code" => -1,
93 | "msg" => "非法的apikey",
94 | "detail" => "请检查的apikey是否正确"
95 | }.to_json
96 | )
97 |
98 | stub_request(:post, send_url).
99 | with(
100 | body: {
101 | "apikey" => apikey,
102 | "mobile" => phone,
103 | "text" => content },
104 | headers: {
105 | 'Content-Type' => 'application/x-www-form-urlencoded'
106 | }).
107 | to_return(
108 | body: {
109 | "code" => -1,
110 | "msg" => "非法的apikey",
111 | "detail" => "请检查的apikey是否正确"
112 | }.to_json
113 | )
114 | end
115 |
116 | context 'string content' do
117 | subject { ChinaSMS::Service::Yunpian.to phone, content, password: apikey }
118 |
119 | its(["code"]) { should eql -1 }
120 | its(["msg"]) { should eql "非法的apikey" }
121 | its(["detail"]) { should eql "请检查的apikey是否正确" }
122 | end
123 |
124 | context 'tpl content' do
125 | subject { ChinaSMS::Service::Yunpian.to phone, tpl_content, password: apikey }
126 |
127 | its(["code"]) { should eql -1 }
128 | its(["msg"]) { should eql "非法的apikey" }
129 | its(["detail"]) { should eql "请检查的apikey是否正确" }
130 | end
131 | end
132 |
133 | end
134 | end
135 |
--------------------------------------------------------------------------------