rack-weixin 0.0.3.1 → 0.0.4.1

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.
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- source 'http://rubygems.org'
2
1
  source 'http://ruby.taobao.org'
3
2
 
4
3
  gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack-weixin (0.0.3.1)
4
+ rack-weixin (0.0.3)
5
5
  multi_xml (>= 0.5.2)
6
6
  rack
7
7
  roxml
@@ -14,7 +14,7 @@ GEM
14
14
  i18n (~> 0.6)
15
15
  multi_json (~> 1.0)
16
16
  diff-lcs (1.1.3)
17
- i18n (0.6.2)
17
+ i18n (0.6.1)
18
18
  multi_json (1.6.1)
19
19
  multi_xml (0.5.3)
20
20
  nokogiri (1.5.6)
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- 微信公众平台 开放消息接口 Rack Middlware
1
+ 微信公众平台 开放消息接口 Rack Middleware
2
2
  ========================================
3
3
 
4
+ Latest version: v0.4.0, supports subscribe/unsubscribe event
5
+
4
6
  * 验证微信请求 with 'weixin/middleware'
5
7
  * 解析推送消息 with 'weixin/model'
6
8
  * 生成回复消息 with 'weixin/model'
@@ -21,8 +23,7 @@ A sinatra demo
21
23
  ```ruby
22
24
  # -*- encoding : utf-8 -*-
23
25
  require 'sinatra'
24
- require 'weixin/middleware'
25
- require 'weixin/model'
26
+ require 'rack-weixin'
26
27
 
27
28
  use Weixin::Middleware, 'your api token', '/your_app_root'
28
29
 
@@ -31,14 +32,89 @@ configure do
31
32
  end
32
33
 
33
34
  helpers do
34
-
35
- def create_message(from, to, type, content, flag=0)
36
- msg = Weixin::TextReplyMessage.new
37
- msg.ToUserName = to
38
- msg.FromUserName = from
39
- msg.Content = content
40
- msg.to_xml
35
+ def text_parse(msg)
36
+ case msg.Content
37
+ when '我想听歌'
38
+ mp3 = Weixin.music(
39
+ '身骑白马',
40
+ '徐佳莹首张专辑《LALA创作专辑》的主打歌。这首歌的是由徐佳莹及其老师苏通达共同创作,其最大的特点是改编自台湾的民间剧种歌仔戏。',
41
+ 'http://nj.baidupcs.com/file/9cbb087ece3da309a31e05a7e14003c9?xcode=70d27743259294de1c42dff2d4720c05d4c19cd5e52a44f7&fid=204864837-250528-3177081425&time=1376666534&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-9NqxJyKhTJYx34SlHyPnK7%2B83vY%3D&to=nb&fm=B,B,T&expires=8h&rt=sh&r=756751042&logid=2171432620&sh=1&fn=%E8%BA%AB%E9%AA%91%E7%99%BD%E9%A9%AC.mp3',
42
+ 'http://nj.baidupcs.com/file/9cbb087ece3da309a31e05a7e14003c9?xcode=c59d095c8566efe4d948c3846269e02ed4c19cd5e52a44f7&fid=204864837-250528-3177081425&time=1376666631&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-yAO3TLeuAQ867emfUs0dYKgMtSE%3D&to=nb&fm=B,B,T&expires=8h&rt=sh&r=897038653&logid=4061975287&sh=1&fn=%E8%BA%AB%E9%AA%91%E7%99%BD%E9%A9%AC.mp3'
43
+ )
44
+ Weixin.music_msg(msg.FromUserName, msg.ToUserName, mp3)
45
+ else
46
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '这件商品暂时缺货~~')
47
+ end
48
+ end
49
+
50
+ def image_parse(msg)
51
+ item = Weixin.item(
52
+ '发现一个基于sinatra的web框架padrino',
53
+ 'gem install padrino
54
+ padrino g project myapp -d datamapper -b
55
+ cd myapp
56
+ padrino g admin
57
+ padrino rake dm:migrate seed
58
+ padrino start',
59
+ 'http://www.padrinorb.com/images/screens.jpg',
60
+ 'http://www.padrinorb.com/'
61
+ )
62
+ Weixin.news_msg(from, to, [item])
63
+ end
64
+
65
+ def location_parse(msg)
66
+ "http://maps.googleapis.com/maps/api/geocode/json?latlng=#{msg.Location_X},#{msg.Location_Y}"
67
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, "#{msg.Label} 周围没有妹子~")
68
+ end
69
+
70
+ def link_parse(msg)
71
+ # Mechanize.new.get(msg.Url) # 爬虫
72
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '这件商品暂时缺货~~')
73
+ end
74
+
75
+ def event_parse(msg)
76
+ case msg.Event
77
+ when 'subscribe' # 订阅
78
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '欢迎订阅【数字尾巴】~~')
79
+ when 'unsubscribe' # 退订
80
+ # 又少了名用户
81
+ when 'CLICK' # 点击菜单
82
+ menu_parse(msg)
83
+ else
84
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '作为一名程序猿,表示压力山大~~')
41
85
  end
86
+ end
87
+
88
+ def menu_parse(msg)
89
+ case msg.EventKey
90
+ when 'profile'
91
+ # ???
92
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '主人您的个人信息丢失啦~')
93
+ else
94
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '您想来点什么?')
95
+ end
96
+ end
97
+
98
+ def msg_router(msg)
99
+ case msg.MsgType
100
+ when 'text'
101
+ text_parse(msg)
102
+ when 'image'
103
+ image_parse(msg)
104
+ when 'location'
105
+ location_parse(msg)
106
+ when 'link'
107
+ link_parse(msg)
108
+ when 'event'
109
+ event_parse(msg)
110
+ when 'voice'
111
+ voice_parse(msg)
112
+ when 'video'
113
+ video_parse(msg)
114
+ else
115
+ Weixin.text_msg(msg.FromUserName, msg.ToUserName, '作为一名程序猿,表示压力山大~~')
116
+ end
117
+ end
42
118
  end
43
119
 
44
120
  get '/your_app_root' do
@@ -49,17 +125,8 @@ post '/your_app_root' do
49
125
  content_type :xml, 'charset' => 'utf-8'
50
126
 
51
127
  message = request.env[Weixin::Middleware::WEIXIN_MSG]
52
- logger.info "message: #{request.env[Weixin::Middleware::WEIXIN_MSG_RAW]}"
53
-
54
- from = message.FromUserName
55
- if message.class == Weixin::TextMessage
56
- content = message.Content
57
- if content == 'Hello2BizUser'
58
- reply_msg_content = "感谢关注!#{reply_msg_content}"
59
- end
60
- end
61
-
62
- create_message(settings.wx_id, from, 'text', reply_msg_content)
128
+ logger.info "原始数据: #{request.env[Weixin::Middleware::WEIXIN_MSG_RAW]}"
129
+ msg_router(message) unless message.nil?
63
130
  end
64
131
  ```
65
132
 
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'weixin/version'
3
+ require 'weixin/middleware'
4
+ require 'weixin/menu'
5
+
6
+ module Weixin
7
+
8
+ autoload :Music, 'weixin/model'
9
+ autoload :Item, 'weixin/model'
10
+
11
+ autoload :TextReplyMessage, 'weixin/model'
12
+ autoload :MusicReplyMessage, 'weixin/model'
13
+ autoload :NewsReplyMessage, 'weixin/model'
14
+
15
+ extend self
16
+
17
+ def music(title, desc, music_url, hq_music_url)
18
+ item = Music.new
19
+ item.Title = title
20
+ item.Description = desc
21
+ item.MusicUrl = music_url
22
+ item.HQMusicUrl = hq_music_url
23
+ item
24
+ end
25
+
26
+ def item(title, desc, pic_url, link_url)
27
+ item = Item.new
28
+ item.Title = title
29
+ item.Description = desc
30
+ item.PicUrl = pic_url
31
+ item.Url = link_url
32
+ item
33
+ end
34
+
35
+ def text_msg(from, to, content, flag=0)
36
+ msg = TextReplyMessage.new
37
+ msg.ToUserName = to
38
+ msg.FromUserName = from
39
+ msg.Content = content
40
+ msg.to_xml
41
+ end
42
+
43
+ def music_msg(from, to, music)
44
+ msg = MusicReplyMessage.new
45
+ msg.ToUserName = to
46
+ msg.FromUserName = from
47
+ msg.Music = music
48
+ msg.to_xml
49
+ end
50
+
51
+ def news_msg(from, to, items)
52
+ msg = NewsReplyMessage.new
53
+ msg.ToUserName = to
54
+ msg.FromUserName = from
55
+ msg.Articles = items
56
+ msg.ArticleCount = items.count
57
+ msg.to_xml
58
+ end
59
+ 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
@@ -32,10 +32,10 @@ module Weixin
32
32
 
33
33
  raw_msg = env[POST_BODY].read
34
34
  begin
35
- env.update WEIXIN_MSG => Weixin::Message.factory(raw_msg), WEIXIN_MSG_RAW => env[POST_BODY]
35
+ env.update WEIXIN_MSG => Weixin::Message.factory(raw_msg), WEIXIN_MSG_RAW => raw_msg
36
36
  @app.call(env)
37
37
  rescue Exception => e
38
- return [500, { 'Content-typ' => 'text/html' }, ["Message parsing error: #{e.to_s}"]]
38
+ return [500, { 'Content-type' => 'text/html' }, ["Message parsing error: #{e.to_s}"]]
39
39
  end
40
40
  else
41
41
  @app.call(env)
data/lib/weixin/model.rb CHANGED
@@ -36,6 +36,10 @@ module Weixin
36
36
  LinkMessage.new(hash)
37
37
  when 'event'
38
38
  EventMessage.new(hash)
39
+ when 'voice'
40
+ VoiceMessage.new(hash)
41
+ when 'video'
42
+ VideoMessage.new(hash)
39
43
  else
40
44
  raise ArgumentError, 'Unknown Message'
41
45
  end
@@ -43,10 +47,59 @@ module Weixin
43
47
 
44
48
  end
45
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>
46
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>
47
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>
48
80
  LinkMessage = Class.new(Message)
49
-
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>
50
103
  class LocationMessage < Message
51
104
 
52
105
  def Location_X
@@ -62,18 +115,45 @@ module Weixin
62
115
  end
63
116
  end
64
117
 
65
- class EventMessage < Message
66
-
67
- def Latitude
68
- @source.Latitude.to_f
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
69
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
70
150
 
71
- def Longitude
72
- @source.Longitude.to_f
151
+ def MediaId
152
+ @source.MediaId
73
153
  end
74
154
 
75
- def Precision
76
- @source.Precision.to_f
155
+ def ThumbMediaId
156
+ @source.ThumbMediaId
77
157
  end
78
158
  end
79
159
 
@@ -136,7 +216,7 @@ module Weixin
136
216
 
137
217
  class NewsReplyMessage < ReplyMessage
138
218
  xml_accessor :ArticleCount, :as => Integer
139
- xml_accessor :Articles, :as => [Item], :in => 'Articles', :from => 'Item'
219
+ xml_accessor :Articles, :as => [Item], :in => 'Articles', :from => 'item'
140
220
 
141
221
  def initialize
142
222
  super
@@ -1,3 +1,3 @@
1
1
  module Weixin
2
- VERSION = "0.0.3.1"
2
+ VERSION = "0.0.4.1"
3
3
  end
data/rack-weixin.gemspec CHANGED
@@ -16,12 +16,15 @@ Gem::Specification.new do |s|
16
16
  s.test_files = s.files.select {|path| path =~ /^spec\/.*_spec\.rb/}
17
17
 
18
18
  s.add_dependency 'rack'
19
+ s.add_dependency 'multi_json', '>= 1.7.9'
19
20
  s.add_dependency 'multi_xml', '>= 0.5.2'
20
21
  s.add_dependency 'roxml'
22
+ s.add_dependency 'nestful'
23
+
21
24
  s.add_development_dependency 'rake'
22
25
  s.add_development_dependency 'rack-test'
23
26
  s.add_development_dependency 'rspec', '>= 2.0.0'
24
27
 
25
28
  s.homepage = 'https://github.com/wolfg1969/rack-weixin'
26
29
  s.require_paths = %w[lib]
27
- end
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-weixin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3.1
4
+ version: 0.0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-27 00:00:00.000000000 Z
12
+ date: 2013-12-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.7.9
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.7.9
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: multi_xml
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +75,22 @@ dependencies:
59
75
  - - ! '>='
60
76
  - !ruby/object:Gem::Version
61
77
  version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: nestful
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
62
94
  - !ruby/object:Gem::Dependency
63
95
  name: rake
64
96
  requirement: !ruby/object:Gem::Requirement
@@ -118,6 +150,8 @@ files:
118
150
  - Gemfile.lock
119
151
  - README.md
120
152
  - Rakefile
153
+ - lib/rack-weixin.rb
154
+ - lib/weixin/menu.rb
121
155
  - lib/weixin/middleware.rb
122
156
  - lib/weixin/model.rb
123
157
  - lib/weixin/version.rb
@@ -136,18 +170,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
170
  - - ! '>='
137
171
  - !ruby/object:Gem::Version
138
172
  version: '0'
139
- segments:
140
- - 0
141
- hash: -95414285
142
173
  required_rubygems_version: !ruby/object:Gem::Requirement
143
174
  none: false
144
175
  requirements:
145
176
  - - ! '>='
146
177
  - !ruby/object:Gem::Version
147
178
  version: '0'
148
- segments:
149
- - 0
150
- hash: -95414285
151
179
  requirements: []
152
180
  rubyforge_project:
153
181
  rubygems_version: 1.8.24