gfd_wechat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +321 -0
  3. data/LICENSE +21 -0
  4. data/README-CN.md +815 -0
  5. data/README.md +844 -0
  6. data/bin/wechat +520 -0
  7. data/lib/action_controller/wechat_responder.rb +72 -0
  8. data/lib/generators/wechat/config_generator.rb +36 -0
  9. data/lib/generators/wechat/install_generator.rb +20 -0
  10. data/lib/generators/wechat/menu_generator.rb +21 -0
  11. data/lib/generators/wechat/redis_store_generator.rb +16 -0
  12. data/lib/generators/wechat/session_generator.rb +36 -0
  13. data/lib/generators/wechat/templates/MENU_README +3 -0
  14. data/lib/generators/wechat/templates/app/controllers/wechats_controller.rb +12 -0
  15. data/lib/generators/wechat/templates/app/models/wechat_config.rb +46 -0
  16. data/lib/generators/wechat/templates/app/models/wechat_session.rb +17 -0
  17. data/lib/generators/wechat/templates/config/initializers/wechat_redis_store.rb +42 -0
  18. data/lib/generators/wechat/templates/config/wechat.yml +72 -0
  19. data/lib/generators/wechat/templates/config/wechat_menu.yml +6 -0
  20. data/lib/generators/wechat/templates/config/wechat_menu_android.yml +15 -0
  21. data/lib/generators/wechat/templates/db/config_migration.rb.erb +40 -0
  22. data/lib/generators/wechat/templates/db/session_migration.rb.erb +10 -0
  23. data/lib/wechat/api.rb +54 -0
  24. data/lib/wechat/api_base.rb +63 -0
  25. data/lib/wechat/api_loader.rb +145 -0
  26. data/lib/wechat/cipher.rb +66 -0
  27. data/lib/wechat/concern/common.rb +217 -0
  28. data/lib/wechat/controller_api.rb +96 -0
  29. data/lib/wechat/corp_api.rb +168 -0
  30. data/lib/wechat/helpers.rb +47 -0
  31. data/lib/wechat/http_client.rb +112 -0
  32. data/lib/wechat/message.rb +265 -0
  33. data/lib/wechat/mp_api.rb +46 -0
  34. data/lib/wechat/responder.rb +308 -0
  35. data/lib/wechat/signature.rb +10 -0
  36. data/lib/wechat/ticket/corp_jsapi_ticket.rb +14 -0
  37. data/lib/wechat/ticket/jsapi_base.rb +84 -0
  38. data/lib/wechat/ticket/public_jsapi_ticket.rb +14 -0
  39. data/lib/wechat/token/access_token_base.rb +53 -0
  40. data/lib/wechat/token/corp_access_token.rb +13 -0
  41. data/lib/wechat/token/public_access_token.rb +13 -0
  42. data/lib/wechat.rb +52 -0
  43. 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: []