|
| 1 | +wechat [](http://badge.fury.io/js/wechat) [](https://travis-ci.org/node-webot/wechat) [](https://david-dm.org/node-webot/wechat) [](https://coveralls.io/r/node-webot/wechat) |
| 2 | +====== |
| 3 | + |
| 4 | +Wechat is a middleware and SDK of Wechat Official Account Admin Platform (mp.weixin.qq.com). |
| 5 | + |
| 6 | +## Features |
| 7 | + |
| 8 | +- Auto reply (text, image, videos, music, thumbnails posts are supported) |
| 9 | +- CRM message (text, image, videos, music, thumbnails posts are supported) |
| 10 | +- Menu settings (CRD are supported) |
| 11 | +- QR codes (CR are supported, both temporary and permanent) |
| 12 | +- Group settings (CRUD are supported) |
| 13 | +- Followers infomation (fetching user's info or followers list) |
| 14 | +- Media (upload or download) |
| 15 | +- Reply Waiter (good for surveys) |
| 16 | +- Sessions |
| 17 | +- OAuth API |
| 18 | + |
| 19 | +API details located [here](http://node-webot.github.io/wechat/api.html) |
| 20 | + |
| 21 | +## Installation |
| 22 | + |
| 23 | +``` |
| 24 | +npm install wechat |
| 25 | +``` |
| 26 | + |
| 27 | +## Use with Connect/Express |
| 28 | + |
| 29 | +``` |
| 30 | +var wechat = require('wechat'); |
| 31 | +
|
| 32 | +app.use(connect.query()); // Or app.use(express.query()); |
| 33 | +app.use('/wechat', wechat('some token', function (req, res, next) { |
| 34 | + // message is located in req.weixin |
| 35 | + var message = req.weixin; |
| 36 | + if (message.FromUserName === 'diaosi') { |
| 37 | + // reply with text |
| 38 | + res.reply('hehe'); |
| 39 | + } else if (message.FromUserName === 'text') { |
| 40 | + // another way to reply with text |
| 41 | + res.reply({ |
| 42 | + content: 'text object', |
| 43 | + type: 'text' |
| 44 | + }); |
| 45 | + } else if (message.FromUserName === 'hehe') { |
| 46 | + // reply with music |
| 47 | + res.reply({ |
| 48 | + type: "music", |
| 49 | + content: { |
| 50 | + title: "Just some music", |
| 51 | + description: "I have nothing to lose", |
| 52 | + musicUrl: "http://mp3.com/xx.mp3", |
| 53 | + hqMusicUrl: "http://mp3.com/xx.mp3" |
| 54 | + } |
| 55 | + }); |
| 56 | + } else { |
| 57 | + // reply with thumbnails posts |
| 58 | + res.reply([ |
| 59 | + { |
| 60 | + title: 'Come to fetch me', |
| 61 | + description: 'or you want to play in another way ?', |
| 62 | + picurl: 'http://nodeapi.cloudfoundry.com/qrcode.jpg', |
| 63 | + url: 'http://nodeapi.cloudfoundry.com/' |
| 64 | + } |
| 65 | + ]); |
| 66 | + } |
| 67 | +})); |
| 68 | +``` |
| 69 | + |
| 70 | +*Tips*: you'll have to apply `token` at [Wechat platform (this page is in Chinese)](http://mp.weixin.qq.com/cgi-bin/callbackprofile?type=info&t=wxm-developer-ahead&lang=zh_CN) |
| 71 | + |
| 72 | +### Reply Messages |
| 73 | + |
| 74 | +auto reply a message when your followers send a message to you. also text, image, videos, music, thumbnails posts are supported. details API goes [here (official documents)](http://mp.weixin.qq.com/wiki/index.php?title=发送被动响应消息) |
| 75 | + |
| 76 | +#### Reply with text |
| 77 | +``` |
| 78 | +res.reply('Hello world!'); |
| 79 | +// or |
| 80 | +res.reply({type: "text", content: 'Hello world!'}); |
| 81 | +``` |
| 82 | +#### Reply with Image |
| 83 | +``` |
| 84 | +res.reply({ |
| 85 | + type: "image", |
| 86 | + content: { |
| 87 | + mediaId: 'mediaId' |
| 88 | + } |
| 89 | +}); |
| 90 | +``` |
| 91 | +#### Reply with voice |
| 92 | +``` |
| 93 | +res.reply({ |
| 94 | + type: "voice", |
| 95 | + content: { |
| 96 | + mediaId: 'mediaId' |
| 97 | + } |
| 98 | +}); |
| 99 | +``` |
| 100 | +#### Reply with Video |
| 101 | +``` |
| 102 | +res.reply({ |
| 103 | + type: "video", |
| 104 | + content: { |
| 105 | + mediaId: 'mediaId', |
| 106 | + thumbMediaId: 'thumbMediaId' |
| 107 | + } |
| 108 | +}); |
| 109 | +``` |
| 110 | +#### Reply with Music |
| 111 | +``` |
| 112 | +res.reply({ |
| 113 | + title: "Just some music", |
| 114 | + description: "I have nothing to lose", |
| 115 | + musicUrl: "http://mp3.com/xx.mp3", |
| 116 | + hqMusicUrl: "http://mp3.com/xx.mp3" |
| 117 | +}); |
| 118 | +``` |
| 119 | +#### Reply with Thumbnails posts |
| 120 | +``` |
| 121 | +res.reply([ |
| 122 | + { |
| 123 | + title: 'Come to fetch me', |
| 124 | + description: 'or you want to play in another way ?', |
| 125 | + picurl: 'http://nodeapi.cloudfoundry.com/qrcode.jpg', |
| 126 | + url: 'http://nodeapi.cloudfoundry.com/' |
| 127 | + } |
| 128 | +]); |
| 129 | +``` |
| 130 | + |
| 131 | +### WXSession |
| 132 | + |
| 133 | +Wechat messages are not communicate like traditional C/S model, therefore nothing Cookies will be store in Wechat client. this WXSession is designed to support access user's infomation via `req.wxsession`, with `connect.session` backed. |
| 134 | + |
| 135 | +It's a simple demo: |
| 136 | + |
| 137 | +``` |
| 138 | +app.use(connect.cookieParser()); |
| 139 | +app.use(connect.session({secret: 'keyboard cat', cookie: {maxAge: 60000}})); |
| 140 | +app.use('/wechat', wechat('some token', wechat.text(function (info, req, res, next) { |
| 141 | + if (info.Content === '=') { |
| 142 | + var exp = req.wxsession.text.join(''); |
| 143 | + req.wxsession.text = ''; |
| 144 | + res.reply(exp); |
| 145 | + } else { |
| 146 | + req.wxsession.text = req.wxsession.text || []; |
| 147 | + req.wxsession.text.push(info.Content); |
| 148 | + res.reply('Message got ' + info.Content); |
| 149 | + } |
| 150 | +}))); |
| 151 | +``` |
| 152 | + |
| 153 | +`req.wxsession` and `req.session` shares same store. width `redis` as persistence database, across processes sharing are supportd. |
| 154 | + |
| 155 | +### Reply Waiter |
| 156 | + |
| 157 | +a reply waiter is seems like a telephone menu system. it must be setup before activation. this function is supported upon WXSession. |
| 158 | + |
| 159 | +``` |
| 160 | +var List = require('wechat').List; |
| 161 | +List.add('view', [ |
| 162 | + ['reply {a}', function (info, req, res) { |
| 163 | + res.reply('Im Answer A'); |
| 164 | + }], |
| 165 | + ['reply {b}', function (info, req, res) { |
| 166 | + res.reply('Im Answer B'); |
| 167 | + }], |
| 168 | + ['reply {c}', 'Im Answer C (the shorthand method)'] |
| 169 | +]); |
| 170 | +``` |
| 171 | + |
| 172 | +active the reply waiter we setuped before: |
| 173 | + |
| 174 | +``` |
| 175 | +var app = connect(); |
| 176 | +app.use(connect.query()); |
| 177 | +app.use(connect.cookieParser()); |
| 178 | +app.use(connect.session({secret: 'keyboard cat', cookie: {maxAge: 60000}})); |
| 179 | +app.use('/wechat', wechat('some token', wechat.text(function (info, req, res, next) { |
| 180 | + if (info.Content === 'list') { |
| 181 | + res.wait('view'); // view is the very waiter we setuped before. |
| 182 | + } else { |
| 183 | + res.reply('hehe'); |
| 184 | + // or stop the waiter and quit. |
| 185 | + // res.nowait('hehe'); |
| 186 | + } |
| 187 | +}))); |
| 188 | +``` |
| 189 | + |
| 190 | +if waiter `view` actived, user will receive messages below: |
| 191 | + |
| 192 | +``` |
| 193 | +reply a |
| 194 | +reply b |
| 195 | +reply c |
| 196 | +``` |
| 197 | + |
| 198 | +reply waiter acquires both function and text as a `callback` action |
| 199 | + |
| 200 | +``` |
| 201 | +List.add('view', [ |
| 202 | + ['reply {a}', function (info, req, res, next) { |
| 203 | + // we callback as a function |
| 204 | + res.reply('Answer A'); |
| 205 | + }], |
| 206 | + // or text as shorthand |
| 207 | + ['reply {c}', 'Answer C'] |
| 208 | +]); |
| 209 | +``` |
| 210 | + |
| 211 | +if user's message is not in waiter's trigger texts. this message will be processd in the `else` way and can be stoped by `res.nowait()`, `res.nowait` method actions like `reply` method. |
| 212 | + |
| 213 | +## Show cases |
| 214 | +### Auto-reply robot based on Node.js |
| 215 | + |
| 216 | + |
| 217 | + |
| 218 | +Codes here <https://github.com/JacksonTian/api-doc-service> |
| 219 | + |
| 220 | +robots can be setup in PAASs like [CloudFoundry](http://www.cloudfoundry.com/), [appfog](https://www.appfog.com/) or [BAE](http://developer.baidu.com/wiki/index.php?title=docs/cplat/rt/node.js). |
| 221 | + |
| 222 | +## API details |
| 223 | +official document locates here [Messages API Guide (in Chinese)](http://mp.weixin.qq.com/wiki/index.php?title=消息接口指南)。 |
| 224 | + |
| 225 | +wachat 0.6.x supports shorthand methods below: |
| 226 | + |
| 227 | +``` |
| 228 | +app.use('/wechat', wechat('some token', wechat.text(function (message, req, res, next) { |
| 229 | + // reply with text |
| 230 | + // { ToUserName: 'gh_d3e07d51b513', |
| 231 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 232 | + // CreateTime: '1359125035', |
| 233 | + // MsgType: 'text', |
| 234 | + // Content: 'http', |
| 235 | + // MsgId: '5837397576500011341' } |
| 236 | +}).image(function (message, req, res, next) { |
| 237 | + // message为图片内容 |
| 238 | + // { ToUserName: 'gh_d3e07d51b513', |
| 239 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 240 | + // CreateTime: '1359124971', |
| 241 | + // MsgType: 'image', |
| 242 | + // PicUrl: 'http://mmsns.qpic.cn/mmsns/bfc815ygvIWcaaZlEXJV7NzhmA3Y2fc4eBOxLjpPI60Q1Q6ibYicwg/0', |
| 243 | + // MediaId: 'media_id', |
| 244 | + // MsgId: '5837397301622104395' } |
| 245 | +}).voice(function (message, req, res, next) { |
| 246 | + // Reply with Voice |
| 247 | + // { ToUserName: 'gh_d3e07d51b513', |
| 248 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 249 | + // CreateTime: '1359125022', |
| 250 | + // MsgType: 'voice', |
| 251 | + // MediaId: 'OMYnpghh8fRfzHL8obuboDN9rmLig4s0xdpoNT6a5BoFZWufbE6srbCKc_bxduzS', |
| 252 | + // Format: 'amr', |
| 253 | + // MsgId: '5837397520665436492' } |
| 254 | +}).video(function (message, req, res, next) { |
| 255 | + // Reply with Video |
| 256 | + // { ToUserName: 'gh_d3e07d51b513', |
| 257 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 258 | + // CreateTime: '1359125022', |
| 259 | + // MsgType: 'video', |
| 260 | + // MediaId: 'OMYnpghh8fRfzHL8obuboDN9rmLig4s0xdpoNT6a5BoFZWufbE6srbCKc_bxduzS', |
| 261 | + // ThumbMediaId: 'media_id', |
| 262 | + // MsgId: '5837397520665436492' } |
| 263 | +}).location(function (message, req, res, next) { |
| 264 | + // Reply with Location (geo) |
| 265 | + // { ToUserName: 'gh_d3e07d51b513', |
| 266 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 267 | + // CreateTime: '1359125311', |
| 268 | + // MsgType: 'location', |
| 269 | + // Location_X: '30.283950', |
| 270 | + // Location_Y: '120.063139', |
| 271 | + // Scale: '15', |
| 272 | + // Label: {}, |
| 273 | + // MsgId: '5837398761910985062' } |
| 274 | +}).link(function (message, req, res, next) { |
| 275 | + // Reply with Link |
| 276 | + // { ToUserName: 'gh_d3e07d51b513', |
| 277 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 278 | + // CreateTime: '1359125022', |
| 279 | + // MsgType: 'link', |
| 280 | + // Title: 'A link', |
| 281 | + // Description: 'A link has its desc', |
| 282 | + // Url: 'http://1024.com/', |
| 283 | + // MsgId: '5837397520665436492' } |
| 284 | +}).event(function (message, req, res, next) { |
| 285 | + // Reply with Event |
| 286 | + // { ToUserName: 'gh_d3e07d51b513', |
| 287 | + // FromUserName: 'oPKu7jgOibOA-De4u8J2RuNKpZRw', |
| 288 | + // CreateTime: '1359125022', |
| 289 | + // MsgType: 'event', |
| 290 | + // Event: 'LOCATION', |
| 291 | + // Latitude: '23.137466', |
| 292 | + // Longitude: '113.352425', |
| 293 | + // Precision: '119.385040', |
| 294 | + // MsgId: '5837397520665436492' } |
| 295 | +}))); |
| 296 | +``` |
| 297 | + |
| 298 | +*Tips*: `text`, `image`, `voice`, `video`, `location`, `link`, `event` must be set at least one. |
| 299 | + |
| 300 | +### More simple APIs |
| 301 | + |
| 302 | +Supported in 0.3.x and above. |
| 303 | + |
| 304 | +``` |
| 305 | +app.use('/wechat', wechat('some token').text(function (message, req, res, next) { |
| 306 | + // TODO |
| 307 | +}).image(function (message, req, res, next) { |
| 308 | + // TODO |
| 309 | +}).voice(function (message, req, res, next) { |
| 310 | + // TODO |
| 311 | +}).video(function (message, req, res, next) { |
| 312 | + // TODO |
| 313 | +}).location(function (message, req, res, next) { |
| 314 | + // TODO |
| 315 | +}).link(function (message, req, res, next) { |
| 316 | + // TODO |
| 317 | +}).event(function (message, req, res, next) { |
| 318 | + // TODO |
| 319 | +}).middlewarify()); |
| 320 | +``` |
| 321 | + |
| 322 | +### Functions Graph |
| 323 | + |
| 324 | + |
| 325 | +*Tips*: Business logic in blue lines. |
| 326 | + |
| 327 | +## License |
| 328 | +The MIT license. |
| 329 | + |
| 330 | +## Donation |
| 331 | +buy me a cup of coffee please. |
| 332 | + |
| 333 | +[](https://me.alipay.com/jacksontian) |
| 334 | + |
| 335 | +## Translator |
| 336 | + |
| 337 | +this wechat document is translated by: |
| 338 | + |
| 339 | +- [Guo Yu](https://github.com/turingou/) |
0 commit comments