wechat 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.org/Eric-Guo/wechat.svg)](https://travis-ci.org/Eric-Guo/wechat) [![
|
1
|
+
WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.org/Eric-Guo/wechat.svg)](https://travis-ci.org/Eric-Guo/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
|
2
2
|
======
|
3
3
|
|
4
4
|
[![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](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 [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.org/Eric-Guo/wechat.svg)](https://travis-ci.org/Eric-Guo/wechat) [![
|
1
|
+
WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.org/Eric-Guo/wechat.svg)](https://travis-ci.org/Eric-Guo/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
|
2
2
|
======
|
3
3
|
|
4
4
|
[![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](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
|