jingubang 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d97f5f5e21ca1c8fd3d37da31cec5382fbd35283
4
+ data.tar.gz: 61f236259c8ecc99c70d1acd2b1da475f459ae45
5
+ SHA512:
6
+ metadata.gz: 6388a8f473b160c3ecd3bb5d94df76ab3883382c72e99fe2f3ccb39ef3f5d5a4c4e5685fe2c429f2d2c3448b31b1b84f35b1720eab4e0542b8ed09afa709d106
7
+ data.tar.gz: bddd4b97b4955bbf3cfdac58b4105db025e6efbade8ea5e7a92c4d95667178b3d1feac5c6597cebeb72d01e5e17a377f02659ff23d019b7fa0bee460bc6259a0
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ tags
2
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jingubang (0.0.1)
5
+ rest-client
6
+ weixin_message_encryptor
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ domain_name (0.5.20170404)
12
+ unf (>= 0.0.5, < 1.0.0)
13
+ http-cookie (1.0.3)
14
+ domain_name (~> 0.5)
15
+ mime-types (3.1)
16
+ mime-types-data (~> 3.2015)
17
+ mime-types-data (3.2016.0521)
18
+ netrc (0.11.0)
19
+ rest-client (2.0.2)
20
+ http-cookie (>= 1.0.2, < 2.0)
21
+ mime-types (>= 1.16, < 4.0)
22
+ netrc (~> 0.8)
23
+ unf (0.1.4)
24
+ unf_ext
25
+ unf_ext (0.0.7.5)
26
+ weixin_message_encryptor (0.1.0.1)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ jingubang!
33
+
34
+ BUNDLED WITH
35
+ 1.16.1
@@ -0,0 +1,75 @@
1
+ module Weixin::Qiye
2
+ class NotificationsController < ActionController::Base
3
+
4
+ before_action :assign_payload, :verify_sign, :decrypt_payload, only: [:index, :create]
5
+ before_action :assign_message, only: [:create]
6
+
7
+ def index
8
+ if service_verify?
9
+ render plain: @payload
10
+ else
11
+ render nonthing: true, status: :bad_request
12
+ end
13
+ end
14
+
15
+ def create
16
+ Rails.logger.info "Weixin Qiye Message: #{@message['InfoType']}"
17
+ case @message['InfoType']
18
+ when 'suite_ticket'
19
+ process_ticket_message
20
+ # when 'authorized'
21
+ # when 'unauthorized'
22
+ # when 'updateauthorized'
23
+ when 'contact_sync'
24
+ process_contact_sync
25
+ end
26
+
27
+ render text: 'success'
28
+ end
29
+
30
+ private
31
+
32
+ def assign_payload
33
+ byebug
34
+ @encrypted_payload = service_verify? ? params[:echostr] : params[:xml][:Encrypt]
35
+ end
36
+
37
+ def verify_sign
38
+ render text: 'YOY', status: 401 unless params[:msg_signature] == calculated_sign
39
+ end
40
+
41
+ def calculated_sign
42
+ salt = [
43
+ @encrypted_payload,
44
+ Jingubang::Setting.qiye[:token],
45
+ params[:timestamp],
46
+ params[:nonce]
47
+ ].sort.join
48
+ Digest::SHA1.hexdigest salt
49
+ end
50
+
51
+ def decrypt_payload
52
+ key = Base64.decode64 Jingubang::Setting.qiye[:encoding_aes_key] + '='
53
+ aes_msg = Base64.decode64 @encrypted_payload
54
+ msg = AESCrypt.decrypt_data aes_msg, key, nil, 'AES-256-CBC'
55
+ @payload = msg[20..-19]
56
+ end
57
+
58
+ def assign_message
59
+ @message = Hash.from_xml(@payload)['xml']
60
+ end
61
+
62
+ def service_verify?
63
+ params[:echostr].present?
64
+ end
65
+
66
+ def process_ticket_message
67
+ Rails.logger.info "Weixin::Qiye save suite ticket: #{@message['SuiteTicket']}"
68
+ end
69
+
70
+ def process_contact_sync
71
+ Rails.logger.info "Weixin::Qiye process contact sync: #{@message['AuthCorpId']}"
72
+ end
73
+
74
+ end
75
+ end
data/jingubang.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require 'jingubang/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'jingubang'
8
+ s.version = Jingubang::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ['Zhichao Feng']
11
+ s.email = ['flankerfc@gmail.com']
12
+ s.homepage = 'https://github.com/flanker'
13
+ s.summary = 'All-in-on weixin integration for Rails'
14
+ s.description = 'All-in-on weixin integration for Rails'
15
+ s.license = 'MIT'
16
+
17
+ s.required_ruby_version = '>= 2.3'
18
+ s.required_rubygems_version = '>= 1.3.6'
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- test/*`.split("\n")
22
+ s.require_path = ['lib']
23
+
24
+ s.add_dependency 'weixin_message_encryptor'
25
+ s.add_dependency 'rest-client'
26
+ end
data/lib/jingubang.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'aescrypt'
2
+
3
+ require 'jingubang/setting'
4
+ require 'jingubang/logger'
5
+ require 'jingubang/http_client'
6
+ require 'jingubang/rails'
7
+ require 'jingubang/weixin'
@@ -0,0 +1,51 @@
1
+ require 'rest_client'
2
+
3
+ module Jingubang
4
+ module HttpClient
5
+
6
+ def self.included(host_class)
7
+ host_class.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # e.g. 'https://qyapi.weixin.qq.com'
13
+ def base_url url
14
+ @api_base_url = url
15
+ end
16
+
17
+ def api_base_url
18
+ @api_base_url
19
+ end
20
+
21
+ def log message
22
+ Jingubang.logger.info message
23
+ end
24
+
25
+ def fire_request path, params, base_url: nil
26
+ base_url = api_base_url unless base_url
27
+ url = "#{base_url}#{path}"
28
+
29
+ log "Jingubang sending request to: #{url}"
30
+ log " with params: #{params}"
31
+
32
+ response = JSON.parse RestClient.post(
33
+ url,
34
+ params.to_json,
35
+ timeout: 60,
36
+ :content_type => :json,
37
+ :accept => :json
38
+ )
39
+
40
+ log "Jingubang response: #{response}"
41
+
42
+ response&.with_indifferent_access
43
+ end
44
+ end
45
+
46
+ def fire_request path, params
47
+ self.class.fire_request path, params, base_url: self.class.api_base_url
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ module Jingubang
2
+
3
+ @@logger = ::Logger.new(STDOUT)
4
+
5
+ def self.logger
6
+ @@logger
7
+ end
8
+
9
+ def self.logger= value
10
+ @@logger = value
11
+ end
12
+
13
+ end
@@ -0,0 +1,7 @@
1
+ require 'jingubang/rails/routes'
2
+
3
+ module Jingubang
4
+ class Engine < ::Rails::Engine
5
+
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+
4
+ def with_jingubang
5
+ jingubang_weixin_qiye
6
+ end
7
+
8
+ private
9
+
10
+ def jingubang_weixin_qiye
11
+ namespace 'weixin' do
12
+ namespace 'qiye' do
13
+ resources :notifications, only: [:index, :create]
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Jingubang
2
+ class Setting
3
+
4
+ def self.qiye
5
+ @qiye_setting ||= YAML.load_file('config/jingubang.yml').with_indifferent_access[:weixin_qiye_account]
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,119 @@
1
+ module Jingubang
2
+ module Test
3
+ module TestHelper
4
+
5
+ def build_qiye_weixin_test_notification echostr, encryptor
6
+ encrypted_payload, timestamp, nonce, signature = encryptor.encrypt_with_signature(echostr)
7
+
8
+ {
9
+ 'echostr' => encrypted_payload,
10
+ 'msg_signature' => signature,
11
+ 'timestamp' => timestamp,
12
+ 'nonce' => nonce
13
+ }
14
+ end
15
+
16
+ def build_qiye_weixin_notification message, encryptor
17
+ encrypted_payload, timestamp, nonce, signature = encryptor.encrypt_with_signature(message)
18
+
19
+ {
20
+ 'xml' => {
21
+ 'ToUserName' => 'testusername',
22
+ 'Encrypt' => encrypted_payload
23
+ },
24
+ 'msg_signature' => signature,
25
+ 'timestamp' => timestamp,
26
+ 'nonce' => nonce
27
+ }
28
+ end
29
+
30
+ class ApiResult
31
+
32
+ class << self
33
+
34
+ def build_weixin_qiye_login_info corpid: 'thecorpid', userid: 'theuserid', error_code: nil
35
+ return build_weixin_qiye_error_result(error_code) if error_code
36
+ {
37
+ usertype: 1,
38
+ user_info: {
39
+ userid: userid,
40
+ name: 'User Name',
41
+ email: 'user@test.com',
42
+ avatar: 'http://p.qlogo.cn/bizmail/thelinktouseravatar/0'
43
+ },
44
+ corp_info: {
45
+ corpid: corpid
46
+ },
47
+ agent: [
48
+ {agentid: 1000010, auth_type: 1},
49
+ {agentid: 1000029, auth_type: 1}
50
+ ],
51
+ auth_info: {
52
+ department: [
53
+ {id: 1, writable: true}
54
+ ]
55
+ }
56
+ }
57
+ end
58
+
59
+ def build_weixin_qiye_permanent_auth_data mobile: '13812345678', email: 'anna@frozen.com'
60
+ {
61
+ access_token: 'ACCESS_TOKEN',
62
+ expires_in: 7200,
63
+ permanent_code: 'PERMANENT_CODE',
64
+ auth_corp_info: {
65
+ corpid: 'WXCORPID',
66
+ corp_name: 'Frozen Tech',
67
+ corp_type: 'unverified',
68
+ corp_round_logo_url: 'http://mmbiz.qpic.cn/mmbiz/round_logo/0',
69
+ corp_square_logo_url: 'http://p.qpic.cn/qqmail_pic/4144698419/square_logo/0',
70
+ corp_user_max: 200,
71
+ corp_agent_max: 0,
72
+ corp_wxqrcode: 'http://shp.qpic.cn/bizmp/wxqrcode/',
73
+ corp_full_name: '',
74
+ subject_type: 1,
75
+ verified_end_time: 0
76
+ },
77
+ auth_info: {
78
+ agent: [{
79
+ agentid: 1000005,
80
+ name: '金数据DEV',
81
+ square_logo_url: 'http://p.qlogo.cn/bizmail/agent_square_logo/0',
82
+ appid: 1,
83
+ privilege: {
84
+ level: 2,
85
+ allow_party: [1],
86
+ allow_user: [],
87
+ allow_tag: [],
88
+ extra_party: [],
89
+ extra_user: [],
90
+ extra_tag: []
91
+ }
92
+ }]
93
+ },
94
+ auth_user_info: {
95
+ userid: 'anna',
96
+ mobile: mobile,
97
+ email: email,
98
+ name: 'Anna',
99
+ avatar: 'http://p.qlogo.cn/bizmail/anna_avatar/0'
100
+ }
101
+ }.with_indifferent_access
102
+ end
103
+
104
+ private
105
+
106
+ def build_weixin_qiye_error_result error_code
107
+ {
108
+ errcode: error_code,
109
+ errmsg: 'invalid code, hint: [1519698145_8_22f41dcc18e2ca451dd57b93b129b35d], more info at https://open.work.weixin.qq.com/devtool/query?e=40029',
110
+ agent: []
111
+ }
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,3 @@
1
+ module Jingubang
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,6 @@
1
+ module Jingubang
2
+ module Weixin
3
+ end
4
+ end
5
+
6
+ require 'jingubang/weixin/qiye'
@@ -0,0 +1,9 @@
1
+ module Jingubang::Weixin
2
+ module Qiye
3
+ end
4
+ end
5
+
6
+ require 'jingubang/weixin/qiye/api_client'
7
+ require 'jingubang/weixin/qiye/url_helper'
8
+ require 'jingubang/weixin/qiye/account'
9
+ require 'jingubang/weixin/qiye/provider_app_account'
@@ -0,0 +1,49 @@
1
+ require 'jingubang/weixin/qiye/account/send_message'
2
+
3
+ module Jingubang::Weixin::Qiye
4
+ module Account
5
+
6
+ # Required fields:
7
+ # field :corpid, type: String
8
+ # field :access_token, type: String
9
+ # field :expired_at, type: Time
10
+ # field :permanent_code, type: String
11
+ # def refreshed_access_token
12
+
13
+ ROOT_DEPARTMENT_ID = 1
14
+
15
+ include Account::SendMessage
16
+
17
+ BASE_URL = 'https://qyapi.weixin.qq.com'
18
+
19
+ def self.included(host_class)
20
+ host_class.include Jingubang::HttpClient
21
+ host_class.base_url BASE_URL
22
+ end
23
+
24
+ # API methods:
25
+
26
+ def fetch_department_list
27
+ path = "/cgi-bin/department/list?access_token=#{refreshed_access_token}"
28
+ response = fire_request path, {}
29
+ response[:department] if response[:errcode]&.zero?
30
+ end
31
+
32
+ def fetch_user_list department_id, fetch_child: false
33
+ fetch_child_param = fetch_child ? 1 : 0
34
+ path = "/cgi-bin/user/list?access_token=#{refreshed_access_token}&department_id=#{department_id}&fetch_child=#{fetch_child_param}"
35
+ response = fire_request path, {}
36
+ response[:userlist] if response[:errcode]&.zero?
37
+ end
38
+
39
+ def fetch_all_user_list
40
+ fetch_user_list ROOT_DEPARTMENT_ID, fetch_child: true
41
+ end
42
+
43
+ def fetch_user userid
44
+ path = "/cgi-bin/user/get?access_token=#{refreshed_access_token}&userid=#{userid}"
45
+ response = fire_request path, {}
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,62 @@
1
+ module Jingubang::Weixin::Qiye::Account
2
+ module SendMessage
3
+
4
+ def send_text_message departmentids: [], userids: [], content: nil
5
+ path = "/cgi-bin/message/send?access_token=#{refreshed_access_token}"
6
+ body = {
7
+ touser: ids_param(userids),
8
+ toparty: ids_param(departmentids),
9
+ msgtype: 'text',
10
+ agentid: agentid,
11
+ text: {
12
+ content: content
13
+ }
14
+ }
15
+ response = fire_request path, body
16
+ end
17
+
18
+ def send_text_card_message departmentids: [], userids: [], title: nil, description: nil, url: nil, button_text: '详情'
19
+ path = "/cgi-bin/message/send?access_token=#{refreshed_access_token}"
20
+ body = {
21
+ touser: ids_param(userids),
22
+ toparty: ids_param(departmentids),
23
+ msgtype: 'textcard',
24
+ agentid: agentid,
25
+ textcard: {
26
+ title: title,
27
+ description: description,
28
+ url: url,
29
+ btntxt: button_text
30
+ }
31
+ }
32
+ response = fire_request path, body
33
+ end
34
+
35
+ def send_news_message departmentids: [], userids: [], title: nil, description: nil, url: nil, button_text: '详情', image_url: nil
36
+ path = "/cgi-bin/message/send?access_token=#{refreshed_access_token}"
37
+ body = {
38
+ touser: ids_param(userids),
39
+ toparty: ids_param(departmentids),
40
+ msgtype: 'news',
41
+ agentid: agentid,
42
+ news: {
43
+ articles: [{
44
+ title: title,
45
+ description: description,
46
+ url: url,
47
+ picurl: image_url,
48
+ btntxt: button_text
49
+ }]
50
+ }
51
+ }
52
+ response = fire_request path, body
53
+ end
54
+
55
+ private
56
+
57
+ def ids_param ids
58
+ ids.join('|') if ids
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ require 'rest_client'
2
+
3
+ module Jingubang::Weixin::Qiye
4
+ class ApiClient
5
+
6
+ GATEWAY_URL = 'https://qyapi.weixin.qq.com'
7
+
8
+ class << self
9
+
10
+ def get_login_info(access_token, auth_code)
11
+ path = "/cgi-bin/service/get_login_info?access_token=#{access_token}"
12
+ fire_request path, {auth_code: auth_code}
13
+ end
14
+
15
+ def get_register_code(access_token, template_id)
16
+ path = "/cgi-bin/service/get_register_code?provider_access_token=#{access_token}"
17
+ response = fire_request path, {template_id: template_id}
18
+ response['register_code']
19
+ end
20
+
21
+ private
22
+
23
+ def fire_request path, params
24
+ url = "#{GATEWAY_URL}#{path}"
25
+ response = JSON.parse RestClient.post(
26
+ url,
27
+ params.to_json,
28
+ timeout: 10,
29
+ content_type: :json,
30
+ accept: :json
31
+ )
32
+ response&.with_indifferent_access
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module Jingubang::Weixin::Qiye
2
+ module ProviderAppAccount
3
+
4
+ # Required fields:
5
+ # field :access_token, type: String
6
+ # field :expired_at, type: Time
7
+ # field :permanent_code, type: String
8
+ # def refreshed_access_token
9
+
10
+ BASE_URL = 'https://qyapi.weixin.qq.com'
11
+
12
+ def self.included(host_class)
13
+ host_class.include Jingubang::HttpClient
14
+ host_class.base_url BASE_URL
15
+ host_class.extend ClassMethods
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ def get_permanent_code auth_code
21
+ path = "/cgi-bin/service/get_permanent_code?suite_access_token=#{refreshed_access_token}"
22
+ fire_request path, {auth_code: auth_code}
23
+ end
24
+
25
+ def get_auth_info corpid, permanent_code
26
+ path = "/cgi-bin/service/get_auth_info?suite_access_token=#{refreshed_access_token}"
27
+ fire_request path, {auth_corpid: corpid, permanent_code: permanent_code}
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ require 'rest_client'
2
+
3
+ module Jingubang::Weixin::Qiye
4
+ class UrlHelper
5
+
6
+ class << self
7
+
8
+ def user_oauth_path(redirect_uri, corpid: nil, agentid: nil, state: '')
9
+ "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{corpid}&redirect_uri=#{redirect_uri}&response_type=code&scope=snsapi_base&agentid=#{agentid}&state=#{state}#wechat_redirect"
10
+ end
11
+
12
+ def third_party_user_oauth_path(redirect_uri, agentid: nil, state: '', user_type: 'member')
13
+ "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=#{agentid}&redirect_uri=#{redirect_uri}&state=#{state}&usertype=#{user_type}"
14
+ end
15
+
16
+ def third_party_install_path(redirect_uri, pre_auth_code: nil, agentid: nil, state: '')
17
+ "https://open.work.weixin.qq.com/3rdapp/install?suite_id=#{agentid}&pre_auth_code=#{pre_auth_code}&redirect_uri=#{redirect_uri}&state=#{state}"
18
+ end
19
+
20
+ def custom_registration_path(register_code)
21
+ "https://open.work.weixin.qq.com/3rdservice/wework/register?register_code=#{register_code}"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jingubang
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Zhichao Feng
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: weixin_message_encryptor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: All-in-on weixin integration for Rails
42
+ email:
43
+ - flankerfc@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - app/controllers/weixin/qiye/notifications_controller.rb
52
+ - jingubang.gemspec
53
+ - lib/jingubang.rb
54
+ - lib/jingubang/http_client.rb
55
+ - lib/jingubang/logger.rb
56
+ - lib/jingubang/rails.rb
57
+ - lib/jingubang/rails/routes.rb
58
+ - lib/jingubang/setting.rb
59
+ - lib/jingubang/test/test_helper.rb
60
+ - lib/jingubang/version.rb
61
+ - lib/jingubang/weixin.rb
62
+ - lib/jingubang/weixin/qiye.rb
63
+ - lib/jingubang/weixin/qiye/account.rb
64
+ - lib/jingubang/weixin/qiye/account/send_message.rb
65
+ - lib/jingubang/weixin/qiye/api_client.rb
66
+ - lib/jingubang/weixin/qiye/provider_app_account.rb
67
+ - lib/jingubang/weixin/qiye/url_helper.rb
68
+ homepage: https://github.com/flanker
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '2.3'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.6
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.6.14
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: All-in-on weixin integration for Rails
92
+ test_files: []