gfd_wechat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +321 -0
  3. data/LICENSE +21 -0
  4. data/README-CN.md +815 -0
  5. data/README.md +844 -0
  6. data/bin/wechat +520 -0
  7. data/lib/action_controller/wechat_responder.rb +72 -0
  8. data/lib/generators/wechat/config_generator.rb +36 -0
  9. data/lib/generators/wechat/install_generator.rb +20 -0
  10. data/lib/generators/wechat/menu_generator.rb +21 -0
  11. data/lib/generators/wechat/redis_store_generator.rb +16 -0
  12. data/lib/generators/wechat/session_generator.rb +36 -0
  13. data/lib/generators/wechat/templates/MENU_README +3 -0
  14. data/lib/generators/wechat/templates/app/controllers/wechats_controller.rb +12 -0
  15. data/lib/generators/wechat/templates/app/models/wechat_config.rb +46 -0
  16. data/lib/generators/wechat/templates/app/models/wechat_session.rb +17 -0
  17. data/lib/generators/wechat/templates/config/initializers/wechat_redis_store.rb +42 -0
  18. data/lib/generators/wechat/templates/config/wechat.yml +72 -0
  19. data/lib/generators/wechat/templates/config/wechat_menu.yml +6 -0
  20. data/lib/generators/wechat/templates/config/wechat_menu_android.yml +15 -0
  21. data/lib/generators/wechat/templates/db/config_migration.rb.erb +40 -0
  22. data/lib/generators/wechat/templates/db/session_migration.rb.erb +10 -0
  23. data/lib/wechat/api.rb +54 -0
  24. data/lib/wechat/api_base.rb +63 -0
  25. data/lib/wechat/api_loader.rb +145 -0
  26. data/lib/wechat/cipher.rb +66 -0
  27. data/lib/wechat/concern/common.rb +217 -0
  28. data/lib/wechat/controller_api.rb +96 -0
  29. data/lib/wechat/corp_api.rb +168 -0
  30. data/lib/wechat/helpers.rb +47 -0
  31. data/lib/wechat/http_client.rb +112 -0
  32. data/lib/wechat/message.rb +265 -0
  33. data/lib/wechat/mp_api.rb +46 -0
  34. data/lib/wechat/responder.rb +308 -0
  35. data/lib/wechat/signature.rb +10 -0
  36. data/lib/wechat/ticket/corp_jsapi_ticket.rb +14 -0
  37. data/lib/wechat/ticket/jsapi_base.rb +84 -0
  38. data/lib/wechat/ticket/public_jsapi_ticket.rb +14 -0
  39. data/lib/wechat/token/access_token_base.rb +53 -0
  40. data/lib/wechat/token/corp_access_token.rb +13 -0
  41. data/lib/wechat/token/public_access_token.rb +13 -0
  42. data/lib/wechat.rb +52 -0
  43. metadata +195 -0
data/README.md ADDED
@@ -0,0 +1,844 @@
1
+ WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.org/Eric-Guo/wechat.svg)](https://travis-ci.org/Eric-Guo/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
2
+ ======
3
+
4
+ [![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Eric-Guo/wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5
+
6
+ [中文文档 Chinese document](/README-CN.md)
7
+
8
+ [Wechat](http://www.wechat.com/) is a free messaging and calling app developed by [Tencent](http://tencent.com/en-us/index.shtml), after linking billion people, Wechat had become [an application platform](https://uxdesign.cc/wechat-the-invincible-app-a-key-to-business-success-in-china-8e9a920deb26?source=wechat_gem).
9
+
10
+ WeChat gem tries to help Rails developer to integrate [enterprise account](https://qy.weixin.qq.com) / [public account](https://mp.weixin.qq.com/) easily. Features below are ready and there is no need to write adapter code for talking to wechat server directly.
11
+
12
+ - [Sending message](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF) API(Can access via console or in rails)
13
+ - [Receiving message](http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%B6%88%E6%81%AF%E4%B8%8E%E4%BA%8B%E4%BB%B6)(You must run on rails server to receiving message)
14
+ - [Wechat JS-SDK](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS%E6%8E%A5%E5%8F%A3) config signature
15
+ - OAuth 2.0 authentication
16
+ - Record session when receiving message from user (Optional)
17
+
18
+
19
+ `wechat` command shares the same API in console, so you can interactive with wechat server quickly, without starting up web environment/code.
20
+
21
+ A responder DSL can be used in Rails controller, giving an event based interface to handle messages sent by end user from wechat server.
22
+
23
+ Wechat provides OAuth2.0 authentication method `wechat_oauth2`, possibly the easiest way, for the users who prefer using devise style authorization gems, [omniauth-wechat-oauth2](https://github.com/skinnyworm/omniauth-wechat-oauth2) can be a good option.
24
+
25
+ There is official [weui](https://github.com/weui/weui), corresponding Rails gem called [weui-rails](https://github.com/Eric-Guo/weui-rails) is available, if you prefer following the same UI design as wechat.
26
+
27
+ For web page only wechat application, please use [`wechat_api`](#wechat_api---rails-controller-wechat-api), which only contains web feature compare with traditional message type [`wechat_responder`](#wechat_responder---rails-responder-controller-dsl).
28
+
29
+ There is a more complete [wechat-starter](https://github.com/goofansu/wechat-starter) demo available, which even includes the payment SDK feature.
30
+
31
+ ## Installation
32
+
33
+ Use `gem install`
34
+
35
+ ```
36
+ gem install "wechat"
37
+ ```
38
+
39
+ Or add it to your app's `Gemfile`:
40
+
41
+ ```
42
+ gem 'wechat'
43
+ ```
44
+
45
+ Run the following command to install it:
46
+
47
+ ```console
48
+ bundle install
49
+ ```
50
+
51
+ Run the generator:
52
+
53
+ ```console
54
+ rails generate wechat:install
55
+ ```
56
+
57
+ `rails g wechat:install` will generated the initial `wechat.yml` configuration file, including an sample wechat controller and corresponding routes.
58
+
59
+ Enable session record:
60
+
61
+ ```console
62
+ rails g wechat:session
63
+ rake db:migrate
64
+ ```
65
+
66
+ Enabling session will generate two files in Rails folder, you can add more columns to *wechat_session* table and add declaration to link to users table, it's also possible to store data directly in **hash_store**. if you are using PostgreSQL, using [hstore](http://guides.rubyonrails.org/active_record_postgresql.html#hstore)/json maybe better, but the best way is still to add a dedicate column to record the data, the Rails way.
67
+
68
+ Using Redis to store wechat token and ticket:
69
+
70
+ ```console
71
+ rails g wechat:redis_store
72
+ ```
73
+
74
+ Redis store supports Rails application running in multi-server, no need to enable it if your Rails application is running on one server only, the wechat command won't read the token/ticket stored in Redis.
75
+
76
+ Enable database wechat configurations:
77
+
78
+ ```console
79
+ rails g wechat:config
80
+ rake db:migrate
81
+ ```
82
+
83
+ After running the migration, a `wechat_configs` table will be created that allows storage of multiple wechat accounts.
84
+
85
+ ## Configuration
86
+
87
+ #### Configure wechat for the first time
88
+
89
+ Make sure to finish all the setup on rails side first, then submit those setting to Tencent wechat management website. Otherwise, wechat will raise error.
90
+
91
+ URL address for wechat created by running `rails g wechat:install` is `http://your-server.com/wechat`
92
+
93
+ How to setup appid/corpid and secret see below section.
94
+
95
+ #### Configure for command line
96
+
97
+ To use `wechat` command solely, you need to create configuration file `~/.wechat.yml` and include content below for public account. The access_token will be written to a file.
98
+
99
+ ```
100
+ appid: "my_appid"
101
+ secret: "my_secret"
102
+ access_token: "/var/tmp/wechat_access_token"
103
+ ```
104
+
105
+ For enterprise account, you need to use `corpid` instead of `appid` as enterprise account supports multiply application (Tencent calls them agents) in one enterprise account. Obtaining the `corpsecret` is a little bit tricky, must be created at management mode->privilege setting and create any of management group to obtain. Due to Tencent currently only providing Chinese interface for their management console, it's highly recommended you find a colleague knowing Mandarin to help you to obtain the `corpsecret`.
106
+
107
+ Windows users need to store `.wechat.yml` at `C:/Users/[user_name]/` (replace with your user name), also pay attention to the direction of folder separator.
108
+
109
+ ```
110
+ corpid: "my_appid"
111
+ corpsecret: "my_secret"
112
+ agentid: 1 # Integer, which can be obtained from application settings
113
+ access_token: "C:/Users/[user_name]/wechat_access_token"
114
+ ```
115
+
116
+ #### Configure for Rails
117
+
118
+ Rails configuration file supports different environment similar to database.yml, after running `rails generate wechat:install` you can find configuration file at `config/wechat.yml`
119
+
120
+ Public account configuration example:
121
+
122
+ ```
123
+ default: &default
124
+ appid: "app_id"
125
+ secret: "app_secret"
126
+ token: "app_token"
127
+ access_token: "/var/tmp/wechat_access_token"
128
+ jsapi_ticket: "/var/tmp/wechat_jsapi_ticket"
129
+
130
+ production:
131
+ appid: <%= ENV['WECHAT_APPID'] %>
132
+ secret: <%= ENV['WECHAT_APP_SECRET'] %>
133
+ token: <%= ENV['WECHAT_TOKEN'] %>
134
+ access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
135
+ jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
136
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %> # seconds
137
+
138
+ development:
139
+ <<: *default
140
+ trusted_domain_fullname: "http://your_dev.proxy.qqbrowser.cc"
141
+
142
+ test:
143
+ <<: *default
144
+ ```
145
+
146
+ Although it's optional for public account, but highly recommended to enable encrypt mode by adding these two items to `wechat.yml`
147
+
148
+
149
+ ```
150
+ default: &default
151
+ encrypt_mode: true
152
+ encoding_aes_key: "my_encoding_aes_key"
153
+ ```
154
+
155
+ Enterprise account must use encrypt mode (`encrypt_mode: true` is on by default, no need to configure).
156
+
157
+ The `token` and `encoding_aes_key` can be obtained from management console -> one of the agent application -> Mode selection, select callback mode and get/set.
158
+
159
+ ```
160
+ default: &default
161
+ corpid: "corpid"
162
+ corpsecret: "corpsecret"
163
+ agentid: 1
164
+ access_token: "C:/Users/[user_name]/wechat_access_token"
165
+ token: ""
166
+ encoding_aes_key: ""
167
+ jsapi_ticket: "C:/Users/[user_name]/wechat_jsapi_ticket"
168
+
169
+ production:
170
+ corpid: <%= ENV['WECHAT_CORPID'] %>
171
+ corpsecret: <%= ENV['WECHAT_CORPSECRET'] %>
172
+ agentid: <%= ENV['WECHAT_AGENTID'] %>
173
+ access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
174
+ token: <%= ENV['WECHAT_TOKEN'] %>
175
+ timeout: 30,
176
+ skip_verify_ssl: true # not recommend
177
+ encoding_aes_key: <%= ENV['WECHAT_ENCODING_AES_KEY'] %>
178
+ jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
179
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
180
+
181
+ development:
182
+ <<: *default
183
+ trusted_domain_fullname: "http://your_dev.proxy.qqbrowser.cc"
184
+
185
+ test:
186
+ <<: *default
187
+
188
+ # Multiple Accounts
189
+ #
190
+ # wx2_development:
191
+ # <<: *default
192
+ # appid: "my_appid"
193
+ # secret: "my_secret"
194
+ # access_token: "tmp/wechat_access_token2"
195
+ # jsapi_ticket: "tmp/wechat_jsapi_ticket2"
196
+ #
197
+ # wx2_test:
198
+ # <<: *default
199
+ # appid: "my_appid"
200
+ # secret: "my_secret"
201
+ #
202
+ # wx2_production:
203
+ # <<: *default
204
+ # appid: "my_appid"
205
+ # secret: "my_secret"
206
+ ```
207
+
208
+ For multiple accounts details reference [PR 150](https://github.com/Eric-Guo/wechat/pull/150)
209
+
210
+ For wechat mini program, can specified by the item `type`:
211
+
212
+ ```yaml
213
+ # Mini Program Accounts
214
+
215
+ mini_development:
216
+ <<: *default
217
+ appid: "my_appid"
218
+ secret: "my_secret"
219
+ # `mp` is short for **mini program**
220
+ type: 'mp'
221
+ ```
222
+
223
+ #### Database wechat account configuration
224
+ After enabling database account configuration, the following table will be created:
225
+
226
+ Attribute | Type | Annotation
227
+ ---- | ---- | ----
228
+ environment | string | Required. Environment of account configuration. Typical values are: `production`, `development` and `test`. For example, a `production` config will only be available in `production`. Default to `development`.
229
+ account | string | Required. Custom wechat account name. Account names must be unique within each environment.
230
+ enabled | boolean | Required. Whether this configuration is activated. Default to `true`.
231
+ appid | string | Public account id. Either this attribute or `corpid` must be specified.
232
+ secret | string | Public account configuration. Required when `appid` exists.
233
+ corpid | string | Corp account id. Either this attribute or `appid` must be specified.
234
+ corpsecret | string | Corp account configuration. Required when `corpid` exists.
235
+ agentid | integer | Corp account configuration. Required when `corpid` exists.
236
+ encrypt_mode | boolean |
237
+ encoding_aes_key | string | Required when `encrypt_mode` is `true`.
238
+ token | string | Required.
239
+ access_token | string | Required. Path to `access token` storage file.
240
+ jsapi_ticket | string | Required. Path to `jsapi ticket` storage file.
241
+ skip_verify_ssl | boolean
242
+ timeout | integer | Default to 20.
243
+ trusted_domain_fullname | string |
244
+
245
+ After updating database account configurations, you need to restart the server, or call `Wechat.reload_config!` to reload the updates.
246
+
247
+ ##### Configure priority
248
+
249
+ Running `wechat` command in the root folder of Rails application will be using the Rails configuration first (`default` section), if can not find it, will relay on `~\.wechat.yml`, such behavior enables managing more wechat public account and enterprise account without changing your home `~\.wechat.yml` file.
250
+
251
+ When database account configuration is enabled, database configurations will be loaded after `yml` configuration file or environment parameters. When configurations with the same account name exist in both database and `yml` file or environment parameter, the one in the database will take precedence.
252
+
253
+ ##### Wechat server timeout setting
254
+
255
+ Stability varies for Tencent wechat server, so setting a long timeout may be needed, default is 20 seconds if not set.
256
+
257
+ ##### Skip the SSL verification
258
+
259
+ SSL Certification can also be corrupted for some reason in China, [it's reported](http://qydev.weixin.qq.com/qa/index.php?qa=11037) and if it happens to you, you can set `skip_verify_ssl: true`. (not recommend)
260
+
261
+ #### Configure individual responder with different appid
262
+
263
+ Sometimes, you may want to host more than one enterprise/public wechat account in one Rails application, so you can provide this configuration info when calling `wechat_responder` or `wechat_api`
264
+
265
+ ```ruby
266
+ class WechatFirstController < ActionController::Base
267
+ wechat_responder account: :new_account, account_from_request: Proc.new{ |request| request.params[:wechat] }
268
+
269
+ on :text, with:"help", respond: "help content"
270
+ end
271
+ ```
272
+
273
+ Or you can provide full list of options.
274
+
275
+ ```ruby
276
+ class WechatFirstController < ActionController::Base
277
+ wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1"),
278
+ account_from_request: Proc.new{ |request| request.params[:wechat] }
279
+
280
+ on :text, with:"help", respond: "help content"
281
+ end
282
+ ```
283
+
284
+ `account_from_request` is a `Proc` that takes in `request` as its parameter, and returns the corresponding wechat account name. In the above examples, `controller` will choose the account based on the `wechat` parameter passed in the `request`. If `account_from_request` is not specified, or this `Proc` evaluates to `nil`, configuration specified by `account` or the full list of options will be used.
285
+
286
+ #### JS-SDK helper
287
+
288
+ JS-SDK gives you control over Wechat App behavior in html, by injecting a config signature, helper `wechat_config_js` does that in a simple way:
289
+
290
+ To make wechat_config_js work, you need to put [`wechat_api`](#wechat_api---rails-controller-wechat-api) or [`wechat_responder`](#wechat_responder---rails-responder-controller-dsl) at controller first.
291
+
292
+ ```erb
293
+ <body>
294
+ <%= wechat_config_js debug: false, api: %w(hideMenuItems closeWindow) -%>
295
+ <script type="application/javascript">
296
+ wx.ready(function() {
297
+ wx.hideOptionMenu();
298
+ });
299
+ </script>
300
+ <a href="javascript:wx.closeWindow();">Close</a>
301
+ </body>
302
+ ```
303
+
304
+ Configure the `trusted_domain_fullname` if you are in development mode and app is running behind a reverse proxy server, otherwise wechat gem won't be able to get the correct url to be signed later.
305
+
306
+ #### OAuth2.0 authentication
307
+
308
+ For public account, code below will get following user's info.
309
+
310
+ ```ruby
311
+ class CartController < ActionController::Base
312
+ wechat_api
313
+ def index
314
+ wechat_oauth2 do |openid|
315
+ @current_user = User.find_by(wechat_openid: openid)
316
+ @articles = @current_user.articles
317
+ end
318
+
319
+ # specify account_name to use arbitrary wechat account configuration
320
+ # wechat_oauth2('snsapi_base', nil, account_name) do |openid|
321
+ # ...
322
+ # end
323
+ end
324
+ end
325
+ ```
326
+
327
+ For enterprise account, code below will get enterprise member's userinfo.
328
+
329
+ ```ruby
330
+ class WechatsController < ActionController::Base
331
+ layout 'wechat'
332
+ wechat_responder
333
+ def apply_new
334
+ wechat_oauth2 do |userid|
335
+ @current_user = User.find_by(wechat_userid: userid)
336
+ @apply = Apply.new
337
+ @apply.user_id = @current_user.id
338
+ end
339
+ end
340
+ end
341
+ ```
342
+
343
+ `wechat_oauth2` already implements the necessary OAuth2.0 and cookie logic. userid defined as the enterprise member UserID. openid defined as the user who following the public account, also notice openid will be different for the same user for different following public accounts.
344
+
345
+ Notice:
346
+ * If you use `wechat_responder` in your controller, you cannot use `create` and `show` action in your controller, otherwise it will throw errors.
347
+ * If you get *redirect_uri parameter error* message, make sure you set the correct callback url value in wechat management console with path *Development center / Webpage service / Webpage authorization for retrieving user basic information*.
348
+
349
+
350
+ ## The API privilege
351
+
352
+ wechat gems won't handle any privilege exceptions. (except token timeout, but it's not important to you as it's auto retry/recovery in gems internally), but Tencent will control a lot of privilege based on your public account type and certification, for more info please reference [official document](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433401084).
353
+
354
+ ## Command line mode
355
+
356
+ The available API is different between public account and enterprise account, so wechat gems provide different set of command.
357
+
358
+ Feel safe if you can not read Chinese in the comments, it's kept there in order to copy & find in the official documentation easier.
359
+
360
+ #### Public account command line
361
+
362
+ ```
363
+ $ wechat
364
+ Wechat Public Account commands:
365
+ wechat callbackip # 获取微信服务器IP地址
366
+ wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
367
+ wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
368
+ wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
369
+ wechat custom_text [OPENID, TEXT_MESSAGE] # 发送文字客服消息
370
+ wechat custom_video [OPENID, VIDEO_PATH] # 发送视频客服消息
371
+ wechat custom_voice [OPENID, VOICE_PATH] # 发送语音客服消息
372
+ wechat customservice_getonlinekflist # 获取在线客服接待信息
373
+ wechat group_create [GROUP_NAME] # 创建分组
374
+ wechat group_delete [GROUP_ID] # 删除分组
375
+ wechat group_update [GROUP_ID, NEW_GROUP_NAME] # 修改分组名
376
+ wechat groups # 查询所有分组
377
+ wechat material [MEDIA_ID, PATH] # 永久媒体下载
378
+ wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
379
+ wechat material_count # 获取永久素材总数
380
+ wechat material_delete [MEDIA_ID] # 删除永久素材
381
+ wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
382
+ wechat media [MEDIA_ID, PATH] # 媒体下载
383
+ wechat media_hq [MEDIA_ID, PATH] # 高清音频下载
384
+ wechat media_create [MEDIA_TYPE, PATH] # 媒体上传
385
+ wechat media_uploadimg [IMAGE_PATH] # 上传图文消息内的图片
386
+ wechat media_uploadnews [MPNEWS_YAML_PATH] # 上传图文消息素材
387
+ wechat menu # 当前菜单
388
+ wechat menu_addconditional [CONDITIONAL_MENU_YAML_PATH] # 创建个性化菜单
389
+ wechat menu_create [MENU_YAML_PATH] # 创建菜单
390
+ wechat menu_delconditional [MENU_ID] # 删除个性化菜单
391
+ wechat menu_delete # 删除菜单
392
+ wechat menu_trymatch [USER_ID] # 测试个性化菜单匹配结果
393
+ wechat message_mass_delete [MSG_ID] # 删除群发消息
394
+ wechat message_mass_get [MSG_ID] # 查询群发消息发送状态
395
+ wechat message_mass_preview [WX_NAME, MPNEWS_MEDIA_ID] # 预览图文消息素材
396
+ wechat qrcode_create_limit_scene [SCENE_ID_OR_STR] # 请求永久二维码
397
+ wechat qrcode_create_scene [SCENE_ID_OR_STR, EXPIRE_SECONDS] # 请求临时二维码
398
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
399
+ wechat short_url [LONG_URL] # 长链接转短链接
400
+ wechat tag [TAGID] # 获取标签下粉丝列表
401
+ wechat tag_add_user [TAG_ID, OPEN_IDS] # 批量为用户打标签
402
+ wechat tag_create [TAGNAME, TAG_ID] # 创建标签
403
+ wechat tag_del_user [TAG_ID, OPEN_IDS] # 批量为用户取消标签
404
+ wechat tag_delete [TAG_ID] # 删除标签
405
+ wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
406
+ wechat tags # 获取所有标签
407
+ wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
408
+ wechat user [OPEN_ID] # 获取用户基本信息
409
+ wechat user_batchget [OPEN_ID_LIST] # 批量获取用户基本信息
410
+ wechat user_change_group [OPEN_ID, TO_GROUP_ID] # 移动用户分组
411
+ wechat user_group [OPEN_ID] # 查询用户所在分组
412
+ wechat user_update_remark [OPEN_ID, REMARK] # 设置备注名
413
+ wechat users # 关注者列表
414
+ wechat wxacode_download [WXA_CODE_PIC_PATH, PATH, WIDTH] # 下载小程序码
415
+ ```
416
+
417
+ #### Enterprise account command line
418
+ ```
419
+ $ wechat
420
+ Wechat Enterprise Account commands:
421
+ wechat agent [AGENT_ID] # 获取企业号应用详情
422
+ wechat agent_list # 获取应用概况列表
423
+ wechat batch_job_result [JOB_ID] # 获取异步任务结果
424
+ wechat batch_replaceparty [BATCH_PARTY_CSV_MEDIA_ID] # 全量覆盖部门
425
+ wechat batch_replaceuser [BATCH_USER_CSV_MEDIA_ID] # 全量覆盖成员
426
+ wechat batch_syncuser [SYNC_USER_CSV_MEDIA_ID] # 增量更新成员
427
+ wechat callbackip # 获取微信服务器IP地址
428
+ wechat convert_to_openid [USER_ID] # userid转换成openid
429
+ wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
430
+ wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
431
+ wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
432
+ wechat custom_text [OPENID, TEXT_MESSAGE] # 发送文字客服消息
433
+ wechat custom_video [OPENID, VIDEO_PATH] # 发送视频客服消息
434
+ wechat custom_voice [OPENID, VOICE_PATH] # 发送语音客服消息
435
+ wechat department [DEPARTMENT_ID] # 获取部门列表
436
+ wechat department_create [NAME, PARENT_ID] # 创建部门
437
+ wechat department_delete [DEPARTMENT_ID] # 删除部门
438
+ wechat department_update [DEPARTMENT_ID, NAME] # 更新部门
439
+ wechat invite_user [USER_ID] # 邀请成员关注
440
+ wechat material [MEDIA_ID, PATH] # 永久媒体下载
441
+ wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
442
+ wechat material_count # 获取永久素材总数
443
+ wechat material_delete [MEDIA_ID] # 删除永久素材
444
+ wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
445
+ wechat media [MEDIA_ID, PATH] # 媒体下载
446
+ wechat media_create [MEDIA_TYPE, PATH] # 媒体上传
447
+ wechat media_uploadimg [IMAGE_PATH] # 上传图文消息内的图片
448
+ wechat menu # 当前菜单
449
+ wechat menu_addconditional [CONDITIONAL_MENU_YAML_PATH] # 创建个性化菜单
450
+ wechat menu_create [MENU_YAML_PATH] # 创建菜单
451
+ wechat menu_delconditional [MENU_ID] # 删除个性化菜单
452
+ wechat menu_delete # 删除菜单
453
+ wechat menu_trymatch [USER_ID] # 测试个性化菜单匹配结果
454
+ wechat message_send [OPENID, TEXT_MESSAGE] # 发送文字消息
455
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
456
+ wechat tag [TAG_ID] # 获取标签成员
457
+ wechat tag_add_department [TAG_ID, PARTY_IDS] # 增加标签部门
458
+ wechat tag_add_user [TAG_ID, USER_IDS] # 增加标签成员
459
+ wechat tag_create [TAGNAME, TAG_ID] # 创建标签
460
+ wechat tag_del_department [TAG_ID, PARTY_IDS] # 删除标签部门
461
+ wechat tag_del_user [TAG_ID, USER_IDS] # 删除标签成员
462
+ wechat tag_delete [TAG_ID] # 删除标签
463
+ wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
464
+ wechat tags # 获取所有标签
465
+ wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
466
+ wechat upload_replaceparty [BATCH_PARTY_CSV_PATH] # 上传文件方式全量覆盖部门
467
+ wechat upload_replaceuser [BATCH_USER_CSV_PATH] # 上传文件方式全量覆盖成员
468
+ wechat user [OPEN_ID] # 获取用户基本信息
469
+ wechat user_batchdelete [USER_ID_LIST] # 批量删除成员
470
+ wechat user_create [USER_ID, NAME] # 创建成员
471
+ wechat user_delete [USER_ID] # 删除成员
472
+ wechat user_list [DEPARTMENT_ID] # 获取部门成员详情
473
+ wechat user_simplelist [DEPARTMENT_ID] # 获取部门成员
474
+ wechat user_update_remark [OPEN_ID, REMARK] # 设置备注名
475
+ ```
476
+
477
+ ### Command line usage demo (partially)
478
+
479
+ ##### Fetch all users open id
480
+
481
+ ```
482
+ $ wechat users
483
+
484
+ {"total"=>4, "count"=>4, "data"=>{"openid"=>["oCfEht9***********", "oCfEhtwqa***********", "oCfEht9oMCqGo***********", "oCfEht_81H5o2***********"]}, "next_openid"=>"oCfEht_81H5o2***********"}
485
+
486
+ ```
487
+
488
+ ##### Fetch user info
489
+
490
+ ```
491
+ $ wechat user "oCfEht9***********"
492
+
493
+ {"subscribe"=>1, "openid"=>"oCfEht9***********", "nickname"=>"Nickname", "sex"=>1, "language"=>"zh_CN", "city"=>"徐汇", "province"=>"上海", "country"=>"中国", "headimgurl"=>"http://wx.qlogo.cn/mmopen/ajNVdqHZLLBd0SG8NjV3UpXZuiaGGPDcaKHebTKiaTyof*********/0", "subscribe_time"=>1395715239}
494
+
495
+ ```
496
+
497
+ ##### Fetch menu
498
+ ```
499
+ $ wechat menu
500
+
501
+ {"menu"=>{"button"=>[{"type"=>"view", "name"=>"保护的", "url"=>"http://***/protected", "sub_button"=>[]}, {"type"=>"view", "name"=>"公开的", "url"=>"http://***", "sub_button"=>[]}]}}
502
+
503
+ ```
504
+
505
+ ##### Menu create
506
+
507
+ Running command `rails g wechat:menu` to generate a menu definition yaml file:
508
+
509
+ ```
510
+ button:
511
+ -
512
+ name: "Want"
513
+ sub_button:
514
+ -
515
+ type: "scancode_waitmsg"
516
+ name: "绑定用餐二维码"
517
+ key: "BINDING_QR_CODE"
518
+ -
519
+ type: "click"
520
+ name: "预订午餐"
521
+ key: "BOOK_LUNCH"
522
+ -
523
+ type: "miniprogram"
524
+ name: "小程序示例"
525
+ url: "http://ericguo.com/"
526
+ appid: "wx1234567890"
527
+ pagepath: "pages/index"
528
+ -
529
+ name: "Query"
530
+ sub_button:
531
+ -
532
+ type: "click"
533
+ name: "进出记录"
534
+ key: "BADGE_IN_OUT"
535
+ -
536
+ type: "click"
537
+ name: "年假余额"
538
+ key: "ANNUAL_LEAVE"
539
+ -
540
+ type: "view"
541
+ name: "About"
542
+ url: "http://blog.cloud-mes.com/"
543
+ ```
544
+
545
+ Running command below to upload the menu:
546
+
547
+ ```
548
+ $ wechat menu_create menu.yaml
549
+ ```
550
+
551
+ Caution: make sure you have management privilege for this application, otherwise you will get [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) error.
552
+
553
+ ##### Send custom news
554
+
555
+
556
+ Sending custom_news should also be defined as a yaml file, like `articles.yml`
557
+
558
+ ```
559
+ articles:
560
+ -
561
+ title: "习近平在布鲁日欧洲学院演讲"
562
+ description: "新华网比利时布鲁日4月1日电 国家主席习近平1日在比利时布鲁日欧洲学院发表重要演讲"
563
+ url: "http://news.sina.com.cn/c/2014-04-01/232629843387.shtml"
564
+ pic_url: "http://i3.sinaimg.cn/dy/c/2014-04-01/1396366518_bYays1.jpg"
565
+ ```
566
+
567
+ After that, you can run this command:
568
+
569
+ ```
570
+ $ wechat custom_news oCfEht9oM*********** articles.yml
571
+
572
+ ```
573
+
574
+ ##### Send template message
575
+
576
+ Sending template message via yaml file is similar, too, define `template.yml` and content is just the template content.
577
+
578
+ ```
579
+ template:
580
+ template_id: "o64KQ62_xxxxxxxxxxxxxxx-Qz-MlNcRKteq8"
581
+ url: "http://weixin.qq.com/download"
582
+ topcolor: "#FF0000"
583
+ data:
584
+ first:
585
+ value: "Hello, you successfully registered"
586
+ color: "#0A0A0A"
587
+ keynote1:
588
+ value: "5km Health Running"
589
+ color: "#CCCCCC"
590
+ keynote2:
591
+ value: "2014-09-16"
592
+ color: "#CCCCCC"
593
+ keynote3:
594
+ value: "Centry Park, Pudong, Shanghai"
595
+ color: "#CCCCCC"
596
+ remark:
597
+ value: "Welcome back"
598
+ color: "#173177"
599
+
600
+ ```
601
+
602
+ After that, you can run this command:
603
+
604
+ ```
605
+ $ wechat template_message oCfEht9oM*********** template.yml
606
+ ```
607
+
608
+ In code:
609
+
610
+ ```ruby
611
+ template = YAML.load(File.read(template_yaml_path))
612
+ Wechat.api.template_message_send Wechat::Message.to(openid).template(template["template"])
613
+ ```
614
+
615
+ If using wechat_api or wechat_responder in controller, can also use wechat as shortcut (supports multi account):
616
+
617
+ ```ruby
618
+ template = YAML.load(File.read(template_yaml_path))
619
+ wechat.template_message_send Wechat::Message.to(openid).template(template["template"])
620
+ ```
621
+
622
+ ## wechat_api - Rails Controller Wechat API
623
+
624
+ Although user can always access all wechat features via Wechat.api, but it's highly recommended to use `wechat` directly in the controller. It's not only mandatory required if you plan to support multi-account, it also helps to separate the wechat specific logic from the model layer.
625
+
626
+ ```ruby
627
+ class WechatReportsController < ApplicationController
628
+ wechat_api
629
+ layout 'wechat'
630
+
631
+ def index
632
+ @lots = Lot.with_preloading.wip_lot
633
+ end
634
+ end
635
+ ```
636
+
637
+ ## Using wechat api at ActiveJob/Rake tasks
638
+
639
+ Using `Wechat.api` to access the wechat api function at any place.
640
+
641
+ ## Checking the signature
642
+ Using `Wechat.decrypt(encrypted_data,session_key, iv)` to decode the data. via. [Signature Checking](https://developers.weixin.qq.com/miniprogram/dev/api/signature.html)
643
+
644
+ ## wechat_responder - Rails Responder Controller DSL
645
+
646
+ In order to respond to the message user sent, Rails developer needs to create a wechat responder controller and define the routing in `routes.rb`
647
+
648
+ ```ruby
649
+ resource :wechat, only: [:show, :create]
650
+ ```
651
+
652
+ So the ActionController should be defined like below:
653
+
654
+ ```ruby
655
+ class WechatsController < ActionController::Base
656
+ wechat_responder
657
+
658
+ # default text responder when no other match
659
+ on :text do |request, content|
660
+ request.reply.text "echo: #{content}" # Just echo
661
+ end
662
+
663
+ # When receive 'help', will trigger this responder
664
+ on :text, with: 'help' do |request|
665
+ request.reply.text 'help content'
666
+ end
667
+
668
+ # When receive '<n>news', will match and will get count as <n> as parameter
669
+ on :text, with: /^(\d+) news$/ do |request, count|
670
+ # Wechat article can only contain max 8 items, large than 8 will be dropped.
671
+ news = (1..count.to_i).each_with_object([]) { |n, memo| memo << { title: 'News title', content: "No. #{n} news content" } }
672
+ request.reply.news(news) do |article, n, index| # article is return object
673
+ article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: 'http://www.baidu.com/img/bdlogo.gif', url: 'http://www.baidu.com/'
674
+ end
675
+ end
676
+
677
+ on :event, with: 'subscribe' do |request|
678
+ request.reply.text "#{request[:FromUserName]} subscribe now"
679
+ end
680
+
681
+ # When unsubscribe user scan qrcode qrscene_xxxxxx to subscribe in public account
682
+ # notice user will subscribe public account at the same time, so wechat won't trigger subscribe event anymore
683
+ on :scan, with: 'qrscene_xxxxxx' do |request, ticket|
684
+ request.reply.text "Unsubscribe user #{request[:FromUserName]} Ticket #{ticket}"
685
+ end
686
+
687
+ # When subscribe user scan scene_id in public account
688
+ on :scan, with: 'scene_id' do |request, ticket|
689
+ request.reply.text "Subscribe user #{request[:FromUserName]} Ticket #{ticket}"
690
+ end
691
+
692
+ # When no any on :scan responder can match subscribe user scanned scene_id
693
+ on :event, with: 'scan' do |request|
694
+ if request[:EventKey].present?
695
+ request.reply.text "event scan got EventKey #{request[:EventKey]} Ticket #{request[:Ticket]}"
696
+ end
697
+ end
698
+
699
+ # When enterprise user press menu BINDING_QR_CODE and success to scan bar code
700
+ on :scan, with: 'BINDING_QR_CODE' do |request, scan_result, scan_type|
701
+ request.reply.text "User #{request[:FromUserName]} ScanResult #{scan_result} ScanType #{scan_type}"
702
+ end
703
+
704
+ # Except QR code, wechat can also scan CODE_39 bar code in enterprise account
705
+ on :scan, with: 'BINDING_BARCODE' do |message, scan_result|
706
+ if scan_result.start_with? 'CODE_39,'
707
+ message.reply.text "User: #{message[:FromUserName]} scan barcode, result is #{scan_result.split(',')[1]}"
708
+ end
709
+ end
710
+
711
+ # When user clicks the menu button
712
+ on :click, with: 'BOOK_LUNCH' do |request, key|
713
+ request.reply.text "User: #{request[:FromUserName]} click #{key}"
714
+ end
715
+
716
+ # When user views URL in the menu button
717
+ on :view, with: 'http://wechat.somewhere.com/view_url' do |request, view|
718
+ request.reply.text "#{request[:FromUserName]} view #{view}"
719
+ end
720
+
721
+ # When user sends an image
722
+ on :image do |request|
723
+ request.reply.image(request[:MediaId]) # Echo the sent image to user
724
+ end
725
+
726
+ # When user sends a voice
727
+ on :voice do |request|
728
+ request.reply.voice(request[:MediaId]) # Echo the sent voice to user
729
+ end
730
+
731
+ # When user sends a video
732
+ on :video do |request|
733
+ nickname = wechat.user(request[:FromUserName])['nickname'] # Call wechat api to get sender nickname
734
+ request.reply.video(request[:MediaId], title: 'Echo', description: "Got #{nickname} sent video") # Echo the sent video to user
735
+ end
736
+
737
+ # When user sends location message with label
738
+ on :label_location do |request|
739
+ request.reply.text("Label: #{request[:Label]} Location_X: #{request[:Location_X]} Location_Y: #{request[:Location_Y]} Scale: #{request[:Scale]}")
740
+ end
741
+
742
+ # When user sends location
743
+ on :location do |request|
744
+ request.reply.text("Latitude: #{request[:Latitude]} Longitude: #{request[:Longitude]} Precision: #{request[:Precision]}")
745
+ end
746
+
747
+ on :event, with: 'unsubscribe' do |request|
748
+ request.reply.success # user can not receive this message
749
+ end
750
+
751
+ # When user enters the app / agent app
752
+ on :event, with: 'enter_agent' do |request|
753
+ request.reply.text "#{request[:FromUserName]} enter agent app now"
754
+ end
755
+
756
+ # When batch job "create/update user (incremental)" is finished.
757
+ on :batch_job, with: 'sync_user' do |request, batch_job|
758
+ request.reply.text "sync_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
759
+ end
760
+
761
+ # When batch job "replace user (full sync)" is finished.
762
+ on :batch_job, with: 'replace_user' do |request, batch_job|
763
+ request.reply.text "replace_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
764
+ end
765
+
766
+ # When batch job "invite user" is finished.
767
+ on :batch_job, with: 'invite_user' do |request, batch_job|
768
+ request.reply.text "invite_user job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
769
+ end
770
+
771
+ # When batch job "replace department (full sync)" is finished.
772
+ on :batch_job, with: 'replace_party' do |request, batch_job|
773
+ request.reply.text "replace_party job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
774
+ end
775
+
776
+ # mass sent job finish result notification
777
+ on :event, with: 'masssendjobfinish' do |request|
778
+ # https://mp.weixin.qq.com/wiki?action=doc&id=mp1481187827_i0l21&t=0.03571905015619936#8
779
+ request.reply.success # request is XML result hash.
780
+ end
781
+
782
+ # If no match above will fallback to below
783
+ on :fallback, respond: 'fallback message'
784
+ end
785
+ ```
786
+
787
+ So the important statement is only `wechat_responder`, all other is just a DSL:
788
+
789
+ ```
790
+ on <message_type> do |message|
791
+ message.reply.text "some text"
792
+ end
793
+ ```
794
+
795
+ The block code will be running to respond to user's message.
796
+
797
+
798
+ Below are currently supported message_types:
799
+
800
+ - :text text message, using `:with` to match text content like `on(:text, with:'help'){|message, content| ...}`
801
+ - :image image message
802
+ - :voice voice message
803
+ - :shortvideo shortvideo message
804
+ - :video video message
805
+ - :label_location location message with label
806
+ - :link link message
807
+ - :event event message, using `:with` to match particular event, supports regular expression match similar to text message.
808
+ - :click virtual event message, wechat still sends event message,but gems will map to menu click event.
809
+ - :view virtual view message, wechat still sends event message,but gems will map to menu view page event.
810
+ - :scan virtual scan message, wechat still sends event message, but gems will map to scan event.
811
+ - :batch_job virtual batch job message
812
+ - :location virtual location message
813
+ - :fallback default message, when no other responder can handle incoming message, will be used as a fallback handler
814
+
815
+ ### Transfer to customer service
816
+
817
+ ```ruby
818
+ class WechatsController < ActionController::Base
819
+ # When no other responder can handle incoming message, will transfer to human customer service.
820
+ on :fallback do |message|
821
+ message.reply.transfer_customer_service
822
+ end
823
+ end
824
+ ```
825
+
826
+ Caution: do not set default text responder if you want to use [multiply human customer service](http://dkf.qq.com/), other will lead text message can not transfer.
827
+
828
+ ### Notifications
829
+
830
+ * `wechat.responder.after_create` data includes request <Wechat::Message> and response <Wechat::Message>.
831
+
832
+ Example:
833
+
834
+ ```ruby
835
+ ActiveSupport::Notifications.subscribe('wechat.responder.after_create') do |name, started, finished, unique_id, data|
836
+ WechatLog.create request: data[:request], response: data[:response]
837
+ end
838
+ ```
839
+
840
+ ## Known Issues
841
+
842
+ * Sometimes, enterprise account can not receive the menu message due to Tencent server unable to resolve DNS, so using IP as a callback URL is more stable, but it never happens for user sent text messages.
843
+ * Enterprise batch "replace users" uses a CSV format file, but if you are using the downloaded template directly, it's [not working](http://qydev.weixin.qq.com/qa/index.php?qa=13978), must open the CSV file in Excel first, then save as CSV format again, seems Tencent only supports Excel "Save as CSV" file format.
844
+ * If you using unicorn behind nginx and https, you need to set `trusted_domain_fullname` and point it to https, otherwise it will be http and will lead to invalid signature in the JS-SDK.