wechat 0.9.0 → 0.10.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +7 -0
- data/README-CN.md +10 -31
- data/README.md +26 -30
- data/lib/action_controller/wechat_responder.rb +1 -0
- data/lib/wechat.rb +15 -0
- data/lib/wechat/api.rb +8 -207
- data/lib/wechat/api_loader.rb +3 -2
- data/lib/wechat/cipher.rb +0 -5
- data/lib/wechat/concern/common.rb +217 -0
- data/lib/wechat/http_client.rb +3 -4
- data/lib/wechat/message.rb +15 -9
- data/lib/wechat/mp_api.rb +46 -0
- data/lib/wechat/responder.rb +25 -11
- metadata +30 -28
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de72dcc5a201ab7841963fb88eee52e109487969e7a2ab05324fae9977fd9dab
|
4
|
+
data.tar.gz: 3f1f797d0224c24d2625c8dfdad2cee66c85cfe98cd2511dace850493d2c11e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a5bf87045b07c01b3d140f981fa3e1dd29a2490e32d718ced9ed82fa55488511514beea467fde8726ed24805d63b874776e6f0f832130f25fa82d592ea5c8f3
|
7
|
+
data.tar.gz: f045e5886f34b243fe9fc86dcbe663efdd6292e076a515578436a4fcaeae778304554a59cbdfd01fc5ed5b6538d3ca23127acfe410764bb1c0b30c0a08a65368
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v0.10.0 (released at 5/31/2018)
|
4
|
+
|
5
|
+
* Support multi wechat account at wechat_responder. by @tuliren #223
|
6
|
+
* Support wechat mini program apis & signature check. by @oiahoon #225
|
7
|
+
* Support sent template message with miniprogram. by @falm #228
|
8
|
+
* Fix request_content could be nil. by @paicha #229
|
9
|
+
|
3
10
|
## v0.9.0 (released at 4/15/2018)
|
4
11
|
|
5
12
|
* Support multi wechat account dynamically loading from DB. by @tuliren #222
|
data/README-CN.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
WeChat [](https://rubygems.org/gems/wechat) [](https://travis-ci.org/Eric-Guo/wechat) [](https://rubygems.org/gems/wechat) [](https://travis-ci.org/Eric-Guo/wechat) [](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
|
2
2
|
======
|
3
3
|
|
4
4
|
[](https://gitter.im/Eric-Guo/wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
@@ -238,7 +238,7 @@ Wechat服务器有报道曾出现[RestClient::SSLCertificateNotVerified](http://
|
|
238
238
|
|
239
239
|
```ruby
|
240
240
|
class WechatFirstController < ActionController::Base
|
241
|
-
wechat_responder account: :new_account
|
241
|
+
wechat_responder account: :new_account, account_from_request: Proc.new{ |request| request.params[:wechat] }
|
242
242
|
|
243
243
|
on :text, with:"help", respond: "help content"
|
244
244
|
end
|
@@ -248,40 +248,14 @@ end
|
|
248
248
|
|
249
249
|
```ruby
|
250
250
|
class WechatFirstController < ActionController::Base
|
251
|
-
wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1")
|
251
|
+
wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1"),
|
252
|
+
account_from_request: Proc.new{ |request| request.params[:wechat] }
|
252
253
|
|
253
254
|
on :text, with:"help", respond: "help content"
|
254
255
|
end
|
255
256
|
```
|
256
257
|
|
257
|
-
|
258
|
-
|
259
|
-
若需要动态处理不同微信公众号的消息,您需要用数据库存储账户设置,然后调用 `wechat_oauth2` 或者 `Wechat#api`:
|
260
|
-
|
261
|
-
```ruby
|
262
|
-
class WechatReportsController < ApplicationController
|
263
|
-
wechat_api
|
264
|
-
layout 'wechat'
|
265
|
-
|
266
|
-
def index
|
267
|
-
# 通过自定义方法,从 request 中得到微信账户名称
|
268
|
-
account_name = get_account_from_url(request)
|
269
|
-
|
270
|
-
wechat_oauth2('snsapi_base', nil, account_name) do |openid|
|
271
|
-
@current_user = User.find_by(wechat_openid: openid)
|
272
|
-
@articles = @current_user.articles
|
273
|
-
end
|
274
|
-
|
275
|
-
Wechat.api(account_name)
|
276
|
-
end
|
277
|
-
|
278
|
-
private
|
279
|
-
# 预期的 URL: .../wechat/<account-name>/...
|
280
|
-
def get_account_from_url(request)
|
281
|
-
request.original_url.match(/wechat\/(.*)\//)[1]
|
282
|
-
end
|
283
|
-
end
|
284
|
-
```
|
258
|
+
其中 `account_from_request` 是一个 `Proc`,接受 `request` 作为唯一参数,返回相应的微信账户名称。以上示例中,`controller` 会根据 `request` 中传入的 `wechat` 参数选择微信账户。如果没有提供 `account_from_request` 或者 `Proc` 的结果是 `nil`,则使用 `account` 或者完整配置。
|
285
259
|
|
286
260
|
#### JS-SDK 支持
|
287
261
|
|
@@ -315,6 +289,11 @@ class CartController < ActionController::Base
|
|
315
289
|
@current_user = User.find_by(wechat_openid: openid)
|
316
290
|
@articles = @current_user.articles
|
317
291
|
end
|
292
|
+
|
293
|
+
# 指定 account_name,可以使用任意微信账户
|
294
|
+
# wechat_oauth2('snsapi_base', nil, account_name) do |openid|
|
295
|
+
# ...
|
296
|
+
# end
|
318
297
|
end
|
319
298
|
end
|
320
299
|
```
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
WeChat [](https://rubygems.org/gems/wechat) [](https://travis-ci.org/Eric-Guo/wechat) [](https://rubygems.org/gems/wechat) [](https://travis-ci.org/Eric-Guo/wechat) [](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
|
2
2
|
======
|
3
3
|
|
4
4
|
[](https://gitter.im/Eric-Guo/wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
@@ -207,6 +207,19 @@ test:
|
|
207
207
|
|
208
208
|
For multiple accounts details reference [PR 150](https://github.com/Eric-Guo/wechat/pull/150)
|
209
209
|
|
210
|
+
For wechat mini program, can specified by the item `type`:
|
211
|
+
|
212
|
+
```yaml
|
213
|
+
# Mini Program Accounts
|
214
|
+
|
215
|
+
mini_development:
|
216
|
+
<<: *default
|
217
|
+
appid: "my_appid"
|
218
|
+
secret: "my_secret"
|
219
|
+
# `mp` is short for **mini program**
|
220
|
+
type: 'mp'
|
221
|
+
```
|
222
|
+
|
210
223
|
#### Database wechat account configuration
|
211
224
|
After enabling database account configuration, the following table will be created:
|
212
225
|
|
@@ -251,7 +264,7 @@ Sometimes, you may want to host more than one enterprise/public wechat account i
|
|
251
264
|
|
252
265
|
```ruby
|
253
266
|
class WechatFirstController < ActionController::Base
|
254
|
-
wechat_responder account: :new_account
|
267
|
+
wechat_responder account: :new_account, account_from_request: Proc.new{ |request| request.params[:wechat] }
|
255
268
|
|
256
269
|
on :text, with:"help", respond: "help content"
|
257
270
|
end
|
@@ -261,39 +274,14 @@ Or you can provide full list of options.
|
|
261
274
|
|
262
275
|
```ruby
|
263
276
|
class WechatFirstController < ActionController::Base
|
264
|
-
wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1")
|
277
|
+
wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1"),
|
278
|
+
account_from_request: Proc.new{ |request| request.params[:wechat] }
|
265
279
|
|
266
280
|
on :text, with:"help", respond: "help content"
|
267
281
|
end
|
268
282
|
```
|
269
283
|
|
270
|
-
|
271
|
-
|
272
|
-
If you want the controller to dynamically apply different account configurations for each request, you need to enable database account configuration, and call `wechat_oauth2` or `Wechat#api`:
|
273
|
-
|
274
|
-
```ruby
|
275
|
-
class WechatReportsController < ApplicationController
|
276
|
-
wechat_api
|
277
|
-
layout 'wechat'
|
278
|
-
|
279
|
-
def index
|
280
|
-
account_name = get_account_from_url(request)
|
281
|
-
|
282
|
-
wechat_oauth2('snsapi_base', nil, account_name) do |openid|
|
283
|
-
@current_user = User.find_by(wechat_openid: openid)
|
284
|
-
@articles = @current_user.articles
|
285
|
-
end
|
286
|
-
|
287
|
-
Wechat.api(account_name)
|
288
|
-
end
|
289
|
-
|
290
|
-
private
|
291
|
-
# Expected URL: .../wechat/<account-name>/...
|
292
|
-
def get_account_from_url(request)
|
293
|
-
request.original_url.match(/wechat\/(.*)\//)[1]
|
294
|
-
end
|
295
|
-
end
|
296
|
-
```
|
284
|
+
`account_from_request` is a `Proc` that takes in `request` as its parameter, and returns the corresponding wechat account name. In the above examples, `controller` will choose the account based on the `wechat` parameter passed in the `request`. If `account_from_request` is not specified, or this `Proc` evaluates to `nil`, configuration specified by `account` or the full list of options will be used.
|
297
285
|
|
298
286
|
#### JS-SDK helper
|
299
287
|
|
@@ -327,6 +315,11 @@ class CartController < ActionController::Base
|
|
327
315
|
@current_user = User.find_by(wechat_openid: openid)
|
328
316
|
@articles = @current_user.articles
|
329
317
|
end
|
318
|
+
|
319
|
+
# specify account_name to use arbitrary wechat account configuration
|
320
|
+
# wechat_oauth2('snsapi_base', nil, account_name) do |openid|
|
321
|
+
# ...
|
322
|
+
# end
|
330
323
|
end
|
331
324
|
end
|
332
325
|
```
|
@@ -643,6 +636,9 @@ end
|
|
643
636
|
|
644
637
|
Using `Wechat.api` to access the wechat api function at any place.
|
645
638
|
|
639
|
+
## Checking the signature
|
640
|
+
Using `Wechat.decrypt(encrypted_data,session_key, iv)` to decode the data. via. [Signature Checking](https://developers.weixin.qq.com/miniprogram/dev/api/signature.html)
|
641
|
+
|
646
642
|
## wechat_responder - Rails Responder Controller DSL
|
647
643
|
|
648
644
|
In order to respond to the message user sent, Rails developer needs to create a wechat responder controller and define the routing in `routes.rb`
|
@@ -10,6 +10,7 @@ module ActionController
|
|
10
10
|
def wechat_responder(opts = {})
|
11
11
|
include Wechat::Responder
|
12
12
|
account = opts.delete(:account)
|
13
|
+
self.account_from_request = opts.delete(:account_from_request)
|
13
14
|
self.wechat_cfg_account = account ? account.to_sym : :default
|
14
15
|
self.wechat_api_client = load_controller_wechat(wechat_cfg_account, opts)
|
15
16
|
end
|
data/lib/wechat.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl/cipher'
|
1
3
|
require 'wechat/api_loader'
|
2
4
|
require 'wechat/api'
|
5
|
+
require 'wechat/mp_api'
|
3
6
|
require 'wechat/corp_api'
|
4
7
|
require 'wechat/helpers'
|
5
8
|
require 'action_controller/wechat_responder'
|
@@ -32,6 +35,18 @@ module Wechat
|
|
32
35
|
def self.reload_config!
|
33
36
|
ApiLoader.reload_config!
|
34
37
|
end
|
38
|
+
|
39
|
+
def self.decrypt(encrypted_data, session_key, iv)
|
40
|
+
cipher = OpenSSL::Cipher.new('AES-128-CBC')
|
41
|
+
cipher.decrypt
|
42
|
+
|
43
|
+
cipher.key = Base64.decode64(session_key)
|
44
|
+
cipher.iv = Base64.decode64(iv)
|
45
|
+
decrypted_data = Base64.decode64(encrypted_data)
|
46
|
+
JSON.parse(cipher.update(decrypted_data) + cipher.final)
|
47
|
+
rescue Exception => e
|
48
|
+
{ 'errcode': 41003, 'errmsg': e.message }
|
49
|
+
end
|
35
50
|
end
|
36
51
|
|
37
52
|
ActionView::Base.send :include, Wechat::Helpers if defined? ActionView::Base
|
data/lib/wechat/api.rb
CHANGED
@@ -2,225 +2,26 @@ require 'wechat/api_base'
|
|
2
2
|
require 'wechat/http_client'
|
3
3
|
require 'wechat/token/public_access_token'
|
4
4
|
require 'wechat/ticket/public_jsapi_ticket'
|
5
|
+
require 'wechat/concern/common'
|
5
6
|
|
6
7
|
module Wechat
|
7
8
|
class Api < ApiBase
|
8
|
-
|
9
|
-
WXA_BASE = 'https://api.weixin.qq.com/wxa/'.freeze
|
10
|
-
|
11
|
-
def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
|
12
|
-
@client = HttpClient.new(API_BASE, timeout, skip_verify_ssl)
|
13
|
-
@access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
|
14
|
-
@jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
|
15
|
-
end
|
16
|
-
|
17
|
-
def groups
|
18
|
-
get 'groups/get'
|
19
|
-
end
|
20
|
-
|
21
|
-
def group_create(group_name)
|
22
|
-
post 'groups/create', JSON.generate(group: { name: group_name })
|
23
|
-
end
|
24
|
-
|
25
|
-
def group_update(groupid, new_group_name)
|
26
|
-
post 'groups/update', JSON.generate(group: { id: groupid, name: new_group_name })
|
27
|
-
end
|
28
|
-
|
29
|
-
def group_delete(groupid)
|
30
|
-
post 'groups/delete', JSON.generate(group: { id: groupid })
|
31
|
-
end
|
32
|
-
|
33
|
-
def users(nextid = nil)
|
34
|
-
params = { params: { next_openid: nextid } } if nextid.present?
|
35
|
-
get('user/get', params || {})
|
36
|
-
end
|
37
|
-
|
38
|
-
def user(openid)
|
39
|
-
get 'user/info', params: { openid: openid }
|
40
|
-
end
|
41
|
-
|
42
|
-
def user_batchget(openids, lang = 'zh-CN')
|
43
|
-
post 'user/info/batchget', JSON.generate(user_list: openids.collect { |v| { openid: v, lang: lang } })
|
44
|
-
end
|
45
|
-
|
46
|
-
def user_group(openid)
|
47
|
-
post 'groups/getid', JSON.generate(openid: openid)
|
48
|
-
end
|
49
|
-
|
50
|
-
def user_change_group(openid, to_groupid)
|
51
|
-
post 'groups/members/update', JSON.generate(openid: openid, to_groupid: to_groupid)
|
52
|
-
end
|
53
|
-
|
54
|
-
def user_update_remark(openid, remark)
|
55
|
-
post 'user/info/updateremark', JSON.generate(openid: openid, remark: remark)
|
56
|
-
end
|
57
|
-
|
58
|
-
def qrcode_create_scene(scene_id_or_str, expire_seconds = 604800)
|
59
|
-
case scene_id_or_str
|
60
|
-
when 0.class
|
61
|
-
post 'qrcode/create', JSON.generate(expire_seconds: expire_seconds,
|
62
|
-
action_name: 'QR_SCENE',
|
63
|
-
action_info: { scene: { scene_id: scene_id_or_str } })
|
64
|
-
else
|
65
|
-
post 'qrcode/create', JSON.generate(expire_seconds: expire_seconds,
|
66
|
-
action_name: 'QR_STR_SCENE',
|
67
|
-
action_info: { scene: { scene_str: scene_id_or_str } })
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
def qrcode_create_limit_scene(scene_id_or_str)
|
73
|
-
case scene_id_or_str
|
74
|
-
when 0.class
|
75
|
-
post 'qrcode/create', JSON.generate(action_name: 'QR_LIMIT_SCENE',
|
76
|
-
action_info: { scene: { scene_id: scene_id_or_str } })
|
77
|
-
else
|
78
|
-
post 'qrcode/create', JSON.generate(action_name: 'QR_LIMIT_STR_SCENE',
|
79
|
-
action_info: { scene: { scene_str: scene_id_or_str } })
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def shorturl(long_url)
|
84
|
-
post 'shorturl', JSON.generate(action: 'long2short', long_url: long_url)
|
85
|
-
end
|
86
|
-
|
87
|
-
def message_mass_sendall(message)
|
88
|
-
post 'message/mass/sendall', message.to_json
|
89
|
-
end
|
90
|
-
|
91
|
-
def message_mass_delete(msg_id)
|
92
|
-
post 'message/mass/delete', JSON.generate(msg_id: msg_id)
|
93
|
-
end
|
94
|
-
|
95
|
-
def message_mass_preview(message)
|
96
|
-
post 'message/mass/preview', message.to_json
|
97
|
-
end
|
98
|
-
|
99
|
-
def message_mass_get(msg_id)
|
100
|
-
post 'message/mass/get', JSON.generate(msg_id: msg_id)
|
101
|
-
end
|
102
|
-
|
103
|
-
def wxa_get_wxacode(path, width = 430)
|
104
|
-
post 'getwxacode', JSON.generate(path: path, width: width), base: WXA_BASE
|
105
|
-
end
|
106
|
-
|
107
|
-
def wxa_create_qrcode(path, width = 430)
|
108
|
-
post 'wxaapp/createwxaqrcode', JSON.generate(path: path, width: width)
|
109
|
-
end
|
110
|
-
|
111
|
-
def menu
|
112
|
-
get 'menu/get'
|
113
|
-
end
|
114
|
-
|
115
|
-
def menu_delete
|
116
|
-
get 'menu/delete'
|
117
|
-
end
|
118
|
-
|
119
|
-
def menu_create(menu)
|
120
|
-
# 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
|
121
|
-
post 'menu/create', JSON.generate(menu)
|
122
|
-
end
|
123
|
-
|
124
|
-
def menu_addconditional(menu)
|
125
|
-
# Wechat not accept 7bit escaped json(eg \uxxxx), must using UTF-8, possible security vulnerability?
|
126
|
-
post 'menu/addconditional', JSON.generate(menu)
|
127
|
-
end
|
128
|
-
|
129
|
-
def menu_trymatch(user_id)
|
130
|
-
post 'menu/trymatch', JSON.generate(user_id: user_id)
|
131
|
-
end
|
132
|
-
|
133
|
-
def menu_delconditional(menuid)
|
134
|
-
post 'menu/delconditional', JSON.generate(menuid: menuid)
|
135
|
-
end
|
136
|
-
|
137
|
-
def material(media_id)
|
138
|
-
get 'material/get', params: { media_id: media_id }, as: :file
|
139
|
-
end
|
140
|
-
|
141
|
-
def material_count
|
142
|
-
get 'material/get_materialcount'
|
143
|
-
end
|
144
|
-
|
145
|
-
def material_list(type, offset, count)
|
146
|
-
post 'material/batchget_material', JSON.generate(type: type, offset: offset, count: count)
|
147
|
-
end
|
148
|
-
|
149
|
-
def material_add(type, file)
|
150
|
-
post_file 'material/add_material', file, params: { type: type }
|
151
|
-
end
|
152
|
-
|
153
|
-
def material_delete(media_id)
|
154
|
-
post 'material/del_material', JSON.generate(media_id: media_id)
|
155
|
-
end
|
156
|
-
|
157
|
-
def custom_message_send(message)
|
158
|
-
post 'message/custom/send', message.to_json, content_type: :json
|
159
|
-
end
|
9
|
+
include Concern::Common
|
160
10
|
|
161
11
|
def template_message_send(message)
|
162
12
|
post 'message/template/send', message.to_json, content_type: :json
|
163
13
|
end
|
164
14
|
|
165
|
-
def
|
166
|
-
get '
|
167
|
-
end
|
168
|
-
|
169
|
-
def tags
|
170
|
-
get 'tags/get'
|
171
|
-
end
|
172
|
-
|
173
|
-
def tag_create(tag_name)
|
174
|
-
post 'tags/create', JSON.generate(tag: { name: tag_name })
|
175
|
-
end
|
176
|
-
|
177
|
-
def tag_update(tagid, new_tag_name)
|
178
|
-
post 'tags/update', JSON.generate(tag: { id: tagid, name: new_tag_name })
|
179
|
-
end
|
180
|
-
|
181
|
-
def tag_delete(tagid)
|
182
|
-
post 'tags/delete', JSON.generate(tag: { id: tagid })
|
183
|
-
end
|
184
|
-
|
185
|
-
def tag_add_user(tagid, openids)
|
186
|
-
post 'tags/members/batchtagging', JSON.generate(openid_list: openids, tagid: tagid)
|
187
|
-
end
|
188
|
-
|
189
|
-
def tag_del_user(tagid, openids)
|
190
|
-
post 'tags/members/batchuntagging', JSON.generate(openid_list: openids, tagid: tagid)
|
191
|
-
end
|
192
|
-
|
193
|
-
def tag(tagid, next_openid = '')
|
194
|
-
post 'user/tag/get', JSON.generate(tagid: tagid, next_openid: next_openid)
|
195
|
-
end
|
196
|
-
|
197
|
-
OAUTH2_BASE = 'https://api.weixin.qq.com/sns/'.freeze
|
198
|
-
|
199
|
-
def web_access_token(code)
|
200
|
-
params = {
|
201
|
-
appid: access_token.appid,
|
202
|
-
secret: access_token.secret,
|
203
|
-
code: code,
|
204
|
-
grant_type: 'authorization_code'
|
205
|
-
}
|
206
|
-
client.get 'oauth2/access_token', params: params, base: OAUTH2_BASE
|
207
|
-
end
|
208
|
-
|
209
|
-
def web_auth_access_token(web_access_token, openid)
|
210
|
-
client.get 'auth', params: { access_token: web_access_token, openid: openid }, base: OAUTH2_BASE
|
15
|
+
def list_message_template
|
16
|
+
get 'template/get_all_private_template'
|
211
17
|
end
|
212
18
|
|
213
|
-
def
|
214
|
-
|
215
|
-
appid: access_token.appid,
|
216
|
-
grant_type: 'refresh_token',
|
217
|
-
refresh_token: user_refresh_token
|
218
|
-
}
|
219
|
-
client.get 'oauth2/refresh_token', params: params, base: OAUTH2_BASE
|
19
|
+
def add_message_template(template_id_short)
|
20
|
+
post 'template/api_add_template', JSON.generate(template_id_short: template_id_short)
|
220
21
|
end
|
221
22
|
|
222
|
-
def
|
223
|
-
|
23
|
+
def del_message_template(template_id)
|
24
|
+
post 'template/del_private_template', JSON.generate(template_id: template_id)
|
224
25
|
end
|
225
26
|
end
|
226
27
|
end
|
data/lib/wechat/api_loader.rb
CHANGED
@@ -6,9 +6,10 @@ module Wechat
|
|
6
6
|
|
7
7
|
token_file = options[:token_file] || c.access_token.presence || '/var/tmp/wechat_access_token'
|
8
8
|
js_token_file = options[:js_token_file] || c.jsapi_ticket.presence || '/var/tmp/wechat_jsapi_ticket'
|
9
|
-
|
9
|
+
type = options[:type] || c.type
|
10
10
|
if c.appid && c.secret && token_file.present?
|
11
|
-
|
11
|
+
wx_class = (type == 'mp') ? Wechat::MpApi : Wechat::Api
|
12
|
+
wx_class.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file)
|
12
13
|
elsif c.corpid && c.corpsecret && token_file.present?
|
13
14
|
Wechat::CorpApi.new(c.corpid, c.corpsecret, token_file, c.agentid, c.timeout, c.skip_verify_ssl, js_token_file)
|
14
15
|
else
|
data/lib/wechat/cipher.rb
CHANGED
@@ -0,0 +1,217 @@
|
|
1
|
+
module Wechat
|
2
|
+
module Concern
|
3
|
+
module Common
|
4
|
+
WXA_BASE = 'https://api.weixin.qq.com/wxa/'.freeze
|
5
|
+
API_BASE = 'https://api.weixin.qq.com/cgi-bin/'.freeze
|
6
|
+
OAUTH2_BASE = 'https://api.weixin.qq.com/sns/'.freeze
|
7
|
+
|
8
|
+
def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
|
9
|
+
@client = HttpClient.new(API_BASE, timeout, skip_verify_ssl)
|
10
|
+
@access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
|
11
|
+
@jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
|
12
|
+
end
|
13
|
+
|
14
|
+
def groups
|
15
|
+
get 'groups/get'
|
16
|
+
end
|
17
|
+
|
18
|
+
def group_create(group_name)
|
19
|
+
post 'groups/create', JSON.generate(group: { name: group_name })
|
20
|
+
end
|
21
|
+
|
22
|
+
def group_update(groupid, new_group_name)
|
23
|
+
post 'groups/update', JSON.generate(group: { id: groupid, name: new_group_name })
|
24
|
+
end
|
25
|
+
|
26
|
+
def group_delete(groupid)
|
27
|
+
post 'groups/delete', JSON.generate(group: { id: groupid })
|
28
|
+
end
|
29
|
+
|
30
|
+
def users(nextid = nil)
|
31
|
+
params = { params: { next_openid: nextid } } if nextid.present?
|
32
|
+
get('user/get', params || {})
|
33
|
+
end
|
34
|
+
|
35
|
+
def user(openid)
|
36
|
+
get 'user/info', params: { openid: openid }
|
37
|
+
end
|
38
|
+
|
39
|
+
def user_batchget(openids, lang = 'zh-CN')
|
40
|
+
post 'user/info/batchget', JSON.generate(user_list: openids.collect { |v| { openid: v, lang: lang } })
|
41
|
+
end
|
42
|
+
|
43
|
+
def user_group(openid)
|
44
|
+
post 'groups/getid', JSON.generate(openid: openid)
|
45
|
+
end
|
46
|
+
|
47
|
+
def user_change_group(openid, to_groupid)
|
48
|
+
post 'groups/members/update', JSON.generate(openid: openid, to_groupid: to_groupid)
|
49
|
+
end
|
50
|
+
|
51
|
+
def user_update_remark(openid, remark)
|
52
|
+
post 'user/info/updateremark', JSON.generate(openid: openid, remark: remark)
|
53
|
+
end
|
54
|
+
|
55
|
+
def qrcode_create_scene(scene_id_or_str, expire_seconds = 604800)
|
56
|
+
case scene_id_or_str
|
57
|
+
when 0.class
|
58
|
+
post 'qrcode/create', JSON.generate(expire_seconds: expire_seconds,
|
59
|
+
action_name: 'QR_SCENE',
|
60
|
+
action_info: { scene: { scene_id: scene_id_or_str } })
|
61
|
+
else
|
62
|
+
post 'qrcode/create', JSON.generate(expire_seconds: expire_seconds,
|
63
|
+
action_name: 'QR_STR_SCENE',
|
64
|
+
action_info: { scene: { scene_str: scene_id_or_str } })
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def qrcode_create_limit_scene(scene_id_or_str)
|
69
|
+
case scene_id_or_str
|
70
|
+
when 0.class
|
71
|
+
post 'qrcode/create', JSON.generate(action_name: 'QR_LIMIT_SCENE',
|
72
|
+
action_info: { scene: { scene_id: scene_id_or_str } })
|
73
|
+
else
|
74
|
+
post 'qrcode/create', JSON.generate(action_name: 'QR_LIMIT_STR_SCENE',
|
75
|
+
action_info: { scene: { scene_str: scene_id_or_str } })
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def shorturl(long_url)
|
80
|
+
post 'shorturl', JSON.generate(action: 'long2short', long_url: long_url)
|
81
|
+
end
|
82
|
+
|
83
|
+
def message_mass_sendall(message)
|
84
|
+
post 'message/mass/sendall', message.to_json
|
85
|
+
end
|
86
|
+
|
87
|
+
def message_mass_delete(msg_id)
|
88
|
+
post 'message/mass/delete', JSON.generate(msg_id: msg_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
def message_mass_preview(message)
|
92
|
+
post 'message/mass/preview', message.to_json
|
93
|
+
end
|
94
|
+
|
95
|
+
def message_mass_get(msg_id)
|
96
|
+
post 'message/mass/get', JSON.generate(msg_id: msg_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
def wxa_get_wxacode(path, width = 430)
|
100
|
+
post 'getwxacode', JSON.generate(path: path, width: width), base: WXA_BASE
|
101
|
+
end
|
102
|
+
|
103
|
+
def wxa_create_qrcode(path, width = 430)
|
104
|
+
post 'wxaapp/createwxaqrcode', JSON.generate(path: path, width: width)
|
105
|
+
end
|
106
|
+
|
107
|
+
def menu
|
108
|
+
get 'menu/get'
|
109
|
+
end
|
110
|
+
|
111
|
+
def menu_delete
|
112
|
+
get 'menu/delete'
|
113
|
+
end
|
114
|
+
|
115
|
+
def menu_create(menu)
|
116
|
+
# 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
|
117
|
+
post 'menu/create', JSON.generate(menu)
|
118
|
+
end
|
119
|
+
|
120
|
+
def menu_addconditional(menu)
|
121
|
+
# Wechat not accept 7bit escaped json(eg \uxxxx), must using UTF-8, possible security vulnerability?
|
122
|
+
post 'menu/addconditional', JSON.generate(menu)
|
123
|
+
end
|
124
|
+
|
125
|
+
def menu_trymatch(user_id)
|
126
|
+
post 'menu/trymatch', JSON.generate(user_id: user_id)
|
127
|
+
end
|
128
|
+
|
129
|
+
def menu_delconditional(menuid)
|
130
|
+
post 'menu/delconditional', JSON.generate(menuid: menuid)
|
131
|
+
end
|
132
|
+
|
133
|
+
def material(media_id)
|
134
|
+
get 'material/get', params: { media_id: media_id }, as: :file
|
135
|
+
end
|
136
|
+
|
137
|
+
def material_count
|
138
|
+
get 'material/get_materialcount'
|
139
|
+
end
|
140
|
+
|
141
|
+
def material_list(type, offset, count)
|
142
|
+
post 'material/batchget_material', JSON.generate(type: type, offset: offset, count: count)
|
143
|
+
end
|
144
|
+
|
145
|
+
def material_add(type, file)
|
146
|
+
post_file 'material/add_material', file, params: { type: type }
|
147
|
+
end
|
148
|
+
|
149
|
+
def material_delete(media_id)
|
150
|
+
post 'material/del_material', JSON.generate(media_id: media_id)
|
151
|
+
end
|
152
|
+
|
153
|
+
def custom_message_send(message)
|
154
|
+
post 'message/custom/send', message.to_json, content_type: :json
|
155
|
+
end
|
156
|
+
|
157
|
+
def customservice_getonlinekflist
|
158
|
+
get 'customservice/getonlinekflist'
|
159
|
+
end
|
160
|
+
|
161
|
+
def tags
|
162
|
+
get 'tags/get'
|
163
|
+
end
|
164
|
+
|
165
|
+
def tag_create(tag_name)
|
166
|
+
post 'tags/create', JSON.generate(tag: { name: tag_name })
|
167
|
+
end
|
168
|
+
|
169
|
+
def tag_update(tagid, new_tag_name)
|
170
|
+
post 'tags/update', JSON.generate(tag: { id: tagid, name: new_tag_name })
|
171
|
+
end
|
172
|
+
|
173
|
+
def tag_delete(tagid)
|
174
|
+
post 'tags/delete', JSON.generate(tag: { id: tagid })
|
175
|
+
end
|
176
|
+
|
177
|
+
def tag_add_user(tagid, openids)
|
178
|
+
post 'tags/members/batchtagging', JSON.generate(openid_list: openids, tagid: tagid)
|
179
|
+
end
|
180
|
+
|
181
|
+
def tag_del_user(tagid, openids)
|
182
|
+
post 'tags/members/batchuntagging', JSON.generate(openid_list: openids, tagid: tagid)
|
183
|
+
end
|
184
|
+
|
185
|
+
def tag(tagid, next_openid = '')
|
186
|
+
post 'user/tag/get', JSON.generate(tagid: tagid, next_openid: next_openid)
|
187
|
+
end
|
188
|
+
|
189
|
+
def web_access_token(code)
|
190
|
+
params = {
|
191
|
+
appid: access_token.appid,
|
192
|
+
secret: access_token.secret,
|
193
|
+
code: code,
|
194
|
+
grant_type: 'authorization_code'
|
195
|
+
}
|
196
|
+
client.get 'oauth2/access_token', params: params, base: OAUTH2_BASE
|
197
|
+
end
|
198
|
+
|
199
|
+
def web_auth_access_token(web_access_token, openid)
|
200
|
+
client.get 'auth', params: { access_token: web_access_token, openid: openid }, base: OAUTH2_BASE
|
201
|
+
end
|
202
|
+
|
203
|
+
def web_refresh_access_token(user_refresh_token)
|
204
|
+
params = {
|
205
|
+
appid: access_token.appid,
|
206
|
+
grant_type: 'refresh_token',
|
207
|
+
refresh_token: user_refresh_token
|
208
|
+
}
|
209
|
+
client.get 'oauth2/refresh_token', params: params, base: OAUTH2_BASE
|
210
|
+
end
|
211
|
+
|
212
|
+
def web_userinfo(web_access_token, openid, lang = 'zh_CN')
|
213
|
+
client.get 'userinfo', params: { access_token: web_access_token, openid: openid, lang: lang }, base: OAUTH2_BASE
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
data/lib/wechat/http_client.rb
CHANGED
@@ -56,12 +56,11 @@ module Wechat
|
|
56
56
|
# 42001: access_token timeout
|
57
57
|
# 40014: invalid access_token
|
58
58
|
# 40001, invalid credential, access_token is invalid or not latest hint
|
59
|
-
# 48001, api unauthorized hint,
|
60
|
-
when 42001, 40014, 40001
|
59
|
+
# 48001, api unauthorized hint, should not handle here # GH-230
|
60
|
+
when 42001, 40014, 40001
|
61
61
|
raise AccessTokenExpiredError
|
62
|
+
# 40029, invalid code for mp # GH-225
|
62
63
|
# 43004, require subscribe hint # GH-214
|
63
|
-
when 43004
|
64
|
-
Rails.logger.info "wechat gem template_message_send failure, errcode 43004, errmsg: #{data['errmsg']}"
|
65
64
|
else
|
66
65
|
raise ResponseError.new(data['errcode'], data['errmsg'])
|
67
66
|
end
|
data/lib/wechat/message.rb
CHANGED
@@ -171,8 +171,11 @@ module Wechat
|
|
171
171
|
update(MsgType: 'ref_mpnews', MpNews: { MediaId: media_id })
|
172
172
|
end
|
173
173
|
|
174
|
+
TEMPLATE_KEYS = %i[template_id form_id page color
|
175
|
+
emphasis_keyword topcolor url miniprogram data].freeze
|
176
|
+
|
174
177
|
def template(opts = {})
|
175
|
-
template_fields = opts.symbolize_keys.slice(
|
178
|
+
template_fields = opts.symbolize_keys.slice(*TEMPLATE_KEYS)
|
176
179
|
update(MsgType: 'template', Template: template_fields)
|
177
180
|
end
|
178
181
|
|
@@ -184,17 +187,20 @@ module Wechat
|
|
184
187
|
end
|
185
188
|
|
186
189
|
TO_JSON_KEY_MAP = {
|
187
|
-
'ToUserName'
|
188
|
-
'ToWxName'
|
189
|
-
'MediaId'
|
190
|
-
'MpNews'
|
191
|
-
'ThumbMediaId'
|
192
|
-
'TemplateId'
|
190
|
+
'ToUserName' => 'touser',
|
191
|
+
'ToWxName' => 'towxname',
|
192
|
+
'MediaId' => 'media_id',
|
193
|
+
'MpNews' => 'mpnews',
|
194
|
+
'ThumbMediaId' => 'thumb_media_id',
|
195
|
+
'TemplateId' => 'template_id',
|
196
|
+
'FormId' => 'form_id',
|
193
197
|
'ContentSourceUrl' => 'content_source_url',
|
194
|
-
'ShowCoverPic'
|
198
|
+
'ShowCoverPic' => 'show_cover_pic'
|
195
199
|
}.freeze
|
196
200
|
|
197
|
-
TO_JSON_ALLOWED = %w
|
201
|
+
TO_JSON_ALLOWED = %w[touser msgtype content image voice video file
|
202
|
+
music news articles template agentid filter
|
203
|
+
send_ignore_reprint mpnews towxname].freeze
|
198
204
|
|
199
205
|
def to_json
|
200
206
|
keep_camel_case_key = message_hash[:MsgType] == 'template'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'wechat/api_base'
|
2
|
+
require 'wechat/http_client'
|
3
|
+
require 'wechat/token/public_access_token'
|
4
|
+
require 'wechat/ticket/public_jsapi_ticket'
|
5
|
+
require 'wechat/concern/common'
|
6
|
+
|
7
|
+
module Wechat
|
8
|
+
class MpApi < ApiBase
|
9
|
+
include Concern::Common
|
10
|
+
|
11
|
+
def template_message_send(message)
|
12
|
+
post 'message/wxopen/template/send', message.to_json, content_type: :json
|
13
|
+
end
|
14
|
+
|
15
|
+
def list_template_library(offset: 0, count: 20)
|
16
|
+
post 'wxopen/template/library/list', JSON.generate(offset: offset, count: count)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list_template_library_keywords(id)
|
20
|
+
post 'wxopen/template/library/get', JSON.generate(id: id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_message_template(id, keyword_id_list)
|
24
|
+
post 'wxopen/template/add', JSON.generate(id: id, keyword_id_list: keyword_id_list)
|
25
|
+
end
|
26
|
+
|
27
|
+
def list_message_template(offset: 0, count: 20)
|
28
|
+
post 'wxopen/template/list', JSON.generate(offset: offset, count: count)
|
29
|
+
end
|
30
|
+
|
31
|
+
def del_message_template(template_id)
|
32
|
+
post 'wxopen/template/del', JSON.generate(template_id: template_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def jscode2session(code)
|
36
|
+
params = {
|
37
|
+
appid: access_token.appid,
|
38
|
+
secret: access_token.secret,
|
39
|
+
js_code: code,
|
40
|
+
grant_type: 'authorization_code'
|
41
|
+
}
|
42
|
+
|
43
|
+
client.get 'jscode2session', params: params, base: OAUTH2_BASE
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/wechat/responder.rb
CHANGED
@@ -19,14 +19,18 @@ module Wechat
|
|
19
19
|
skip_before_action :verify_authenticity_token, raise: false
|
20
20
|
end
|
21
21
|
|
22
|
+
before_action :config_account, only: [:show, :create]
|
22
23
|
before_action :verify_signature, only: [:show, :create]
|
23
24
|
else
|
24
25
|
skip_before_filter :verify_authenticity_token
|
26
|
+
before_filter :config_account, only: [:show, :create]
|
25
27
|
before_filter :verify_signature, only: [:show, :create]
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
31
|
module ClassMethods
|
32
|
+
attr_accessor :account_from_request
|
33
|
+
|
30
34
|
def on(message_type, with: nil, respond: nil, &block)
|
31
35
|
raise 'Unknow message type' unless [:text, :image, :voice, :video, :shortvideo, :link, :event, :click, :view, :scan, :batch_job, :location, :label_location, :fallback].include?(message_type)
|
32
36
|
config = respond.nil? ? {} : { respond: respond }
|
@@ -170,8 +174,8 @@ module Wechat
|
|
170
174
|
end
|
171
175
|
|
172
176
|
def show
|
173
|
-
if
|
174
|
-
echostr, _corp_id = unpack(decrypt(Base64.decode64(params[:echostr]),
|
177
|
+
if @we_corpid.present?
|
178
|
+
echostr, _corp_id = unpack(decrypt(Base64.decode64(params[:echostr]), @we_encoding_aes_key))
|
175
179
|
if Rails::VERSION::MAJOR >= 4
|
176
180
|
render plain: echostr
|
177
181
|
else
|
@@ -207,17 +211,27 @@ module Wechat
|
|
207
211
|
|
208
212
|
private
|
209
213
|
|
214
|
+
def config_account
|
215
|
+
account = self.class.account_from_request&.call(request)
|
216
|
+
config = account ? Wechat.config(account) : nil
|
217
|
+
|
218
|
+
@we_encrypt_mode = config&.encrypt_mode || self.class.encrypt_mode
|
219
|
+
@we_encoding_aes_key = config&.encoding_aes_key || self.class.encoding_aes_key
|
220
|
+
@we_token = config&.token || self.class.token
|
221
|
+
@we_corpid = config&.corpid || self.class.corpid
|
222
|
+
end
|
223
|
+
|
210
224
|
def verify_signature
|
211
|
-
if
|
225
|
+
if @we_encrypt_mode
|
212
226
|
signature = params[:signature] || params[:msg_signature]
|
213
227
|
msg_encrypt = params[:echostr] || request_encrypt_content
|
214
228
|
else
|
215
229
|
signature = params[:signature]
|
216
230
|
end
|
217
231
|
|
218
|
-
msg_encrypt = nil unless
|
232
|
+
msg_encrypt = nil unless @we_corpid.present?
|
219
233
|
|
220
|
-
render plain: 'Forbidden', status: 403 if signature != Signature.hexdigest(
|
234
|
+
render plain: 'Forbidden', status: 403 if signature != Signature.hexdigest(@we_token,
|
221
235
|
params[:timestamp],
|
222
236
|
params[:nonce],
|
223
237
|
msg_encrypt)
|
@@ -226,8 +240,8 @@ module Wechat
|
|
226
240
|
def post_xml
|
227
241
|
data = request_content
|
228
242
|
|
229
|
-
if
|
230
|
-
content, @
|
243
|
+
if @we_encrypt_mode && request_encrypt_content.present?
|
244
|
+
content, @we_app_id = unpack(decrypt(Base64.decode64(request_encrypt_content), @we_encoding_aes_key))
|
231
245
|
data = Hash.from_xml(content)
|
232
246
|
end
|
233
247
|
|
@@ -265,8 +279,8 @@ module Wechat
|
|
265
279
|
def process_response(response)
|
266
280
|
msg = response[:MsgType] == 'success' ? 'success' : response.to_xml
|
267
281
|
|
268
|
-
if
|
269
|
-
encrypt = Base64.strict_encode64(encrypt(pack(msg, @
|
282
|
+
if @we_encrypt_mode
|
283
|
+
encrypt = Base64.strict_encode64(encrypt(pack(msg, @we_app_id), @we_encoding_aes_key))
|
270
284
|
msg = gen_msg(encrypt, params[:timestamp], params[:nonce])
|
271
285
|
end
|
272
286
|
|
@@ -274,7 +288,7 @@ module Wechat
|
|
274
288
|
end
|
275
289
|
|
276
290
|
def gen_msg(encrypt, timestamp, nonce)
|
277
|
-
msg_sign = Signature.hexdigest(
|
291
|
+
msg_sign = Signature.hexdigest(@we_token, timestamp, nonce, encrypt)
|
278
292
|
|
279
293
|
{ Encrypt: encrypt,
|
280
294
|
MsgSignature: msg_sign,
|
@@ -284,7 +298,7 @@ module Wechat
|
|
284
298
|
end
|
285
299
|
|
286
300
|
def request_encrypt_content
|
287
|
-
request_content
|
301
|
+
request_content&.dig('xml', 'Encrypt')
|
288
302
|
end
|
289
303
|
|
290
304
|
def request_content
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wechat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skinnyworm
|
@@ -31,7 +31,7 @@ cert_chain:
|
|
31
31
|
fGxGnQhzVaW07NKOCRrAZlrF8iqso4JR7Vm9bhFdzxUPLr70njwHLtDS2CHgo1VW
|
32
32
|
1xAjN8ZXXpAmVv7V6cI9RTmQHPu/fFn+E0sG9w==
|
33
33
|
-----END CERTIFICATE-----
|
34
|
-
date: 2018-
|
34
|
+
date: 2018-05-31 00:00:00.000000000 Z
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: activesupport
|
@@ -53,6 +53,26 @@ dependencies:
|
|
53
53
|
- - "<="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '5.2'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: http
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.0.4
|
63
|
+
- - "<"
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '4'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 1.0.4
|
73
|
+
- - "<"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4'
|
56
76
|
- !ruby/object:Gem::Dependency
|
57
77
|
name: nokogiri
|
58
78
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,25 +102,19 @@ dependencies:
|
|
82
102
|
- !ruby/object:Gem::Version
|
83
103
|
version: '0'
|
84
104
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
105
|
+
name: rails
|
86
106
|
requirement: !ruby/object:Gem::Requirement
|
87
107
|
requirements:
|
88
108
|
- - ">="
|
89
109
|
- !ruby/object:Gem::Version
|
90
|
-
version: 1
|
91
|
-
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version: '4'
|
94
|
-
type: :runtime
|
110
|
+
version: '5.1'
|
111
|
+
type: :development
|
95
112
|
prerelease: false
|
96
113
|
version_requirements: !ruby/object:Gem::Requirement
|
97
114
|
requirements:
|
98
115
|
- - ">="
|
99
116
|
- !ruby/object:Gem::Version
|
100
|
-
version: 1
|
101
|
-
- - "<"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '4'
|
117
|
+
version: '5.1'
|
104
118
|
- !ruby/object:Gem::Dependency
|
105
119
|
name: rspec-rails
|
106
120
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,20 +129,6 @@ dependencies:
|
|
115
129
|
- - "~>"
|
116
130
|
- !ruby/object:Gem::Version
|
117
131
|
version: '3.6'
|
118
|
-
- !ruby/object:Gem::Dependency
|
119
|
-
name: rails
|
120
|
-
requirement: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '5.1'
|
125
|
-
type: :development
|
126
|
-
prerelease: false
|
127
|
-
version_requirements: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '5.1'
|
132
132
|
- !ruby/object:Gem::Dependency
|
133
133
|
name: sqlite3
|
134
134
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,11 +176,13 @@ files:
|
|
176
176
|
- lib/wechat/api_base.rb
|
177
177
|
- lib/wechat/api_loader.rb
|
178
178
|
- lib/wechat/cipher.rb
|
179
|
+
- lib/wechat/concern/common.rb
|
179
180
|
- lib/wechat/controller_api.rb
|
180
181
|
- lib/wechat/corp_api.rb
|
181
182
|
- lib/wechat/helpers.rb
|
182
183
|
- lib/wechat/http_client.rb
|
183
184
|
- lib/wechat/message.rb
|
185
|
+
- lib/wechat/mp_api.rb
|
184
186
|
- lib/wechat/responder.rb
|
185
187
|
- lib/wechat/signature.rb
|
186
188
|
- lib/wechat/ticket/corp_jsapi_ticket.rb
|
@@ -199,9 +201,9 @@ require_paths:
|
|
199
201
|
- lib
|
200
202
|
required_ruby_version: !ruby/object:Gem::Requirement
|
201
203
|
requirements:
|
202
|
-
- - "
|
204
|
+
- - "~>"
|
203
205
|
- !ruby/object:Gem::Version
|
204
|
-
version: '
|
206
|
+
version: '2.3'
|
205
207
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
208
|
requirements:
|
207
209
|
- - ">="
|
metadata.gz.sig
CHANGED
Binary file
|