wechat 0.4.2 → 0.5.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ff11a10da8e6a405f646702fc63e4b72e778ace
4
- data.tar.gz: 5269fd75b8a4044d6b140fdb197f3dd8381fc868
3
+ metadata.gz: 26688ce4d31dd3c7df24fce05a5c722f255251a8
4
+ data.tar.gz: 8c504cbd4bd2b95eeadbafc51d99771777a6e23b
5
5
  SHA512:
6
- metadata.gz: 03775bee616b1c070efefaff727330ea8ce3411232ea3dffb3f8da2cd21d309b1aa8e86a07831711f198da5dbb5460bc71fa831900eba27c29ac291badad7548
7
- data.tar.gz: 6e1001ea2b328e4c69fd6960bb8c7b50a0b178950c2c922ed482b37eacabdcc5c0b4cf7ec213490a80de6530695d79b428becf61d72c33f462dfc22586145433
6
+ metadata.gz: 7505d3220b2a8adfcd2b8883bdbbc76d2ed1ef65893e88e73deaf05219e484ade74d5822b6b8a10dcd0e26cb9b9f46ad0bb37a2acad4635ce9d53bd21c3080e3
7
+ data.tar.gz: acfe7dae6763e066bdceef9d97f5d91ea517ab33b3213f7b0278fba847def719b0df768eb967879e06119ae8e891910f00ec3c6a49b63ecf93fc458892210a34
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.5.0 (released at 9/25/2015)
4
+
5
+ * Only relay on activesupport on run time, so will greatly improve wechat cli startup time
6
+ * Add rails generator support `rails g wechat:install`
7
+ * Add batch job support for enterprise account like batch create user/department, both API, callback responder and CLI
8
+ * Add material management API and CLI
9
+ * Add tag API and CLI for enterprise account
10
+ * Add QR code scene function for public account
11
+
3
12
  ## v0.4.2 (released at 9/7/2015)
4
13
 
5
14
  * Fix wrong number of arguments at Wechat::Responder.on by using arity #47
data/README.md CHANGED
@@ -28,6 +28,20 @@ Or add to your app's `Gemfile`:
28
28
  gem 'wechat'
29
29
  ```
30
30
 
31
+ Run the following command to install it:
32
+
33
+ ```console
34
+ bundle install
35
+ ```
36
+
37
+ Run the generator:
38
+
39
+ ```console
40
+ rails generate wechat:install
41
+ ```
42
+
43
+ 运行`rails g wechat:install`后会自动生成wechat.yml配置,还有wechat controller及相关路由配置到当前Rails项目。
44
+
31
45
 
32
46
  ## 配置
33
47
 
@@ -46,7 +60,7 @@ Windows或者使用企业号,需要存放在`C:/Users/[user_name]/`下,其
46
60
  ```
47
61
  corpid: "my_appid"
48
62
  corpsecret: "my_secret"
49
- agentid: "1" # 企业应用的id,整型。可在应用的设置页面查看
63
+ agentid: 1 # 企业应用的id,整型。可在应用的设置页面查看
50
64
  access_token: "C:/Users/[user_name]/wechat_access_token"
51
65
  ```
52
66
 
@@ -81,7 +95,7 @@ test:
81
95
  default: &default
82
96
  corpid: "corpid"
83
97
  corpsecret: "corpsecret"
84
- agentid: "1"
98
+ agentid: 1
85
99
  access_token: "C:/Users/[user_name]/wechat_access_token"
86
100
  token: ""
87
101
  encoding_aes_key: ""
@@ -108,7 +122,7 @@ test:
108
122
 
109
123
  ##### 配置跳过SSL认证
110
124
 
111
- Wechat服务器有报道曾出现[RestClient::SSLCertificateNotVerified](http://qydev.weixin.qq.com/qa/index.php?qa=11037)错误,此时可以选择关闭SSL验证(skip_verify_ssl)。
125
+ Wechat服务器有报道曾出现[RestClient::SSLCertificateNotVerified](http://qydev.weixin.qq.com/qa/index.php?qa=11037)错误,此时可以选择关闭SSL验证。`skip_verify_ssl: true`
112
126
 
113
127
  #### 为每个Responder配置不同的appid和secret
114
128
 
@@ -139,6 +153,12 @@ wechat gems 内部不会检查权限。但因公众号类型不同,和微信
139
153
  ```
140
154
  $ wechat
141
155
  Wechat commands:
156
+ wechat agent [AGENT_ID] # 获取企业号应用详情
157
+ wechat agent_list # 获取应用概况列表
158
+ wechat batch_job_result [JOB_ID] # 获取异步任务结果
159
+ wechat batch_replaceparty [BATCH_PARTY_CSV_MEDIA_ID] # 全量覆盖部门
160
+ wechat batch_replaceuser [BATCH_USER_CSV_MEDIA_ID] # 全量覆盖成员
161
+ wechat batch_syncuser [SYNC_USER_CSV_MEDIA_ID] # 增量更新成员
142
162
  wechat callbackip # 获取微信服务器IP地址
143
163
  wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
144
164
  wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
@@ -148,6 +168,7 @@ Wechat commands:
148
168
  wechat custom_voice [OPENID, VOICE_PATH] # 发送语音客服消息
149
169
  wechat department [DEPARTMENT_ID] # 获取部门列表
150
170
  wechat department_create [NAME, PARENT_ID] # 创建部门
171
+ wechat department_delete [DEPARTMENT_ID] # 删除部门
151
172
  wechat group_create [GROUP_NAME] # 创建分组
152
173
  wechat group_delete [GROUP_ID] # 删除分组
153
174
  wechat group_update [GROUP_ID, NEW_GROUP_NAME] # 修改分组名
@@ -155,19 +176,35 @@ Wechat commands:
155
176
  wechat invite_user [USER_ID] # 邀请成员关注
156
177
  wechat material [MEDIA_ID, PATH] # 永久媒体下载
157
178
  wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
158
- wechat material_count # 获取素材总数
179
+ wechat material_count # 获取永久素材总数
159
180
  wechat material_delete [MEDIA_ID] # 删除永久素材
181
+ wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
160
182
  wechat media [MEDIA_ID, PATH] # 媒体下载
161
183
  wechat media_create [MEDIA_TYPE, PATH] # 媒体上传
162
184
  wechat menu # 当前菜单
163
185
  wechat menu_create [MENU_YAML_PATH] # 创建菜单
164
186
  wechat menu_delete # 删除菜单
165
187
  wechat message_send [OPENID, TEXT_MESSAGE] # 发送文字消息(仅企业号)
188
+ wechat qrcode_create_limit_scene [SCENE_ID_OR_STR] # 请求永久二维码
189
+ wechat qrcode_create_scene [SCENE_ID, EXPIRE_SECONDS] # 请求临时二维码
190
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
191
+ wechat tag [TAG_ID] # 获取标签成员
192
+ wechat tag_add_department [TAG_ID, PARTY_IDS] # 增加标签部门
193
+ wechat tag_add_user [TAG_ID, USER_IDS] # 增加标签成员
194
+ wechat tag_create [TAGNAME, TAG_ID] # 创建标签
195
+ wechat tag_del_user [TAG_ID, USER_IDS] # 删除标签成员
196
+ wechat tag_del_department [TAG_ID, PARTY_IDS] # 删除标签部门
197
+ wechat tag_delete [TAG_ID] # 删除标签
198
+ wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
199
+ wechat tags # 获取标签列表
166
200
  wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
167
201
  wechat user [OPEN_ID] # 获取用户基本信息
168
202
  wechat user_change_group [OPEN_ID, TO_GROUP_ID] # 移动用户分组
169
203
  wechat user_delete [USER_ID] # 删除成员
170
204
  wechat user_group [OPEN_ID] # 查询用户所在分组
205
+ wechat user_list [DEPARTMENT_ID] # 获取部门成员详情
206
+ wechat user_simplelist [DEPARTMENT_ID] # 获取部门成员
207
+ wechat user_update_remark [OPEN_ID, REMARK] # 设置备注名
171
208
  wechat users # 关注者列表
172
209
  ```
173
210
 
@@ -221,20 +258,14 @@ button:
221
258
  type: "scancode_waitmsg"
222
259
  name: "绑定用餐二维码"
223
260
  key: "BINDING_QR_CODE"
224
- sub_button:
225
- -
226
261
  -
227
262
  type: "click"
228
263
  name: "预订午餐"
229
264
  key: "BOOK_LUNCH"
230
- sub_button:
231
- -
232
265
  -
233
266
  type: "click"
234
267
  name: "预订晚餐"
235
268
  key: "BOOK_DINNER"
236
- sub_button:
237
- -
238
269
  -
239
270
  name: "查询"
240
271
  sub_button:
@@ -242,19 +273,14 @@ button:
242
273
  type: "click"
243
274
  name: "进出记录"
244
275
  key: "BADGE_IN_OUT"
245
- sub_button:
246
- -
247
276
  -
248
277
  type: "click"
249
278
  name: "年假余额"
250
279
  key: "ANNUAL_LEAVE"
251
- sub_button:
252
- -
253
280
  -
254
281
  type: "view"
255
282
  name: "关于"
256
283
  url: "http://blog.cloud-mes.com/"
257
-
258
284
  ```
259
285
 
260
286
  然后执行命令行,需确保设置,权限管理中有对此应用的管理权限,否则会报[60011](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%85%A8%E5%B1%80%E8%BF%94%E5%9B%9E%E7%A0%81%E8%AF%B4%E6%98%8E)错。
@@ -316,7 +342,6 @@ template:
316
342
 
317
343
  ```
318
344
  $ wechat template_message oCfEht9oM*********** template.yml
319
-
320
345
  ```
321
346
 
322
347
  ## Rails Responder Controller DSL
@@ -325,7 +350,6 @@ $ wechat template_message oCfEht9oM*********** template.yml
325
350
 
326
351
  ```ruby
327
352
  resource :wechat, only:[:show, :create]
328
-
329
353
  ```
330
354
 
331
355
  然后创建Controller class, 例如
@@ -340,16 +364,16 @@ class WechatsController < ApplicationController
340
364
  end
341
365
 
342
366
  # 当请求的文字信息内容为'help'时, 使用这个responder处理
343
- on :text, with:"help" do |request, help|
344
- request.reply.text "help content" #回复帮助信息
367
+ on :text, with: 'help' do |request|
368
+ request.reply.text 'help content' #回复帮助信息
345
369
  end
346
370
 
347
371
  # 当请求的文字信息内容为'<n>条新闻'时, 使用这个responder处理, 并将n作为第二个参数
348
372
  on :text, with: /^(\d+)条新闻$/ do |request, count|
349
373
  # 微信最多显示10条新闻,大于10条将只取前10条
350
- news = (1..count.to_i).each_with_object([]) { |n, memo| memo << {title: "新闻标题", content: "第#{n}条新闻的内容#{n.hash}"} }
374
+ news = (1..count.to_i).each_with_object([]) { |n, memo| memo << { title: '新闻标题', content: "第#{n}条新闻的内容#{n.hash}" } }
351
375
  request.reply.news(news) do |article, n, index| # 回复"articles"
352
- article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: "http://www.baidu.com/img/bdlogo.gif", url:"http://www.baidu.com/"
376
+ article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: 'http://www.baidu.com/img/bdlogo.gif', url: 'http://www.baidu.com/'
353
377
  end
354
378
  end
355
379
 
@@ -377,8 +401,8 @@ class WechatsController < ApplicationController
377
401
 
378
402
  # 处理视频信息
379
403
  on :video do |request|
380
- nickname = wechat.user(request[:FromUserName])["nickname"] #调用 api 获得发送者的nickname
381
- request.reply.video(request[:MediaId], title: "回声", description: "#{nickname}发来的视频请求") #直接视频返回给用户
404
+ nickname = wechat.user(request[:FromUserName])['nickname'] #调用 api 获得发送者的nickname
405
+ request.reply.video(request[:MediaId], title: '回声', description: "#{nickname}发来的视频请求") #直接视频返回给用户
382
406
  end
383
407
 
384
408
  # 处理地理位置信息
@@ -393,13 +417,37 @@ class WechatsController < ApplicationController
393
417
 
394
418
  # 当用户取消关注订阅
395
419
  on :event, with: 'unsubscribe' do |request|
396
- request.reply.text "#{request[:FromUserName]}无法收到这条消息。"
420
+ request.reply.text "#{request[:FromUserName]} can not receive this message"
421
+ end
422
+
423
+ # 成员进入应用的事件推送
424
+ on :event, with: 'enter_agent' do |request|
425
+ request.reply.text "#{request[:FromUserName]} enter agent app now"
426
+ end
427
+
428
+ # 当异步任务增量更新成员完成时推送
429
+ on :event, with: 'sync_user' do |request, batch_job|
430
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
431
+ end
432
+
433
+ # 当异步任务全量覆盖成员完成时推送
434
+ on :event, with: 'replace_user' do |request, batch_job|
435
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
436
+ end
437
+
438
+ # 当异步任务邀请成员关注完成时推送
439
+ on :event, with: 'invite_user' do |request, batch_job|
440
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
441
+ end
442
+
443
+ # 当异步任务全量覆盖部门完成时推送
444
+ on :event, with: 'replace_party' do |request, batch_job|
445
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
397
446
  end
398
447
 
399
448
  # 当无任何responder处理用户信息时,使用这个responder处理
400
- on :fallback, respond: "fallback message"
449
+ on :fallback, respond: 'fallback message'
401
450
  end
402
-
403
451
  ```
404
452
 
405
453
  在controller中使用`wechat_responder`引入Responder DSL, 之后可以用
@@ -444,4 +492,5 @@ Wechat 的核心是一个Message DSL,帮助开发者构建各种类型的消息
444
492
 
445
493
  ## 已知问题
446
494
 
447
- 企业号接受菜单消息时,Wechat腾讯服务器无法解析部分域名,请使用IP绑定回调URL,用户的普通消息目前不受影响。
495
+ * 企业号接受菜单消息时,Wechat腾讯服务器无法解析部分域名,请使用IP绑定回调URL,用户的普通消息目前不受影响。
496
+ * 企业号全量覆盖成员使用的csv通讯录格式,直接将下载的模板导入[是不工作的](http://qydev.weixin.qq.com/qa/index.php?qa=13978),必须使用Excel打开,然后另存为csv格式才会变成合法格式。
data/bin/wechat CHANGED
@@ -45,7 +45,7 @@ HELP
45
45
  home_config_file = File.join(Dir.home, '.wechat.yml')
46
46
 
47
47
  if File.exist?(rails_config_file)
48
- config = YAML.load(ERB.new(File.new(rails_config_file).read).result)['default']
48
+ config = YAML.load(ERB.new(File.read(rails_config_file)).result)['default']
49
49
  if config.present? && (config['appid'] || config['corpid'])
50
50
  puts 'Using rails project config/wechat.yml default setting...'
51
51
  else
@@ -68,6 +68,13 @@ HELP
68
68
  puts Helper.with(options).callbackip
69
69
  end
70
70
 
71
+ desc 'qrcode_download [TICKET, QR_CODE_PIC_PATH]', '通过ticket下载二维码'
72
+ def qrcode_download(ticket, qr_code_pic_path)
73
+ tmp_file = Helper.with(options).qrcode(ticket)
74
+ FileUtils.mv(tmp_file.path, qr_code_pic_path)
75
+ puts 'File downloaded'
76
+ end
77
+
71
78
  desc 'groups', '所有用户分组列表'
72
79
  def groups
73
80
  puts Helper.with(options).groups
@@ -95,9 +102,121 @@ HELP
95
102
  puts Helper.with(options).department_create(name, api_opts[:parentid] || '1')
96
103
  end
97
104
 
105
+ desc 'department_delete [DEPARTMENT_ID]', '删除部门'
106
+ def department_delete(departmentid)
107
+ puts Helper.with(options).department_delete(departmentid)
108
+ end
109
+
98
110
  desc 'department [DEPARTMENT_ID]', '获取部门列表'
99
- def department(departmentid)
100
- puts Helper.with(options).department(departmentid)
111
+ def department(departmentid = 0)
112
+ r = Helper.with(options).department(departmentid)
113
+ puts "errcode: #{r['errcode']} errmsg: #{r['errmsg']}"
114
+ puts 'Or# pid id name'
115
+ r['department'].sort_by { |d| d['order'].to_i + d['parentid'].to_i * 1000 } .each do |i|
116
+ puts format('%3d %3d %3d %s', i['order'], i['parentid'], i['id'], i['name'])
117
+ end
118
+ end
119
+
120
+ desc 'user_simplelist [DEPARTMENT_ID]', '获取部门成员'
121
+ method_option :fetch_child, aliases: '-c', desc: '是否递归获取子部门下面的成员', default: 1
122
+ method_option :status, aliases: '-s', desc: '0 获取全部成员,1 获取已关注成员列表,2 获取禁用成员列表,4 获取未关注成员列表。status可叠加', default: 0
123
+ def user_simplelist(departmentid = 0)
124
+ api_opts = options.slice(:fetch_child, :status)
125
+
126
+ r = Helper.with(options).user_simplelist(departmentid, api_opts[:fetch_child], api_opts[:status])
127
+ puts "errcode: #{r['errcode']} errmsg: #{r['errmsg']}"
128
+ puts " userid Name #{' ' * 20} department_ids"
129
+ r['userlist'].sort_by { |d| d['userid'] } .each do |i|
130
+ puts format('%7s %-25s %-14s', i['userid'], i['name'], i['department'])
131
+ end
132
+ end
133
+
134
+ desc 'user_list [DEPARTMENT_ID]', '获取部门成员详情'
135
+ method_option :fetch_child, aliases: '-c', desc: '是否递归获取子部门下面的成员', default: 0
136
+ method_option :status, aliases: '-s', desc: '0 获取全部成员,1 获取已关注成员列表,2 获取禁用成员列表,4 获取未关注成员列表。status可叠加', default: 0
137
+ def user_list(departmentid = 0)
138
+ api_opts = options.slice(:fetch_child, :status)
139
+
140
+ r = Helper.with(options).user_list(departmentid, api_opts[:fetch_child], api_opts[:status])
141
+ puts "errcode: #{r['errcode']} errmsg: #{r['errmsg']}"
142
+ puts " userid Name #{' ' * 15} department_ids position mobile #{' ' * 5}gender email #{' ' * 10}weixinid status extattr"
143
+ r['userlist'].sort_by { |d| d['userid'] } .each do |i|
144
+ puts format('%7s %-20s %-14s %-8s %-11s %-6s %-15s %-15s %-6s %s',
145
+ i['userid'], i['name'], i['department'], i['position'], i['mobile'],
146
+ i['gender'], i['email'], i['weixinid'], i['status'], i['extattr'])
147
+ end
148
+ end
149
+
150
+ desc 'tag_create [TAGNAME, TAG_ID]', '创建标签'
151
+ method_option :tagid, aliases: '-id', desc: '整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增'
152
+ def tag_create(name)
153
+ api_opts = options.slice(:tagid)
154
+ puts Helper.with(options).tag_create(name, api_opts[:tagid])
155
+ end
156
+
157
+ desc 'tag_update [TAG_ID, TAGNAME]', '更新标签名字'
158
+ def tag_update(tagid, tagname)
159
+ puts Helper.with(options).tag_update(tagid, tagname)
160
+ end
161
+
162
+ desc 'tag_delete [TAG_ID]', '删除标签'
163
+ def tag_delete(tagid)
164
+ puts Helper.with(options).tag_delete(tagid)
165
+ end
166
+
167
+ desc 'tags', '获取标签列表'
168
+ def tags
169
+ puts Helper.with(options).tags
170
+ end
171
+
172
+ desc 'tag [TAG_ID]', '获取标签成员'
173
+ def tag(tagid)
174
+ puts Helper.with(options).tag(tagid)
175
+ end
176
+
177
+ desc 'tag_add_user [TAG_ID, USER_IDS]', '增加标签成员'
178
+ def tag_add_user(tagid, userids)
179
+ puts Helper.with(options).tag_add_user(tagid, userids.split(','))
180
+ end
181
+
182
+ desc 'tag_add_department [TAG_ID, PARTY_IDS]', '增加标签部门'
183
+ def tag_add_department(tagid, partyids)
184
+ puts Helper.with(options).tag_add_user(tagid, nil, partyids.split(','))
185
+ end
186
+
187
+ desc 'tag_del_user [TAG_ID, USER_IDS]', '删除标签成员'
188
+ def tag_del_user(tagid, userids)
189
+ puts Helper.with(options).tag_del_user(tagid, userids.split(','))
190
+ end
191
+
192
+ desc 'tag_del_department [TAG_ID, PARTY_IDS]', '删除标签部门'
193
+ def tag_del_department(tagid, partyids)
194
+ puts Helper.with(options).tag_del_user(tagid, nil, partyids.split(','))
195
+ end
196
+
197
+ desc 'agent_list', '获取应用概况列表'
198
+ def agent_list
199
+ r = Helper.with(options).agent_list
200
+ puts "errcode: #{r['errcode']} errmsg: #{r['errmsg']}"
201
+ puts 'ag# name square_logo_url round_logo_url'
202
+ r['agentlist'].sort_by { |d| d['agentid'] } .each do |i|
203
+ puts format('%3d %s %s %s', i['agentid'], i['name'], i['square_logo_url'], i['round_logo_url'])
204
+ end
205
+ end
206
+
207
+ desc 'agent [AGENT_ID]', '获取企业号应用详情'
208
+ def agent(agentid)
209
+ r = Helper.with(options).agent(agentid)
210
+ puts "agentid: #{r['agentid']} errcode: #{r['errcode']} errmsg: #{r['errmsg']}"
211
+ puts "name: #{r['name']}"
212
+ puts "description: #{r['description']}"
213
+ puts " square_logo_url: #{r['square_logo_url']}"
214
+ puts " round_logo_url: #{r['round_logo_url']}"
215
+ puts "allow_userinfos: #{r['allow_userinfos']}"
216
+ puts "allow_partys: #{r['allow_partys']}"
217
+ puts "allow_tags: #{r['allow_tags']}"
218
+ puts "close: #{r['close']} redirect_domain: #{r['redirect_domain']}"
219
+ puts "report_location_flag: #{r['report_location_flag']} isreportuser: #{r['isreportuser']} isreportenter: #{r['isreportenter']}"
101
220
  end
102
221
 
103
222
  desc 'users', '关注者列表'
@@ -120,6 +239,26 @@ HELP
120
239
  puts Helper.with(options).user_delete(userid)
121
240
  end
122
241
 
242
+ desc 'batch_job_result [JOB_ID]', '获取异步任务结果'
243
+ def batch_job_result(job_id)
244
+ puts Helper.with(options).batch_job_result(job_id)
245
+ end
246
+
247
+ desc 'batch_replaceparty [BATCH_PARTY_CSV_MEDIA_ID]', '全量覆盖部门'
248
+ def batch_replaceparty(batch_party_csv_media_id)
249
+ puts Helper.with(options).batch_replaceparty(batch_party_csv_media_id)
250
+ end
251
+
252
+ desc 'batch_syncuser [SYNC_USER_CSV_MEDIA_ID]', '增量更新成员'
253
+ def batch_syncuser(sync_user_csv_media_id)
254
+ puts Helper.with(options).batch_syncuser(sync_user_csv_media_id)
255
+ end
256
+
257
+ desc 'batch_replaceuser [BATCH_USER_CSV_MEDIA_ID]', '全量覆盖成员'
258
+ def batch_replaceuser(batch_user_csv_media_id)
259
+ puts Helper.with(options).batch_replaceuser(batch_user_csv_media_id)
260
+ end
261
+
123
262
  desc 'user_group [OPEN_ID]', '查询用户所在分组'
124
263
  def user_group(openid)
125
264
  puts Helper.with(options).user_group(openid)
@@ -130,6 +269,21 @@ HELP
130
269
  puts Helper.with(options).user_change_group(openid, to_groupid)
131
270
  end
132
271
 
272
+ desc 'user_update_remark [OPEN_ID, REMARK]', '设置备注名'
273
+ def user_update_remark(openid, remark)
274
+ puts Helper.with(options).user_update_remark(openid, remark)
275
+ end
276
+
277
+ desc 'qrcode_create_scene [SCENE_ID, EXPIRE_SECONDS]', '请求临时二维码'
278
+ def qrcode_create_scene(scene_id, expire_seconds = 604800)
279
+ puts Helper.with(options).qrcode_create_scene(scene_id, expire_seconds)
280
+ end
281
+
282
+ desc 'qrcode_create_limit_scene [SCENE_ID_OR_STR]', '请求永久二维码'
283
+ def qrcode_create_limit_scene(scene_id_or_str)
284
+ puts Helper.with(options).qrcode_create_limit_scene(scene_id_or_str)
285
+ end
286
+
133
287
  desc 'menu', '当前菜单'
134
288
  def menu
135
289
  puts Helper.with(options).menu
@@ -142,7 +296,7 @@ HELP
142
296
 
143
297
  desc 'menu_create [MENU_YAML_PATH]', '创建菜单'
144
298
  def menu_create(menu_yaml_path)
145
- menu = YAML.load(File.new(menu_yaml_path).read)
299
+ menu = YAML.load(File.read(menu_yaml_path))
146
300
  puts 'Menu created' if Helper.with(options).menu_create(menu)
147
301
  end
148
302
 
@@ -177,11 +331,24 @@ HELP
177
331
  puts Helper.with(options).material_delete(media_id)
178
332
  end
179
333
 
180
- desc 'material_count', '获取素材总数'
334
+ desc 'material_count', '获取永久素材总数'
181
335
  def material_count
182
336
  puts Helper.with(options).material_count
183
337
  end
184
338
 
339
+ desc 'material_list [TYPE, OFFSET, COUNT]', '获取永久素材列表'
340
+ def material_list(type, offset, count)
341
+ r = Helper.with(options).material_list(type, offset, count)
342
+ if %w(image voice video file).include?(type)
343
+ puts "errcode: #{r['errcode']} errmsg: #{r['errmsg']} total_count: #{r['total_count']} item_count: #{r['item_count']}"
344
+ r['itemlist'].each do |i|
345
+ puts "#{i['media_id']} #{i['filename']} #{Time.at(i['update_time'].to_i)}"
346
+ end
347
+ else
348
+ puts r
349
+ end
350
+ end
351
+
185
352
  desc 'message_send [OPENID, TEXT_MESSAGE]', '发送文字消息(仅企业号)'
186
353
  def message_send(openid, text_message)
187
354
  puts Helper.with(options).message_send openid, text_message
@@ -237,13 +404,13 @@ HELP
237
404
 
238
405
  desc 'custom_news [OPENID, NEWS_YAML_PATH]', '发送图文客服消息'
239
406
  def custom_news(openid, news_yaml_path)
240
- articles = YAML.load(File.new(news_yaml_path).read)
407
+ articles = YAML.load(File.read(news_yaml_path))
241
408
  puts Helper.with(options).custom_message_send Wechat::Message.to(openid).news(articles['articles'])
242
409
  end
243
410
 
244
411
  desc 'template_message [OPENID, TEMPLATE_YAML_PATH]', '模板消息接口'
245
412
  def template_message(openid, template_yaml_path)
246
- template = YAML.load(File.new(template_yaml_path).read)
413
+ template = YAML.load(File.read(template_yaml_path))
247
414
  puts Helper.with(options).template_message_send Wechat::Message.to(openid).template(template['template'])
248
415
  end
249
416
  end
@@ -0,0 +1,20 @@
1
+ module Wechat
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ desc 'Install Wechat support files'
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def copy_config
8
+ template 'config/wechat.yml'
9
+ end
10
+
11
+ def add_wechat_route
12
+ route 'resource :wechat, only: [:show, :create]'
13
+ end
14
+
15
+ def copy_wechat_controller
16
+ template 'app/controllers/wechats_controller.rb'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,91 @@
1
+ class WechatsController < ApplicationController
2
+ wechat_responder
3
+
4
+ # default text responder when no other match
5
+ on :text do |request, content|
6
+ request.reply.text "echo: #{content}" # Just echo
7
+ end
8
+
9
+ # When receive 'help', will trigger this responder
10
+ on :text, with: 'help' do |request|
11
+ request.reply.text 'help content'
12
+ end
13
+
14
+ # When receive '<n>news', will match and will got count as <n> as parameter
15
+ on :text, with: /^(\d+) news$/ do |request, count|
16
+ # Wechat article can only contain max 10 items, large than 10 will dropped.
17
+ news = (1..count.to_i).each_with_object([]) { |n, memo| memo << { title: 'News title', content: "No. #{n} news content" } }
18
+ request.reply.news(news) do |article, n, index| # article is return object
19
+ article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: 'http://www.baidu.com/img/bdlogo.gif', url: 'http://www.baidu.com/'
20
+ end
21
+ end
22
+
23
+ # When user press menu BINDING_QR_CODE and success to scan bar code
24
+ on :event, with: 'BINDING_QR_CODE' do |request, scan_result, scan_type|
25
+ request.reply.text "User #{request[:FromUserName]} ScanResult #{scan_result} ScanType #{scan_type}"
26
+ end
27
+
28
+ # Except QR code, wechat can also scan CODE_39 bar code
29
+ on :event, with: 'BINDING_BARCODE' do |message, scan_result|
30
+ if scan_result.start_with? 'CODE_39,'
31
+ message.reply.text "User: #{message[:FromUserName]} scan barcode, result is #{scan_result.split(',')[1]}"
32
+ end
33
+ end
34
+
35
+ # When user sent the imsage
36
+ on :image do |request|
37
+ request.reply.image(request[:MediaId]) # Echo the sent image to user
38
+ end
39
+
40
+ # When user sent the voice
41
+ on :voice do |request|
42
+ request.reply.voice(request[:MediaId]) # Echo the sent voice to user
43
+ end
44
+
45
+ # When user sent the video
46
+ on :video do |request|
47
+ nickname = wechat.user(request[:FromUserName])['nickname'] # Call wechat api to get sender nickname
48
+ request.reply.video(request[:MediaId], title: 'Echo', description: "Got #{nickname} sent video") # Echo the sent video to user
49
+ end
50
+
51
+ # When user sent location
52
+ on :location do |request|
53
+ request.reply.text("#{request[:Location_X]}, #{request[:Location_Y]}") # replay the GPS location
54
+ end
55
+
56
+ on :event, with: 'subscribe' do |request|
57
+ request.reply.text "#{request[:FromUserName]} subscribe now"
58
+ end
59
+
60
+ on :event, with: 'unsubscribe' do |request|
61
+ request.reply.text "#{request[:FromUserName]} can not receive this message"
62
+ end
63
+
64
+ # When user enter the app / agent app
65
+ on :event, with: 'enter_agent' do |request|
66
+ request.reply.text "#{request[:FromUserName]} enter agent app now"
67
+ end
68
+
69
+ # When batch job create/update user (incremental) finished.
70
+ on :event, with: 'sync_user' do |request, batch_job|
71
+ request.reply.text "sync_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
72
+ end
73
+
74
+ # When batch job replace user (full sync) finished.
75
+ on :event, with: 'replace_user' do |request, batch_job|
76
+ request.reply.text "replace_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
77
+ end
78
+
79
+ # When batch job invent user finished.
80
+ on :event, with: 'invite_user' do |request, batch_job|
81
+ request.reply.text "invite_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
82
+ end
83
+
84
+ # When batch job replace department (full sync) finished.
85
+ on :event, with: 'replace_party' do |request, batch_job|
86
+ request.reply.text "replace_party job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
87
+ end
88
+
89
+ # Any not match above will fail to below
90
+ on :fallback, respond: 'fallback message'
91
+ end
@@ -0,0 +1,27 @@
1
+ default: &default
2
+ corpid: "corpid"
3
+ corpsecret: "corpsecret"
4
+ agentid: 1
5
+ # Or if using public account, only need above two line
6
+ # appid: "my_appid"
7
+ # secret: "my_secret"
8
+ token: "token"
9
+ access_token: "C:/Users/[username]/wechat_access_token"
10
+ encoding_aes_key: "encoding_aes_key"
11
+
12
+ production:
13
+ corpid: <%%= ENV['WECHAT_CORPID'] %>
14
+ corpsecret: <%%= ENV['WECHAT_CORPSECRET'] %>
15
+ agentid: <%%= ENV['WECHAT_AGENTID'] %>
16
+ # Or if using public account, only need above two line
17
+ # appid: <%= ENV['WECHAT_APPID'] %>
18
+ # secret: <%= ENV['WECHAT_APP_SECRET'] %>
19
+ token: <%%= ENV['WECHAT_TOKEN'] %>
20
+ access_token: <%%= ENV['WECHAT_ACCESS_TOKEN'] %>
21
+ encoding_aes_key: <%%= ENV['WECHAT_ENCODING_AES_KEY'] %>
22
+
23
+ development:
24
+ <<: *default
25
+
26
+ test:
27
+ <<: *default
@@ -18,7 +18,7 @@ module Wechat
18
18
  end
19
19
 
20
20
  def groups
21
- get('groups/get')
21
+ get 'groups/get'
22
22
  end
23
23
 
24
24
  def group_create(group_name)
@@ -39,7 +39,7 @@ module Wechat
39
39
  end
40
40
 
41
41
  def user(openid)
42
- get('user/info', params: { openid: openid })
42
+ get 'user/info', params: { openid: openid }
43
43
  end
44
44
 
45
45
  def user_group(openid)
@@ -50,17 +50,38 @@ module Wechat
50
50
  post 'groups/members/update', JSON.generate(openid: openid, to_groupid: to_groupid)
51
51
  end
52
52
 
53
+ def user_update_remark(openid, remark)
54
+ post 'user/info/updateremark', JSON.generate(openid: openid, remark: remark)
55
+ end
56
+
57
+ def qrcode_create_scene(scene_id, expire_seconds = 604800)
58
+ post 'qrcode/create', JSON.generate(expire_seconds: expire_seconds,
59
+ action_name: 'QR_SCENE',
60
+ action_info: { scene: { scene_id: scene_id } })
61
+ end
62
+
63
+ def qrcode_create_limit_scene(scene_id_or_str)
64
+ case scene_id_or_str
65
+ when Fixnum
66
+ post 'qrcode/create', JSON.generate(action_name: 'QR_LIMIT_SCENE',
67
+ action_info: { scene: { scene_id: scene_id_or_str } })
68
+ else
69
+ post 'qrcode/create', JSON.generate(action_name: 'QR_LIMIT_STR_SCENE',
70
+ action_info: { scene: { scene_str: scene_id_or_str } })
71
+ end
72
+ end
73
+
53
74
  def menu
54
- get('menu/get')
75
+ get 'menu/get'
55
76
  end
56
77
 
57
78
  def menu_delete
58
- get('menu/delete')
79
+ get 'menu/delete'
59
80
  end
60
81
 
61
82
  def menu_create(menu)
62
83
  # 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
63
- post('menu/create', JSON.generate(menu))
84
+ post 'menu/create', JSON.generate(menu)
64
85
  end
65
86
 
66
87
  def media(media_id)
@@ -79,6 +100,10 @@ module Wechat
79
100
  get 'material/get_materialcount'
80
101
  end
81
102
 
103
+ def material_list(type, offset, count)
104
+ post 'material/batchget_material', JSON.generate(type: type, offset: offset, count: count)
105
+ end
106
+
82
107
  def material_add(type, file)
83
108
  post 'material/add_material', { upload: { media: file } }, params: { type: type }, base: FILE_BASE
84
109
  end
@@ -1,9 +1,17 @@
1
+ require 'cgi'
2
+
1
3
  module Wechat
2
4
  class ApiBase
3
5
  attr_reader :access_token, :client
4
6
 
7
+ MP_BASE = 'https://mp.weixin.qq.com/cgi-bin/'
8
+
5
9
  def callbackip
6
- get('getcallbackip')
10
+ get 'getcallbackip'
11
+ end
12
+
13
+ def qrcode(ticket)
14
+ client.get 'showqrcode', ticket: CGI.escape(ticket), base: MP_BASE, as: :file
7
15
  end
8
16
 
9
17
  protected
@@ -23,8 +23,16 @@ module Wechat
23
23
  @agentid = agentid
24
24
  end
25
25
 
26
+ def agent_list
27
+ get 'agent/list'
28
+ end
29
+
30
+ def agent(agentid)
31
+ get 'agent/get', params: { agentid: agentid }
32
+ end
33
+
26
34
  def user(userid)
27
- get('user/get', params: { userid: userid })
35
+ get 'user/get', params: { userid: userid }
28
36
  end
29
37
 
30
38
  def invite_user(userid)
@@ -32,27 +40,83 @@ module Wechat
32
40
  end
33
41
 
34
42
  def user_auth_success(userid)
35
- get('user/authsucc', params: { userid: userid })
43
+ get 'user/authsucc', params: { userid: userid }
36
44
  end
37
45
 
38
46
  def user_delete(userid)
39
- get('user/delete', params: { userid: userid })
47
+ get 'user/delete', params: { userid: userid }
48
+ end
49
+
50
+ def batch_job_result(jobid)
51
+ get 'batch/getresult', params: { jobid: jobid }
52
+ end
53
+
54
+ def batch_replaceparty(media_id)
55
+ post 'batch/replaceparty', JSON.generate(media_id: media_id)
56
+ end
57
+
58
+ def batch_syncuser(media_id)
59
+ post 'batch/syncuser', JSON.generate(media_id: media_id)
60
+ end
61
+
62
+ def batch_replaceuser(media_id)
63
+ post 'batch/replaceuser', JSON.generate(media_id: media_id)
40
64
  end
41
65
 
42
66
  def department_create(name, parentid)
43
- post('department/create', JSON.generate(name: name, parentid: parentid))
67
+ post 'department/create', JSON.generate(name: name, parentid: parentid)
68
+ end
69
+
70
+ def department_delete(departmentid)
71
+ get 'department/delete', params: { id: departmentid }
44
72
  end
45
73
 
46
74
  def department(departmentid = 1)
47
- get('department/list', params: { id: departmentid })
75
+ get 'department/list', params: { id: departmentid }
76
+ end
77
+
78
+ def user_simplelist(departmentid, fetch_child = 0, status = 0)
79
+ get 'user/simplelist', params: { departmentid: departmentid, fetch_child: fetch_child, status: status }
80
+ end
81
+
82
+ def user_list(departmentid, fetch_child = 0, status = 0)
83
+ get 'user/list', params: { departmentid: departmentid, fetch_child: fetch_child, status: status }
84
+ end
85
+
86
+ def tag_create(tagname, tagid = nil)
87
+ post 'tag/create', JSON.generate(tagname: tagname, tagid: tagid)
88
+ end
89
+
90
+ def tag_update(tagid, tagname)
91
+ post 'tag/update', JSON.generate(tagid: tagid, tagname: tagname)
92
+ end
93
+
94
+ def tag_delete(tagid)
95
+ get 'tag/delete', params: { tagid: tagid }
96
+ end
97
+
98
+ def tags
99
+ get 'tag/list'
100
+ end
101
+
102
+ def tag(tagid)
103
+ get 'tag/get', params: { tagid: tagid }
104
+ end
105
+
106
+ def tag_add_user(tagid, userids = nil, departmentids = nil)
107
+ post 'tag/addtagusers', JSON.generate(tagid: tagid, userlist: userids, partylist: departmentids)
108
+ end
109
+
110
+ def tag_del_user(tagid, userids = nil, departmentids = nil)
111
+ post 'tag/deltagusers', JSON.generate(tagid: tagid, userlist: userids, partylist: departmentids)
48
112
  end
49
113
 
50
114
  def menu
51
- get('menu/get', params: { agentid: agentid })
115
+ get 'menu/get', params: { agentid: agentid }
52
116
  end
53
117
 
54
118
  def menu_delete
55
- get('menu/delete', params: { agentid: agentid })
119
+ get 'menu/delete', params: { agentid: agentid }
56
120
  end
57
121
 
58
122
  def menu_create(menu)
@@ -68,6 +132,10 @@ module Wechat
68
132
  get 'material/get_count', params: { agentid: agentid }
69
133
  end
70
134
 
135
+ def material_list(type, offset, count)
136
+ post 'material/batchget', JSON.generate(type: type, agentid: agentid, offset: offset, count: count)
137
+ end
138
+
71
139
  def media_create(type, file)
72
140
  post 'media/upload', { upload: { media: file } }, params: { type: type }
73
141
  end
@@ -25,30 +25,33 @@ module Wechat
25
25
  config.merge!(with: with) if with.present?
26
26
  end
27
27
 
28
- responders(message_type) << config
28
+ user_defined_responders(message_type) << config
29
29
  config
30
30
  end
31
31
 
32
- def responders(type)
32
+ def user_defined_responders(type)
33
33
  @responders ||= {}
34
34
  @responders[type] ||= []
35
35
  end
36
36
 
37
- def responder_for(message, &block)
37
+ def responder_for(message)
38
38
  message_type = message[:MsgType].to_sym
39
- responders = responders(message_type)
39
+ responders = user_defined_responders(message_type)
40
40
 
41
41
  case message_type
42
42
  when :text
43
43
  yield(* match_responders(responders, message[:Content]))
44
-
45
44
  when :event
46
45
  if 'click' == message[:Event]
47
46
  yield(* match_responders(responders, message[:EventKey]))
48
47
  elsif %w(scancode_push scancode_waitmsg).include? message[:Event]
49
- yield(* match_responders(responders, event_key: message[:EventKey],
48
+ yield(* match_responders(responders, event: 'scancode',
49
+ event_key: message[:EventKey],
50
50
  scan_type: message[:ScanCodeInfo][:ScanType],
51
51
  scan_result: message[:ScanCodeInfo][:ScanResult]))
52
+ elsif 'batch_job_result' == message[:Event]
53
+ yield(* match_responders(responders, event: 'batch_job',
54
+ batch_job: message[:BatchJob]))
52
55
  else
53
56
  yield(* match_responders(responders, message[:Event]))
54
57
  end
@@ -71,7 +74,9 @@ module Wechat
71
74
  if condition.is_a? Regexp
72
75
  memo[:scoped] ||= [responder] + $LAST_MATCH_INFO.captures if value =~ condition
73
76
  elsif value.is_a? Hash
74
- memo[:scoped] ||= [responder, value[:scan_result], value[:scan_type]] if value[:event_key] == condition
77
+ memo[:scoped] ||= [responder, value[:scan_result], value[:scan_type]] if value[:event_key] == condition && value[:event] == 'scancode'
78
+ memo[:scoped] ||= [responder, value[:batch_job]] if value[:event] == 'batch_job' &&
79
+ %w(sync_user replace_user invite_user replace_party).include?(condition.downcase)
75
80
  else
76
81
  memo[:scoped] ||= [responder, value] if value == condition
77
82
  end
@@ -133,7 +138,7 @@ module Wechat
133
138
 
134
139
  def run_responder(request)
135
140
  self.class.responder_for(request) do |responder, *args|
136
- responder ||= self.class.responders(:fallback).first
141
+ responder ||= self.class.user_defined_responders(:fallback).first
137
142
 
138
143
  next if responder.nil?
139
144
  case
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Skinnyworm
@@ -9,10 +9,10 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-09-07 00:00:00.000000000 Z
12
+ date: 2015-09-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rails
15
+ name: activesupport
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
@@ -67,6 +67,20 @@ dependencies:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '3.3'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rails
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '3.2'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '3.2'
70
84
  description: API and message handling for WeChat in Rails
71
85
  email: eric.guocz@gmail.com
72
86
  executables:
@@ -80,6 +94,9 @@ files:
80
94
  - Rakefile
81
95
  - bin/wechat
82
96
  - lib/action_controller/wechat_responder.rb
97
+ - lib/generators/wechat/install_generator.rb
98
+ - lib/generators/wechat/templates/app/controllers/wechats_controller.rb
99
+ - lib/generators/wechat/templates/config/wechat.yml
83
100
  - lib/wechat.rb
84
101
  - lib/wechat/access_token.rb
85
102
  - lib/wechat/api.rb