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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bae019a8d3da8b0f78aba677ba633a46ecebdf975be517f09b5ec82050b2aee9
4
- data.tar.gz: 1bdb9f6dd87d72071071e3ff36d4d6334c0ddb0825d2ef5abe243b6298983ee2
3
+ metadata.gz: 761a53485d1170176263ef051a032606c7ae1c5533c6ec9c3f4808a4aeb5cd66
4
+ data.tar.gz: be822b26ee8bc46bcc548190d8f7082e0424d9214ed88a26e3846be39c7fcde2
5
5
  SHA512:
6
- metadata.gz: 0c87ecd9b34fc0c34538bd754e6b35e20cc6e87f138b7434fabf92c3f0ec2a0cfbbc44a7d0e7c88fa52237b293f1744408697043c3f7e624e5eba9c871ba2153
7
- data.tar.gz: 8a431320b86e529acd98de9482070052fbeb4dcf80538dc9e0d5c2e6a7507460fa848a0bb0c3692c68c89ef8b7dbd0243ef537442680a678b3e871ff4f4ec40f
6
+ metadata.gz: 261f379c71351cf41d93e4bc3953c66f62d11c2f136111f2642379e78cde736e2e2667817c71f3001fec80d4432f83dcde2f4ac5d6f380b67daa55d6477252e4
7
+ data.tar.gz: b57190737963d214a9e644334ead4af99e68b774b3973f49ae652f6576b8aeb248407062e0ffc0fbb3ac86347b81b6b90942b632090eef6729ce6dc98504f4fe
checksums.yaml.gz.sig CHANGED
@@ -1 +1,3 @@
1
- a�u�A�lGh�iCkgQPjUG��gAs|�&{;M��Ԓ�[9P��vC*�9���A˧�a�t���Ά��Դ �-�Ln>����~� ӌV �rqd�eIth�@����c蹩}�� 8k}eow��£7/H�X��7����j�g[<�k~�F��`��h��=s ���ZA�i�a#���&4h��Ds�D�g]�vN9��V�#�"�qx��7���ċD(��
1
+ ��އ�i�(�m;L��nuq_HJ�*��-q$ 8n{k#���kMp=�� f���0�#�]����uZT.C_��$�o*���L}[&�1��ɉ�n������@˼?՛4��l}�W[��^������GD���p"<�9��yt���T��x��I*��")rgDKAr���*9HwCNo0!�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.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)
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.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)
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.skip_verify_ssl = if opts.key?(:skip_verify_ssl)
42
- opts[:skip_verify_ssl]
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
- if corpid.present?
53
- corpsecret = opts[:corpsecret] || cfg.corpsecret
54
- Wechat::CorpApi.new(corpid, corpsecret, access_token, \
55
- agentid, timeout, skip_verify_ssl, jsapi_ticket)
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 = opts[:secret] || cfg.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[:base] << 'Either appid or corpid must be set'
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
- { 'errcode': 41003, 'errmsg': e.message }
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)
@@ -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
@@ -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
- wx_class = type == 'mp' ? Wechat::MpApi : Wechat::Api
14
- wx_class.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file)
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[(20 + msg_len)..-1]
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
 
@@ -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 }.reject { |_k, v| v.nil? })
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)
@@ -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 if ios?
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].join("','")}']
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
@@ -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 = :TLSv1_2
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\/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
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
@@ -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 }.reject { |_k, v| v.nil? }
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 }.reject { |_k, v| v.nil? }
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).reject { |_k, v| v.nil? })
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).reject { |_k, v| v.nil? })
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
@@ -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
- if condition.is_a? Regexp
149
+ case condition
150
+ when Regexp
149
151
  memo[:scoped] ||= [responder] + $LAST_MATCH_INFO.captures if value =~ condition
150
- elsif value == condition
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.11.10
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: 2020-09-02 00:00:00.000000000 Z
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.74'
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.74'
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: '4.0'
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: '4.0'
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.1.4
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