rack-weixin 0.0.3.1 → 0.0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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