sinatra-wechat 0.0.2 → 0.1.0

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