mp-weixin 0.0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.8.7
6
+
7
+ # uncomment this line if your project needs to run something other than `rake`:
8
+ # script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ source 'http://ruby.taobao.org'
3
+
4
+ gemspec
@@ -0,0 +1,46 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-weixin (0.0.4.2)
5
+ multi_json (>= 1.7.9)
6
+ multi_xml (>= 0.5.2)
7
+ nestful
8
+ rack
9
+ roxml
10
+
11
+ GEM
12
+ remote: http://ruby.taobao.org/
13
+ specs:
14
+ activesupport (3.2.12)
15
+ i18n (~> 0.6)
16
+ multi_json (~> 1.0)
17
+ diff-lcs (1.1.3)
18
+ i18n (0.6.5)
19
+ multi_json (1.8.2)
20
+ multi_xml (0.5.3)
21
+ nestful (1.0.7)
22
+ nokogiri (1.5.6)
23
+ rack (1.5.2)
24
+ rack-test (0.6.2)
25
+ rack (>= 1.0)
26
+ rake (10.0.3)
27
+ roxml (3.3.1)
28
+ activesupport (>= 2.3.0)
29
+ nokogiri (>= 1.3.3)
30
+ rspec (2.12.0)
31
+ rspec-core (~> 2.12.0)
32
+ rspec-expectations (~> 2.12.0)
33
+ rspec-mocks (~> 2.12.0)
34
+ rspec-core (2.12.2)
35
+ rspec-expectations (2.12.1)
36
+ diff-lcs (~> 1.1.3)
37
+ rspec-mocks (2.12.2)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ rack-test
44
+ rack-weixin!
45
+ rake
46
+ rspec (>= 2.0.0)
@@ -0,0 +1,113 @@
1
+ 微信公众平台 开放消息接口 Rack Middleware
2
+ ========================================
3
+
4
+ [![Build Status](https://travis-ci.org/wolfg1969/rack-weixin.png?branch=master)](https://travis-ci.org/wolfg1969/rack-weixin) [![Gem Version](https://badge.fury.io/rb/rack-weixin.png)](http://badge.fury.io/rb/rack-weixin)
5
+
6
+ * 验证微信请求 with 'weixin/middleware'
7
+ * 解析推送消息 with 'weixin/model'
8
+ * 生成回复消息 with 'weixin/model'
9
+
10
+
11
+ Installation
12
+ ------------
13
+ ```
14
+ $ gem install rack-weixin
15
+ ```
16
+
17
+
18
+ Usage
19
+ -----
20
+
21
+ A sinatra demo
22
+
23
+ ```ruby
24
+ # -*- encoding : utf-8 -*-
25
+ require 'sinatra'
26
+ require 'rack-weixin'
27
+
28
+ use Weixin::Middleware, 'your api token', '/your_app_root'
29
+
30
+ configure do
31
+ set :wx_id, 'your_weixin_account'
32
+ end
33
+
34
+ helpers do
35
+ def msg_router(msg)
36
+ case msg.MsgType
37
+ when 'text'
38
+ # text message handler
39
+ when 'image'
40
+ # image message handler
41
+ when 'location'
42
+ # location message handler
43
+ when 'link'
44
+ # link message handler
45
+ when 'event'
46
+ # event messge handler
47
+ when 'voice'
48
+ # voice message handler
49
+ when 'video'
50
+ # video message handler
51
+ else
52
+ Weixin.text_msg(msg.ToUserName, msg.FromUserName, '未知消息类型')
53
+ end
54
+ end
55
+ end
56
+
57
+ get '/your_app_root' do
58
+ params[:echostr]
59
+ end
60
+
61
+ post '/your_app_root' do
62
+ content_type :xml, 'charset' => 'utf-8'
63
+
64
+ message = request.env[Weixin::Middleware::WEIXIN_MSG]
65
+ logger.info "原始数据: #{request.env[Weixin::Middleware::WEIXIN_MSG_RAW]}"
66
+
67
+ # handle the message according to your business logic
68
+ msg_router(message) unless message.nil?
69
+ end
70
+ ```
71
+
72
+ ### Padrino下使用
73
+ 在`Gemfile`里加入:
74
+
75
+ gem 'rack-weixin'
76
+
77
+ 在`config/apps.rb`关闭以下两个:
78
+
79
+ set :protection, false
80
+ set :protect_from_csrf, false
81
+
82
+ 在`app.rb`里加入:
83
+
84
+ use Weixin::Middleware, 'your api token', '/your_app_root'
85
+
86
+ configure do
87
+ set :wx_id, 'your_weixin_account'
88
+ end
89
+
90
+
91
+
92
+ TODO
93
+ ----
94
+
95
+ Copyright
96
+ ---------
97
+
98
+ Permission is hereby granted, free of charge, to any person obtaining a copy
99
+ of this software and associated documentation files (the "Software"), to
100
+ deal in the Software without restriction, including without limitation the
101
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
102
+ sell copies of the Software, and to permit persons to whom the Software is
103
+ furnished to do so, subject to the following conditions:
104
+
105
+ The above copyright notice and this permission notice shall be included in
106
+ all copies or substantial portions of the Software.
107
+
108
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
109
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
110
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
111
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
112
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
113
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ desc 'Run all the tests'
5
+ task :default => [:test]
6
+
7
+ desc "Run all the tests"
8
+ task :test do
9
+ sh "rspec -Ilib/*:spec spec"
10
+ end
11
+
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'weixin/version'
3
+ require 'weixin/middleware'
4
+ require 'weixin/menu'
5
+ require 'weixin/model'
6
+
7
+ module Weixin
8
+
9
+ extend self
10
+
11
+ def music(title, desc, music_url, hq_music_url)
12
+ item = Music.new
13
+ item.Title = title
14
+ item.Description = desc
15
+ item.MusicUrl = music_url
16
+ item.HQMusicUrl = hq_music_url
17
+ item
18
+ end
19
+
20
+ def item(title, desc, pic_url, link_url)
21
+ item = Item.new
22
+ item.Title = title
23
+ item.Description = desc
24
+ item.PicUrl = pic_url
25
+ item.Url = link_url
26
+ item
27
+ end
28
+
29
+ def text_msg(from, to, content)
30
+ msg = TextReplyMessage.new
31
+ msg.ToUserName = to
32
+ msg.FromUserName = from
33
+ msg.Content = content
34
+ msg.to_xml
35
+ end
36
+
37
+ def music_msg(from, to, music)
38
+ msg = MusicReplyMessage.new
39
+ msg.ToUserName = to
40
+ msg.FromUserName = from
41
+ msg.Music = music
42
+ msg.to_xml
43
+ end
44
+
45
+ def news_msg(from, to, items)
46
+ msg = NewsReplyMessage.new
47
+ msg.ToUserName = to
48
+ msg.FromUserName = from
49
+ msg.Articles = items
50
+ msg.ArticleCount = items.count
51
+ msg.to_xml
52
+ end
53
+ end
@@ -0,0 +1,60 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'multi_json'
3
+ require 'nestful'
4
+
5
+ module Weixin
6
+
7
+ class Menu
8
+ def initialize(api, key)
9
+ @api = api
10
+ @key = key
11
+
12
+ @access_token = nil
13
+ @expired_at = Time.now
14
+ @endpoint = 'https://api.weixin.qq.com/cgi-bin'
15
+ end
16
+
17
+ def access_token
18
+ if Time.now >= @expired_at
19
+ authenticate
20
+ end
21
+ @access_token
22
+ end
23
+
24
+ def gw_path(method)
25
+ "/menu/#{method}?access_token=#{access_token}"
26
+ end
27
+
28
+ def gw_url(method)
29
+ "#{@endpoint}" + gw_path(method)
30
+ end
31
+
32
+ def get
33
+ request = Nestful.get gw_url('get') rescue nil
34
+ MultiJson.load(request.body) unless request.nil?
35
+ end
36
+
37
+ def add(menu)
38
+ request = Nestful::Connection.new(gw_url('create')).post(gw_path('create'), MultiJson.dump(menu)) rescue nil
39
+ unless request.nil?
40
+ errcode = MultiJson.load(request.body)['errcode']
41
+ return true if errcode == 0
42
+ end
43
+ false
44
+ end
45
+
46
+ def authenticate
47
+ url = "#{@endpoint}/token"
48
+ request = Nestful.get url, { :grant_type => 'client_credential', :appid => @api, :secret => @key } rescue nil
49
+ unless request.nil?
50
+ auth = MultiJson.load(request.body)
51
+ unless auth.has_key?('errcode')
52
+ @access_token = auth['access_token']
53
+ @expired_at = Time.now + auth['expires_in'].to_i
54
+ end
55
+ @access_token
56
+ end
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,62 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'digest/sha1'
3
+
4
+ module Weixin
5
+
6
+ class Middleware
7
+
8
+ POST_BODY = 'rack.input'.freeze
9
+ WEIXIN_MSG = 'weixin.msg'.freeze
10
+ WEIXIN_MSG_RAW = 'weixin.msg.raw'.freeze
11
+
12
+ def initialize(app, app_token, path)
13
+ @app = app
14
+ @app_token = app_token
15
+ @path = path
16
+ end
17
+
18
+ def call(env)
19
+ dup._call(env)
20
+ end
21
+
22
+ def _call(env)
23
+ if @path == env['PATH_INFO'].to_s && ['GET', 'POST'].include?(env['REQUEST_METHOD'])
24
+
25
+ @req = Rack::Request.new(env)
26
+ return invalid_request! unless request_is_valid?
27
+ return [
28
+ 200,
29
+ { 'Content-type' => 'text/plain', 'Content-length' => @req.params['echostr'].length.to_s },
30
+ [ @req.params['echostr'] ]
31
+ ] if @req.get?
32
+
33
+ raw_msg = env[POST_BODY].read
34
+ begin
35
+ env.update WEIXIN_MSG => Weixin::Message.factory(raw_msg), WEIXIN_MSG_RAW => raw_msg
36
+ @app.call(env)
37
+ rescue Exception => e
38
+ return [500, { 'Content-type' => 'text/html' }, ["Message parsing error: #{e.to_s}"]]
39
+ end
40
+ else
41
+ @app.call(env)
42
+ end
43
+
44
+ end
45
+
46
+ def invalid_request!
47
+ [401, { 'Content-type' => 'text/html', 'Content-Length' => '0'}, []]
48
+ end
49
+
50
+ def request_is_valid?
51
+ begin
52
+ param_array = [@app_token, @req.params['timestamp'], @req.params['nonce']]
53
+ sign = Digest::SHA1.hexdigest( param_array.sort.join )
54
+ sign == @req.params['signature'] ? true : false
55
+ rescue
56
+ false
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
@@ -0,0 +1,224 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'roxml'
3
+ require 'multi_xml'
4
+ require 'ostruct'
5
+
6
+ module Weixin
7
+
8
+ class Message
9
+
10
+ def initialize(hash)
11
+ @source = OpenStruct.new(hash)
12
+ end
13
+
14
+ def method_missing(method, *args, &block)
15
+ @source.send(method, *args, &block)
16
+ end
17
+
18
+ def CreateTime
19
+ @source.CreateTime.to_i
20
+ end
21
+
22
+ def MsgId
23
+ @source.MsgId.to_i
24
+ end
25
+
26
+ def Message.factory(xml)
27
+ hash = MultiXml.parse(xml)['xml']
28
+ case hash['MsgType']
29
+ when 'text'
30
+ TextMessage.new(hash)
31
+ when 'image'
32
+ ImageMessage.new(hash)
33
+ when 'location'
34
+ LocationMessage.new(hash)
35
+ when 'link'
36
+ LinkMessage.new(hash)
37
+ when 'event'
38
+ EventMessage.new(hash)
39
+ when 'voice'
40
+ VoiceMessage.new(hash)
41
+ when 'video'
42
+ VideoMessage.new(hash)
43
+ else
44
+ raise ArgumentError, 'Unknown Message'
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ # <xml>
51
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
52
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
53
+ # <CreateTime>1348831860</CreateTime>
54
+ # <MsgType><![CDATA[text]]></MsgType>
55
+ # <Content><![CDATA[this is a test]]></Content>
56
+ # <MsgId>1234567890123456</MsgId>
57
+ # </xml>
58
+ TextMessage = Class.new(Message)
59
+
60
+ # <xml>
61
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
62
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
63
+ # <CreateTime>1348831860</CreateTime>
64
+ # <MsgType><![CDATA[image]]></MsgType>
65
+ # <PicUrl><![CDATA[this is a url]]></PicUrl>
66
+ # <MsgId>1234567890123456</MsgId>
67
+ # </xml>
68
+ ImageMessage = Class.new(Message)
69
+
70
+ # <xml>
71
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
72
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
73
+ # <CreateTime>1351776360</CreateTime>
74
+ # <MsgType><![CDATA[link]]></MsgType>
75
+ # <Title><![CDATA[公众平台官网链接]]></Title>
76
+ # <Description><![CDATA[公众平台官网链接]]></Description>
77
+ # <Url><![CDATA[url]]></Url>
78
+ # <MsgId>1234567890123456</MsgId>
79
+ # </xml>
80
+ LinkMessage = Class.new(Message)
81
+
82
+ # <xml>
83
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
84
+ # <FromUserName><![CDATA[FromUser]]></FromUserName>
85
+ # <CreateTime>123456789</CreateTime>
86
+ # <MsgType><![CDATA[event]]></MsgType>
87
+ # <Event><![CDATA[EVENT]]></Event>
88
+ # <EventKey><![CDATA[EVENTKEY]]></EventKey>
89
+ # </xml>
90
+ EventMessage = Class.new(Message)
91
+
92
+ # <xml>
93
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
94
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
95
+ # <CreateTime>1351776360</CreateTime>
96
+ # <MsgType><![CDATA[location]]></MsgType>
97
+ # <Location_X>23.134521</Location_X>
98
+ # <Location_Y>113.358803</Location_Y>
99
+ # <Scale>20</Scale>
100
+ # <Label><![CDATA[位置信息]]></Label>
101
+ # <MsgId>1234567890123456</MsgId>
102
+ # </xml>
103
+ class LocationMessage < Message
104
+
105
+ def Location_X
106
+ @source.Location_X.to_f
107
+ end
108
+
109
+ def Location_Y
110
+ @source.Location_Y.to_f
111
+ end
112
+
113
+ def Scale
114
+ @source.Scale.to_i
115
+ end
116
+ end
117
+
118
+ # <xml>
119
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
120
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
121
+ # <CreateTime>1376632760</CreateTime>
122
+ # <MsgType><![CDATA[voice]]></MsgType>
123
+ # <MediaId><![CDATA[Qyb0tgux6QLjhL6ipvFZJ-kUt2tcQtkn0BU365Vt3wUAtqfGam4QpZU35RXVhv6G]]></MediaId>
124
+ # <Format><![CDATA[amr]]></Format>
125
+ # <MsgId>5912592682802219078</MsgId>
126
+ # <Recognition><![CDATA[]]></Recognition>
127
+ # </xml>
128
+ class VoiceMessage < Message
129
+
130
+ def MediaId
131
+ @source.MediaId
132
+ end
133
+
134
+ def Format
135
+ @source.Format
136
+ end
137
+ end
138
+
139
+
140
+ # <xml>
141
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
142
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
143
+ # <CreateTime>1376632994</CreateTime>
144
+ # <MsgType><![CDATA[video]]></MsgType>
145
+ # <MediaId><![CDATA[TAAGb6iS5LcZR1d5ICiZTWGWi6-Upic9tlWDpAKcNJA]]></MediaId>
146
+ # <ThumbMediaId><![CDATA[U-xulPW4kq6KKMWFNaBSPc65Bcgr7Qopwex0DfCeyQs]]></ThumbMediaId>
147
+ # <MsgId>5912593687824566343</MsgId>
148
+ # </xml>
149
+ class VideoMessage < Message
150
+
151
+ def MediaId
152
+ @source.MediaId
153
+ end
154
+
155
+ def ThumbMediaId
156
+ @source.ThumbMediaId
157
+ end
158
+ end
159
+
160
+ class ReplyMessage
161
+ include ROXML
162
+ xml_name :xml
163
+ #xml_convention :camelcase
164
+
165
+ xml_accessor :ToUserName, :cdata => true
166
+ xml_accessor :FromUserName, :cdata => true
167
+ xml_reader :CreateTime, :as => Integer
168
+ xml_reader :MsgType, :cdata => true
169
+
170
+ def initialize
171
+ @CreateTime = Time.now.to_i
172
+ end
173
+
174
+ def to_xml
175
+ super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
176
+ end
177
+ end
178
+
179
+ class TextReplyMessage < ReplyMessage
180
+ xml_accessor :Content, :cdata => true
181
+
182
+ def initialize
183
+ super
184
+ @MsgType = 'text'
185
+ end
186
+ end
187
+
188
+ class Music
189
+ include ROXML
190
+
191
+ xml_accessor :Title, :cdata => true
192
+ xml_accessor :Description, :cdata => true
193
+ xml_accessor :MusicUrl, :cdata => true
194
+ xml_accessor :HQMusicUrl, :cdata => true
195
+ end
196
+
197
+ class MusicReplyMessage < ReplyMessage
198
+ xml_accessor :Music, :as => Music
199
+
200
+ def initialize
201
+ super
202
+ @MsgType = 'music'
203
+ end
204
+ end
205
+
206
+ class Item
207
+ include ROXML
208
+
209
+ xml_accessor :Title, :cdata => true
210
+ xml_accessor :Description, :cdata => true
211
+ xml_accessor :PicUrl, :cdata => true
212
+ xml_accessor :Url, :cdata => true
213
+ end
214
+
215
+ class NewsReplyMessage < ReplyMessage
216
+ xml_accessor :ArticleCount, :as => Integer
217
+ xml_accessor :Articles, :as => [Item], :in => 'Articles', :from => 'item'
218
+
219
+ def initialize
220
+ super
221
+ @MsgType = 'news'
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,3 @@
1
+ module Weixin
2
+ VERSION = "0.0.4.2"
3
+ end
@@ -0,0 +1,30 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "weixin/version"
3
+
4
+ Gem::Specification.new do |s|
5
+
6
+ s.name = 'mp-weixin'
7
+ s.version = Weixin::VERSION
8
+
9
+ s.description = 'Rack middleware for Weixin apps: message validation/parser/generator'
10
+ s.summary = 'Rack middleware for Weixin apps'
11
+
12
+ s.authors = ['wolfg1969']
13
+ s.email = 'wolfg1969@gmail.com'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec\.rb/}
17
+
18
+ s.add_dependency 'rack'
19
+ s.add_dependency 'multi_json', '>= 1.7.9'
20
+ s.add_dependency 'multi_xml', '>= 0.5.2'
21
+ s.add_dependency 'roxml'
22
+ s.add_dependency 'nestful'
23
+
24
+ s.add_development_dependency 'rake'
25
+ s.add_development_dependency 'rack-test'
26
+ s.add_development_dependency 'rspec', '>= 2.0.0'
27
+
28
+ s.homepage = 'https://github.com/wolfg1969/rack-weixin'
29
+ s.require_paths = %w[lib]
30
+ end
@@ -0,0 +1,104 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rspec'
3
+ require 'weixin/model'
4
+
5
+ describe 'weixin/model' do
6
+
7
+ context 'Weixin:Message' do
8
+ it 'is a text message' do
9
+ msg = Weixin::Message.factory(%(
10
+ <xml>
11
+ <ToUserName><![CDATA[to]]></ToUserName>
12
+ <FromUserName><![CDATA[from]]></FromUserName>
13
+ <CreateTime>1360391199</CreateTime>
14
+ <MsgType><![CDATA[text]]></MsgType>
15
+ <Content><![CDATA[Hello2BizUser]]></Content>
16
+ <MsgId>5842835709471227904</MsgId>
17
+ </xml>
18
+ ))
19
+ msg.class.should == Weixin::TextMessage
20
+ msg.MsgType.should == 'text'
21
+ msg.ToUserName.should == 'to'
22
+ msg.FromUserName.should == 'from'
23
+ msg.CreateTime.should == 1360391199
24
+ msg.Content.should == 'Hello2BizUser'
25
+ msg.MsgId.should == 5842835709471227904
26
+ end
27
+
28
+ it 'is a image message' do
29
+ msg = Weixin::Message.factory(%(
30
+ <xml>
31
+ <ToUserName><![CDATA[to]]></ToUserName>
32
+ <FromUserName><![CDATA[from]]></FromUserName>
33
+ <CreateTime>1360391199</CreateTime>
34
+ <MsgType><![CDATA[image]]></MsgType>
35
+ <PicUrl><![CDATA[http://mmsns.qpic.cn/mmsns/Leiaa5NQF4FOTOSo3hXrEsGsodU2jHcWZiaInTxmTh6GaCIJ8hBHicIDA/0]]></PicUrl>
36
+ <MsgId>5842835709471227904</MsgId>
37
+ </xml>
38
+ ))
39
+ msg.class.should == Weixin::ImageMessage
40
+ msg.MsgType.should == 'image'
41
+ msg.ToUserName.should == 'to'
42
+ msg.FromUserName.should == 'from'
43
+ msg.CreateTime.should == 1360391199
44
+ msg.MsgId.should == 5842835709471227904
45
+ msg.PicUrl.should_not nil
46
+ end
47
+
48
+ it 'is a location message' do
49
+ msg = Weixin::Message.factory(%(
50
+ <xml>
51
+ <ToUserName><![CDATA[to]]></ToUserName>
52
+ <FromUserName><![CDATA[from]]></FromUserName>
53
+ <CreateTime>1360391199</CreateTime>
54
+ <MsgType><![CDATA[location]]></MsgType>
55
+ <Location_X>69.866013</Location_X>
56
+ <Location_Y>136.269449</Location_Y>
57
+ <Scale>15</Scale>
58
+ <Label><![CDATA[somewhere]]></Label>
59
+ <MsgId>5842835709471227904</MsgId>
60
+ </xml>
61
+ ))
62
+ msg.class.should == Weixin::LocationMessage
63
+ msg.MsgType.should == 'location'
64
+ msg.ToUserName.should == 'to'
65
+ msg.FromUserName.should == 'from'
66
+ msg.CreateTime.should == 1360391199
67
+ msg.MsgId.should == 5842835709471227904
68
+ msg.Label.should_not nil
69
+ msg.Scale.should == 15
70
+ msg.Location_X.should == 69.866013
71
+ msg.Location_Y.should == 136.269449
72
+ end
73
+
74
+ end
75
+
76
+ context 'Weixin::ReplyMessage' do
77
+
78
+ it 'is a text reply message' do
79
+ msg = Weixin::TextReplyMessage.new
80
+ msg.ToUserName = 'to'
81
+ msg.FromUserName = 'from'
82
+ msg.Content = 'blah'
83
+ msg.MsgType.should == 'text'
84
+ end
85
+
86
+ it 'is a news reply message' do
87
+ msg = Weixin::NewsReplyMessage.new
88
+ msg.ToUserName = 'to'
89
+ msg.FromUserName = 'from'
90
+ item1 = Weixin::Item.new
91
+ item1.Title = 'title1'
92
+ item1.Description = 'blah'
93
+ item2 = Weixin::Item.new
94
+ item2.Title = 'title2'
95
+ item2.Description = 'blah blah'
96
+ msg.Articles = [item1, item2]
97
+ msg.ArticleCount = 2
98
+ msg.MsgType.should == 'news'
99
+ #puts msg.to_xml
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rspec'
3
+ require 'rack'
4
+ require 'rack/test'
5
+ require 'rack/mock'
6
+ require 'digest/sha1'
7
+ require 'weixin/middleware'
8
+
9
+ describe "Weixin::Middleware" do
10
+
11
+ include Rack::Test::Methods
12
+
13
+ before(:all) do
14
+ @app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['hello']] }
15
+ @app_token = 'mytoken'
16
+ @context_path = '/'
17
+ end
18
+
19
+ def middleware()
20
+ Weixin::Middleware.new @app, @app_token, @context_path
21
+ end
22
+
23
+ def mock_env(echostr, token = @app_token, path = '/')
24
+ timestamp = Time.now.to_i.to_s
25
+ nonce = '123456'
26
+ param_array = [token, timestamp, nonce]
27
+ sign = Digest::SHA1.hexdigest( param_array.sort.join )
28
+ url = "#{path}?echostr=#{echostr}&timestamp=#{timestamp}&nonce=#{nonce}&signature=#{sign}"
29
+ Rack::MockRequest.env_for(url)
30
+ end
31
+
32
+ it 'does not match the path info' do
33
+ app = middleware
34
+ echostr = '123'
35
+ status, headers, body = app.call mock_env(echostr, @app_token, '/not_weixin_app')
36
+ status.should eq(200)
37
+ body.should_not == [echostr]
38
+ end
39
+
40
+ it 'is valid weixin request' do
41
+ app = middleware
42
+ echostr = '123'
43
+ status, headers, body = app.call mock_env(echostr)
44
+ status.should eq(200)
45
+ body.should == [echostr]
46
+ end
47
+
48
+ it 'is invalid weixin request' do
49
+ app = middleware
50
+ status, headers, body = app.call mock_env('123', 'wrong_token')
51
+ status.should eq(401)
52
+ body.should == []
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,199 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mp-weixin
3
+ version: !ruby/object:Gem::Version
4
+ hash: 91
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ - 2
11
+ version: 0.0.4.2
12
+ platform: ruby
13
+ authors:
14
+ - wolfg1969
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2014-01-06 00:00:00 +08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rack
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: multi_json
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 25
45
+ segments:
46
+ - 1
47
+ - 7
48
+ - 9
49
+ version: 1.7.9
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: multi_xml
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 15
61
+ segments:
62
+ - 0
63
+ - 5
64
+ - 2
65
+ version: 0.5.2
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: roxml
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ type: :runtime
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: nestful
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ type: :runtime
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: rake
98
+ prerelease: false
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ type: :development
109
+ version_requirements: *id006
110
+ - !ruby/object:Gem::Dependency
111
+ name: rack-test
112
+ prerelease: false
113
+ requirement: &id007 !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ type: :development
123
+ version_requirements: *id007
124
+ - !ruby/object:Gem::Dependency
125
+ name: rspec
126
+ prerelease: false
127
+ requirement: &id008 !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 15
133
+ segments:
134
+ - 2
135
+ - 0
136
+ - 0
137
+ version: 2.0.0
138
+ type: :development
139
+ version_requirements: *id008
140
+ description: "Rack middleware for Weixin apps: message validation/parser/generator"
141
+ email: wolfg1969@gmail.com
142
+ executables: []
143
+
144
+ extensions: []
145
+
146
+ extra_rdoc_files: []
147
+
148
+ files:
149
+ - .gitignore
150
+ - .travis.yml
151
+ - Gemfile
152
+ - Gemfile.lock
153
+ - README.md
154
+ - Rakefile
155
+ - lib/rack-weixin.rb
156
+ - lib/weixin/menu.rb
157
+ - lib/weixin/middleware.rb
158
+ - lib/weixin/model.rb
159
+ - lib/weixin/version.rb
160
+ - rack-weixin.gemspec
161
+ - spec/model_spec.rb
162
+ - spec/rack_middleware_spec.rb
163
+ has_rdoc: true
164
+ homepage: https://github.com/wolfg1969/rack-weixin
165
+ licenses: []
166
+
167
+ post_install_message:
168
+ rdoc_options: []
169
+
170
+ require_paths:
171
+ - lib
172
+ required_ruby_version: !ruby/object:Gem::Requirement
173
+ none: false
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ hash: 3
178
+ segments:
179
+ - 0
180
+ version: "0"
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ none: false
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ hash: 3
187
+ segments:
188
+ - 0
189
+ version: "0"
190
+ requirements: []
191
+
192
+ rubyforge_project:
193
+ rubygems_version: 1.3.7
194
+ signing_key:
195
+ specification_version: 3
196
+ summary: Rack middleware for Weixin apps
197
+ test_files:
198
+ - spec/model_spec.rb
199
+ - spec/rack_middleware_spec.rb