sinatra-wechat 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f5f6095ebdc047a6f9b4ab8e1c18deb9eb5da15
4
- data.tar.gz: b1404892968fb949f40caee255de6e2f6a88355a
3
+ metadata.gz: 86233c3037ef8bd2e6d48fa0d321aa6a4279e41f
4
+ data.tar.gz: 2d2eb697ffa61f47e149a4718c0b21b4dbdb7212
5
5
  SHA512:
6
- metadata.gz: 2cc6dc338939d72ce8115828813fae4e2ccca02652eb026397b435e016fdeeb02a830717dfff0be80ef707d857f21d99bb5b1acb6f1284d8e1bff8ed353e4714
7
- data.tar.gz: 5266b028367585f2b96f290e8cd0177ba0aa73be2b987642ae60e284a44d7fecfe3268442511d4876a659ce0cfec33c56e7ac823b5e07a9b07ded6f15dc67753
6
+ metadata.gz: 97f9ff1ed899844dd1aef81127b765b8b14e783b132e77e594facc8864fdd2a90f2261aaf50f39c9edf75ee147108ad62edfc45d0a2ddce63e73e3be97e47a26
7
+ data.tar.gz: fe24ff24774e0c9d15c3efc4eb7e284d9704ab6fa5e73f38a65ada3ec45cba63c18b3c6856990b71f654b3aea7ac3c02e9dc7f6212e42ea9fa044fbde3bf1776
data/README.md CHANGED
@@ -12,14 +12,14 @@ This extension is used to support [Tencent Wechat](https://mp.weixin.qq.com/) ra
12
12
  # Usage
13
13
 
14
14
  Below code implement a simple wechat robot, reply text `你好` when message sent by end user contains number `%r{\d+}`.
15
- > use `:message_validation => false` to disable wechat message validation, otherwise need to append signature to the URL. The default value of `:message_validation` is `true`
15
+ > use `:validate_msg => false` to disable wechat message validation, otherwise need to append signature to the URL. The default value of `:validate_msg` is `true`
16
16
 
17
17
  ```ruby
18
18
  # app.rb
19
19
  require 'sinatra'
20
20
  require 'sinatra/wechat'
21
21
 
22
- wechat('/', :wechat_token => 'test-token', :message_validation => false) {
22
+ wechat('/', :wechat_token => 'test-token', :validate_msg => false) {
23
23
  text(:content => %r{\d+}) {
24
24
  content_type 'application/xml'
25
25
  erb :hello, :locals => request[:wechat_values]
@@ -17,7 +17,7 @@ location_event_reply = proc {
17
17
  builder.to_xml
18
18
  }
19
19
 
20
- wechat('/wechat', :wechat_token => 'test-token', :message_validation => false) {
20
+ wechat('/wechat', :wechat_token => 'test-token', :validate_msg => false) {
21
21
  location {
22
22
  instance_eval &location_event_reply
23
23
  }
@@ -1,5 +1,5 @@
1
1
  module Sinatra
2
2
  module Wechat
3
- VERSION = "0.0.2"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -1,65 +1,65 @@
1
- require 'sinatra/base'
2
- require 'blankslate'
3
- require 'nokogiri'
1
+ ['sinatra/base', 'blankslate', 'nokogiri'].each { |m| require m }
4
2
 
5
3
  module Sinatra
6
4
  module Wechat
7
- module EndpointActions
8
- class WechatDispatcher < ::BlankSlate
9
- attr_reader :wechat_token, :verfiy_message
10
- def initialize
11
- super
5
+ module Endpoint
6
+ # Work as a Ruby Builder, treat every Wechat 'MsgType' as method name
7
+ # the method arguments are the Wechat message it self
8
+ class DispatcherBuilder < ::BlankSlate
9
+ def initialize(&block)
12
10
  @message_handlers = {}
11
+ instance_eval(&block) if block_given?
13
12
  end
14
13
 
15
- def method_missing(sym, *args, &block)
14
+ # resp_blk is used to generate HTTP response, need to eval in Sinatra context
15
+ def method_missing(sym, *args, &resp_blk)
16
16
  @message_handlers[sym] ||= []
17
- matchers = args.collect do |v|
18
- if v.respond_to?(:call) then lambda { |values| v.call(values) }
17
+ matchers = args.collect do |arg|
18
+ if arg.respond_to?(:call) then lambda &arg
19
19
  # for named parameters
20
- elsif v.respond_to?(:all?) then lambda { |values| v.all? { |k,v| v === values[k]} }
20
+ elsif arg.respond_to?(:all?) then lambda { |values| arg.all? { |k,v| v === values[k]} }
21
21
  else raise TypeError, "\"#{v} (#{v.class})\" is not an acceptable condition"
22
- end
22
+ end
23
23
  end
24
- matcher = lambda do |values|
25
- matchers.all? {|m| m.call(values)}
26
- end
27
- @message_handlers[sym] << [ matcher, block ]
24
+ matcher = lambda { |values| matchers.all? { |m| m.call(values) } }
25
+ @message_handlers[sym] << [ matcher, resp_blk ]
28
26
  end
29
27
 
30
- def route!(values)
31
- type = values[:msg_type].to_sym
32
- handlers = @message_handlers[type] || []
33
- _, handler = handlers.find { |m, _| m.call(values) }
34
- handler
28
+ def dispatch!(values)
29
+ return nil unless msg_type = values[:msg_type]
30
+ handlers = @message_handlers[msg_type.to_sym] || []
31
+ handlers.find { |m, _| m.call(values) }
35
32
  end
36
33
  end
37
34
 
38
- def wechat(endpoint = '/', wechat_token: '', message_validation: true, &block)
39
- dispatcher = WechatDispatcher.new
40
- dispatcher.instance_eval &block
35
+ def wechat(endpoint = '/', wechat_token: '', validate_msg: true, &block)
36
+ before endpoint do
37
+ if validate_msg then
38
+ raw = [wechat_token, params[:timestamp], params[:nonce]].compact.sort.join
39
+ halt 403 unless Digest::SHA1.hexdigest(raw) == params[:signature]
40
+ end
41
+ end
41
42
 
42
43
  get endpoint do
43
- halt 403 unless validate_messages(wechat_token) if message_validation
44
44
  content_type 'text/plain'
45
45
  params[:echostr]
46
46
  end
47
47
 
48
- post endpoint do
49
- halt 403 unless validate_messages(wechat_token) if message_validation
48
+ dispatcher = DispatcherBuilder.new(&block)
50
49
 
50
+ post endpoint do
51
51
  body = request.body.read || ""
52
- halt 501 if body.empty?
52
+ halt 400 if body.empty? # bad request, cannot handle this kind of message
53
53
 
54
- doc = Nokogiri::XML(body).root
55
- values = doc.element_children.each_with_object(Hash.new) do |e, v|
54
+ xmldoc = Nokogiri::XML(body).root
55
+ values = xmldoc.element_children.each_with_object(Hash.new) do |e, v|
56
56
  name = e.name.gsub(/(.)([A-Z])/,'\1_\2').downcase
57
57
  # rename 'Location_X' to 'location__x' then to 'location_x'
58
58
  name = name.gsub(/(_{2,})/,'_')
59
59
  v[name.to_sym] = e.content
60
60
  end
61
- handler = dispatcher.route!(values)
62
- halt 501 unless handler
61
+ _, handler = dispatcher.dispatch!(values)
62
+ halt 404 unless handler
63
63
 
64
64
  request[:wechat_values] = values
65
65
  instance_eval(&handler)
@@ -68,18 +68,11 @@ module Sinatra
68
68
  end
69
69
  end
70
70
 
71
- def self.registered(app)
72
- app.extend(Wechat::EndpointActions)
73
- app.helpers do
74
- def validate_messages token
75
- raw = [token, params[:timestamp], params[:nonce]].compact.sort.join
76
- Digest::SHA1.hexdigest(raw) == params[:signature]
77
- end
78
- end
79
- # expose to classic style
80
- Delegator.delegate(:wechat)
71
+ def self.registered(application)
72
+ application.extend(Wechat::Endpoint)
73
+ Sinatra::Delegator.delegate(:wechat) # expose wechat method to classic style
81
74
  end
82
75
  end
83
76
 
84
- register Wechat
77
+ register Sinatra::Wechat
85
78
  end
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Lu, Jun"]
10
10
  spec.email = ["luj1985@gmail.com"]
11
11
  spec.summary = "Sinatra extension for Tencent Wechat"
12
- spec.description = "Provide Extensible Sinatra API to support Wechat development mode"
12
+ spec.description = "Provide extensible Sinatra API to support rapid Wechat development"
13
13
  spec.homepage = "https://github.com/luj1985/sinatra-wechat"
14
14
  spec.license = "MIT"
15
15
 
data/spec/spec_helper.rb CHANGED
@@ -8,3 +8,7 @@ require 'sinatra'
8
8
  require 'sinatra/wechat'
9
9
 
10
10
  set :environment, :test
11
+
12
+ RSpec.configure do |conf|
13
+ conf.include Rack::Test::Methods
14
+ end
data/spec/wechat_spec.rb CHANGED
@@ -1,48 +1,36 @@
1
- require File.expand_path '../spec_helper.rb', __FILE__
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe Sinatra::Wechat do
4
- include Rack::Test::Methods
5
4
 
6
- it "GET should have message verification" do
7
- def app
8
- instance = Sinatra.new do
9
- register Sinatra::Wechat
10
- end
11
- instance.wechat(:wechat_token => 'test-token') { }
12
- end
5
+ let (:app) { Sinatra.new { register Sinatra::Wechat } }
6
+
7
+ it "should have message validation on GET method" do
8
+ app.wechat(:wechat_token => 'test-token')
13
9
 
14
10
  get '/'
15
11
  expect(last_response.status).to eq(403)
16
12
 
17
- get '/', {:timestamp => '201407191804',
18
- :nonce => 'nonce',
19
- :signature => '9a91a1cea1cb60b87a9abb29dae06dce14721258',
20
- :echostr => 'echo string'}
21
- expect(last_response.status).to eq(200)
13
+ get '/', {
14
+ :timestamp => '201407191804',
15
+ :nonce => 'nonce',
16
+ :signature => '9a91a1cea1cb60b87a9abb29dae06dce14721258',
17
+ :echostr => 'echo string'
18
+ }
19
+ expect(last_response).to be_ok
22
20
  expect(last_response.body).to eq('echo string')
23
21
  end
24
22
 
25
23
  it "Can disable message validation" do
26
- def app
27
- instance = Sinatra.new do
28
- register Sinatra::Wechat
29
- end
30
- instance.wechat(:message_validation => false) { }
31
- end
24
+ app.wechat(:validate_msg => false)
32
25
 
33
- get '/'
34
- expect(last_response.status).to eq(200)
26
+ get '/', { :echostr => 'echo' }
27
+ expect(last_response).to be_ok
28
+ expect(last_response.body).to eq('echo')
35
29
  end
36
30
 
37
- it "POST should have message verification" do
38
- def app
39
- instance = Sinatra.new do
40
- register Sinatra::Wechat
41
- end
42
- instance.wechat(:wechat_token => 'test-token') {
43
- text { 'text response' }
44
- }
45
- end
31
+ it "should have message validation on POST method" do
32
+ app.wechat(:wechat_token => 'test-token') { text { 'text response' } }
33
+
46
34
  post '/'
47
35
  expect(last_response.status).to eq(403)
48
36
 
@@ -58,19 +46,13 @@ describe Sinatra::Wechat do
58
46
  EOF
59
47
 
60
48
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', body
61
- expect(last_response.status).to eq(200)
49
+ expect(last_response).to be_ok
62
50
  expect(last_response.body).to eq('text response')
63
51
  end
64
52
 
65
53
  it "can switch wechat endpoint" do
66
- def app
67
- instance = Sinatra.new do
68
- register Sinatra::Wechat
69
- end
70
- instance.wechat('/wechat', :wechat_token => 'test-token') {
71
- image { 'relocated response' }
72
- }
73
- end
54
+ app.wechat('/wechat', :wechat_token => 'test-token') { image { 'relocated response' } }
55
+
74
56
  body = <<-EOF
75
57
  <xml>
76
58
  <ToUserName><![CDATA[toUser]]></ToUserName>
@@ -87,30 +69,25 @@ describe Sinatra::Wechat do
87
69
  expect(last_response.status).to eq(404)
88
70
 
89
71
  post '/wechat?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', body
90
- expect(last_response.status).to eq(200)
72
+ expect(last_response).to be_ok
91
73
  expect(last_response.body).to eq('relocated response')
92
74
 
93
75
  end
94
76
 
95
77
  it "should accept wechat message push" do
96
- def app
97
- instance = Sinatra.new do
98
- register Sinatra::Wechat
99
- end
100
- instance.wechat(:wechat_token => 'test-token') {
101
- text(:content => %r{regex match}) { 'regex match' }
102
- text(lambda {|values| values[:content] == 'function match'}) { 'function match' }
103
- text { 'default match' }
104
- voice {
105
- values = request[:wechat_values]
106
- values[:msg_id]
107
- }
108
- location {
109
- values = request[:wechat_values]
110
- values[:label]
111
- }
78
+ app.wechat(:wechat_token => 'test-token') {
79
+ text(:content => %r{regex match}) { 'regex match' }
80
+ text(lambda {|values| values[:content] == 'function match'}) { 'function match' }
81
+ text { 'default match' }
82
+ voice {
83
+ values = request[:wechat_values]
84
+ values[:msg_id]
85
+ }
86
+ location {
87
+ values = request[:wechat_values]
88
+ values[:label]
112
89
  }
113
- end
90
+ }
114
91
 
115
92
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
116
93
  <xml>
@@ -122,10 +99,9 @@ describe Sinatra::Wechat do
122
99
  <MsgId>1234567890123456</MsgId>
123
100
  </xml>
124
101
  EOF
125
- expect(last_response.status).to eq(200)
102
+ expect(last_response).to be_ok
126
103
  expect(last_response.body).to eq('regex match')
127
104
 
128
-
129
105
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
130
106
  <xml>
131
107
  <ToUserName>tousername</ToUserName>
@@ -136,10 +112,9 @@ describe Sinatra::Wechat do
136
112
  <MsgId>1234567890123456</MsgId>
137
113
  </xml>
138
114
  EOF
139
- expect(last_response.status).to eq(200)
115
+ expect(last_response).to be_ok
140
116
  expect(last_response.body).to eq('function match')
141
117
 
142
-
143
118
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
144
119
  <xml>
145
120
  <ToUserName>tousername</ToUserName>
@@ -150,10 +125,9 @@ describe Sinatra::Wechat do
150
125
  <MsgId>1234567890123456</MsgId>
151
126
  </xml>
152
127
  EOF
153
- expect(last_response.status).to eq(200)
128
+ expect(last_response).to be_ok
154
129
  expect(last_response.body).to eq('default match')
155
130
 
156
-
157
131
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
158
132
  <xml>
159
133
  <ToUserName><![CDATA[toUser]]></ToUserName>
@@ -165,7 +139,7 @@ describe Sinatra::Wechat do
165
139
  <MsgId>1234567890123456</MsgId>
166
140
  </xml>
167
141
  EOF
168
- expect(last_response.status).to eq(200)
142
+ expect(last_response).to be_ok
169
143
  expect(last_response.body).to eq('1234567890123456')
170
144
 
171
145
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
@@ -181,7 +155,7 @@ describe Sinatra::Wechat do
181
155
  <MsgId>1234567890123456</MsgId>
182
156
  </xml>
183
157
  EOF
184
- expect(last_response.status).to eq(200)
158
+ expect(last_response).to be_ok
185
159
  expect(last_response.body).to eq('位置信息')
186
160
 
187
161
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
@@ -189,21 +163,16 @@ describe Sinatra::Wechat do
189
163
  <MsgType>unknown</MsgType>
190
164
  </xml>
191
165
  EOF
192
- expect(last_response.status).to eq(501)
166
+ expect(last_response.status).to eq(404)
193
167
  end
194
168
 
195
169
  it "should accept complex match" do
196
- def app
197
- instance = Sinatra.new do
198
- register Sinatra::Wechat
199
- end
200
- instance.wechat(:wechat_token => 'test-token') {
201
- future(lambda {|vs| vs[:to_user_name] == 'test' }, :content => %r{future}, :create_time => '1348831860') {
202
- 'complex match'
203
- }
204
- range(:to_user_name => 'tesa'..'testz') { 'range match' }
170
+ app.wechat(:wechat_token => 'test-token') {
171
+ future(lambda {|vs| vs[:to_user_name] == 'test' }, :content => %r{future}, :create_time => '1348831860') {
172
+ 'complex match'
205
173
  }
206
- end
174
+ range(:to_user_name => 'tesa'..'testz') { 'range match' }
175
+ }
207
176
 
208
177
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
209
178
  <xml>
@@ -215,51 +184,41 @@ describe Sinatra::Wechat do
215
184
  <MsgId>1234567890123456</MsgId>
216
185
  </xml>
217
186
  EOF
218
- expect(last_response.status).to eq(200)
187
+ expect(last_response).to be_ok
219
188
  expect(last_response.body).to eq('complex match')
220
189
 
221
-
222
-
223
190
  post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
224
191
  <xml>
225
192
  <ToUserName>test</ToUserName>
226
193
  <MsgType>range</MsgType>
227
194
  </xml>
228
195
  EOF
229
- expect(last_response.status).to eq(200)
196
+ expect(last_response).to be_ok
230
197
  expect(last_response.body).to eq('range match')
231
198
  end
232
199
 
233
200
  it "should raise error when invalid condition set" do
234
- instance = Sinatra.new do
235
- register Sinatra::Wechat
236
- end
237
201
  expect {
238
- instance.wechat(:wechat_token => 'test-token') {
202
+ app.wechat(:wechat_token => 'test-token') {
239
203
  future('invalid condition') { 'complex match' }
240
204
  }
241
205
  }.to raise_exception
242
206
  end
243
207
 
244
208
  it "can have multiple endpoint" do
245
- def app
246
- instance = Sinatra.new do
247
- register Sinatra::Wechat
209
+ app.wechat('/wechat1', :wechat_token => 'test-token') {
210
+ selector = lambda do |values|
211
+ x = values[:location_x].to_f
212
+ 20 < x && x < 30
248
213
  end
249
- instance.wechat('/wechat1', :wechat_token => 'test-token') {
250
- selector = lambda do |values|
251
- x = values[:location_x].to_f
252
- 20 < x && x < 30
253
- end
254
- location(selector) { 'matched location range' }
255
- }
256
- instance.wechat('/wechat2', :wechat_token => 'test') {
257
- text { 'this is another wechat endpoint' }
258
- }
259
- instance.wechat('/wechat3', :wechat_token => 'unknown', :message_validation => false) {
260
- text { 'disable message validation' }
261
- }
262
- end
214
+ location(selector) { 'matched location range' }
215
+ }
216
+ app.wechat('/wechat2', :wechat_token => 'test') {
217
+ text { 'this is another wechat endpoint' }
218
+ }
219
+ app.wechat('/wechat3', :wechat_token => 'unknown', :validate_msg => false) {
220
+ text { 'disable message validation' }
221
+ }
263
222
 
264
223
  post '/wechat1?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
265
224
  <xml>
@@ -269,16 +228,14 @@ describe Sinatra::Wechat do
269
228
  <Scale>20</Scale>
270
229
  </xml>
271
230
  EOF
272
- expect(last_response.status).to eq(200)
231
+ expect(last_response).to be_ok
273
232
  expect(last_response.body).to eq('matched location range')
274
233
 
275
-
276
234
  post '/wechat2?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', '<xml><MsgType>text</MsgType></xml>'
277
235
  expect(last_response.status).to eq(403)
278
236
 
279
-
280
237
  post '/wechat2?timestamp=201407191804&nonce=nonce&signature=8149d14c72f418819b1eaab851aeab2c308f15cc', '<xml><MsgType>text</MsgType></xml>'
281
- expect(last_response.status).to eq(200)
238
+ expect(last_response).to be_ok
282
239
  expect(last_response.body).to eq('this is another wechat endpoint')
283
240
 
284
241
  get '/wechat3?echostr=return'
@@ -287,6 +244,16 @@ describe Sinatra::Wechat do
287
244
 
288
245
  post '/wechat3', '<xml><MsgType>text</MsgType></xml>'
289
246
  expect(last_response.body).to eq('disable message validation')
247
+ end
248
+
249
+ it "can handle bad formatted xml" do
250
+ app.wechat(:wechat_token => 'test-token') { text { 'bare' } }
290
251
 
252
+ post '/?timestamp=201407191804&nonce=nonce&signature=9a91a1cea1cb60b87a9abb29dae06dce14721258', <<-EOF
253
+ <xml>
254
+ <invalid>message</invalid>>
255
+ </xml>
256
+ EOF
257
+ expect(last_response.status).to eq(404)
291
258
  end
292
259
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-wechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lu, Jun
@@ -122,7 +122,7 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
- description: Provide Extensible Sinatra API to support Wechat development mode
125
+ description: Provide extensible Sinatra API to support rapid Wechat development
126
126
  email:
127
127
  - luj1985@gmail.com
128
128
  executables: []