wechat 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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