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,82 @@
|
|
|
1
|
+
mp_weixin_errors:
|
|
2
|
+
'-1': 系统繁忙
|
|
3
|
+
'0': 请求成功
|
|
4
|
+
'40001': 获取access_token时AppSecret错误,或者access_token无效
|
|
5
|
+
'40002': 不合法的凭证类型
|
|
6
|
+
'40003': 不合法的OpenID
|
|
7
|
+
'40004': 不合法的媒体文件类型
|
|
8
|
+
'40005': 不合法的文件类型
|
|
9
|
+
'40006': 不合法的文件大小
|
|
10
|
+
'40007': 不合法的媒体文件id
|
|
11
|
+
'40008': 不合法的消息类型
|
|
12
|
+
'40009': 不合法的图片文件大小
|
|
13
|
+
'40010': 不合法的语音文件大小
|
|
14
|
+
'40011': 不合法的视频文件大小
|
|
15
|
+
'40012': 不合法的缩略图文件大小
|
|
16
|
+
'40013': 不合法的APPID
|
|
17
|
+
'40014': 不合法的access_token
|
|
18
|
+
'40015': 不合法的菜单类型
|
|
19
|
+
'40016': 不合法的按钮个数
|
|
20
|
+
'40017': 不合法的按钮个数
|
|
21
|
+
'40018': 不合法的按钮名字长度
|
|
22
|
+
'40019': 不合法的按钮KEY长度
|
|
23
|
+
'40020': 不合法的按钮URL长度
|
|
24
|
+
'40021': 不合法的菜单版本号
|
|
25
|
+
'40022': 不合法的子菜单级数
|
|
26
|
+
'40023': 不合法的子菜单按钮个数
|
|
27
|
+
'40024': 不合法的子菜单按钮类型
|
|
28
|
+
'40025': 不合法的子菜单按钮名字长度
|
|
29
|
+
'40026': 不合法的子菜单按钮KEY长度
|
|
30
|
+
'40027': 不合法的子菜单按钮URL长度
|
|
31
|
+
'40028': 不合法的自定义菜单使用用户
|
|
32
|
+
'40029': 不合法的oauth_code
|
|
33
|
+
'40030': 不合法的refresh_token
|
|
34
|
+
'40031': 不合法的openid列表
|
|
35
|
+
'40032': 不合法的openid列表长度
|
|
36
|
+
'40033': 不合法的请求字符,不能包含\uxxxx格式的字符
|
|
37
|
+
'40035': 不合法的参数
|
|
38
|
+
'40038': 不合法的请求格式
|
|
39
|
+
'40039': 不合法的URL长度
|
|
40
|
+
'40050': 不合法的分组id
|
|
41
|
+
'40051': 分组名字不合法
|
|
42
|
+
'41001': 缺少access_token参数
|
|
43
|
+
'41002': 缺少appid参数
|
|
44
|
+
'41003': 缺少refresh_token参数
|
|
45
|
+
'41004': 缺少secret参数
|
|
46
|
+
'41005': 缺少多媒体文件数据
|
|
47
|
+
'41006': 缺少media_id参数
|
|
48
|
+
'41007': 缺少子菜单数据
|
|
49
|
+
'41009': 缺少openid
|
|
50
|
+
'42001': access_token超时
|
|
51
|
+
'42002': refresh_token超时
|
|
52
|
+
'42003': oauth_code超时
|
|
53
|
+
'43001': 需要GET请求
|
|
54
|
+
'43002': 需要POST请求
|
|
55
|
+
'43003': 需要HTTPS请求
|
|
56
|
+
'43004': 需要接收者关注
|
|
57
|
+
'43005': 需要好友关系
|
|
58
|
+
'44001': 多媒体文件为空
|
|
59
|
+
'44002': POST的数据包为空
|
|
60
|
+
'44003': 图文消息内容为空
|
|
61
|
+
'44004': 文本消息内容为空
|
|
62
|
+
'45001': 多媒体文件大小超过限制
|
|
63
|
+
'45002': 消息内容超过限制
|
|
64
|
+
'45003': 标题字段超过限制
|
|
65
|
+
'45004': 描述字段超过限制
|
|
66
|
+
'45005': 链接字段超过限制
|
|
67
|
+
'45006': 图片链接字段超过限制
|
|
68
|
+
'45007': 语音播放时间超过限制
|
|
69
|
+
'45008': 图文消息超过限制
|
|
70
|
+
'45009': 接口调用超过限制
|
|
71
|
+
'45010': 创建菜单个数超过限制
|
|
72
|
+
'45015': 回复时间超过限制
|
|
73
|
+
'45016': 系统分组,不允许修改
|
|
74
|
+
'45017': 分组名字过长
|
|
75
|
+
'45018': 分组数量超过上限
|
|
76
|
+
'46001': 不存在媒体数据
|
|
77
|
+
'46002': 不存在的菜单版本
|
|
78
|
+
'46003': 不存在的菜单数据
|
|
79
|
+
'46004': 不存在的用户
|
|
80
|
+
'47001': 解析JSON/XML内容错误
|
|
81
|
+
'48001': api功能未授权
|
|
82
|
+
'50001': 用户未授权该api
|
data/lib/mp_weixin.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
|
|
6
|
+
if defined?(Bundler)
|
|
7
|
+
Bundler.require
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require 'roxml'
|
|
11
|
+
require 'multi_xml'
|
|
12
|
+
require 'ostruct'
|
|
13
|
+
|
|
14
|
+
require 'faraday'
|
|
15
|
+
require 'active_model'
|
|
16
|
+
require 'active_support/all'
|
|
17
|
+
|
|
18
|
+
require 'support/active_model'
|
|
19
|
+
|
|
20
|
+
require "mp_weixin/version"
|
|
21
|
+
require "mp_weixin/config"
|
|
22
|
+
require "mp_weixin/error"
|
|
23
|
+
|
|
24
|
+
# require models
|
|
25
|
+
|
|
26
|
+
# require client
|
|
27
|
+
|
|
28
|
+
## require client dependence
|
|
29
|
+
require "mp_weixin/response"
|
|
30
|
+
require "mp_weixin/access_token"
|
|
31
|
+
|
|
32
|
+
## require interface
|
|
33
|
+
require "mp_weixin/interface/base"
|
|
34
|
+
require "mp_weixin/interface/message"
|
|
35
|
+
require "mp_weixin/interface/menu"
|
|
36
|
+
require "mp_weixin/interface/promotion"
|
|
37
|
+
require "mp_weixin/interface/group"
|
|
38
|
+
require "mp_weixin/interface/user"
|
|
39
|
+
|
|
40
|
+
require "mp_weixin/client"
|
|
41
|
+
|
|
42
|
+
# require server
|
|
43
|
+
require 'sinatra'
|
|
44
|
+
require 'digest/md5'
|
|
45
|
+
require 'rexml/document'
|
|
46
|
+
|
|
47
|
+
# include base class Message
|
|
48
|
+
# and some children class TextMessage, ImageMessage, LocationMessage,
|
|
49
|
+
# LinkMessage, VoiceMessage, VideoMessage
|
|
50
|
+
require 'mp_weixin/models/message'
|
|
51
|
+
require 'mp_weixin/models/event'
|
|
52
|
+
require 'mp_weixin/models/reply_message'
|
|
53
|
+
# require 'mp_weixin/models/location_message'
|
|
54
|
+
|
|
55
|
+
## require helpers
|
|
56
|
+
require 'mp_weixin/server_helper'
|
|
57
|
+
require 'mp_weixin/response_rule'
|
|
58
|
+
|
|
59
|
+
require 'mp_weixin/server'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
module MpWeixin
|
|
2
|
+
class AccessToken
|
|
3
|
+
attr_reader :client, :token, :expires_in, :expires_at, :params
|
|
4
|
+
attr_accessor :options, :refresh_token
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
# Initializes an AccessToken from a Hash
|
|
8
|
+
#
|
|
9
|
+
# @param [Client] the MpWeixin::Client instance
|
|
10
|
+
# @param [Hash] a hash of AccessToken property values
|
|
11
|
+
# @return [AccessToken] the initalized AccessToken
|
|
12
|
+
def from_hash(client, hash)
|
|
13
|
+
self.new(client, hash.delete('access_token') || hash.delete(:access_token), hash)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
|
|
17
|
+
#
|
|
18
|
+
# @param [Client] client the MpWeixin::Client instance
|
|
19
|
+
# @param [String] kvform the application/x-www-form-urlencoded string
|
|
20
|
+
# @return [AccessToken] the initalized AccessToken
|
|
21
|
+
def from_kvform(client, kvform)
|
|
22
|
+
from_hash(client, Rack::Utils.parse_query(kvform))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Initalize an AccessToken
|
|
27
|
+
#
|
|
28
|
+
# @param [Client] client the MpWeixin::Client instance
|
|
29
|
+
# @param [String] token the Access Token value
|
|
30
|
+
# @param [Hash] opts the options to create the Access Token with
|
|
31
|
+
# @option opts [String] :refresh_token (nil) the refresh_token value
|
|
32
|
+
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
|
|
33
|
+
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
|
|
34
|
+
# @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value
|
|
35
|
+
# one of :header, :body or :query
|
|
36
|
+
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
|
|
37
|
+
# @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
|
|
38
|
+
# Access Token value in :body or :query transmission mode
|
|
39
|
+
def initialize(client, token, opts={})
|
|
40
|
+
@client = client
|
|
41
|
+
@token = token.to_s
|
|
42
|
+
[:refresh_token, :expires_in, :expires_at].each do |arg|
|
|
43
|
+
instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
|
|
44
|
+
end
|
|
45
|
+
@expires_in ||= opts.delete('expires')
|
|
46
|
+
@expires_in &&= @expires_in.to_i
|
|
47
|
+
@expires_at &&= @expires_at.to_i
|
|
48
|
+
@expires_at ||= Time.now.to_i + @expires_in if @expires_in
|
|
49
|
+
@options = {:mode => opts.delete(:mode) || :header,
|
|
50
|
+
:header_format => opts.delete(:header_format) || 'Bearer %s',
|
|
51
|
+
:param_name => opts.delete(:param_name) || 'access_token'}
|
|
52
|
+
@params = opts
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Indexer to additional params present in token response
|
|
56
|
+
#
|
|
57
|
+
# @param [String] key entry key to Hash
|
|
58
|
+
def [](key)
|
|
59
|
+
@params[key]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Whether or not the token expires
|
|
63
|
+
#
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def expires?
|
|
66
|
+
!!@expires_at
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether or not the token is expired
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def expired?
|
|
73
|
+
expires? && (expires_at < Time.now.to_i)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Refreshes the current Access Token
|
|
77
|
+
#
|
|
78
|
+
# @return [AccessToken] a new AccessToken
|
|
79
|
+
# @note options should be carried over to the new AccessToken
|
|
80
|
+
def refresh!(params={})
|
|
81
|
+
raise "A refresh_token is not available" unless refresh_token
|
|
82
|
+
params.merge!(:client_id => @client.id,
|
|
83
|
+
:client_secret => @client.secret,
|
|
84
|
+
:grant_type => 'client_credential'
|
|
85
|
+
)
|
|
86
|
+
new_token = @client.get_token(params)
|
|
87
|
+
new_token.options = options
|
|
88
|
+
new_token.refresh_token = refresh_token unless new_token.refresh_token
|
|
89
|
+
new_token
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
|
|
93
|
+
#
|
|
94
|
+
# @return [Hash] a hash of AccessToken property values
|
|
95
|
+
def to_hash
|
|
96
|
+
params.merge({:access_token => token, :refresh_token => refresh_token, :expires_at => expires_at})
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Make a request with the Access Token
|
|
100
|
+
#
|
|
101
|
+
# @param [Symbol] verb the HTTP request method
|
|
102
|
+
# @param [String] path the HTTP URL path of the request
|
|
103
|
+
# @param [Hash] opts the options to make the request with
|
|
104
|
+
# @see Client#request
|
|
105
|
+
def request(verb, path, opts={}, &block)
|
|
106
|
+
set_token(opts)
|
|
107
|
+
@client.request(verb, path, opts, &block)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Make a GET request with the Access Token
|
|
111
|
+
#
|
|
112
|
+
# @see AccessToken#request
|
|
113
|
+
def get(path, opts={}, &block)
|
|
114
|
+
request(:get, path, opts, &block)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Make a POST request with the Access Token
|
|
118
|
+
#
|
|
119
|
+
# @see AccessToken#request
|
|
120
|
+
def post(path, opts={}, &block)
|
|
121
|
+
request(:post, path, opts, &block)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Make a PUT request with the Access Token
|
|
125
|
+
#
|
|
126
|
+
# @see AccessToken#request
|
|
127
|
+
def put(path, opts={}, &block)
|
|
128
|
+
request(:put, path, opts, &block)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Make a PATCH request with the Access Token
|
|
132
|
+
#
|
|
133
|
+
# @see AccessToken#request
|
|
134
|
+
def patch(path, opts={}, &block)
|
|
135
|
+
request(:patch, path, opts, &block)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Make a DELETE request with the Access Token
|
|
139
|
+
#
|
|
140
|
+
# @see AccessToken#request
|
|
141
|
+
def delete(path, opts={}, &block)
|
|
142
|
+
request(:delete, path, opts, &block)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Get the headers hash (includes Authorization token)
|
|
146
|
+
def headers
|
|
147
|
+
{ 'Authorization' => options[:header_format] % token }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
def set_token(opts)
|
|
152
|
+
case options[:mode]
|
|
153
|
+
when :header
|
|
154
|
+
opts[:headers] ||= {}
|
|
155
|
+
opts[:headers].merge!(headers)
|
|
156
|
+
when :query
|
|
157
|
+
opts[:params] ||= {}
|
|
158
|
+
opts[:params][options[:param_name]] = token
|
|
159
|
+
when :body
|
|
160
|
+
opts[:body] ||= {}
|
|
161
|
+
if opts[:body].is_a?(Hash)
|
|
162
|
+
opts[:body][options[:param_name]] = token
|
|
163
|
+
else
|
|
164
|
+
opts[:body] << "&#{options[:param_name]}=#{token}"
|
|
165
|
+
end
|
|
166
|
+
# @todo support for multi-part (file uploads)
|
|
167
|
+
else
|
|
168
|
+
raise "invalid :mode option of #{options[:mode]}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module MpWeixin
|
|
3
|
+
# The MpWeixin::Client class
|
|
4
|
+
# reference to The OAuth2::Client class
|
|
5
|
+
class Client
|
|
6
|
+
attr_reader :id, :secret, :site
|
|
7
|
+
attr_accessor :options, :token
|
|
8
|
+
attr_writer :connection
|
|
9
|
+
|
|
10
|
+
# Instantiate a new client using the
|
|
11
|
+
# Client ID and Client Secret registered to your
|
|
12
|
+
# weixin mp account.
|
|
13
|
+
#
|
|
14
|
+
# @param [String] app_id the app_id value
|
|
15
|
+
# @param [String] app_secret the app_secret value
|
|
16
|
+
# @param [Hash] opts the options to create the client with
|
|
17
|
+
# @option opts [String] :site the site host provider to connection
|
|
18
|
+
# @option opts [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
|
|
19
|
+
# @option opts [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
|
|
20
|
+
# @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
|
|
21
|
+
# @option opts [Boolean] :raise_errors (true) whether or not to raise an MpWeixin::Error
|
|
22
|
+
# on responses with 400+ status codes
|
|
23
|
+
# @yield [builder] The Faraday connection builder
|
|
24
|
+
def initialize(app_id = nil, app_secret = nil, opts={}, &block)
|
|
25
|
+
_opts = opts.dup
|
|
26
|
+
@id = app_id || Config.app_id
|
|
27
|
+
@secret = app_secret || Config.app_secret
|
|
28
|
+
@site = _opts.delete(:site) || "https://api.weixin.qq.com/"
|
|
29
|
+
ssl = _opts.delete(:ssl)
|
|
30
|
+
@options = {:authorize_url => '/oauth/authorize',
|
|
31
|
+
:token_url => '/cgi-bin/token',
|
|
32
|
+
:token_method => :post,
|
|
33
|
+
:connection_opts => {},
|
|
34
|
+
:connection_build => block,
|
|
35
|
+
:raise_errors => true}.merge(_opts)
|
|
36
|
+
@options[:connection_opts][:ssl] = ssl if ssl
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Whether or not the client is authorized
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def is_authorized?
|
|
43
|
+
!!token && !token.expired?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Set the site host
|
|
47
|
+
#
|
|
48
|
+
# @param [String] the MpWeixin provider site host
|
|
49
|
+
def site=(value)
|
|
50
|
+
@connection = nil
|
|
51
|
+
@site = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The Faraday connection object
|
|
55
|
+
def connection
|
|
56
|
+
@connection ||= begin
|
|
57
|
+
conn = Faraday.new(site, options[:connection_opts])
|
|
58
|
+
conn.build do |b|
|
|
59
|
+
options[:connection_build].call(b)
|
|
60
|
+
end if options[:connection_build]
|
|
61
|
+
conn
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Makes a request relative to the specified site root.
|
|
66
|
+
#
|
|
67
|
+
# @param [Symbol] verb one of :get, :post, :put, :delete
|
|
68
|
+
# @param [String] url URL path of request
|
|
69
|
+
# @param [Hash] opts the options to make the request with
|
|
70
|
+
# @option opts [Hash] :params additional query parameters for the URL of the request
|
|
71
|
+
# @option opts [Hash, String] :body the body of the request
|
|
72
|
+
# @option opts [Hash] :headers http request headers
|
|
73
|
+
# @option opts [Boolean] :raise_errors whether or not to raise an MpWeixin::Error on 400+ status
|
|
74
|
+
def request(verb, url, opts={})
|
|
75
|
+
url = self.connection.build_url(url, opts[:params]).to_s
|
|
76
|
+
|
|
77
|
+
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
|
|
78
|
+
yield(req) if block_given?
|
|
79
|
+
end
|
|
80
|
+
response = Response.new(response, :parse => opts[:parse])
|
|
81
|
+
|
|
82
|
+
case response.status
|
|
83
|
+
when 301, 302, 303, 307
|
|
84
|
+
opts[:redirect_count] ||= 0
|
|
85
|
+
opts[:redirect_count] += 1
|
|
86
|
+
return response if opts[:redirect_count] > options[:max_redirects]
|
|
87
|
+
if response.status == 303
|
|
88
|
+
verb = :get
|
|
89
|
+
opts.delete(:body)
|
|
90
|
+
end
|
|
91
|
+
request(verb, response.headers['location'], opts)
|
|
92
|
+
when 200..299, 300..399
|
|
93
|
+
# on non-redirecting 3xx statuses, just return the response
|
|
94
|
+
response
|
|
95
|
+
when 400..599
|
|
96
|
+
e = Error.new(response)
|
|
97
|
+
raise e if opts.fetch(:raise_errors, options[:raise_errors])
|
|
98
|
+
response.error = e
|
|
99
|
+
response
|
|
100
|
+
else
|
|
101
|
+
raise Error.new(response), "Unhandled status code value of #{response.status}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The authorize endpoint URL of the MpWeixin provider
|
|
106
|
+
#
|
|
107
|
+
# @param [Hash] params additional query parameters
|
|
108
|
+
def authorize_url(params=nil)
|
|
109
|
+
connection.build_url(options[:authorize_url], params).to_s
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The token endpoint URL of the MpWeixin provider
|
|
113
|
+
#
|
|
114
|
+
# @param [Hash] params additional query parameters
|
|
115
|
+
def token_url(params=nil)
|
|
116
|
+
connection.build_url(options[:token_url], params).to_s
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Initializes an AccessToken by making a request to the token endpoint
|
|
120
|
+
#
|
|
121
|
+
# @param [Hash] params a Hash of params for the token endpoint
|
|
122
|
+
# @param [Hash] access token options, to pass to the AccessToken object
|
|
123
|
+
# @param [Class] class of access token for easier subclassing MpWeixin::AccessToken
|
|
124
|
+
# @return [AccessToken] the initalized AccessToken
|
|
125
|
+
def get_token(params = {}, access_token_opts = {}, access_token_class = AccessToken)
|
|
126
|
+
params = ActiveSupport::HashWithIndifferentAccess.new(params)
|
|
127
|
+
params.reverse_merge!(grant_type: "client_credential", appid: id, secret: secret)
|
|
128
|
+
|
|
129
|
+
opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
|
|
130
|
+
if options[:token_method] == :post
|
|
131
|
+
headers = params.delete(:headers)
|
|
132
|
+
opts[:body] = params
|
|
133
|
+
opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
|
134
|
+
opts[:headers].merge!(headers) if headers
|
|
135
|
+
else
|
|
136
|
+
opts[:params] = params
|
|
137
|
+
end
|
|
138
|
+
response = request(options[:token_method], token_url, opts)
|
|
139
|
+
raise Error.new(response) if options[:raise_errors] && !(response.parsed.is_a?(Hash) && response.parsed['access_token'])
|
|
140
|
+
@token = access_token_class.from_hash(self, response.parsed.merge(access_token_opts))
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Initializes an AccessToken from a hash
|
|
144
|
+
#
|
|
145
|
+
# @param [Hash] hash a Hash contains access_token and expires
|
|
146
|
+
# @return [AccessToken] the initalized AccessToken
|
|
147
|
+
def get_token_from_hash(hash)
|
|
148
|
+
access_token = hash.delete('access_token') || hash.delete(:access_token) || hash.delete('oauth_token') || hash.delete(:oauth_token)
|
|
149
|
+
opts = {:expires_at => hash["expires"] || hash[:expires],
|
|
150
|
+
:header_format => "OAuth2 %s",
|
|
151
|
+
:param_name => "access_token"}
|
|
152
|
+
|
|
153
|
+
@token = AccessToken.new(self, access_token, opts)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Initializes a new Client from a hash
|
|
157
|
+
#
|
|
158
|
+
# @param [Hash] a Hash contains access_token and expires
|
|
159
|
+
# @param [Hash] opts the options to create the client with
|
|
160
|
+
# @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
|
|
161
|
+
# @option opts [FixNum] :max_redirects (5) maximum number of redirects to follow
|
|
162
|
+
# @yield [builder] The Faraday connection builder
|
|
163
|
+
def self.from_hash(hash, opts={}, &block)
|
|
164
|
+
client = self.new(opts, &block)
|
|
165
|
+
client.get_token_from_hash(hash)
|
|
166
|
+
|
|
167
|
+
client
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
#
|
|
171
|
+
# APIs
|
|
172
|
+
#
|
|
173
|
+
|
|
174
|
+
# assocation an Interface::Message instance to client
|
|
175
|
+
def message
|
|
176
|
+
@message ||= Interface::Message.new(self)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# assocation an Interface::Menu instance to client
|
|
180
|
+
def menu
|
|
181
|
+
@menu ||= Interface::Menu.new(self)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# assocation an Interface::Promotion instance to client
|
|
185
|
+
def promotion
|
|
186
|
+
@promotion ||= Interface::Promotion.new(self)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# assocation an Interface::Group instance to client
|
|
190
|
+
def group
|
|
191
|
+
@group ||= Interface::Group.new(self)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# assocation an Interface::User instance to client
|
|
195
|
+
def user
|
|
196
|
+
@user ||= Interface::User.new(self)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|