jingubang 0.0.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.
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: []