mp_weixin 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 +15 -0
- data/.gitignore +22 -0
- data/.rspec +7 -0
- data/.travis.yml +20 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +308 -0
- data/Rakefile +31 -0
- data/lib/config/mp_weixin_error.yml +82 -0
- data/lib/mp_weixin.rb +59 -0
- data/lib/mp_weixin/access_token.rb +172 -0
- data/lib/mp_weixin/client.rb +199 -0
- data/lib/mp_weixin/config.rb +36 -0
- data/lib/mp_weixin/error.rb +27 -0
- data/lib/mp_weixin/interface/base.rb +43 -0
- data/lib/mp_weixin/interface/group.rb +92 -0
- data/lib/mp_weixin/interface/menu.rb +73 -0
- data/lib/mp_weixin/interface/message.rb +38 -0
- data/lib/mp_weixin/interface/promotion.rb +48 -0
- data/lib/mp_weixin/interface/user.rb +39 -0
- data/lib/mp_weixin/models/event.rb +123 -0
- data/lib/mp_weixin/models/message.rb +227 -0
- data/lib/mp_weixin/models/reply_message.rb +180 -0
- data/lib/mp_weixin/response.rb +93 -0
- data/lib/mp_weixin/response_rule.rb +46 -0
- data/lib/mp_weixin/server.rb +39 -0
- data/lib/mp_weixin/server_helper.rb +94 -0
- data/lib/mp_weixin/version.rb +3 -0
- data/lib/support/active_model.rb +3 -0
- data/lib/support/active_model/model.rb +99 -0
- data/mp_weixin.gemspec +44 -0
- data/spec/client_spec.rb +87 -0
- data/spec/mp_weixin/access_token_spec.rb +140 -0
- data/spec/mp_weixin/client_spec.rb +111 -0
- data/spec/mp_weixin/config_spec.rb +24 -0
- data/spec/mp_weixin/interface/base_spec.rb +16 -0
- data/spec/mp_weixin/interface/group_spec.rb +133 -0
- data/spec/mp_weixin/interface/menu_spec.rb +72 -0
- data/spec/mp_weixin/interface/message_spec.rb +36 -0
- data/spec/mp_weixin/interface/promotion_spec.rb +48 -0
- data/spec/mp_weixin/interface/user_spec.rb +76 -0
- data/spec/mp_weixin/models/event_spec.rb +94 -0
- data/spec/mp_weixin/models/message_spec.rb +300 -0
- data/spec/mp_weixin/models/reply_message_spec.rb +365 -0
- data/spec/mp_weixin/server_helper_spec.rb +165 -0
- data/spec/mp_weixin/server_spec.rb +56 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/mp_weixin.rb +7 -0
- data/spec/support/rspec_mixin.rb +8 -0
- data/spec/support/weixin.yml +12 -0
- metadata +363 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module MpWeixin
|
|
4
|
+
# The MpWeixin::Message class
|
|
5
|
+
#
|
|
6
|
+
class Message
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
attr_accessor :ToUserName, :FromUserName,
|
|
9
|
+
:CreateTime, :MsgType, :MsgId
|
|
10
|
+
|
|
11
|
+
# Instantiate a new Message with a hash of attributes
|
|
12
|
+
#
|
|
13
|
+
# @param [Hash] attributes the attributes value
|
|
14
|
+
def initialize(attributes = nil)
|
|
15
|
+
# Dynamic attr_accessible
|
|
16
|
+
# maybe cause secret problem
|
|
17
|
+
# singleton_class.class_eval do
|
|
18
|
+
# attr_accessor *attributes.keys
|
|
19
|
+
# end
|
|
20
|
+
|
|
21
|
+
super
|
|
22
|
+
@source = ActiveSupport::HashWithIndifferentAccess.new(attributes)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# same as @attributes CreateTime of an Message instance
|
|
26
|
+
#
|
|
27
|
+
# @return [Integer]
|
|
28
|
+
def create_time
|
|
29
|
+
self.CreateTime.to_i
|
|
30
|
+
end
|
|
31
|
+
# alias :CreateTime :create_time
|
|
32
|
+
|
|
33
|
+
# convert create_time to an Time instance
|
|
34
|
+
#
|
|
35
|
+
# @return [Time]
|
|
36
|
+
def created_at
|
|
37
|
+
Time.at create_time rescue nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def msg_id
|
|
41
|
+
self.MsgId.to_i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# initialize an ReplyMessage
|
|
45
|
+
# @msg_type [string] the MsgType of ReplyMessage
|
|
46
|
+
# @attributes [Hash] the attributes of ReplyMessage
|
|
47
|
+
# @return an instance of #{MsgType}ReplyMessage
|
|
48
|
+
def reply(msg_type, attributes)
|
|
49
|
+
if attributes.is_a?(Hash)
|
|
50
|
+
attributes = attributes.deep_symbolize_keys
|
|
51
|
+
attributes.reverse_merge!({
|
|
52
|
+
ToUserName: self.FromUserName,
|
|
53
|
+
FromUserName: self.ToUserName
|
|
54
|
+
})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
case msg_type
|
|
58
|
+
when 'text'
|
|
59
|
+
MpWeixin::TextReplyMessage.new(attributes)
|
|
60
|
+
when 'image'
|
|
61
|
+
MpWeixin::ImageReplyMessage.new(attributes)
|
|
62
|
+
when 'voice'
|
|
63
|
+
MpWeixin::VoiceReplyMessage.new(attributes)
|
|
64
|
+
when 'video'
|
|
65
|
+
MpWeixin::VideoReplyMessage.new(attributes)
|
|
66
|
+
when 'music'
|
|
67
|
+
MpWeixin::MusicReplyMessage.new(attributes)
|
|
68
|
+
when 'news'
|
|
69
|
+
MpWeixin::NewsReplyMessage.new(attributes)
|
|
70
|
+
else
|
|
71
|
+
# raise 'Unknown Message data'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# initialize an TextReplyMessage
|
|
76
|
+
# @attributes [Hash] the attributes of TextReplyMessage
|
|
77
|
+
def reply_text_message(attributes)
|
|
78
|
+
reply("text", attributes)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# initialize an ImageReplyMessage
|
|
82
|
+
# @attributes [Hash] the attributes of ImageReplyMessage
|
|
83
|
+
def reply_image_message(attributes)
|
|
84
|
+
reply("image", attributes)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# initialize an VoiceReplyMessage
|
|
88
|
+
# @attributes [Hash] the attributes of VoiceReplyMessage
|
|
89
|
+
def reply_voice_message(attributes)
|
|
90
|
+
reply("voice", attributes)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# initialize an VideoReplyMessage
|
|
94
|
+
# @attributes [Hash] the attributes of VideoReplyMessage
|
|
95
|
+
def reply_video_message(attributes)
|
|
96
|
+
reply("video", attributes)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# initialize an MusicReplyMessage
|
|
100
|
+
# @attributes [Hash] the attributes of MusicReplyMessage
|
|
101
|
+
def reply_music_message(attributes)
|
|
102
|
+
reply("music", attributes)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# initialize an NewsReplyMessage
|
|
106
|
+
# @attributes [Hash] the attributes of NewsReplyMessage
|
|
107
|
+
def reply_news_message(attributes)
|
|
108
|
+
reply("news", attributes)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class << self
|
|
112
|
+
def from_xml(xml)
|
|
113
|
+
begin
|
|
114
|
+
hash = MultiXml.parse(xml)['xml']
|
|
115
|
+
message = case hash['MsgType']
|
|
116
|
+
when 'text'
|
|
117
|
+
TextMessage.new(hash)
|
|
118
|
+
when 'image'
|
|
119
|
+
ImageMessage.new(hash)
|
|
120
|
+
when 'location'
|
|
121
|
+
LocationMessage.new(hash)
|
|
122
|
+
when 'link'
|
|
123
|
+
LinkMessage.new(hash)
|
|
124
|
+
when 'event'
|
|
125
|
+
# EventMessage.new(hash)
|
|
126
|
+
Event.from_xml(xml)
|
|
127
|
+
when 'voice'
|
|
128
|
+
VoiceMessage.new(hash)
|
|
129
|
+
when 'video'
|
|
130
|
+
VideoMessage.new(hash)
|
|
131
|
+
else
|
|
132
|
+
# raise 'Unknown Message data'
|
|
133
|
+
end
|
|
134
|
+
rescue
|
|
135
|
+
logger.info('Unknown Message data #{xml}') if self.respond_to?(:logger)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# <xml>
|
|
142
|
+
# <ToUserName><![CDATA[toUser]]></ToUserName>
|
|
143
|
+
# <FromUserName><![CDATA[fromUser]]></FromUserName>
|
|
144
|
+
# <CreateTime>1348831860</CreateTime>
|
|
145
|
+
# <MsgType><![CDATA[text]]></MsgType>
|
|
146
|
+
# <Content><![CDATA[this is a test]]></Content>
|
|
147
|
+
# <MsgId>1234567890123456</MsgId>
|
|
148
|
+
# </xml>
|
|
149
|
+
# TextMessage = Class.new(Message)
|
|
150
|
+
class TextMessage < Message
|
|
151
|
+
attr_accessor :Content
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# <xml>
|
|
155
|
+
# <ToUserName><![CDATA[toUser]]></ToUserName>
|
|
156
|
+
# <FromUserName><![CDATA[fromUser]]></FromUserName>
|
|
157
|
+
# <CreateTime>1348831860</CreateTime>
|
|
158
|
+
# <MsgType><![CDATA[image]]></MsgType>
|
|
159
|
+
# <PicUrl><![CDATA[this is a url]]></PicUrl>
|
|
160
|
+
# <MsgId>1234567890123456</MsgId>
|
|
161
|
+
# </xml>
|
|
162
|
+
# ImageMessage = Class.new(Message)
|
|
163
|
+
class ImageMessage < Message
|
|
164
|
+
attr_accessor :PicUrl, :MediaId
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# <xml>
|
|
168
|
+
# <ToUserName><![CDATA[toUser]]></ToUserName>
|
|
169
|
+
# <FromUserName><![CDATA[fromUser]]></FromUserName>
|
|
170
|
+
# <CreateTime>1376632760</CreateTime>
|
|
171
|
+
# <MsgType><![CDATA[voice]]></MsgType>
|
|
172
|
+
# <MediaId><![CDATA[Qyb0tgux6QLjhL6ipvFZJ-kUt2tcQtkn0BU365Vt3wUAtqfGam4QpZU35RXVhv6G]]></MediaId>
|
|
173
|
+
# <Format><![CDATA[amr]]></Format>
|
|
174
|
+
# <MsgId>5912592682802219078</MsgId>
|
|
175
|
+
# <Recognition><![CDATA[]]></Recognition>
|
|
176
|
+
# </xml>
|
|
177
|
+
# VoiceMessage = Class.new(Message)
|
|
178
|
+
class VoiceMessage < Message
|
|
179
|
+
attr_accessor :MediaId, :Format
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# <xml>
|
|
183
|
+
# <ToUserName><![CDATA[toUser]]></ToUserName>
|
|
184
|
+
# <FromUserName><![CDATA[fromUser]]></FromUserName>
|
|
185
|
+
# <CreateTime>1376632994</CreateTime>
|
|
186
|
+
# <MsgType><![CDATA[video]]></MsgType>
|
|
187
|
+
# <MediaId><![CDATA[TAAGb6iS5LcZR1d5ICiZTWGWi6-Upic9tlWDpAKcNJA]]></MediaId>
|
|
188
|
+
# <ThumbMediaId><![CDATA[U-xulPW4kq6KKMWFNaBSPc65Bcgr7Qopwex0DfCeyQs]]></ThumbMediaId>
|
|
189
|
+
# <MsgId>5912593687824566343</MsgId>
|
|
190
|
+
# </xml>
|
|
191
|
+
# VideoMessage = Class.new(Message)
|
|
192
|
+
class VideoMessage < Message
|
|
193
|
+
attr_accessor :MediaId, :ThumbMediaId
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# <xml>
|
|
197
|
+
# <ToUserName><![CDATA[toUser]]></ToUserName>
|
|
198
|
+
# <FromUserName><![CDATA[fromUser]]></FromUserName>
|
|
199
|
+
# <CreateTime>1351776360</CreateTime>
|
|
200
|
+
# <MsgType><![CDATA[location]]></MsgType>
|
|
201
|
+
# <Location_X>23.134521</Location_X>
|
|
202
|
+
# <Location_Y>113.358803</Location_Y>
|
|
203
|
+
# <Scale>20</Scale>
|
|
204
|
+
# <Label><![CDATA[位置信息]]></Label>
|
|
205
|
+
# <MsgId>1234567890123456</MsgId>
|
|
206
|
+
# </xml>
|
|
207
|
+
# LocationMessage = Class.new(Message)
|
|
208
|
+
class LocationMessage < Message
|
|
209
|
+
attr_accessor :Location_X , :Location_Y, :Scale, :Label
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# <xml>
|
|
213
|
+
# <ToUserName><![CDATA[toUser]]></ToUserName>
|
|
214
|
+
# <FromUserName><![CDATA[fromUser]]></FromUserName>
|
|
215
|
+
# <CreateTime>1351776360</CreateTime>
|
|
216
|
+
# <MsgType><![CDATA[link]]></MsgType>
|
|
217
|
+
# <Title><![CDATA[公众平台官网链接]]></Title>
|
|
218
|
+
# <Description><![CDATA[公众平台官网链接]]></Description>
|
|
219
|
+
# <Url><![CDATA[url]]></Url>
|
|
220
|
+
# <MsgId>1234567890123456</MsgId>
|
|
221
|
+
# </xml>
|
|
222
|
+
#
|
|
223
|
+
# LinkMessage = Class.new(Message)
|
|
224
|
+
class LinkMessage < Message
|
|
225
|
+
attr_accessor :Title , :Description, :Url
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module MpWeixin
|
|
4
|
+
class ReplyMessage
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
include ROXML
|
|
7
|
+
|
|
8
|
+
xml_name :xml
|
|
9
|
+
#xml_convention :camelcase
|
|
10
|
+
|
|
11
|
+
attr_accessor :ToUserName, :FromUserName,
|
|
12
|
+
:CreateTime, :MsgType
|
|
13
|
+
|
|
14
|
+
xml_accessor :ToUserName, :cdata => true
|
|
15
|
+
xml_accessor :FromUserName, :cdata => true
|
|
16
|
+
xml_reader :CreateTime, :as => Integer
|
|
17
|
+
xml_reader :MsgType, :cdata => true
|
|
18
|
+
|
|
19
|
+
def initialize(attributes = {})
|
|
20
|
+
super
|
|
21
|
+
@CreateTime ||= Time.now.to_i
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_xml
|
|
25
|
+
super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def set_nested_attr(nested_attr_name)
|
|
30
|
+
# define_method(:hi) { puts "Hello World!" }
|
|
31
|
+
class_eval <<-STR
|
|
32
|
+
def #{nested_attr_name}=(nested_attr = [])
|
|
33
|
+
@#{nested_attr_name} = nested_attr.is_a?(#{nested_attr_name}) ? nested_attr : #{nested_attr_name}.new(nested_attr)
|
|
34
|
+
end
|
|
35
|
+
STR
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TextReplyMessage < ReplyMessage
|
|
41
|
+
# include ActiveModel::Model
|
|
42
|
+
|
|
43
|
+
attr_accessor :Content
|
|
44
|
+
xml_accessor :Content, :cdata => true
|
|
45
|
+
|
|
46
|
+
def initialize(attributes = {})
|
|
47
|
+
super
|
|
48
|
+
@MsgType ||= 'text'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Image
|
|
53
|
+
include ActiveModel::Model
|
|
54
|
+
include ROXML
|
|
55
|
+
|
|
56
|
+
attr_accessor :MediaId
|
|
57
|
+
xml_accessor :MediaId
|
|
58
|
+
|
|
59
|
+
# other important functionality
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class ImageReplyMessage < ReplyMessage
|
|
63
|
+
attr_accessor :Image
|
|
64
|
+
xml_accessor :Image, as: Image, :cdata => true
|
|
65
|
+
|
|
66
|
+
set_nested_attr :Image
|
|
67
|
+
def initialize(attributes = {})
|
|
68
|
+
super
|
|
69
|
+
@MsgType ||= 'image'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Voice
|
|
75
|
+
include ActiveModel::Model
|
|
76
|
+
include ROXML
|
|
77
|
+
|
|
78
|
+
xml_accessor :MediaId
|
|
79
|
+
|
|
80
|
+
# other important functionality
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class VoiceReplyMessage < ReplyMessage
|
|
84
|
+
attr_accessor :Voice
|
|
85
|
+
xml_accessor :Voice, as: Voice, :cdata => true
|
|
86
|
+
set_nested_attr :Voice
|
|
87
|
+
|
|
88
|
+
def initialize(attributes = {})
|
|
89
|
+
super
|
|
90
|
+
@MsgType ||= 'voice'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Video
|
|
96
|
+
include ActiveModel::Model
|
|
97
|
+
include ROXML
|
|
98
|
+
|
|
99
|
+
attr_accessor :MediaId, :Title, :Description
|
|
100
|
+
|
|
101
|
+
xml_accessor :MediaId, :cdata => true
|
|
102
|
+
xml_accessor :Title, :cdata => true
|
|
103
|
+
xml_accessor :Description, :cdata => true
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class VideoReplyMessage < ReplyMessage
|
|
108
|
+
attr_accessor :Video
|
|
109
|
+
xml_accessor :Video, as: Video, :cdata => true
|
|
110
|
+
set_nested_attr :Video
|
|
111
|
+
|
|
112
|
+
def initialize(attributes = {})
|
|
113
|
+
super
|
|
114
|
+
@MsgType ||= 'video'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class Music
|
|
120
|
+
include ActiveModel::Model
|
|
121
|
+
include ROXML
|
|
122
|
+
|
|
123
|
+
attr_accessor :Title, :Description, :MusicUrl, :HQMusicUrl, :ThumbMediaId
|
|
124
|
+
|
|
125
|
+
xml_accessor :Title, :cdata => true
|
|
126
|
+
xml_accessor :Description, :cdata => true
|
|
127
|
+
xml_accessor :MusicUrl, :cdata => true
|
|
128
|
+
xml_accessor :HQMusicUrl, :cdata => true
|
|
129
|
+
xml_accessor :ThumbMediaId, :cdata => true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class MusicReplyMessage < ReplyMessage
|
|
133
|
+
attr_accessor :Music
|
|
134
|
+
xml_accessor :Music, :as => Music
|
|
135
|
+
set_nested_attr :Music
|
|
136
|
+
|
|
137
|
+
def initialize(attributes = {})
|
|
138
|
+
super
|
|
139
|
+
@MsgType ||= 'music'
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class Item
|
|
145
|
+
include ActiveModel::Model
|
|
146
|
+
include ROXML
|
|
147
|
+
|
|
148
|
+
attr_accessor :Title, :Description, :PicUrl, :Url
|
|
149
|
+
xml_accessor :Title, :cdata => true
|
|
150
|
+
xml_accessor :Description, :cdata => true
|
|
151
|
+
xml_accessor :PicUrl, :cdata => true
|
|
152
|
+
xml_accessor :Url, :cdata => true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
class NewsReplyMessage < ReplyMessage
|
|
156
|
+
attr_accessor :ArticleCount, :Articles
|
|
157
|
+
xml_accessor :ArticleCount, :as => Integer
|
|
158
|
+
xml_accessor :Articles, :as => [Item], :in => 'Articles', :from => 'item'
|
|
159
|
+
set_nested_attr :Articles
|
|
160
|
+
|
|
161
|
+
def initialize(attributes = {})
|
|
162
|
+
super
|
|
163
|
+
@MsgType ||= 'news'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def Articles=(attributes)
|
|
167
|
+
_attributes = attributes.is_a?(Hash) ? attributes.deep_symbolize_keys[:item] : attributes.dup
|
|
168
|
+
|
|
169
|
+
@Articles = _attributes.collect do |item_attr|
|
|
170
|
+
|
|
171
|
+
if item_attr.is_a?(Item)
|
|
172
|
+
item_attr
|
|
173
|
+
else
|
|
174
|
+
item_attr = item_attr.deep_symbolize_keys
|
|
175
|
+
Item.new(item_attr)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'multi_json'
|
|
2
|
+
require 'rack'
|
|
3
|
+
|
|
4
|
+
module MpWeixin
|
|
5
|
+
# MpWeixin::Response class
|
|
6
|
+
class Response
|
|
7
|
+
attr_reader :response
|
|
8
|
+
attr_accessor :error, :options
|
|
9
|
+
|
|
10
|
+
# Adds a new content type parser.
|
|
11
|
+
#
|
|
12
|
+
# @param [Symbol] key A descriptive symbol key such as :json or :query.
|
|
13
|
+
# @param [Array] One or more mime types to which this parser applies.
|
|
14
|
+
# @yield [String] A block returning parsed content.
|
|
15
|
+
def self.register_parser(key, mime_types, &block)
|
|
16
|
+
key = key.to_sym
|
|
17
|
+
PARSERS[key] = block
|
|
18
|
+
Array(mime_types).each do |mime_type|
|
|
19
|
+
CONTENT_TYPES[mime_type] = key
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Initializes a Response instance
|
|
24
|
+
#
|
|
25
|
+
# @param [Faraday::Response] response The Faraday response instance
|
|
26
|
+
# @param [Hash] opts options in which to initialize the instance
|
|
27
|
+
# @option opts [Symbol] :parse (:automatic) how to parse the response body. one of :query (for x-www-form-urlencoded),
|
|
28
|
+
# :json, or :automatic (determined by Content-Type response header)
|
|
29
|
+
def initialize(response, opts={})
|
|
30
|
+
@response = response
|
|
31
|
+
@options = {:parse => :automatic}.merge(opts)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The HTTP response headers
|
|
35
|
+
def headers
|
|
36
|
+
response.headers
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The HTTP response status code
|
|
40
|
+
def status
|
|
41
|
+
response.status
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The HTTP response body
|
|
45
|
+
def body
|
|
46
|
+
response.body || ''
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Procs that, when called, will parse a response body according
|
|
50
|
+
# to the specified format.
|
|
51
|
+
PARSERS = {
|
|
52
|
+
# Can't reliably detect whether MultiJson responds to load, since it's
|
|
53
|
+
# a reserved word. Use adapter as a proxy for new features.
|
|
54
|
+
:json => lambda{ |body| MultiJson.respond_to?(:adapter) ? MultiJson.load(body) : MultiJson.decode(body) rescue body },
|
|
55
|
+
:query => lambda{ |body| Rack::Utils.parse_query(body) },
|
|
56
|
+
:text => lambda{ |body| body }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Content type assignments for various potential HTTP content types.
|
|
60
|
+
CONTENT_TYPES = {
|
|
61
|
+
'application/json' => :json,
|
|
62
|
+
'text/javascript' => :json,
|
|
63
|
+
'application/x-www-form-urlencoded' => :query,
|
|
64
|
+
'text/plain' => :text
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# The parsed response body.
|
|
68
|
+
# Will attempt to parse application/x-www-form-urlencoded and
|
|
69
|
+
# application/json Content-Type response bodies
|
|
70
|
+
def parsed
|
|
71
|
+
return nil unless PARSERS.key?(parser)
|
|
72
|
+
@parsed ||= PARSERS[parser].call(body)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Attempts to determine the content type of the response.
|
|
76
|
+
def content_type
|
|
77
|
+
((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Determines the parser that will be used to supply the content of #parsed
|
|
81
|
+
def parser
|
|
82
|
+
return options[:parse].to_sym if PARSERS.key?(options[:parse])
|
|
83
|
+
CONTENT_TYPES[content_type]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
require 'multi_xml'
|
|
90
|
+
MpWeixin::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml']) do |body|
|
|
91
|
+
MultiXml.parse(body) rescue body
|
|
92
|
+
end
|
|
93
|
+
rescue LoadError; end
|