wechat 0.11.10 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/wechat CHANGED
@@ -6,13 +6,10 @@ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
6
6
  require 'thor'
7
7
  require 'wechat'
8
8
  require 'json'
9
- require 'active_support' # To support Rails 4.2.1, see #17936
10
- require 'active_support/dependencies/autoload'
11
9
  require 'active_support/core_ext'
12
10
  require 'active_support/json'
13
11
  require 'fileutils'
14
12
  require 'yaml'
15
- require 'wechat/api_loader'
16
13
  require 'cgi'
17
14
 
18
15
  class App < Thor
@@ -187,6 +184,11 @@ class App < Thor
187
184
  puts wechat_api.convert_to_openid(userid)
188
185
  end
189
186
 
187
+ desc 'convert_to_userid [OPENID]', 'openid转换成userid'
188
+ def convert_to_userid(openid)
189
+ puts wechat_api.convert_to_userid(openid)
190
+ end
191
+
190
192
  desc 'agent_list', '获取应用概况列表'
191
193
  def agent_list
192
194
  r = wechat_api.agent_list
@@ -38,37 +38,30 @@ 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)
56
- else
57
- secret = opts[:secret] || cfg.secret
58
- Wechat::Api.new(appid, secret, access_token, \
59
- timeout, skip_verify_ssl, jsapi_ticket)
60
- end
61
- end
62
- end
51
+ api_type = opts[:type] || cfg.type
52
+ secret = corpid.present? ? opts[:corpsecret] || cfg.corpsecret : opts[:secret] || cfg.secret
63
53
 
64
- if defined? Base
65
- class << Base
66
- include WechatResponder
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)
67
55
  end
68
- end
69
- if defined? API
70
- class << API
71
- include WechatResponder
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)
62
+ else
63
+ Wechat::Api.new(appid, secret, access_token, timeout, skip_verify_ssl, jsapi_ticket)
64
+ end
72
65
  end
73
66
  end
74
67
  end
@@ -31,7 +31,7 @@ module Wechat
31
31
  private
32
32
 
33
33
  def migration_version
34
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if Rails.version >= '5.0.0'
34
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
35
35
  end
36
36
  end
37
37
  end
@@ -31,7 +31,7 @@ module Wechat
31
31
  private
32
32
 
33
33
  def migration_version
34
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if Rails.version >= '5.0.0'
34
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
35
35
  end
36
36
  end
37
37
  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
@@ -1,24 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'zeitwerk'
4
+ loader = Zeitwerk::Loader.for_gem
5
+ loader.ignore("#{__dir__}/generators/**/*.rb")
6
+ loader.setup
7
+
3
8
  require 'base64'
4
9
  require 'openssl/cipher'
5
- require 'wechat/api_loader'
6
- require 'wechat/api'
7
- require 'wechat/mp_api'
8
- require 'wechat/corp_api'
9
- require 'wechat/helpers'
10
- require 'action_controller/wechat_responder'
11
10
 
12
11
  module Wechat
13
- autoload :Message, 'wechat/message'
14
- autoload :Responder, 'wechat/responder'
15
- autoload :Cipher, 'wechat/cipher'
16
- autoload :ControllerApi, 'wechat/controller_api'
17
-
18
12
  class AccessTokenExpiredError < StandardError; end
13
+
19
14
  class InvalidCredentialError < StandardError; end
15
+
20
16
  class ResponseError < StandardError
21
17
  attr_reader :error_code
18
+
22
19
  def initialize(errcode, errmsg)
23
20
  @error_code = errcode
24
21
  super "#{errmsg}(#{error_code})"
@@ -47,8 +44,26 @@ module Wechat
47
44
  decrypted_data = Base64.decode64(encrypted_data)
48
45
  JSON.parse(cipher.update(decrypted_data) + cipher.final)
49
46
  rescue StandardError => e
50
- { 'errcode': 41003, 'errmsg': e.message }
47
+ { errcode: 41003, errmsg: e.message }
51
48
  end
52
49
  end
53
50
 
54
51
  ActionView::Base.include Wechat::Helpers if defined? ActionView::Base
52
+ require 'action_controller/wechat_responder' # To make wechat_api and wechat_responder available
53
+
54
+ module ActionController
55
+ if defined? Base
56
+ ActiveSupport.on_load(:action_controller_base) do
57
+ class << Base
58
+ include WechatResponder
59
+ end
60
+ end
61
+ end
62
+ if defined? API
63
+ ActiveSupport.on_load(:action_controller_api) do
64
+ class << API
65
+ include WechatResponder
66
+ end
67
+ end
68
+ end
69
+ end
data/lib/wechat/api.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'wechat/api_base'
4
- require 'wechat/http_client'
5
- require 'wechat/token/public_access_token'
6
- require 'wechat/ticket/public_jsapi_ticket'
7
- require 'wechat/concern/common'
8
-
9
3
  module Wechat
10
4
  class Api < ApiBase
5
+ def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
6
+ super()
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
+ @qcloud = nil
11
+ end
12
+
11
13
  include Concern::Common
12
14
 
13
15
  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)..]
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
@@ -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
 
@@ -58,7 +58,7 @@ module Wechat
58
58
  cookies.signed_or_encrypted[:we_access_token] = { value: access_info['access_token'], expires: self.class.oauth2_cookie_duration.from_now }
59
59
  yield access_info['openid'], access_info
60
60
  else
61
- Rails::VERSION::MAJOR >= 6 ? (redirect_to generate_oauth2_url(oauth2_params), allow_other_host: true) : (redirect_to generate_oauth2_url(oauth2_params))
61
+ redirect_to generate_oauth2_url(oauth2_params), allow_other_host: true
62
62
  end
63
63
  end
64
64
 
@@ -73,7 +73,7 @@ module Wechat
73
73
  cookies.signed_or_encrypted[:we_deviceid] = { value: userinfo['DeviceId'], expires: self.class.oauth2_cookie_duration.from_now }
74
74
  yield userinfo['UserId'], userinfo
75
75
  else
76
- Rails::VERSION::MAJOR >= 6 ? (redirect_to generate_oauth2_url(oauth2_params), allow_other_host: true) : (redirect_to generate_oauth2_url(oauth2_params))
76
+ redirect_to generate_oauth2_url(oauth2_params), allow_other_host: true
77
77
  end
78
78
  end
79
79
 
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'wechat/api_base'
4
- require 'wechat/http_client'
5
- require 'wechat/token/corp_access_token'
6
- require 'wechat/ticket/corp_jsapi_ticket'
7
-
8
3
  module Wechat
9
4
  class CorpApi < ApiBase
10
5
  attr_reader :agentid
11
6
 
12
7
  def initialize(appid, secret, token_file, agentid, timeout, skip_verify_ssl, jsapi_ticket_file)
8
+ super()
13
9
  @client = HttpClient.new(QYAPI_BASE, timeout, skip_verify_ssl)
14
10
  @access_token = Token::CorpAccessToken.new(@client, appid, secret, token_file)
15
11
  @agentid = agentid
16
12
  @jsapi_ticket = Ticket::CorpJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
13
+ @qcloud = nil
17
14
  end
18
15
 
19
16
  def agent_list
@@ -45,6 +42,10 @@ module Wechat
45
42
  post 'user/convert_to_openid', JSON.generate(userid: userid, agentid: agentid)
46
43
  end
47
44
 
45
+ def convert_to_userid(openid)
46
+ post 'user/convert_to_userid', JSON.generate(openid: openid)
47
+ end
48
+
48
49
  def invite_user(userid)
49
50
  post 'invite/send', JSON.generate(userid: userid)
50
51
  end
@@ -90,7 +91,7 @@ module Wechat
90
91
  end
91
92
 
92
93
  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? })
94
+ post 'department/update', JSON.generate({ id: departmentid, name: name, parentid: parentid, order: order }.compact)
94
95
  end
95
96
 
96
97
  def department(departmentid = 1)