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 +4 -4
- data/README.md +2 -2
- data/examples/wechat_example.rb +1 -1
- data/lib/sinatra/version.rb +1 -1
- data/lib/sinatra/wechat.rb +37 -44
- data/sinatra-wechat.gemspec +1 -1
- data/spec/spec_helper.rb +4 -0
- data/spec/wechat_spec.rb +73 -106
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86233c3037ef8bd2e6d48fa0d321aa6a4279e41f
|
4
|
+
data.tar.gz: 2d2eb697ffa61f47e149a4718c0b21b4dbdb7212
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 `:
|
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', :
|
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]
|
data/examples/wechat_example.rb
CHANGED
@@ -17,7 +17,7 @@ location_event_reply = proc {
|
|
17
17
|
builder.to_xml
|
18
18
|
}
|
19
19
|
|
20
|
-
wechat('/wechat', :wechat_token => 'test-token', :
|
20
|
+
wechat('/wechat', :wechat_token => 'test-token', :validate_msg => false) {
|
21
21
|
location {
|
22
22
|
instance_eval &location_event_reply
|
23
23
|
}
|
data/lib/sinatra/version.rb
CHANGED
data/lib/sinatra/wechat.rb
CHANGED
@@ -1,65 +1,65 @@
|
|
1
|
-
|
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
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
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 |
|
18
|
-
if
|
17
|
+
matchers = args.collect do |arg|
|
18
|
+
if arg.respond_to?(:call) then lambda &arg
|
19
19
|
# for named parameters
|
20
|
-
elsif
|
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
|
25
|
-
|
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
|
31
|
-
|
32
|
-
handlers = @message_handlers[
|
33
|
-
|
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: '',
|
39
|
-
|
40
|
-
|
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
|
-
|
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
|
52
|
+
halt 400 if body.empty? # bad request, cannot handle this kind of message
|
53
53
|
|
54
|
-
|
55
|
-
values =
|
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.
|
62
|
-
halt
|
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(
|
72
|
-
|
73
|
-
|
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
|
data/sinatra-wechat.gemspec
CHANGED
@@ -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
|
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
data/spec/wechat_spec.rb
CHANGED
@@ -1,48 +1,36 @@
|
|
1
|
-
|
1
|
+
require_relative 'spec_helper'
|
2
2
|
|
3
3
|
describe Sinatra::Wechat do
|
4
|
-
include Rack::Test::Methods
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 '/', {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
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 "
|
38
|
-
|
39
|
-
|
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
|
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
|
-
|
67
|
-
|
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
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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(
|
166
|
+
expect(last_response.status).to eq(404)
|
193
167
|
end
|
194
168
|
|
195
169
|
it "should accept complex match" do
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
246
|
-
|
247
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
}
|
256
|
-
|
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
|
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
|
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
|
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
|
125
|
+
description: Provide extensible Sinatra API to support rapid Wechat development
|
126
126
|
email:
|
127
127
|
- luj1985@gmail.com
|
128
128
|
executables: []
|