diff --git a/README.md b/README.md index 474a6bea..671b4645 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,74 @@ List.add('view', [ 如果用户触发等待回复事务后,没有按照`{}`中的进行回复,那么将会由原有的默认函数进行处理。在原有函数中,可以选择调用`res.nowait()`中断事务。`nowait()`除了能中断事务外,与`reply`的行为一致。 +### 菜单操作 +#### 获取口令 +获取访问口令,用于进一步操作菜单。 + +``` +var API = require('wechat').API; +var api = new API('appid', 'secret'); +api.getAccessToken(function (err, token) { + // token + // {"access_token":"ACCESS_TOKEN","expires_in":7200} +}); +``` + +#### 创建菜单 +获取口令之后,就能创建菜单了。 + +``` +var menu = { + "button":[ + { + "type":"click", + "name":"今日歌曲", + "key":"V1001_TODAY_MUSIC" + }, + { + "type":"click", + "name":"歌手简介", + "key":"V1001_TODAY_SINGER" + }, + { + "name":"菜单", + "sub_button":[ + { + "type":"click", + "name":"hello word", + "key":"V1001_HELLO_WORLD" + }, + { + "type":"click", + "name":"赞一下我们", + "key":"V1001_GOOD" + } + ] + }] +}; +api.createMenu(menu, function (err, data) { + // TODO +}); +``` + +#### 获取菜单 +创建菜单之后,就可以获取菜单了: + +``` +api.getMenu(function (err, menu) { + // menu +}); +``` + +#### 删除菜单 +也可以删除掉菜单: + +``` +api.removeMenu(function (err, data) { + // TODO +}); +``` + ## Show cases ### Node.js API自动回复 diff --git a/index.js b/index.js index 51dbeaa0..661a48ab 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ var wechat = require('./lib/wechat'); wechat.List = require('./lib/list'); -module.exports = wechat; +wechat.API = require('./lib/common'); +module.exports = wechat; \ No newline at end of file diff --git a/lib/common.js b/lib/common.js new file mode 100644 index 00000000..683bac1d --- /dev/null +++ b/lib/common.js @@ -0,0 +1,98 @@ +var urllib = require('urllib'); + +// http://mp.weixin.qq.com/wiki/index.php?title=%E8%BF%94%E5%9B%9E%E7%A0%81%E8%AF%B4%E6%98%8E +var wrapper = function (callback) { + return function (err, data, res) { + if (err) { + err.name = 'WeChatAPI' + err.name; + return callback(err, data, res); + } + if (data.errcode) { + err = new Error(data.errmsg); + err.name = 'WeChatAPIError'; + return callback(err, data, res); + } + callback(null, data, res); + }; +}; + +var API = function (appid, appsecret) { + this.appid = appid; + this.appsecret = appsecret; + this.prefix = 'https://api.weixin.qq.com/cgi-bin/'; +}; + +/** + * 根据appid和appsecret获取access token + * @param {String} appid appid + * @param {String} appsecret secret + * @param {Function} callback 回调函数 + */ +API.prototype.getAccessToken = function (callback) { + var that = this; + var url = this.prefix + 'token?grant_type=client_credential&appid=' + this.appid + '&secret=' + this.appsecret; + urllib.request(url, {dataType: 'json'}, wrapper(function (err, data) { + if (err) { + return callback(err); + } + that.token = data.access_token; + callback(err, data); + })); + return this; +}; + +/** + * 创建菜单 + * @param {Object} menu 菜单对象 + * @param {Function} callback 回调函数 + */ +API.prototype.createMenu = function (menu, callback) { + var url = this.prefix + 'menu/create?access_token=' + this.token; + var args = { + dataType: 'json', + type: 'POST', + data: menu, + headers: { + 'Content-Type': 'application/json' + } + }; + urllib.request(url, args, wrapper(callback)); +}; + +/** + * 获取菜单 + * @param {Function} callback 回调函数 + */ +API.prototype.getMenu = function (callback) { + var url = this.prefix + 'menu/get?access_token=' + this.token; + urllib.request(url, {dataType: 'json'}, wrapper(callback)); +}; + +/** + * 删除菜单 + * @param {Function} callback 回调函数 + */ +API.prototype.removeMenu = function (callback) { + var url = this.prefix + 'menu/delete?access_token=' + this.token; + urllib.request(url, {dataType: 'json'}, wrapper(callback)); +}; + +/** + * 获取二维码 + * @param {Object} data 二维码数据 + * @param {Function} callback 回调函数 + */ +API.prototype.getQRCode = function (data, callback) { + var url = this.prefix + 'qrcode/get?access_token=' + this.token; + var args = { + dataType: 'json', + type: 'POST', + data: data, + headers: { + 'Content-Type': 'application/json' + } + }; + urllib.request(url, args, wrapper(callback)); +}; + +module.exports = API; diff --git a/lib/menu.js b/lib/menu.js new file mode 100644 index 00000000..f099f712 --- /dev/null +++ b/lib/menu.js @@ -0,0 +1,18 @@ +var Menu = function () { + this.buttons = []; +}; + +Menu.prototype.addButton = function (button, subButtons) { + if (subButtons) { + delete button.type; + delete button.key; + button.sub_button = subButtons; + } + this.buttons.push(button); + return this; +}; + +Menu.prototype.toString = function () { + return JSON.stringify({"button": this.buttons}); +}; +exports.Menu = Menu; diff --git a/package.json b/package.json index 86d298d4..39f9fb71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wechat", - "version": "0.4.8", + "version": "0.5.0", "description": "微信公共平台自动回复接口服务", "main": "index.js", "scripts": { @@ -26,7 +26,8 @@ "dependencies": { "xml2js": "0.2.6", "ejs": ">=0.8.3", - "bufferhelper": ">=0.2.0" + "bufferhelper": ">=0.2.0", + "urllib": ">=0.3.4" }, "devDependencies": { "supertest": "*", @@ -36,7 +37,8 @@ "blanket": "*", "travis-cov": "*", "coveralls": "*", - "mocha-lcov-reporter": "*" + "mocha-lcov-reporter": "*", + "muk": "*" }, "author": "Jackson Tian", "license": "MIT", diff --git a/test/common.test.js b/test/common.test.js new file mode 100644 index 00000000..b6bb59c4 --- /dev/null +++ b/test/common.test.js @@ -0,0 +1,114 @@ +var should = require('should'); +var urllib = require('urllib'); +var muk = require('muk'); +var API = require('../').API; + +describe('common.js', function () { + describe('getAccessToken', function () { + var api = new API('appid', 'secret'); + + it('should not ok', function (done) { + api.getAccessToken(function (err, token) { + should.exist(err); + err.name.should.be.equal('WeChatAPIError'); + err.message.should.be.equal('invalid appid'); + done(); + }); + }); + + describe('mock urllib err', function () { + before(function () { + muk(urllib, 'request', function (url, args, callback) { + var err = new Error('Urllib Error'); + err.name = 'UrllibError'; + callback(err); + }); + }); + + after(function () { + muk.restore(); + }); + + it('should get mock error', function (done) { + api.getAccessToken(function (err, token) { + should.exist(err); + err.name.should.be.equal('WeChatAPIUrllibError'); + err.message.should.be.equal('Urllib Error'); + done(); + }); + }); + }); + + describe('mock token', function () { + before(function () { + muk(urllib, 'request', function (url, args, callback) { + process.nextTick(function () { + callback(null, {"access_token": "ACCESS_TOKEN","expires_in": 7200}); + }); + }); + }); + after(function () { + muk.restore(); + }); + + it('should ok', function (done) { + api.getAccessToken(function (err, token) { + should.not.exist(err); + token.should.have.property('access_token', 'ACCESS_TOKEN'); + token.should.have.property('expires_in', 7200); + done(); + }); + }); + }); + }); + + describe('invalid token', function () { + var api = new API('appid', 'secret'); + before(function (done) { + api.getAccessToken(function (err) { + done(); + }); + }); + + it('createMenu should not ok', function (done) { + api.createMenu('{}', function (err, menu) { + should.exist(err); + err.name.should.be.equal('WeChatAPIError'); + err.message.should.be.equal('invalid credential'); + done(); + }); + }); + + it('getMenu should not ok', function (done) { + api.getMenu(function (err, menu) { + should.exist(err); + err.name.should.be.equal('WeChatAPIError'); + err.message.should.be.equal('invalid credential'); + done(); + }); + }); + + it('removeMenu should not ok', function (done) { + api.removeMenu(function (err, token) { + should.exist(err); + err.name.should.be.equal('WeChatAPIError'); + err.message.should.be.equal('invalid credential'); + done(); + }); + }); + + it('getQRCode should not ok', function (done) { + var ticket = {"expire_seconds": 1800, "action_name": "SCAN_SCENE", "action_info": {"scene": {"scene_id": 123}}}; + api.getQRCode(ticket, function (err, data, res) { + should.exist(err); + err.name.should.be.equal('WeChatAPIError'); + err.message.should.be.equal('invalid credential'); + done(); + }); + }); + }); + + describe('mock', function () { + + }); +});