wechat 0.11.10 → 0.12.4
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 +3 -1
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +25 -0
- data/README-CN.md +3 -1
- data/README.md +4 -2
- data/bin/wechat +11 -0
- data/lib/action_controller/wechat_responder.rb +16 -12
- data/lib/generators/wechat/templates/app/models/wechat_config.rb +1 -1
- data/lib/wechat.rb +4 -1
- data/lib/wechat/api.rb +8 -0
- data/lib/wechat/api_base.rb +2 -1
- data/lib/wechat/api_loader.rb +14 -2
- data/lib/wechat/cipher.rb +3 -3
- data/lib/wechat/concern/common.rb +4 -6
- data/lib/wechat/concern/qcloud.rb +115 -0
- data/lib/wechat/controller_api.rb +1 -1
- data/lib/wechat/corp_api.rb +7 -1
- data/lib/wechat/helpers.rb +3 -8
- data/lib/wechat/http_client.rb +7 -7
- data/lib/wechat/message.rb +5 -4
- data/lib/wechat/mp_api.rb +11 -0
- data/lib/wechat/qcloud/token.rb +66 -0
- data/lib/wechat/responder.rb +4 -2
- metadata +24 -8
- 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: 761a53485d1170176263ef051a032606c7ae1c5533c6ec9c3f4808a4aeb5cd66
|
4
|
+
data.tar.gz: be822b26ee8bc46bcc548190d8f7082e0424d9214ed88a26e3846be39c7fcde2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 261f379c71351cf41d93e4bc3953c66f62d11c2f136111f2642379e78cde736e2e2667817c71f3001fec80d4432f83dcde2f4ac5d6f380b67daa55d6477252e4
|
7
|
+
data.tar.gz: b57190737963d214a9e644334ead4af99e68b774b3973f49ae652f6576b8aeb248407062e0ffc0fbb3ac86347b81b6b90942b632090eef6729ce6dc98504f4fe
|
checksums.yaml.gz.sig
CHANGED
@@ -1 +1,3 @@
|
|
1
|
-
|
1
|
+
��އ�i�(�m;L��nuq_HJ�*��-q$�8n{k#���k�Mp=�� f���0�#�]����u�Z�T.C�_��$�o�*���L}[&�1��ɉ�n������@˼?՛4��l}�W[��^������GD���p"<�9��y�t���T��x��I*��")�rgD�KAr���*9HwCN�o0!�S���-^��Z�X���
|
2
|
+
L��n�8%��Z����ڭ}��d�>`6�^��q�J2{X6M�
|
3
|
+
>�@J߆�L�S��Ǣt>=zUL��U�(9ۺ��&��%�yR�
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v0.12.4 (released at 4/21/2021)
|
4
|
+
|
5
|
+
* New material_add_news API, by @zlei1 #300
|
6
|
+
* Support open_tag, by @xiajian2019 #299
|
7
|
+
|
8
|
+
## v0.12.3 (released at 3/15/2021)
|
9
|
+
|
10
|
+
* Fix MpApi initialize bug, by @hardywu #296
|
11
|
+
|
12
|
+
## v0.12.2 (released at 3/3/2021)
|
13
|
+
|
14
|
+
* New convert_to_userid API
|
15
|
+
|
16
|
+
## v0.12.1 (released at 28/12/2020)
|
17
|
+
|
18
|
+
* Support Ruby 3.0.
|
19
|
+
* Qcloud_token support.
|
20
|
+
* CRUD of tencent cloud DB for miniapp
|
21
|
+
* TCB storage API support.
|
22
|
+
* Set default branch to *main*.
|
23
|
+
|
24
|
+
## v0.11.11 (released at 09/13/2020)
|
25
|
+
|
26
|
+
* FIX: fix_load_controller_wechat not support MP type, by @Msms-NJ #281
|
27
|
+
|
3
28
|
## v0.11.10 (released at 09/02/2020)
|
4
29
|
|
5
30
|
* ADD: Wechat::MpApi.wxa_msg_sec_check.
|
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.
|
1
|
+
WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.com/Eric-Guo/wechat.svg)](https://travis-ci.com/github/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)
|
@@ -362,6 +362,7 @@ Wechat Public Account commands:
|
|
362
362
|
wechat groups # 查询所有分组
|
363
363
|
wechat material_get [MEDIA_ID, PATH] # 永久媒体下载
|
364
364
|
wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
|
365
|
+
wechat material_add_news [MPNEWS_YAML_PATH] # 永久图文素材上传
|
365
366
|
wechat material_count # 获取永久素材总数
|
366
367
|
wechat material_delete [MEDIA_ID] # 删除永久素材
|
367
368
|
wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
|
@@ -416,6 +417,7 @@ Wechat Enterprise Account commands:
|
|
416
417
|
wechat callbackip # 获取微信服务器IP地址
|
417
418
|
wechat clear_quota # 接口调用次数清零
|
418
419
|
wechat convert_to_openid [USER_ID] # userid转换成openid
|
420
|
+
wechat convert_to_userid [OPENID] # openid转换成userid
|
419
421
|
wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
|
420
422
|
wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
|
421
423
|
wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
|
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.
|
1
|
+
WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.com/Eric-Guo/wechat.svg)](https://travis-ci.com/github/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)
|
@@ -391,8 +391,9 @@ Wechat Public Account commands:
|
|
391
391
|
wechat group_delete [GROUP_ID] # 删除分组
|
392
392
|
wechat group_update [GROUP_ID, NEW_GROUP_NAME] # 修改分组名
|
393
393
|
wechat groups # 查询所有分组
|
394
|
-
wechat material_get [MEDIA_ID, PATH]
|
394
|
+
wechat material_get [MEDIA_ID, PATH] # 永久媒体下载
|
395
395
|
wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
|
396
|
+
wechat material_add_news [MPNEWS_YAML_PATH] # 永久图文素材上传
|
396
397
|
wechat material_count # 获取永久素材总数
|
397
398
|
wechat material_delete [MEDIA_ID] # 删除永久素材
|
398
399
|
wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
|
@@ -448,6 +449,7 @@ Wechat Enterprise Account commands:
|
|
448
449
|
wechat callbackip # 获取微信服务器IP地址
|
449
450
|
wechat clear_quota # 接口调用次数清零
|
450
451
|
wechat convert_to_openid [USER_ID] # userid转换成openid
|
452
|
+
wechat convert_to_userid [OPENID] # openid转换成userid
|
451
453
|
wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
|
452
454
|
wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
|
453
455
|
wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
|
data/bin/wechat
CHANGED
@@ -187,6 +187,11 @@ class App < Thor
|
|
187
187
|
puts wechat_api.convert_to_openid(userid)
|
188
188
|
end
|
189
189
|
|
190
|
+
desc 'convert_to_userid [OPENID]', 'openid转换成userid'
|
191
|
+
def convert_to_userid(openid)
|
192
|
+
puts wechat_api.convert_to_userid(openid)
|
193
|
+
end
|
194
|
+
|
190
195
|
desc 'agent_list', '获取应用概况列表'
|
191
196
|
def agent_list
|
192
197
|
r = wechat_api.agent_list
|
@@ -406,6 +411,12 @@ class App < Thor
|
|
406
411
|
puts wechat_api.material_add(type, path)
|
407
412
|
end
|
408
413
|
|
414
|
+
desc 'material_add_news [MPNEWS_YAML_PATH]', '永久图文素材上传'
|
415
|
+
def material_add_news(mpnews_yaml_path)
|
416
|
+
new = YAML.load(File.read(mpnews_yaml_path))
|
417
|
+
puts wechat_api.material_add_news(Wechat::Message.new(MsgType: 'mpnews').mpnews(new['articles']))
|
418
|
+
end
|
419
|
+
|
409
420
|
desc 'material_delete [MEDIA_ID]', '删除永久素材'
|
410
421
|
def material_delete(media_id)
|
411
422
|
puts wechat_api.material_delete(media_id)
|
@@ -38,25 +38,29 @@ module ActionController
|
|
38
38
|
self.trusted_domain_fullname = opts[:trusted_domain_fullname] || cfg.trusted_domain_fullname
|
39
39
|
self.oauth2_cookie_duration = opts[:oauth2_cookie_duration] || cfg.oauth2_cookie_duration.to_i.seconds
|
40
40
|
self.timeout = opts[:timeout] || cfg.timeout
|
41
|
-
self.
|
42
|
-
|
43
|
-
else
|
44
|
-
cfg.skip_verify_ssl
|
45
|
-
end
|
41
|
+
self.qcloud_token_lifespan = opts[:qcloud_token_lifespan] || cfg.qcloud_token_lifespan
|
42
|
+
self.skip_verify_ssl = opts.key?(:skip_verify_ssl) ? opts[:skip_verify_ssl] : cfg.skip_verify_ssl
|
46
43
|
|
47
44
|
return Wechat.api if account == :default && opts.empty?
|
48
45
|
|
49
46
|
access_token = opts[:access_token] || cfg.access_token
|
50
47
|
jsapi_ticket = opts[:jsapi_ticket] || cfg.jsapi_ticket
|
48
|
+
qcloud_env = opts[:qcloud_env] || cfg.qcloud_env
|
49
|
+
qcloud_token = opts[:qcloud_token] || cfg.qcloud_token
|
51
50
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
api_type = opts[:type] || cfg.type
|
52
|
+
secret = corpid.present? ? opts[:corpsecret] || cfg.corpsecret : opts[:secret] || cfg.secret
|
53
|
+
|
54
|
+
get_wechat_api(api_type, corpid, appid, secret, access_token, agentid, timeout, skip_verify_ssl, jsapi_ticket, qcloud_env, qcloud_token, qcloud_token_lifespan)
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_wechat_api(api_type, corpid, appid, secret, access_token, agentid, timeout, skip_verify_ssl, jsapi_ticket, qcloud_env, qcloud_token, qcloud_token_lifespan)
|
58
|
+
if api_type && api_type.to_sym == :mp
|
59
|
+
Wechat::MpApi.new(appid, secret, access_token, timeout, skip_verify_ssl, jsapi_ticket, qcloud_env, qcloud_token, qcloud_token_lifespan)
|
60
|
+
elsif corpid.present?
|
61
|
+
Wechat::CorpApi.new(corpid, secret, access_token, agentid, timeout, skip_verify_ssl, jsapi_ticket)
|
56
62
|
else
|
57
|
-
secret
|
58
|
-
Wechat::Api.new(appid, secret, access_token, \
|
59
|
-
timeout, skip_verify_ssl, jsapi_ticket)
|
63
|
+
Wechat::Api.new(appid, secret, access_token, timeout, skip_verify_ssl, jsapi_ticket)
|
60
64
|
end
|
61
65
|
end
|
62
66
|
end
|
@@ -35,7 +35,7 @@ class WechatConfig < ActiveRecord::Base
|
|
35
35
|
errors.add(:corpsecret, 'cannot be nil when corpid is set') if self[:corpsecret].blank?
|
36
36
|
errors.add(:agentid, 'cannot be nil when corpid is set') if self[:agentid].blank?
|
37
37
|
else
|
38
|
-
errors
|
38
|
+
errors.add(:base, 'Either appid or corpid must be set')
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
data/lib/wechat.rb
CHANGED
@@ -16,9 +16,12 @@ module Wechat
|
|
16
16
|
autoload :ControllerApi, 'wechat/controller_api'
|
17
17
|
|
18
18
|
class AccessTokenExpiredError < StandardError; end
|
19
|
+
|
19
20
|
class InvalidCredentialError < StandardError; end
|
21
|
+
|
20
22
|
class ResponseError < StandardError
|
21
23
|
attr_reader :error_code
|
24
|
+
|
22
25
|
def initialize(errcode, errmsg)
|
23
26
|
@error_code = errcode
|
24
27
|
super "#{errmsg}(#{error_code})"
|
@@ -47,7 +50,7 @@ module Wechat
|
|
47
50
|
decrypted_data = Base64.decode64(encrypted_data)
|
48
51
|
JSON.parse(cipher.update(decrypted_data) + cipher.final)
|
49
52
|
rescue StandardError => e
|
50
|
-
{
|
53
|
+
{ errcode: 41003, errmsg: e.message }
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
data/lib/wechat/api.rb
CHANGED
@@ -8,6 +8,14 @@ require 'wechat/concern/common'
|
|
8
8
|
|
9
9
|
module Wechat
|
10
10
|
class Api < ApiBase
|
11
|
+
def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
|
12
|
+
super()
|
13
|
+
@client = HttpClient.new(Wechat::Api::API_BASE, timeout, skip_verify_ssl)
|
14
|
+
@access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
|
15
|
+
@jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
|
16
|
+
@qcloud = nil
|
17
|
+
end
|
18
|
+
|
11
19
|
include Concern::Common
|
12
20
|
|
13
21
|
def template_message_send(message)
|
data/lib/wechat/api_base.rb
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
module Wechat
|
4
4
|
class ApiBase
|
5
|
-
attr_reader :access_token, :client, :jsapi_ticket
|
5
|
+
attr_reader :access_token, :client, :jsapi_ticket, :qcloud
|
6
6
|
|
7
7
|
API_BASE = 'https://api.weixin.qq.com/cgi-bin/'
|
8
8
|
MP_BASE = 'https://mp.weixin.qq.com/cgi-bin/'
|
9
9
|
WXA_BASE = 'https://api.weixin.qq.com/wxa/'
|
10
10
|
OAUTH2_BASE = 'https://api.weixin.qq.com/sns/'
|
11
11
|
DATACUBE_BASE = 'https://api.weixin.qq.com/datacube/'
|
12
|
+
TCB_BASE = 'https://api.weixin.qq.com/tcb/'
|
12
13
|
QYAPI_BASE = 'https://qyapi.weixin.qq.com/cgi-bin/'
|
13
14
|
|
14
15
|
def callbackip
|
data/lib/wechat/api_loader.rb
CHANGED
@@ -9,9 +9,16 @@ module Wechat
|
|
9
9
|
token_file = options[:token_file] || c.access_token.presence || '/var/tmp/wechat_access_token'
|
10
10
|
js_token_file = options[:js_token_file] || c.jsapi_ticket.presence || '/var/tmp/wechat_jsapi_ticket'
|
11
11
|
type = options[:type] || c.type
|
12
|
+
|
12
13
|
if c.appid && c.secret && token_file.present?
|
13
|
-
|
14
|
-
|
14
|
+
if type == 'mp'
|
15
|
+
qcloud_env = options[:qcloud_env] || c.qcloud_env
|
16
|
+
qcloud_token_file = options[:qcloud_token_file] || c.qcloud_token_file.presence || '/var/tmp/qcloud_access_token'
|
17
|
+
qcloud_token_lifespan = options[:qcloud_token_lifespan] || c.qcloud_token_lifespan
|
18
|
+
Wechat::MpApi.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file, qcloud_env, qcloud_token_file, qcloud_token_lifespan)
|
19
|
+
else
|
20
|
+
Wechat::Api.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file)
|
21
|
+
end
|
15
22
|
elsif c.corpid && c.corpsecret && token_file.present?
|
16
23
|
Wechat::CorpApi.new(c.corpid, c.corpsecret, token_file, c.agentid, c.timeout, c.skip_verify_ssl, js_token_file)
|
17
24
|
else
|
@@ -46,11 +53,13 @@ module Wechat
|
|
46
53
|
configs.each do |_, cfg|
|
47
54
|
cfg[:access_token] ||= Rails.root.try(:join, 'tmp/access_token').try(:to_path)
|
48
55
|
cfg[:jsapi_ticket] ||= Rails.root.try(:join, 'tmp/jsapi_ticket').try(:to_path)
|
56
|
+
cfg[:qcloud_token] ||= Rails.root.try(:join, 'tmp/qcloud_token').try(:to_path)
|
49
57
|
end
|
50
58
|
end
|
51
59
|
|
52
60
|
configs.each do |_, cfg|
|
53
61
|
cfg[:timeout] ||= 20
|
62
|
+
cfg[:qcloud_token_lifespan] ||= 7200
|
54
63
|
cfg[:have_session_class] = class_exists?('WechatSession')
|
55
64
|
cfg[:oauth2_cookie_duration] ||= 1.hour
|
56
65
|
end
|
@@ -135,6 +144,9 @@ module Wechat
|
|
135
144
|
skip_verify_ssl: ENV['WECHAT_SKIP_VERIFY_SSL'],
|
136
145
|
encoding_aes_key: ENV['WECHAT_ENCODING_AES_KEY'],
|
137
146
|
jsapi_ticket: ENV['WECHAT_JSAPI_TICKET'],
|
147
|
+
qcloud_env: ENV['WECHAT_QCLOUD_ENV'],
|
148
|
+
qcloud_token_file: ENV['WECHAT_QCLOUD_TOKEN'],
|
149
|
+
qcloud_token_lifespan: ENV['WECHAT_QCLOUD_TOKEN_LIFESPAN'],
|
138
150
|
trusted_domain_fullname: ENV['WECHAT_TRUSTED_DOMAIN_FULLNAME'] }
|
139
151
|
{ default: value }
|
140
152
|
end
|
data/lib/wechat/cipher.rb
CHANGED
@@ -10,7 +10,7 @@ module Wechat
|
|
10
10
|
cipher.encrypt
|
11
11
|
|
12
12
|
cipher.padding = 0
|
13
|
-
key_data = Base64.decode64(encoding_aes_key
|
13
|
+
key_data = Base64.decode64("#{encoding_aes_key}=")
|
14
14
|
cipher.key = key_data
|
15
15
|
cipher.iv = [key_data].pack('H*')
|
16
16
|
|
@@ -22,7 +22,7 @@ module Wechat
|
|
22
22
|
cipher.decrypt
|
23
23
|
|
24
24
|
cipher.padding = 0
|
25
|
-
key_data = Base64.decode64(encoding_aes_key
|
25
|
+
key_data = Base64.decode64("#{encoding_aes_key}=")
|
26
26
|
cipher.key = key_data
|
27
27
|
cipher.iv = [key_data].pack('H*')
|
28
28
|
|
@@ -43,7 +43,7 @@ module Wechat
|
|
43
43
|
msg = decode_padding(msg)
|
44
44
|
msg_len = msg[16, 4].reverse.unpack1('V')
|
45
45
|
content = msg[20, msg_len]
|
46
|
-
app_id = msg[(
|
46
|
+
app_id = msg[(msg_len + 20)..-1]
|
47
47
|
|
48
48
|
[content, app_id]
|
49
49
|
end
|
@@ -3,12 +3,6 @@
|
|
3
3
|
module Wechat
|
4
4
|
module Concern
|
5
5
|
module Common
|
6
|
-
def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
|
7
|
-
@client = HttpClient.new(Wechat::Api::API_BASE, timeout, skip_verify_ssl)
|
8
|
-
@access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
|
9
|
-
@jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
|
10
|
-
end
|
11
|
-
|
12
6
|
def groups
|
13
7
|
get 'groups/get'
|
14
8
|
end
|
@@ -158,6 +152,10 @@ module Wechat
|
|
158
152
|
post_file 'material/add_material', file, params: { type: type }
|
159
153
|
end
|
160
154
|
|
155
|
+
def material_add_news(mpnews_message)
|
156
|
+
post 'material/add_news', mpnews_message.to_json
|
157
|
+
end
|
158
|
+
|
161
159
|
def material_delete(media_id)
|
162
160
|
post 'material/del_material', JSON.generate(media_id: media_id)
|
163
161
|
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wechat
|
4
|
+
module Concern
|
5
|
+
module Qcloud
|
6
|
+
def invoke_cloud_function(function_name, post_body_params)
|
7
|
+
post 'invokecloudfunction', JSON.generate(post_body_params), params: { env: qcloud.qcloud_env, name: function_name }, base: Wechat::Api::TCB_BASE
|
8
|
+
end
|
9
|
+
|
10
|
+
def qdb_migrate_import(collection_name, file_path, file_type: Wechat::Qcloud::FILE_TYPE_JSON, stop_on_error: false, conflict_mode: Wechat::Qcloud::CONFLICT_MODE_UPSERT)
|
11
|
+
import_params_hash = { env: qcloud.qcloud_env,
|
12
|
+
collection_name: collection_name,
|
13
|
+
file_path: file_path,
|
14
|
+
file_type: file_type,
|
15
|
+
stop_on_error: stop_on_error,
|
16
|
+
conflict_mode: conflict_mode }
|
17
|
+
post 'databasemigrateimport', JSON.generate(import_params_hash), base: Wechat::Api::TCB_BASE
|
18
|
+
end
|
19
|
+
|
20
|
+
def qdb_migrate_export(query, file_path, file_type: Wechat::Qcloud::FILE_TYPE_JSON)
|
21
|
+
export_params_hash = { env: qcloud.qcloud_env,
|
22
|
+
file_path: file_path,
|
23
|
+
file_type: file_type,
|
24
|
+
query: query }
|
25
|
+
post 'databasemigrateexport', JSON.generate(export_params_hash), base: Wechat::Api::TCB_BASE
|
26
|
+
end
|
27
|
+
|
28
|
+
def qdb_migrate_query(job_id)
|
29
|
+
query_info_hash = { env: qcloud.qcloud_env,
|
30
|
+
job_id: job_id }
|
31
|
+
|
32
|
+
post 'databasemigratequeryinfo', JSON.generate(query_info_hash), base: Wechat::Api::TCB_BASE
|
33
|
+
end
|
34
|
+
|
35
|
+
def qdb_update_index(collection_name, create_indexes: [], drop_indexes: [])
|
36
|
+
update_index_params_hash = { env: qcloud.qcloud_env,
|
37
|
+
collection_name: collection_name,
|
38
|
+
create_indexes: create_indexes,
|
39
|
+
drop_indexes: drop_indexes }
|
40
|
+
post 'updateindex', JSON.generate(update_index_params_hash), base: Wechat::Api::TCB_BASE
|
41
|
+
end
|
42
|
+
|
43
|
+
def qdb_collection_add(collection_name)
|
44
|
+
collection_add_params_hash = { env: qcloud.qcloud_env,
|
45
|
+
collection_name: collection_name }
|
46
|
+
post 'databasecollectionadd', JSON.generate(collection_add_params_hash), base: Wechat::Api::TCB_BASE
|
47
|
+
end
|
48
|
+
|
49
|
+
def qdb_collection_delete(collection_name)
|
50
|
+
collection_delete_params_hash = { env: qcloud.qcloud_env,
|
51
|
+
collection_name: collection_name }
|
52
|
+
post 'databasecollectiondelete', JSON.generate(collection_delete_params_hash), base: Wechat::Api::TCB_BASE
|
53
|
+
end
|
54
|
+
|
55
|
+
def qdb_collections(limit: 10, offset: 0)
|
56
|
+
get_collections_params_hash = { env: qcloud.qcloud_env,
|
57
|
+
limit: limit,
|
58
|
+
offset: offset }
|
59
|
+
post 'databasecollectionget', JSON.generate(get_collections_params_hash), base: Wechat::Api::TCB_BASE
|
60
|
+
end
|
61
|
+
|
62
|
+
def qdb_add(add_query)
|
63
|
+
post 'databaseadd', JSON.generate(env: qcloud.qcloud_env, query: add_query), base: Wechat::Api::TCB_BASE
|
64
|
+
end
|
65
|
+
|
66
|
+
def qdb_delete(delete_query)
|
67
|
+
post 'databasedelete', JSON.generate(env: qcloud.qcloud_env, query: delete_query), base: Wechat::Api::TCB_BASE
|
68
|
+
end
|
69
|
+
|
70
|
+
def qdb_update(update_query)
|
71
|
+
post 'databaseupdate', JSON.generate(env: qcloud.qcloud_env, query: update_query), base: Wechat::Api::TCB_BASE
|
72
|
+
end
|
73
|
+
|
74
|
+
def qdb_query(query)
|
75
|
+
post 'databasequery', JSON.generate(env: qcloud.qcloud_env, query: query), base: Wechat::Api::TCB_BASE
|
76
|
+
end
|
77
|
+
|
78
|
+
def qdb_aggregate(aggregate_query)
|
79
|
+
post 'databaseaggregate', JSON.generate(env: qcloud.qcloud_env, query: aggregate_query), base: Wechat::Api::TCB_BASE
|
80
|
+
end
|
81
|
+
|
82
|
+
def qdb_count(count_query)
|
83
|
+
post 'databasecount', JSON.generate(env: qcloud.qcloud_env, query: count_query), base: Wechat::Api::TCB_BASE
|
84
|
+
end
|
85
|
+
|
86
|
+
def tcb_delete_files(fileid_list)
|
87
|
+
post 'batchdeletefile', JSON.generate(env: qcloud.qcloud_env, fileid_list: fileid_list), base: Wechat::Api::TCB_BASE
|
88
|
+
end
|
89
|
+
|
90
|
+
def tcb_download_files(file_list)
|
91
|
+
post 'batchdownloadfile', JSON.generate(env: qcloud.qcloud_env, file_list: file_list), base: Wechat::Api::TCB_BASE
|
92
|
+
end
|
93
|
+
|
94
|
+
def tcb_preflight_upload_file(q_path)
|
95
|
+
post 'uploadfile', JSON.generate(env: qcloud.qcloud_env, path: q_path), base: Wechat::Api::TCB_BASE
|
96
|
+
end
|
97
|
+
|
98
|
+
def tcb_do_upload_file(q_path, upload_url, signature, x_cos_security_token, x_cos_meta_fileid, file)
|
99
|
+
form_file = file.is_a?(HTTP::FormData::File) ? file : HTTP::FormData::File.new(file)
|
100
|
+
form_data = HTTP::FormData.create({ key: q_path,
|
101
|
+
Signature: signature,
|
102
|
+
"x-cos-security-token": x_cos_security_token,
|
103
|
+
'x-cos-meta-fileid': x_cos_meta_fileid,
|
104
|
+
file: form_file })
|
105
|
+
client.httprb.post(upload_url, form: form_data, ssl_context: client.ssl_context)
|
106
|
+
end
|
107
|
+
|
108
|
+
def tcb_upload_file(q_path, file)
|
109
|
+
res = tcb_preflight_upload_file(q_path)
|
110
|
+
tcb_do_upload_file(q_path, res['url'], res['authorization'], res['token'], res['cos_file_id'], file)
|
111
|
+
res
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -5,7 +5,7 @@ module Wechat
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
attr_accessor :wechat_api_client, :wechat_cfg_account, :token, :appid, :corpid, :agentid, :encrypt_mode, :timeout,
|
8
|
+
attr_accessor :wechat_api_client, :wechat_cfg_account, :token, :appid, :corpid, :agentid, :encrypt_mode, :timeout, :qcloud_token_lifespan,
|
9
9
|
:skip_verify_ssl, :encoding_aes_key, :trusted_domain_fullname, :oauth2_cookie_duration
|
10
10
|
end
|
11
11
|
|
data/lib/wechat/corp_api.rb
CHANGED
@@ -10,10 +10,12 @@ module Wechat
|
|
10
10
|
attr_reader :agentid
|
11
11
|
|
12
12
|
def initialize(appid, secret, token_file, agentid, timeout, skip_verify_ssl, jsapi_ticket_file)
|
13
|
+
super()
|
13
14
|
@client = HttpClient.new(QYAPI_BASE, timeout, skip_verify_ssl)
|
14
15
|
@access_token = Token::CorpAccessToken.new(@client, appid, secret, token_file)
|
15
16
|
@agentid = agentid
|
16
17
|
@jsapi_ticket = Ticket::CorpJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
|
18
|
+
@qcloud = nil
|
17
19
|
end
|
18
20
|
|
19
21
|
def agent_list
|
@@ -45,6 +47,10 @@ module Wechat
|
|
45
47
|
post 'user/convert_to_openid', JSON.generate(userid: userid, agentid: agentid)
|
46
48
|
end
|
47
49
|
|
50
|
+
def convert_to_userid(openid)
|
51
|
+
post 'user/convert_to_userid', JSON.generate(openid: openid)
|
52
|
+
end
|
53
|
+
|
48
54
|
def invite_user(userid)
|
49
55
|
post 'invite/send', JSON.generate(userid: userid)
|
50
56
|
end
|
@@ -90,7 +96,7 @@ module Wechat
|
|
90
96
|
end
|
91
97
|
|
92
98
|
def department_update(departmentid, name = nil, parentid = nil, order = nil)
|
93
|
-
post 'department/update', JSON.generate({ id: departmentid, name: name, parentid: parentid, order: order }.
|
99
|
+
post 'department/update', JSON.generate({ id: departmentid, name: name, parentid: parentid, order: order }.compact)
|
94
100
|
end
|
95
101
|
|
96
102
|
def department(departmentid = 1)
|
data/lib/wechat/helpers.rb
CHANGED
@@ -24,7 +24,7 @@ module Wechat
|
|
24
24
|
else
|
25
25
|
controller.request.original_url
|
26
26
|
end
|
27
|
-
page_url = page_url.split('#').first
|
27
|
+
page_url = page_url.split('#').first
|
28
28
|
js_hash = api.jsapi_ticket.signature(page_url)
|
29
29
|
|
30
30
|
config_js = <<~WECHAT_CONFIG_JS
|
@@ -34,16 +34,11 @@ module Wechat
|
|
34
34
|
timestamp: "#{js_hash[:timestamp]}",
|
35
35
|
nonceStr: "#{js_hash[:noncestr]}",
|
36
36
|
signature: "#{js_hash[:signature]}",
|
37
|
-
jsApiList: ['#{config_options[:api]
|
37
|
+
jsApiList: ['#{config_options[:api]&.join("','")}'],
|
38
|
+
openTagList: ['#{config_options[:open_tags]&.join("','")}']
|
38
39
|
});
|
39
40
|
WECHAT_CONFIG_JS
|
40
41
|
javascript_tag config_js, type: 'application/javascript'
|
41
42
|
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def ios?
|
46
|
-
controller.request.user_agent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
|
47
|
-
end
|
48
43
|
end
|
49
44
|
end
|
data/lib/wechat/http_client.rb
CHANGED
@@ -14,7 +14,7 @@ module Wechat
|
|
14
14
|
HTTP.timeout(:global, write: timeout, connect: timeout, read: timeout)
|
15
15
|
end
|
16
16
|
@ssl_context = OpenSSL::SSL::SSLContext.new
|
17
|
-
@ssl_context.ssl_version =
|
17
|
+
@ssl_context.ssl_version = 'TLSv1_2'
|
18
18
|
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE if skip_verify_ssl
|
19
19
|
end
|
20
20
|
|
@@ -77,12 +77,12 @@ module Wechat
|
|
77
77
|
def parse_response(response, as_type)
|
78
78
|
content_type = response.headers[:content_type]
|
79
79
|
parse_as = {
|
80
|
-
%r{^application
|
81
|
-
%r{^image
|
82
|
-
%r{^audio
|
83
|
-
%r{^voice
|
84
|
-
%r{^text
|
85
|
-
%r{^text
|
80
|
+
%r{^application/json} => :json,
|
81
|
+
%r{^image/.*} => :file,
|
82
|
+
%r{^audio/.*} => :file,
|
83
|
+
%r{^voice/.*} => :file,
|
84
|
+
%r{^text/html} => :xml,
|
85
|
+
%r{^text/plain} => :probably_json
|
86
86
|
}.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || as_type || :text
|
87
87
|
|
88
88
|
# try to parse response as json, fallback to user-specified format or text if failed
|
data/lib/wechat/message.rb
CHANGED
@@ -32,6 +32,7 @@ module Wechat
|
|
32
32
|
|
33
33
|
class ArticleBuilder
|
34
34
|
attr_reader :items
|
35
|
+
|
35
36
|
delegate :count, to: :items
|
36
37
|
def initialize
|
37
38
|
@items = []
|
@@ -40,14 +41,14 @@ module Wechat
|
|
40
41
|
|
41
42
|
class NewsArticleBuilder < ArticleBuilder
|
42
43
|
def item(title: 'title', description: nil, pic_url: nil, url: nil)
|
43
|
-
items << { Title: title, Description: description, PicUrl: pic_url, Url: url }.
|
44
|
+
items << { Title: title, Description: description, PicUrl: pic_url, Url: url }.compact
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
48
|
class MpNewsArticleBuilder < ArticleBuilder
|
48
49
|
def item(thumb_media_id:, title:, content:, author: nil, content_source_url: nil, digest: nil, show_cover_pic: '0')
|
49
50
|
items << { Thumb_Media_ID: thumb_media_id, Author: author, Title: title, ContentSourceUrl: content_source_url,
|
50
|
-
Content: content, Digest: digest, ShowCoverPic: show_cover_pic }.
|
51
|
+
Content: content, Digest: digest, ShowCoverPic: show_cover_pic }.compact
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -168,7 +169,7 @@ module Wechat
|
|
168
169
|
items = article.items
|
169
170
|
else
|
170
171
|
items = collection.collect do |item|
|
171
|
-
camelize_hash_keys(item.symbolize_keys.slice(:title, :description, :pic_url, :url).
|
172
|
+
camelize_hash_keys(item.symbolize_keys.slice(:title, :description, :pic_url, :url).compact)
|
172
173
|
end
|
173
174
|
end
|
174
175
|
|
@@ -183,7 +184,7 @@ module Wechat
|
|
183
184
|
items = article.items
|
184
185
|
else
|
185
186
|
items = collection.collect do |item|
|
186
|
-
camelize_hash_keys(item.symbolize_keys.slice(:thumb_media_id, :title, :content, :author, :content_source_url, :digest, :show_cover_pic).
|
187
|
+
camelize_hash_keys(item.symbolize_keys.slice(:thumb_media_id, :title, :content, :author, :content_source_url, :digest, :show_cover_pic).compact)
|
187
188
|
end
|
188
189
|
end
|
189
190
|
|
data/lib/wechat/mp_api.rb
CHANGED
@@ -4,11 +4,22 @@ require 'wechat/api_base'
|
|
4
4
|
require 'wechat/http_client'
|
5
5
|
require 'wechat/token/public_access_token'
|
6
6
|
require 'wechat/ticket/public_jsapi_ticket'
|
7
|
+
require 'wechat/qcloud/token'
|
7
8
|
require 'wechat/concern/common'
|
9
|
+
require 'wechat/concern/qcloud'
|
8
10
|
|
9
11
|
module Wechat
|
10
12
|
class MpApi < ApiBase
|
13
|
+
def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file, qcloud_env, qcloud_token_file, qcloud_token_lifespan)
|
14
|
+
super()
|
15
|
+
@client = HttpClient.new(Wechat::Api::API_BASE, timeout, skip_verify_ssl)
|
16
|
+
@access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
|
17
|
+
@jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
|
18
|
+
@qcloud = Qcloud::Token.new(@client, @access_token, qcloud_env, qcloud_token_file, qcloud_token_lifespan)
|
19
|
+
end
|
20
|
+
|
11
21
|
include Concern::Common
|
22
|
+
include Concern::Qcloud
|
12
23
|
|
13
24
|
def template_message_send(message)
|
14
25
|
post 'message/wxopen/template/send', message.to_json, content_type: :json
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wechat
|
4
|
+
module Qcloud
|
5
|
+
FILE_TYPE_JSON = 1
|
6
|
+
FILE_TYPE_CSV = 2
|
7
|
+
CONFLICT_MODE_INSERT = 1
|
8
|
+
CONFLICT_MODE_UPSERT = 2
|
9
|
+
|
10
|
+
class Token
|
11
|
+
attr_reader :client, :access_token, :qcloud_env, :qcloud_token_file, :qcloud_token_lifespan, :qcloud_token, :qcloud_token_expired_time
|
12
|
+
|
13
|
+
def initialize(client, access_token, qcloud_env, qcloud_token_file, lifespan)
|
14
|
+
@client = client
|
15
|
+
@access_token = access_token
|
16
|
+
@qcloud_env = qcloud_env
|
17
|
+
@qcloud_token_file = qcloud_token_file
|
18
|
+
@qcloud_token_lifespan = lifespan
|
19
|
+
@random_generator = Random.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def token(tries = 2)
|
23
|
+
# Possible two worker running, one worker refresh ticket, other unaware, so must read every time
|
24
|
+
read_qcloud_token_from_store
|
25
|
+
refresh if remain_life_seconds < @random_generator.rand(30..3 * 60)
|
26
|
+
qcloud_token
|
27
|
+
rescue AccessTokenExpiredError
|
28
|
+
access_token.refresh
|
29
|
+
retry unless (tries -= 1).zero?
|
30
|
+
end
|
31
|
+
|
32
|
+
def refresh
|
33
|
+
data = client.post('getqcloudtoken', JSON.generate(lifespan: qcloud_token_lifespan), base: ::Wechat::ApiBase::TCB_BASE, params: { access_token: access_token.token })
|
34
|
+
write_qcloud_token_to_store(data)
|
35
|
+
read_qcloud_token_from_store
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def read_qcloud_token_from_store
|
41
|
+
td = read_qcloud_token
|
42
|
+
@qcloud_token_expired_time = td.fetch('qcloud_token_expired_time').to_i
|
43
|
+
@qcloud_token = td.fetch('token') # return qcloud_token same time
|
44
|
+
rescue JSON::ParserError, Errno::ENOENT, KeyError, TypeError
|
45
|
+
refresh
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_qcloud_token_to_store(qcloud_token_hash)
|
49
|
+
qcloud_token_hash['qcloud_token_expired_time'] = qcloud_token_hash.delete('expired_time')
|
50
|
+
write_qcloud_token(qcloud_token_hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_qcloud_token
|
54
|
+
JSON.parse(File.read(qcloud_token_file))
|
55
|
+
end
|
56
|
+
|
57
|
+
def write_qcloud_token(qcloud_token_hash)
|
58
|
+
File.write(qcloud_token_file, qcloud_token_hash.to_json)
|
59
|
+
end
|
60
|
+
|
61
|
+
def remain_life_seconds
|
62
|
+
qcloud_token_expired_time - Time.now.to_i
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/wechat/responder.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'English'
|
4
|
+
require 'rexml/document'
|
4
5
|
require 'wechat/signature'
|
5
6
|
|
6
7
|
module Wechat
|
@@ -145,9 +146,10 @@ module Wechat
|
|
145
146
|
next
|
146
147
|
end
|
147
148
|
|
148
|
-
|
149
|
+
case condition
|
150
|
+
when Regexp
|
149
151
|
memo[:scoped] ||= [responder] + $LAST_MATCH_INFO.captures if value =~ condition
|
150
|
-
|
152
|
+
when value
|
151
153
|
memo[:scoped] ||= [responder, value]
|
152
154
|
end
|
153
155
|
end
|
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.12.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skinnyworm
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
spvOK5/LPXWX6ZGc2SR8SH/s7ftYH2EkeM1VUbtemow08NdgCwJ4IG+fRQ9dcrJ+
|
36
36
|
L9TbpLHvVrCe1w8duMqNeUmqj+M1iC/5Zst2vIe14QcOTuAh
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date:
|
38
|
+
date: 2021-04-21 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: activesupport
|
@@ -105,20 +105,34 @@ dependencies:
|
|
105
105
|
- - ">="
|
106
106
|
- !ruby/object:Gem::Version
|
107
107
|
version: '0'
|
108
|
+
- !ruby/object:Gem::Dependency
|
109
|
+
name: rexml
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
type: :runtime
|
116
|
+
prerelease: false
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
108
122
|
- !ruby/object:Gem::Dependency
|
109
123
|
name: rubocop
|
110
124
|
requirement: !ruby/object:Gem::Requirement
|
111
125
|
requirements:
|
112
126
|
- - "~>"
|
113
127
|
- !ruby/object:Gem::Version
|
114
|
-
version: '0
|
128
|
+
version: '1.0'
|
115
129
|
type: :development
|
116
130
|
prerelease: false
|
117
131
|
version_requirements: !ruby/object:Gem::Requirement
|
118
132
|
requirements:
|
119
133
|
- - "~>"
|
120
134
|
- !ruby/object:Gem::Version
|
121
|
-
version: '0
|
135
|
+
version: '1.0'
|
122
136
|
- !ruby/object:Gem::Dependency
|
123
137
|
name: rails
|
124
138
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,14 +153,14 @@ dependencies:
|
|
139
153
|
requirements:
|
140
154
|
- - "~>"
|
141
155
|
- !ruby/object:Gem::Version
|
142
|
-
version: '
|
156
|
+
version: '5.0'
|
143
157
|
type: :development
|
144
158
|
prerelease: false
|
145
159
|
version_requirements: !ruby/object:Gem::Requirement
|
146
160
|
requirements:
|
147
161
|
- - "~>"
|
148
162
|
- !ruby/object:Gem::Version
|
149
|
-
version: '
|
163
|
+
version: '5.0'
|
150
164
|
- !ruby/object:Gem::Dependency
|
151
165
|
name: sqlite3
|
152
166
|
requirement: !ruby/object:Gem::Requirement
|
@@ -195,12 +209,14 @@ files:
|
|
195
209
|
- lib/wechat/api_loader.rb
|
196
210
|
- lib/wechat/cipher.rb
|
197
211
|
- lib/wechat/concern/common.rb
|
212
|
+
- lib/wechat/concern/qcloud.rb
|
198
213
|
- lib/wechat/controller_api.rb
|
199
214
|
- lib/wechat/corp_api.rb
|
200
215
|
- lib/wechat/helpers.rb
|
201
216
|
- lib/wechat/http_client.rb
|
202
217
|
- lib/wechat/message.rb
|
203
218
|
- lib/wechat/mp_api.rb
|
219
|
+
- lib/wechat/qcloud/token.rb
|
204
220
|
- lib/wechat/responder.rb
|
205
221
|
- lib/wechat/signature.rb
|
206
222
|
- lib/wechat/ticket/corp_jsapi_ticket.rb
|
@@ -219,7 +235,7 @@ require_paths:
|
|
219
235
|
- lib
|
220
236
|
required_ruby_version: !ruby/object:Gem::Requirement
|
221
237
|
requirements:
|
222
|
-
- - "
|
238
|
+
- - ">="
|
223
239
|
- !ruby/object:Gem::Version
|
224
240
|
version: '2.4'
|
225
241
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -228,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
228
244
|
- !ruby/object:Gem::Version
|
229
245
|
version: '0'
|
230
246
|
requirements: []
|
231
|
-
rubygems_version: 3.
|
247
|
+
rubygems_version: 3.2.16
|
232
248
|
signing_key:
|
233
249
|
specification_version: 4
|
234
250
|
summary: DSL for wechat message handling and API
|
metadata.gz.sig
CHANGED
Binary file
|