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 +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +56 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +44 -0
- data/LICENSE.txt +22 -0
- data/README.md +370 -0
- data/Rakefile +2 -0
- data/lib/.DS_Store +0 -0
- data/lib/yol_qy_weixin/client.rb +20 -0
- data/lib/yol_qy_weixin/connection.rb +3 -0
- data/lib/yol_qy_weixin/connections/base.rb +63 -0
- data/lib/yol_qy_weixin/connections/qrcode.rb +17 -0
- data/lib/yol_qy_weixin/connections/template.rb +15 -0
- data/lib/yol_qy_weixin/connections/user.rb +15 -0
- data/lib/yol_qy_weixin/helpers/pkcs7_encoder.rb +28 -0
- data/lib/yol_qy_weixin/helpers/prpcrypt.rb +48 -0
- data/lib/yol_qy_weixin/models/configuration.rb +13 -0
- data/lib/yol_qy_weixin/models/encrypt_message.rb +24 -0
- data/lib/yol_qy_weixin/models/message.rb +187 -0
- data/lib/yol_qy_weixin/version.rb +3 -0
- data/lib/yol_qy_weixin.rb +31 -0
- data/yol_qy_weixin.gemspec +27 -0
- metadata +136 -0
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
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
|
+
[](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
|
+

|
data/Rakefile
ADDED
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,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,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,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: []
|