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