gfd_wechat 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 +7 -0
- data/CHANGELOG.md +321 -0
- data/LICENSE +21 -0
- data/README-CN.md +815 -0
- data/README.md +844 -0
- data/bin/wechat +520 -0
- data/lib/action_controller/wechat_responder.rb +72 -0
- data/lib/generators/wechat/config_generator.rb +36 -0
- data/lib/generators/wechat/install_generator.rb +20 -0
- data/lib/generators/wechat/menu_generator.rb +21 -0
- data/lib/generators/wechat/redis_store_generator.rb +16 -0
- data/lib/generators/wechat/session_generator.rb +36 -0
- data/lib/generators/wechat/templates/MENU_README +3 -0
- data/lib/generators/wechat/templates/app/controllers/wechats_controller.rb +12 -0
- data/lib/generators/wechat/templates/app/models/wechat_config.rb +46 -0
- data/lib/generators/wechat/templates/app/models/wechat_session.rb +17 -0
- data/lib/generators/wechat/templates/config/initializers/wechat_redis_store.rb +42 -0
- data/lib/generators/wechat/templates/config/wechat.yml +72 -0
- data/lib/generators/wechat/templates/config/wechat_menu.yml +6 -0
- data/lib/generators/wechat/templates/config/wechat_menu_android.yml +15 -0
- data/lib/generators/wechat/templates/db/config_migration.rb.erb +40 -0
- data/lib/generators/wechat/templates/db/session_migration.rb.erb +10 -0
- data/lib/wechat/api.rb +54 -0
- data/lib/wechat/api_base.rb +63 -0
- data/lib/wechat/api_loader.rb +145 -0
- data/lib/wechat/cipher.rb +66 -0
- data/lib/wechat/concern/common.rb +217 -0
- data/lib/wechat/controller_api.rb +96 -0
- data/lib/wechat/corp_api.rb +168 -0
- data/lib/wechat/helpers.rb +47 -0
- data/lib/wechat/http_client.rb +112 -0
- data/lib/wechat/message.rb +265 -0
- data/lib/wechat/mp_api.rb +46 -0
- data/lib/wechat/responder.rb +308 -0
- data/lib/wechat/signature.rb +10 -0
- data/lib/wechat/ticket/corp_jsapi_ticket.rb +14 -0
- data/lib/wechat/ticket/jsapi_base.rb +84 -0
- data/lib/wechat/ticket/public_jsapi_ticket.rb +14 -0
- data/lib/wechat/token/access_token_base.rb +53 -0
- data/lib/wechat/token/corp_access_token.rb +13 -0
- data/lib/wechat/token/public_access_token.rb +13 -0
- data/lib/wechat.rb +52 -0
- metadata +195 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Wechat
|
5
|
+
module Ticket
|
6
|
+
class JsapiBase
|
7
|
+
attr_reader :client, :access_token, :jsapi_ticket_file, :access_ticket, :ticket_life_in_seconds, :got_ticket_at
|
8
|
+
|
9
|
+
def initialize(client, access_token, jsapi_ticket_file)
|
10
|
+
@client = client
|
11
|
+
@access_token = access_token
|
12
|
+
@jsapi_ticket_file = jsapi_ticket_file
|
13
|
+
@random_generator = Random.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def ticket(tries = 2)
|
17
|
+
# Possible two worker running, one worker refresh ticket, other unaware, so must read every time
|
18
|
+
read_ticket_from_store
|
19
|
+
refresh if remain_life_seconds < @random_generator.rand(30..3 * 60)
|
20
|
+
access_ticket
|
21
|
+
rescue AccessTokenExpiredError
|
22
|
+
access_token.refresh
|
23
|
+
retry unless (tries -= 1).zero?
|
24
|
+
end
|
25
|
+
|
26
|
+
def oauth2_state
|
27
|
+
ticket
|
28
|
+
@oauth2_state
|
29
|
+
end
|
30
|
+
|
31
|
+
# Obtain the wechat jssdk config signature parameter and return below hash
|
32
|
+
# params = {
|
33
|
+
# noncestr: noncestr,
|
34
|
+
# timestamp: timestamp,
|
35
|
+
# jsapi_ticket: ticket,
|
36
|
+
# url: url,
|
37
|
+
# signature: signature
|
38
|
+
# }
|
39
|
+
def signature(url)
|
40
|
+
params = {
|
41
|
+
noncestr: SecureRandom.base64(16),
|
42
|
+
timestamp: Time.now.to_i,
|
43
|
+
jsapi_ticket: ticket,
|
44
|
+
url: url
|
45
|
+
}
|
46
|
+
pairs = params.keys.sort.map do |key|
|
47
|
+
"#{key}=#{params[key]}"
|
48
|
+
end
|
49
|
+
result = Digest::SHA1.hexdigest pairs.join('&')
|
50
|
+
params.merge(signature: result)
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def read_ticket_from_store
|
56
|
+
td = read_ticket
|
57
|
+
@ticket_life_in_seconds = td.fetch('ticket_expires_in').to_i
|
58
|
+
@got_ticket_at = td.fetch('got_ticket_at').to_i
|
59
|
+
@oauth2_state = td.fetch('oauth2_state')
|
60
|
+
@access_ticket = td.fetch('ticket') # return access_ticket same time
|
61
|
+
rescue JSON::ParserError, Errno::ENOENT, KeyError, TypeError
|
62
|
+
refresh
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_ticket_to_store(ticket_hash)
|
66
|
+
ticket_hash['got_ticket_at'.freeze] = Time.now.to_i
|
67
|
+
ticket_hash['ticket_expires_in'.freeze] = ticket_hash.delete('expires_in')
|
68
|
+
write_ticket(ticket_hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_ticket
|
72
|
+
JSON.parse(File.read(jsapi_ticket_file))
|
73
|
+
end
|
74
|
+
|
75
|
+
def write_ticket(ticket_hash)
|
76
|
+
File.write(jsapi_ticket_file, ticket_hash.to_json)
|
77
|
+
end
|
78
|
+
|
79
|
+
def remain_life_seconds
|
80
|
+
ticket_life_in_seconds - (Time.now.to_i - got_ticket_at)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'wechat/ticket/jsapi_base'
|
2
|
+
|
3
|
+
module Wechat
|
4
|
+
module Ticket
|
5
|
+
class PublicJsapiTicket < JsapiBase
|
6
|
+
def refresh
|
7
|
+
data = client.get('ticket/getticket', params: { access_token: access_token.token, type: 'jsapi' })
|
8
|
+
data['oauth2_state'] = SecureRandom.hex(16)
|
9
|
+
write_ticket_to_store(data)
|
10
|
+
read_ticket_from_store
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Wechat
|
2
|
+
module Token
|
3
|
+
class AccessTokenBase
|
4
|
+
attr_reader :client, :appid, :secret, :token_file, :access_token, :token_life_in_seconds, :got_token_at
|
5
|
+
|
6
|
+
def initialize(client, appid, secret, token_file)
|
7
|
+
@appid = appid
|
8
|
+
@secret = secret
|
9
|
+
@client = client
|
10
|
+
@token_file = token_file
|
11
|
+
@random_generator = Random.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def token
|
15
|
+
# Possible two worker running, one worker refresh token, other unaware, so must read every time
|
16
|
+
read_token_from_store
|
17
|
+
refresh if remain_life_seconds < @random_generator.rand(30..3 * 60)
|
18
|
+
access_token
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def read_token_from_store
|
24
|
+
td = read_token
|
25
|
+
@token_life_in_seconds = td.fetch('token_expires_in').to_i
|
26
|
+
@got_token_at = td.fetch('got_token_at').to_i
|
27
|
+
@access_token = td.fetch('access_token') # return access_token same time
|
28
|
+
rescue JSON::ParserError, Errno::ENOENT, KeyError, TypeError
|
29
|
+
refresh
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_token_to_store(token_hash)
|
33
|
+
raise InvalidCredentialError unless token_hash.is_a?(Hash) && token_hash['access_token']
|
34
|
+
|
35
|
+
token_hash['got_token_at'.freeze] = Time.now.to_i
|
36
|
+
token_hash['token_expires_in'.freeze] = token_hash.delete('expires_in')
|
37
|
+
write_token(token_hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_token
|
41
|
+
JSON.parse(File.read(token_file))
|
42
|
+
end
|
43
|
+
|
44
|
+
def write_token(token_hash)
|
45
|
+
File.write(token_file, token_hash.to_json)
|
46
|
+
end
|
47
|
+
|
48
|
+
def remain_life_seconds
|
49
|
+
token_life_in_seconds - (Time.now.to_i - got_token_at)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'wechat/token/access_token_base'
|
2
|
+
|
3
|
+
module Wechat
|
4
|
+
module Token
|
5
|
+
class CorpAccessToken < AccessTokenBase
|
6
|
+
def refresh
|
7
|
+
data = client.get('gettoken', params: { corpid: appid, corpsecret: secret })
|
8
|
+
write_token_to_store(data)
|
9
|
+
read_token_from_store
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'wechat/token/access_token_base'
|
2
|
+
|
3
|
+
module Wechat
|
4
|
+
module Token
|
5
|
+
class PublicAccessToken < AccessTokenBase
|
6
|
+
def refresh
|
7
|
+
data = client.get('token', params: { grant_type: 'client_credential', appid: appid, secret: secret })
|
8
|
+
write_token_to_store(data)
|
9
|
+
read_token_from_store
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/wechat.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl/cipher'
|
3
|
+
require 'wechat/api_loader'
|
4
|
+
require 'wechat/api'
|
5
|
+
require 'wechat/mp_api'
|
6
|
+
require 'wechat/corp_api'
|
7
|
+
require 'wechat/helpers'
|
8
|
+
require 'action_controller/wechat_responder'
|
9
|
+
|
10
|
+
module Wechat
|
11
|
+
autoload :Message, 'wechat/message'
|
12
|
+
autoload :Responder, 'wechat/responder'
|
13
|
+
autoload :Cipher, 'wechat/cipher'
|
14
|
+
autoload :ControllerApi, 'wechat/controller_api'
|
15
|
+
|
16
|
+
class AccessTokenExpiredError < StandardError; end
|
17
|
+
class InvalidCredentialError < StandardError; end
|
18
|
+
class ResponseError < StandardError
|
19
|
+
attr_reader :error_code
|
20
|
+
def initialize(errcode, errmsg)
|
21
|
+
@error_code = errcode
|
22
|
+
super "#{errmsg}(#{error_code})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.config(account = :default)
|
27
|
+
ApiLoader.config(account)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.api(account = :default)
|
31
|
+
@wechat_apis ||= {}
|
32
|
+
@wechat_apis[account.to_sym] ||= ApiLoader.with(account: account)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.reload_config!
|
36
|
+
ApiLoader.reload_config!
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.decrypt(encrypted_data, session_key, iv)
|
40
|
+
cipher = OpenSSL::Cipher.new('AES-128-CBC')
|
41
|
+
cipher.decrypt
|
42
|
+
|
43
|
+
cipher.key = Base64.decode64(session_key)
|
44
|
+
cipher.iv = Base64.decode64(iv)
|
45
|
+
decrypted_data = Base64.decode64(encrypted_data)
|
46
|
+
JSON.parse(cipher.update(decrypted_data) + cipher.final)
|
47
|
+
rescue Exception => e
|
48
|
+
{ 'errcode': 41003, 'errmsg': e.message }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ActionView::Base.send :include, Wechat::Helpers if defined? ActionView::Base
|
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gfd_wechat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chunyu
|
8
|
+
- Zhou
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-11-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.2'
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '6'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '3.2'
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6'
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: http
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.4
|
41
|
+
- - "<"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '4'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.0.4
|
51
|
+
- - "<"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '4'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: nokogiri
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.6.0
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.6.0
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: thor
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rails
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '5.1'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '5.1'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rspec-rails
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.6'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.6'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: sqlite3
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
description: API, command and message handling for WeChat in Rails
|
125
|
+
email: zhouchunyu@163.com
|
126
|
+
executables: []
|
127
|
+
extensions: []
|
128
|
+
extra_rdoc_files: []
|
129
|
+
files:
|
130
|
+
- CHANGELOG.md
|
131
|
+
- LICENSE
|
132
|
+
- README-CN.md
|
133
|
+
- README.md
|
134
|
+
- bin/wechat
|
135
|
+
- lib/action_controller/wechat_responder.rb
|
136
|
+
- lib/generators/wechat/config_generator.rb
|
137
|
+
- lib/generators/wechat/install_generator.rb
|
138
|
+
- lib/generators/wechat/menu_generator.rb
|
139
|
+
- lib/generators/wechat/redis_store_generator.rb
|
140
|
+
- lib/generators/wechat/session_generator.rb
|
141
|
+
- lib/generators/wechat/templates/MENU_README
|
142
|
+
- lib/generators/wechat/templates/app/controllers/wechats_controller.rb
|
143
|
+
- lib/generators/wechat/templates/app/models/wechat_config.rb
|
144
|
+
- lib/generators/wechat/templates/app/models/wechat_session.rb
|
145
|
+
- lib/generators/wechat/templates/config/initializers/wechat_redis_store.rb
|
146
|
+
- lib/generators/wechat/templates/config/wechat.yml
|
147
|
+
- lib/generators/wechat/templates/config/wechat_menu.yml
|
148
|
+
- lib/generators/wechat/templates/config/wechat_menu_android.yml
|
149
|
+
- lib/generators/wechat/templates/db/config_migration.rb.erb
|
150
|
+
- lib/generators/wechat/templates/db/session_migration.rb.erb
|
151
|
+
- lib/wechat.rb
|
152
|
+
- lib/wechat/api.rb
|
153
|
+
- lib/wechat/api_base.rb
|
154
|
+
- lib/wechat/api_loader.rb
|
155
|
+
- lib/wechat/cipher.rb
|
156
|
+
- lib/wechat/concern/common.rb
|
157
|
+
- lib/wechat/controller_api.rb
|
158
|
+
- lib/wechat/corp_api.rb
|
159
|
+
- lib/wechat/helpers.rb
|
160
|
+
- lib/wechat/http_client.rb
|
161
|
+
- lib/wechat/message.rb
|
162
|
+
- lib/wechat/mp_api.rb
|
163
|
+
- lib/wechat/responder.rb
|
164
|
+
- lib/wechat/signature.rb
|
165
|
+
- lib/wechat/ticket/corp_jsapi_ticket.rb
|
166
|
+
- lib/wechat/ticket/jsapi_base.rb
|
167
|
+
- lib/wechat/ticket/public_jsapi_ticket.rb
|
168
|
+
- lib/wechat/token/access_token_base.rb
|
169
|
+
- lib/wechat/token/corp_access_token.rb
|
170
|
+
- lib/wechat/token/public_access_token.rb
|
171
|
+
homepage: https://github.com/Eric-Guo/wechat
|
172
|
+
licenses:
|
173
|
+
- MIT
|
174
|
+
metadata: {}
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - "~>"
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '2.3'
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
requirements: []
|
190
|
+
rubyforge_project:
|
191
|
+
rubygems_version: 2.6.11
|
192
|
+
signing_key:
|
193
|
+
specification_version: 4
|
194
|
+
summary: DSL for wechat message handling and API
|
195
|
+
test_files: []
|