wechat 0.11.10 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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)