yol_qy_weixin 0.0.3

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
+ SHA256:
3
+ metadata.gz: 492bc402556844ff268a6cc561decc01273d6c625cfbe3d6fead5cd990a3c543
4
+ data.tar.gz: f245159f2eb8175688c586950c1ddc4e0ef20f2a1837f430ceab8898b6bf3581
5
+ SHA512:
6
+ metadata.gz: '04889e7bf6966e97dd0b9bb3e5d68cd686216a8cd9878f616388f12915d3789ac1de792452b97fabd968a6673dd2364b1acb30fbd8dfe8893cbd28bbe2de80a0'
7
+ data.tar.gz: e8eb257736eff86043bfe00a2a5e98fd2135d106d825dbe472eec07b9e82d0da0a4ac0a8add84cc84a72fffebe68a4de3cb7f659bff6a03bb93817c06fcee519
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yol_qy_weixin.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ yol_qy_weixin (0.0.3)
5
+ multi_xml
6
+ nokogiri
7
+ roxml
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (6.0.3.2)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (>= 0.7, < 2)
15
+ minitest (~> 5.1)
16
+ tzinfo (~> 1.1)
17
+ zeitwerk (~> 2.2, >= 2.2.2)
18
+ concurrent-ruby (1.1.8)
19
+ i18n (1.8.9)
20
+ concurrent-ruby (~> 1.0)
21
+ minitest (5.14.4)
22
+ multi_xml (0.6.0)
23
+ nokogiri (1.11.1-x86_64-darwin)
24
+ racc (~> 1.4)
25
+ racc (1.5.2)
26
+ rake (13.0.1)
27
+ roxml (4.1.1)
28
+ activesupport (>= 4.0)
29
+ nokogiri (>= 1.3.3)
30
+ thread_safe (0.3.6)
31
+ tzinfo (1.2.9)
32
+ thread_safe (~> 0.1)
33
+ zeitwerk (2.4.0)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ bundler
40
+ rake
41
+ yol_qy_weixin!
42
+
43
+ BUNDLED WITH
44
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # YolQyWeixin
2
+
3
+ This project rocks and uses MIT-LICENSE.
4
+
5
+ https://rubygems.org/gems/yol_qy_weixin
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/yol_qy_weixin.svg)](http://badge.fury.io/rb/yol_qy_weixin)
8
+
9
+ **企业号对应多个管理组,请前往 `设置` => `权限管理` 任意创建一个管理组,在管理组最下角即可获取 CorpID Secret**
10
+
11
+ **有问题请及时提issue**
12
+
13
+ ```ruby
14
+ gem "qy_wechat_api", git: "https://github.com/lanrion/qy_wechat_api.git"
15
+ ```
16
+
17
+ # 配置
18
+
19
+ ## jsticket、suite_token存储方案
20
+ 如果你是在Rails框架下,默认情况下直接使用Rails.cache,如果你想独立cache,请配置如下:
21
+ ```ruby
22
+ QyWechatApi.configure do |config|
23
+ config.cache_store = YourCustomCacheStore
24
+ end
25
+ ```
26
+ cache_store按照Rails.cache的接口实现,强烈建议使用ActiveSupport::Cache
27
+
28
+ ## 日志配置
29
+ 如果你是在Rails框架下,默认情况下直接使用Rails.logger,如果你想独立cache,请配置如下:
30
+ ```ruby
31
+ QyWechatApi.configure do |config|
32
+ config.logger = YourCustomLogger
33
+ end
34
+ ```
35
+ logger按照Rails.logger的接口实现,强烈建议使用ActiveSupport::Logger
36
+
37
+ ## Token 存储方案(TODO: 待重构直接使用cache_store)
38
+
39
+ ### 对象存储
40
+ 如果你是单个企业号,建议使用这个方案,无需任何配置即可使用。
41
+
42
+ ### Redis 存储
43
+ ```ruby
44
+ redis = Redis.new(host: "127.0.0.1", port: "6379")
45
+
46
+ namespace = "qy_wechat_api:redis_storage"
47
+
48
+ # cleanup keys in the current namespace when restart server everytime.
49
+ exist_keys = redis.keys("#{namespace}:*")
50
+ exist_keys.each{|key|redis.del(key)}
51
+
52
+ redis_with_ns = Redis::Namespace.new("#{namespace}", redis: redis)
53
+
54
+ QyWechatApi.configure do |config|
55
+ config.redis = redis_with_ns
56
+ end
57
+ ```
58
+
59
+ # API基本用法
60
+
61
+ 请务必结合:http://qydev.weixin.qq.com/wiki/index.php 理解以下API参数使用。
62
+
63
+ ## 初始化
64
+
65
+ ```ruby
66
+ group_client = QyWechatApi::Client.new(corpid, corpsecret)
67
+
68
+ # 为了确保用户输入的corpid, corpsecret是准确的,请务必执行:
69
+ group_client.is_valid?
70
+ ```
71
+
72
+ 如果需要使用通过第三方应用 **获取企业号access_token** API 获取的 access_token
73
+ 做如下处理:
74
+
75
+ ```ruby
76
+ options = {access_token: "access_token"}
77
+ # redis_key 也可定制
78
+ group_client = QyWechatApi::Client.new(corpid, corpsecret, options)
79
+ ```
80
+
81
+ ## 部门
82
+
83
+ ```ruby
84
+ group_client.department.create(name, parent_id, order=nil, id=nil)
85
+ group_client.department.update(id, name, parent_id, order=nil)
86
+ group_client.department.delete(id)
87
+ group_client.department.list
88
+ ```
89
+
90
+ ## 成员
91
+
92
+ ```ruby
93
+ # 创建成员
94
+ group_client.user.create(user_id, name, options={})
95
+
96
+ # 更新成员
97
+ group_client.user.update(user_id, options={})
98
+
99
+ # 删除成员
100
+ group_client.user.delete(user_id)
101
+
102
+ # 批量删除成员
103
+ group_client.user.batch_delete(user_ids)
104
+
105
+ # 获取成员
106
+ group_client.user.get(user_id)
107
+
108
+ # 获取部门成员
109
+ group_client.user.simple_list(department_id, fetch_child=nil, status=nil)
110
+
111
+ # 获取部门成员(详情)
112
+ group_client.user.full_list(department_id, fetch_child=nil, status=nil)
113
+
114
+ # 邀请成员关注
115
+ group_client.user.send_invitation(user_id, tips=nil)
116
+
117
+ # userid转换成openid接口(企业支付需要使用到)
118
+ group_client.covert_to_open_id(user_id, agent_id="")
119
+
120
+ # openid转换成userid接口
121
+ group_client.covert_to_user_id(open_id)
122
+ ```
123
+
124
+ ## 标签
125
+
126
+ ```ruby
127
+ group_client.tag.create(name)
128
+ group_client.tag.update(id, name)
129
+ group_client.tag.delete(id)
130
+ group_client.tag.get(id)
131
+ group_client.tag.add_tag_users(id, user_ids)
132
+ group_client.tag.delete_tag_users(id, user_ids)
133
+ group_client.tag.list
134
+ ```
135
+
136
+ ## 自定义菜单
137
+
138
+ menu_json的生成方法请参考:
139
+ https://github.com/lanrion/weixin_rails_middleware/wiki/DIY-menu
140
+
141
+ ```ruby
142
+ group_client.menu.create(menu_json, app_id)
143
+ group_client.menu.delete(app_id)
144
+ group_client.menu.get(app_id)
145
+ ```
146
+
147
+ ## Oauth2用法
148
+
149
+ 先要配置你应用的 可信域名 `2458023e.ngrok.com`
150
+ state 为开发者自定义参数,可选
151
+
152
+ ```ruby
153
+ # 生成授权url
154
+ group_client.oauth.authorize_url("http://2458023e.ngrok.com", "state")
155
+
156
+ # 获取code后,获取用户信息
157
+ # app_id: 跳转链接时所在的企业应用ID
158
+ group_client.oauth.get_user_info("code", "app_id")
159
+ ```
160
+
161
+ ## 发送消息
162
+
163
+ ```ruby
164
+ # params: (users, parties, tags, agent_id, content, safe=0)
165
+ # users, parties, tags 如果是多个用户,传数组,如果是全部,则直接传 "@all"
166
+ group_client.message.send_text("@all", "@all", "@all", app_id, text_message)
167
+ ```
168
+ **其他发送消息方法请查看 api/message.rb**
169
+
170
+ ## 上传多媒体文件
171
+ ```ruby
172
+ # params: media, media_type
173
+ group_client.media.upload(image_jpg_file, "image")
174
+
175
+ # 获取下载链接
176
+ # 返回一个URL,请开发者自行使用此url下载
177
+ group_client.media.get_media_by_id(media_id)
178
+
179
+ # 上传永久图文素材
180
+ # articles 为图文列表:
181
+ {
182
+ "title": "Title01",
183
+ "thumb_media_id": "2-G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0",
184
+ "author": "zs",
185
+ "content_source_url": "",
186
+ "content": "Content001",
187
+ "digest": "airticle01",
188
+ "show_cover_pic": "0"
189
+ }
190
+ group_client.material.add_mpnews(agent_id, articles)
191
+
192
+ # 更新图文素材
193
+ group_client.material.update_mpnews(agent_id, media_id, articles=[])
194
+
195
+ # 上传其他类型永久素材
196
+ # type: "image", "voice", "video", "file"
197
+ # file: File
198
+ group_client.material.add_material(agent_id, type, file)
199
+
200
+ # 删除永久素材
201
+ group_client.material.del(agent_id, media_id)
202
+
203
+ # 获取素材总数
204
+ group_client.material.get_count(agent_id)
205
+
206
+ # 获取素材列表
207
+ group_client.material.list(agent_id, type, offset, count=20)
208
+ ```
209
+
210
+ ## 第三方应用
211
+
212
+ 这里特别注意:保留 suite_access_token的cache是直接利用了前文配置的cache_store缓存。
213
+
214
+ ### api 使用介绍
215
+
216
+ ```ruby
217
+ suite_api = QyWechatApi::Suite.service(suite_id, suite_secret, suite_ticket)
218
+
219
+ # 获取预授权码
220
+ suite_api.get_pre_auth_code(appid=[])
221
+
222
+ # 获取企业号的永久授权码
223
+ suite_api.get_permanent_code(auth_code)
224
+
225
+ # 获取企业号的授权信息
226
+ suite_api.get_auth_info(auth_corpid, code)
227
+
228
+ # 获取企业号应用
229
+ suite_api.get_agent(auth_corpid, code, agent_id)
230
+
231
+ # 设置授权方的企业应用的选项设置信息
232
+ suite_api.set_agent(auth_corpid, permanent_code, agent_info)
233
+
234
+ # 调用企业接口所需的access_token
235
+ suite_api.get_corp_token(auth_corpid, permanent_code)
236
+
237
+ # 生成授权URL
238
+ suite_api.auth_url(code, uri, state="suite")
239
+
240
+ ```
241
+
242
+ ## 企业号登录授权
243
+
244
+ ```ruby
245
+ # 获取登录授权URL
246
+ # state default 'qy_wechat', option
247
+ # 此处授权回调时会传递auth_code、expires_in,auth_code用于get_login_info(获取企业号管理员登录信息)接口使用
248
+ group_client.auth_login.auth_login_url("redirect_uri", "state")
249
+
250
+ # 获取应用提供商凭证
251
+ # provider_secret:提供商的secret,在提供商管理页面可见
252
+ # 此处会返回:provider_access_token(已通过QyWechatApi.cache缓存7100s)
253
+ group_client.auth_login.get_provider_token(provider_secret)
254
+
255
+ # 通过传递provider_access_token,获取企业号管理员登录信息
256
+ group_client.auth_login.get_login_info(auth_code, provider_access_token)
257
+
258
+ # 通过传递provider_secret,获取企业号管理员登录信息
259
+ group_client.auth_login.get_login_info_by_secret(auth_code, provider_secret)
260
+
261
+ # 获取登录企业号官网的url
262
+ group_client.auth_login.get_login_url(ticket, provider_token, target, agentid=nil)
263
+ ```
264
+
265
+ ## 异步任务接口
266
+
267
+ ```ruby
268
+ # 邀请成员关注
269
+ group_client.async_task.invite_user(callback, invite_info={})
270
+ # 增量更新成员
271
+ group_client.async_task.sync_user(callback, media_id)
272
+ # 全量覆盖成员
273
+ group_client.async_task.replace_user(callback, media_id)
274
+ # 全量覆盖部门
275
+ group_client.async_task.replace_party(callback, media_id)
276
+ # 获取异步任务结果
277
+ group_client.async_task.get_result(job_id)
278
+ ```
279
+
280
+ ## 获取js api签名包
281
+ ```ruby
282
+ group_client.sign_package(request.url)
283
+ ```
284
+
285
+ ## 管理企业号应用
286
+
287
+ ```ruby
288
+ # 获取应用概况列表
289
+ group_client.agent.list
290
+
291
+ # 设置企业号应用
292
+ # agentid 企业应用的id
293
+ # report_location_flag 企业应用是否打开地理位置上报 0:不上报;1:进入会话上报;2:持续上报
294
+ # logo_mediaid 企业应用头像的mediaid,通过多媒体接口上传图片获得mediaid,上传后会自动裁剪成方形和圆形两个头像
295
+ # name 企业应用名称
296
+ # description 企业应用详情
297
+ # redirect_domain 企业应用可信域名
298
+ # isreportuser 是否接收用户变更通知。0:不接收;1:接收
299
+ # isreportenter 是否上报用户进入应用事件。0:不接收;1:接收
300
+ group_client.agent.set()
301
+
302
+ ## 获取企业号应用
303
+ group_client.agent.get(agent_id)
304
+ ```
305
+
306
+ ### 应用套件的回调通知处理
307
+
308
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AC%AC%E4%B8%89%E6%96%B9%E5%9B%9E%E8%B0%83%E5%8D%8F%E8%AE%AE
309
+
310
+ ```ruby
311
+ class QyServicesController < ApplicationController
312
+ skip_before_filter :verify_authenticity_token, only: :receive_ticket
313
+
314
+ # TODO: 需要创建表: suites
315
+ def receive_ticket
316
+ param_xml = request.body.read
317
+ aes_key = "NJgquXf6vnYlGpD5APBqlndAq7Nx8fToiEz5Wbaka47"
318
+ aes_key = Base64.decode64("#{aes_key}=")
319
+ hash = MultiXml.parse(param_xml)['xml']
320
+ @body_xml = OpenStruct.new(hash)
321
+ suite_id = "tj86cd0f5b8f7ce20d"
322
+ content = QyWechat::Prpcrypt.decrypt(aes_key, @body_xml.Encrypt, suite_id)[0]
323
+ hash = MultiXml.parse(content)["xml"]
324
+ Rails.logger.info hash
325
+ render text: "success"
326
+ # {"SuiteId"=>"tj86cd0f5b8f7ce20d",
327
+ # "SuiteTicket"=>"Pb5M0PEQFZSNondlK1K_atu2EoobY9piMcQCdE3URiCG3aTwX5WBTQaSsqCzaD-0",
328
+ # "InfoType"=>"suite_ticket",
329
+ # "TimeStamp"=>"1426988061"}
330
+ end
331
+ end
332
+ ```
333
+
334
+ ### 企业号消息接口
335
+
336
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E5%8F%B7%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
337
+
338
+ ```ruby
339
+ group_client.chat.send_single_text(sender, user_id, msg)
340
+ group_client.chat.send_single_image(sender, user_id, media_id)
341
+ group_client.chat.send_single_file(sender, user_id, media_id)
342
+ group_client.chat.send_group_text(sender, chat_id, msg)
343
+ group_client.chat.send_group_image(sender, chat_id, media_id)
344
+ group_client.chat.send_group_file(sender, chat_id, media_id)
345
+ ```
346
+
347
+ ### 企业客服服务
348
+
349
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=企业客服接口说明
350
+
351
+ ```ruby
352
+ # msg_struct请根据文档结构拼接传入
353
+ group_client.kf.send(msg_struct)
354
+ ```
355
+
356
+ ### 企业号摇一摇周边
357
+
358
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=获取设备及用户信息
359
+
360
+ ```ruby
361
+ # 获取设备及用户信息
362
+ # 摇周边业务的ticket,可在摇到的URL中得到,ticket生效时间为30分钟,每一次摇都会重新生成新的ticket
363
+ group_client.get_shake_info(ticket)
364
+ ```
365
+
366
+ ## 捐赠支持
367
+
368
+ 如果你觉得我的gem对你有帮助,欢迎打赏支持,:smile:
369
+
370
+ ![](https://raw.githubusercontent.com/lanrion/my_config/master/imagex/donation_me_wx.jpg)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,20 @@
1
+ require "monitor"
2
+ require "redis"
3
+ require 'digest/md5'
4
+ module YolQyWeixin
5
+ class Client
6
+
7
+ include Connection::Base
8
+ include Connection::Qrcode
9
+ include Connection::Template
10
+ include Connection::User
11
+
12
+ attr_accessor :corpid, :secret, :redis
13
+
14
+ def initialize(options = {})
15
+ @corpid = options[:corpid] || YolQyWeixin.configuration.corpid
16
+ @secret = options[:secret] || YolQyWeixin.configuration.secret
17
+ @redis = options[:redis] || YolQyWeixin.configuration.redis
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/connections/*.rb"].each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,63 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module Base
4
+ def http_post(url, params)
5
+ uri = URI(url)
6
+ req = Net::HTTP.new(uri.host, uri.port)
7
+ req.use_ssl = true
8
+ res = req.post("#{uri.path}?#{uri.query}", params.to_json)
9
+ handle_res(res)
10
+ end
11
+
12
+ def http_get(url)
13
+ uri = URI(url)
14
+ req = Net::HTTP.new(uri.host, uri.port)
15
+ req.use_ssl = true
16
+ res = req.get("#{uri.path}?#{uri.query}")
17
+ handle_res(res)
18
+ end
19
+
20
+ def get_access_token
21
+ # access_token = redis.find("access_token_#{appid}")
22
+
23
+ # if access_token.nil?
24
+ # access_token_res = get_token(corpid, secret)
25
+
26
+ # # TODO:
27
+ # access_token = JSON.parse(access_token_res)["access_token"] rescue nil
28
+
29
+ # if access_token.nil?
30
+ # raise Exception.new("Weixin access token authorize false, appid: #{appid},
31
+ # appsecret: #{appsecret}, access_token_res: #{access_token_res.to_s}")
32
+ # else
33
+ # redis.save("access_token_#{appid}", access_token)
34
+ # redis.expire("access_token_#{appid}", 7200)
35
+ # end
36
+ # end
37
+
38
+ # access_token
39
+ access_token_res = get_token(corpid, secret)
40
+ access_token = access_token_res["access_token"] rescue nil
41
+ access_token
42
+ end
43
+
44
+ private
45
+
46
+ def get_token(app_id, app_secret)
47
+ http_get(token_url(app_id, app_secret))
48
+ end
49
+
50
+ def token_url(corpid, secret)
51
+ "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=#{corpid}&corpsecret=#{secret}"
52
+ end
53
+
54
+ def handle_res(res)
55
+ if res.code == '200'
56
+ return JSON.parse(res.body)
57
+ else
58
+ return {:code => res.code}.to_json
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module Qrcode
4
+ def get_limited_qr(scene_id)
5
+ post_params = {"expire_seconds": 3600, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": scene_id}}}
6
+ http_post(qrcode_create_url, post_params)
7
+ end
8
+
9
+ private
10
+
11
+ def qrcode_create_url
12
+ "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=#{get_access_token}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,15 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module Template
4
+ def send_template_message(template_params)
5
+ http_post(message_template_url, template_params)
6
+ end
7
+
8
+ private
9
+
10
+ def message_template_url
11
+ "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=#{get_access_token}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module User
4
+ def get_user_info(open_id)
5
+ http_get(user_info_url(open_id))
6
+ end
7
+
8
+ private
9
+
10
+ def user_info_url(open_id)
11
+ "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=#{get_access_token}&userid=#{open_id}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module YolQyWeixin
4
+ module PKCS7Encoder
5
+ extend self
6
+
7
+ BLOCK_SIZE = 32
8
+
9
+ def decode(text)
10
+ pad = text[-1].ord
11
+ pad = 0 if (pad < 1 || pad > BLOCK_SIZE)
12
+ size = text.size - pad
13
+ text[0...size]
14
+ end
15
+
16
+ # 对需要加密的明文进行填充补位
17
+ # 返回补齐明文字符串
18
+ def encode(text)
19
+ # 计算需要填充的位数
20
+ amount_to_pad = BLOCK_SIZE - (text.length % BLOCK_SIZE)
21
+ amount_to_pad = BLOCK_SIZE if amount_to_pad == 0
22
+ # 获得补位所用的字符
23
+ pad_chr = amount_to_pad.chr
24
+ "#{text}#{pad_chr * amount_to_pad}"
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ require "yol_qy_weixin/helpers/pkcs7_encoder"
3
+ module YolQyWeixin
4
+ module Prpcrypt
5
+ extend self
6
+
7
+ # 对密文进行解密.
8
+ # text 需要解密的密文
9
+ def decrypt(aes_key, text, app_id)
10
+ status = 200
11
+ text = Base64.decode64(text)
12
+ text = handle_cipher(:decrypt, aes_key, text)
13
+ result = PKCS7Encoder.decode(text)
14
+ content = result[16...result.length]
15
+ len_list = content[0...4].unpack("N")
16
+ xml_len = len_list[0]
17
+ xml_content = content[4...4 + xml_len]
18
+ from_app_id = content[xml_len + 4...content.size]
19
+ # TODO: refactor
20
+ if app_id != from_app_id
21
+ # raise BizErr, "#{__FILE__}:#{__LINE__} Failure because app_id != from_app_id"
22
+ status = 500
23
+ end
24
+ [xml_content, status]
25
+ end
26
+
27
+ # 加密
28
+ def encrypt(aes_key, text, app_id)
29
+ text = text.force_encoding("ASCII-8BIT")
30
+ random = SecureRandom.hex(8)
31
+ msg_len = [text.length].pack("N")
32
+ text = "#{random}#{msg_len}#{text}#{app_id}"
33
+ text = PKCS7Encoder.encode(text)
34
+ text = handle_cipher(:encrypt, aes_key, text)
35
+ Base64.encode64(text)
36
+ end
37
+
38
+ private
39
+ def handle_cipher(action, aes_key, text)
40
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
41
+ cipher.send(action)
42
+ cipher.padding = 0
43
+ cipher.key = aes_key
44
+ cipher.iv = aes_key[0...16]
45
+ cipher.update(text) + cipher.final
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ module YolQyWeixin
2
+ class Configuration
3
+
4
+ OPTIONS = [:corpid, :secret, :redis].freeze
5
+
6
+ attr_accessor :corpid
7
+
8
+ attr_accessor :secret
9
+
10
+ attr_accessor :redis
11
+
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # 标准的回包
3
+ # <xml>
4
+ # <Encrypt><![CDATA[msg_encrypt]]></Encrypt>
5
+ # <MsgSignature><![CDATA[msg_signature]]></MsgSignature>
6
+ # <TimeStamp>timestamp</TimeStamp>
7
+ # <Nonce><![CDATA[nonce]]></Nonce>
8
+ # </xml>
9
+
10
+ module YolQyWeixin
11
+ class EncryptMessage
12
+ include ROXML
13
+ xml_name :xml
14
+
15
+ xml_accessor :Encrypt, :cdata => true
16
+ xml_accessor :Nonce, :cdata => true
17
+ xml_accessor :TimeStamp, :as => Integer
18
+ xml_accessor :MsgSignature, :cdata => true
19
+
20
+ def to_xml
21
+ super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,187 @@
1
+ # encoding: utf-8
2
+ # multi_xml will use Nokogiri if it is available
3
+ MultiXml.parser = :nokogiri
4
+
5
+ module YolQyWeixin
6
+
7
+ class Message
8
+
9
+ def initialize(hash)
10
+ @source = OpenStruct.new(hash)
11
+ end
12
+
13
+ def method_missing(method, *args, &block)
14
+ @source.send(method, *args, &block)
15
+ end
16
+
17
+ def CreateTime
18
+ @source.CreateTime.to_i
19
+ end
20
+
21
+ def MsgId
22
+ @source.MsgId.to_i rescue nil
23
+ end
24
+
25
+ def self.factory(xml)
26
+ hash = MultiXml.parse(xml)['xml']
27
+
28
+ # 优先检测服务器信息
29
+ unless hash['InfoType'].to_s.empty?
30
+ case hash['InfoType']
31
+ when 'component_verify_ticket'
32
+ TicketMessage.new(hash)
33
+ when 'unauthorized'
34
+ AuthorizeMessage.new(hash)
35
+ else
36
+ raise ArgumentError, 'Unknown Weixin Message' + hash.to_s
37
+ end
38
+ else
39
+ case hash['MsgType']
40
+ when 'text'
41
+ TextMessage.new(hash)
42
+ when 'image'
43
+ ImageMessage.new(hash)
44
+ when 'location'
45
+ LocationMessage.new(hash)
46
+ when 'link'
47
+ LinkMessage.new(hash)
48
+ when 'event'
49
+ EventMessage.new(hash)
50
+ when 'voice'
51
+ VoiceMessage.new(hash)
52
+ when 'video'
53
+ VideoMessage.new(hash)
54
+ else
55
+ raise ArgumentError, 'Unknown Weixin Message' + hash.to_s
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ # <xml>
63
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
64
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
65
+ # <CreateTime>1348831860</CreateTime>
66
+ # <MsgType><![CDATA[text]]></MsgType>
67
+ # <Content><![CDATA[this is a test]]></Content>
68
+ # <MsgId>1234567890123456</MsgId>
69
+ # </xml>
70
+ TextMessage = Class.new(Message)
71
+
72
+ # <xml>
73
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
74
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
75
+ # <CreateTime>1348831860</CreateTime>
76
+ # <MsgType><![CDATA[image]]></MsgType>
77
+ # <PicUrl><![CDATA[this is a url]]></PicUrl>
78
+ # <MsgId>1234567890123456</MsgId>
79
+ # </xml>
80
+ ImageMessage = Class.new(Message)
81
+
82
+ # <xml>
83
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
84
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
85
+ # <CreateTime>1351776360</CreateTime>
86
+ # <MsgType><![CDATA[link]]></MsgType>
87
+ # <Title><![CDATA[公众平台官网链接]]></Title>
88
+ # <Description><![CDATA[公众平台官网链接]]></Description>
89
+ # <Url><![CDATA[url]]></Url>
90
+ # <MsgId>1234567890123456</MsgId>
91
+ # </xml>
92
+ LinkMessage = Class.new(Message)
93
+
94
+ # <xml>
95
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
96
+ # <FromUserName><![CDATA[FromUser]]></FromUserName>
97
+ # <CreateTime>123456789</CreateTime>
98
+ # <MsgType><![CDATA[event]]></MsgType>
99
+ # <Event><![CDATA[EVENT]]></Event>
100
+ # <EventKey><![CDATA[EVENTKEY]]></EventKey>
101
+ # </xml>
102
+ EventMessage = Class.new(Message)
103
+
104
+ # <xml>
105
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
106
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
107
+ # <CreateTime>1351776360</CreateTime>
108
+ # <MsgType><![CDATA[location]]></MsgType>
109
+ # <Location_X>23.134521</Location_X>
110
+ # <Location_Y>113.358803</Location_Y>
111
+ # <Scale>20</Scale>
112
+ # <Label><![CDATA[位置信息]]></Label>
113
+ # <MsgId>1234567890123456</MsgId>
114
+ # </xml>
115
+ class LocationMessage < Message
116
+
117
+ def Location_X
118
+ @source.Location_X.to_f
119
+ end
120
+
121
+ def Location_Y
122
+ @source.Location_Y.to_f
123
+ end
124
+
125
+ def Scale
126
+ @source.Scale.to_i
127
+ end
128
+ end
129
+
130
+ # <xml>
131
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
132
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
133
+ # <CreateTime>1376632760</CreateTime>
134
+ # <MsgType><![CDATA[voice]]></MsgType>
135
+ # <MediaId><![CDATA[Qyb0tgux6QLjhL6ipvFZJ-kUt2tcQtkn0BU365Vt3wUAtqfGam4QpZU35RXVhv6G]]></MediaId>
136
+ # <Format><![CDATA[amr]]></Format>
137
+ # <MsgId>5912592682802219078</MsgId>
138
+ # <Recognition><![CDATA[]]></Recognition>
139
+ # </xml>
140
+ class VoiceMessage < Message
141
+
142
+ def MediaId
143
+ @source.MediaId
144
+ end
145
+
146
+ def Format
147
+ @source.Format
148
+ end
149
+ end
150
+
151
+ # <xml>
152
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
153
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
154
+ # <CreateTime>1376632994</CreateTime>
155
+ # <MsgType><![CDATA[video]]></MsgType>
156
+ # <MediaId><![CDATA[TAAGb6iS5LcZR1d5ICiZTWGWi6-Upic9tlWDpAKcNJA]]></MediaId>
157
+ # <ThumbMediaId><![CDATA[U-xulPW4kq6KKMWFNaBSPc65Bcgr7Qopwex0DfCeyQs]]></ThumbMediaId>
158
+ # <MsgId>5912593687824566343</MsgId>
159
+ # </xml>
160
+ class VideoMessage < Message
161
+
162
+ def MediaId
163
+ @source.MediaId
164
+ end
165
+
166
+ def ThumbMediaId
167
+ @source.ThumbMediaId
168
+ end
169
+ end
170
+
171
+ # <xml>
172
+ # <AppId> </AppId>
173
+ # <CreateTime>1413192605 </CreateTime>
174
+ # <InfoType> </InfoType>
175
+ # <ComponentVerifyTicket> </ComponentVerifyTicket>
176
+ # </xml>
177
+ TicketMessage = Class.new(Message)
178
+
179
+ # <xml>
180
+ # <AppId></AppId>
181
+ # <CreateTime>1413192760</CreateTime>
182
+ # <InfoType> </InfoType>
183
+ # <AuthorizerAppid></AuthorizerAppid>
184
+ # </xml>
185
+ AuthorizeMessage = Class.new(Message)
186
+
187
+ end
@@ -0,0 +1,3 @@
1
+ module YolQyWeixin
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'roxml'
2
+ require 'multi_xml'
3
+ require 'ostruct'
4
+
5
+ require "yol_qy_weixin/version"
6
+
7
+ require "yol_qy_weixin/models/configuration"
8
+ require "yol_qy_weixin/models/encrypt_message"
9
+ require "yol_qy_weixin/models/message"
10
+ require "yol_qy_weixin/helpers/prpcrypt"
11
+
12
+ require "yol_qy_weixin/connection"
13
+ require "yol_qy_weixin/client"
14
+
15
+ module YolQyWeixin
16
+
17
+ class << self
18
+
19
+ # A YolQyWeixin configuration object. See YolQyWeixin::Configuration.
20
+ attr_writer :configuration
21
+
22
+ def configure
23
+ yield(configuration)
24
+ end
25
+
26
+ def configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'yol_qy_weixin/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "yol_qy_weixin"
8
+ spec.version = YolQyWeixin::VERSION
9
+ spec.authors = ["cjl"]
10
+ spec.email = ["chenjialiang@yeezon.com"]
11
+ spec.summary = %q{Shop middleware for Weixin.}
12
+ spec.description = ""
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency 'nokogiri'
25
+ spec.add_dependency 'multi_xml'
26
+ spec.add_dependency 'roxml'
27
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yol_qy_weixin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - cjl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
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: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: multi_xml
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: roxml
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ''
84
+ email:
85
+ - chenjialiang@yeezon.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".DS_Store"
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/.DS_Store
98
+ - lib/yol_qy_weixin.rb
99
+ - lib/yol_qy_weixin/client.rb
100
+ - lib/yol_qy_weixin/connection.rb
101
+ - lib/yol_qy_weixin/connections/base.rb
102
+ - lib/yol_qy_weixin/connections/qrcode.rb
103
+ - lib/yol_qy_weixin/connections/template.rb
104
+ - lib/yol_qy_weixin/connections/user.rb
105
+ - lib/yol_qy_weixin/helpers/pkcs7_encoder.rb
106
+ - lib/yol_qy_weixin/helpers/prpcrypt.rb
107
+ - lib/yol_qy_weixin/models/configuration.rb
108
+ - lib/yol_qy_weixin/models/encrypt_message.rb
109
+ - lib/yol_qy_weixin/models/message.rb
110
+ - lib/yol_qy_weixin/version.rb
111
+ - yol_qy_weixin.gemspec
112
+ homepage: ''
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.7.9
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Shop middleware for Weixin.
136
+ test_files: []