wechat-lite
Version:
wechat js api
681 lines (653 loc) • 20.7 kB
JavaScript
const URI = require('url');
const util = require('util');
const http = require('http');
const https = require('https');
const crypto = require('crypto');
const EventEmitter = require('events');
const ERROR_CODES = require('../errcode');
/**
* [WeChat description]
* @param {[type]} options [description]
*/
class WeChat extends EventEmitter {
constructor(options = {}) {
super();
const { appId, appSecret } = options;
this.appid = appId;
this.secret = appSecret;
return Object.assign(this, options);
}
static parseJS(code, scope){
const window = {};
if(scope) window[ scope ] = {};
eval(code);
return scope ? window[scope] : window;
};
/**
* [function description]
* @param {[type]} method [description]
* @param {[type]} url [description]
* @param {[type]} query [description]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
static request(url, params = {}) {
let { query, body } = params;
const urlObj = URI.parse(url, true);
delete urlObj.href;
delete urlObj.search;
Object.assign(params, urlObj, params);
query = Object.assign(urlObj.query, query);
params.path += '?' + Object
.keys(query)
.filter(key => query[key] !== undefined)
.map(key => [key, encodeURIComponent(query[key])].join('=')).join('&');
if (body && typeof body === 'object') {
body = JSON.stringify(body);
params.headers = params.headers || {};
params.headers['Content-Type'] = 'application/json; charset=utf-8';
params.headers['Content-Length'] = new Buffer(body).length;
}
if (params.unescape) params.path = unescape(params.path);
if (params.keepAlive) params.agent = new http.Agent(params);
return new Promise(function (resolve, reject) {
const client = require(params.protocol.slice(0, -1));
const req = client.request(params, response => {
const iconv = require('iconv-lite');
const buffer = []; response
.on('error', reject)
.on('data', buffer.push.bind(buffer))
.on('end', () => {
response.data = Buffer.concat(buffer);
response.blob = () => response.data;
response.json = () => JSON.parse(response.text());
response.text = ({ encoding = 'utf8' } = {}) =>
iconv.decode(response.data, encoding);
resolve(response);
});
});
if (body) req.write(body);
req.on('error', reject);
req.end();
});
}
requestToken() {
return this.token().then(({ access_token }) => access_token);
}
/**
* [getToken description]
* @param {[type]} grantType [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html
*/
token(grant_type = 'client_credential') {
const { appid, secret } = this;
return new Promise(function (accept, reject) {
https.get(URI.format({
pathname: WeChat.API_CORE + '/token',
query: {
appid,
secret,
grant_type
}
}), res => {
var buffer = ''; res
.on('error', reject)
.on('data', chunk => buffer += chunk)
.on('end', () => {
try {
accept(JSON.parse(buffer))
} catch (e) {
reject(e);
};
});
})
});
}
/**
* [getAuthorizeURL description]
* @param {[type]} callbackURL [description]
* @param {[type]} scope [snsapi_base|snsapi_userinfo]
* @param {[type]} state [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/4/9ac2e7b1f1d22e9e57260f6553822520.html
*/
auth_url(callbackURL, scope = WeChat.AUTH_SCOPE.BASE, state) {
const { appid } = this;
const api = 'https://open.weixin.qq.com/connect/oauth2/authorize';
// NOTES: QUERYSTRING ORDER IS VERY IMPORTANT !!!
const args = [
{ appid: appid },
{ redirect_uri: callbackURL },
{ response_type: 'code' },
{ scope: scope },
{ state: state }
];
api += '?' + args.map(function (i, index) {
var key = Object.keys(args[index])[0],
val = args[index][key];
if (val) return [key, encodeURIComponent(val)].join('=');
}).filter(function (arg) { return !!arg; }).join('&');
return api + '#wechat_redirect';
}
/**
* [getUser description]
* @param {[type]} token [description]
* @param {[type]} openId [description]
* @param {[type]} language [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html
*/
user(openid, lang = 'en') {
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/user/info', {
query: {
access_token,
openid,
lang
}
}).then(res => res.json());
});
}
/**
* [getTicket description]
* @param {[type]} token [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html
*/
ticket(token) {
return Promise
.resolve(token || this.requestToken())
.then(access_token => {
return WeChat.request(WeChat.API_CORE + '/ticket/getticket', {
query: { type: 'jsapi', access_token }
}).then(res => res.json());
})
}
/**
* [refreshAuthorizeToken description]
* @param {[type]} refreshToken [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
*/
auth_refresh_token(refresh_token) {
const { appid } = this;
return WeChat.request(WeChat.API_SNS + '/oauth2/refresh_token', {
query: {
appid,
refresh_token,
grant_type: 'refresh_token',
}
});
}
/**
* [getAuthorizeToken description]
* @param {[type]} code [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
*/
auth_token(code, grant_type = 'authorization_code') {
const { appid, secret } = this;
return WeChat.request(WeChat.API_SNS + '/oauth2/access_token', {
query: {
code,
appid,
secret,
grant_type
}
});
}
qrconnect({redirect_uri, scope = 'snsapi_login', state = 'login'}, callback){
const { appid } = this;
WeChat
.request(`${WeChat.OPEN_WEIXIN}/connect/qrconnect?appid=${appid}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}#wechat_redirect`)
.then(res => res.text({ encoding: 'gbk' }))
.then(html => {
const rQrcode = /src="\/connect\/qrcode\/(.+)"/;
const rLongPull = /"https:\/\/long.open.weixin.qq.com\/connect\/l\/qrconnect\?uuid=(.+?)"/;
const m = html.match(rQrcode);
if(!m){
const err_msg = html.match(/<h4 class="weui_msg_title">(.*)<\/h4>/)[1];
const err = new Error(err_msg);
return callback(err);
}
const uuid = m[1];
const qrcode = `${WeChat.OPEN_WEIXIN}/connect/qrcode/${uuid}`;
callback(null, { state: 0, qrcode });
let lastState = 0;
(function loop(){
WeChat
.request(`https://long.open.weixin.qq.com/connect/l/qrconnect?uuid=${uuid}&_=${Date.now()}`)
.then(res => res.text())
.then(WeChat.parseJS)
.then(({ wx_errcode, wx_code }) => {
if(wx_errcode !== 666){
setTimeout(loop, 200);
}
if(lastState !== wx_errcode){
lastState = wx_errcode;
callback(null, { state: wx_errcode, code: wx_code });
}
})
})();
}).catch(err => process.nextTick(() => callback(err)));
}
}
/**
* [API description]
* @type {String}
*/
WeChat.API = 'https://api.weixin.qq.com';
WeChat.API_SNS = WeChat.API + '/sns';
WeChat.API_CORE = WeChat.API + '/cgi-bin';
WeChat.OPEN_WEIXIN = 'https://open.weixin.qq.com';
/**
* [SCOPE description]
* @type {Object}
*/
WeChat.AUTH_SCOPE = {
BASE: 'snsapi_base',
USER: 'snsapi_userinfo'
};
/**
* [QR_SCENE description]
* @type {String}
* @docs http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html
*/
WeChat.QR_SCENE = 'QR_SCENE';
WeChat.QR_LIMIT_SCENE = 'QR_LIMIT_SCENE';
WeChat.QR_LIMIT_STR_SCENE = 'QR_LIMIT_STR_SCENE';
/**
* [function description]
* @param {[type]} err [description]
* @return {[type]} [description]
*/
WeChat.Error = function (msg, code) {
Error.call(this);
this.name = 'WeChatError';
this.code = code;
this.message = msg;
};
/**
* [prototype description]
* @type {[type]}
*/
WeChat.Error.Codes = ERROR_CODES;
WeChat.Error.prototype = Error.prototype;
/**
* [getCallbackIP description]
* @param {[type]} token [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html
*/
WeChat.prototype.callback_ip = function () {
return this.requestToken()
.then(access_token => {
return WeChat.request(WeChat.API_CORE + '/getcallbackip', {
query: { access_token }
}).then(res => res.json());
})
};
/**
* [checkAuthorizeToken description]
* @param {[type]} token [description]
* @param {[type]} openId [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
*/
WeChat.prototype.auth_check_token = function (openId) {
return WeChat.request(WeChat.API_SNS + '/auth', { openid: openId });
};
/**
* [getUser description]
* @param {[type]} token [description]
* @param {[type]} openId [description]
* @param {[type]} language [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
*/
WeChat.prototype.auth_user = function (token, openId, language) {
return WeChat.request(WeChat.API_SNS + '/userinfo', {
access_token: token,
openid: openId,
lang: language || 'en'
});
};
/**
* [function description]
* @param {[type]} token [description]
* @param {[type]} openId [description]
* @param {[type]} remark [description]
* @return {[type]} [description]
*/
WeChat.prototype.user_remark = function (openId, remark) {
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/user/info/updateremark', {
method: 'post',
query: { access_token },
body: {
openid: openId,
remark: remark
}
}).then(res => res.json());
})
};
/**
* [getUserList description]
* @param {[type]} token [description]
* @param {[type]} next_openid [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/12/54773ff6da7b8bdc95b7d2667d84b1d4.html
*/
WeChat.prototype.users = function (next) {
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/user/get', {
query: {
access_token,
next_openid: next
}
}).then(res => res.json());
});
};
/**
* [getUserBatch description]
* @param {[type]} token [description]
* @param {[type]} list [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html
*/
WeChat.prototype.users_info = function (list, language) {
language = language || 'en';
list = list.map(function (item) {
if (item.openid && item.lang)
return item;
if (item.openid) {
item.lang = language;
return item;
}
return {
openid: item,
lang: language
};
});
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/user/info/batchget', {
method: 'post',
query: { access_token },
body: { user_list: list }
}).then(res => res.json());
});
};
/**
* [function description]
* @param {[type]} token [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/17/4dc4b0514fdad7a5fbbd477aa9aab5ed.html
*/
WeChat.prototype.menu_list = function (token) {
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/get_current_selfmenu_info', {
query: { access_token }
}).then(res => res.json());
});
};
/**
* [function description]
* @param {[type]} to [description]
* @param {[type]} templateId [description]
* @param {[type]} data [description]
* @param {[type]} url [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html
*/
WeChat.prototype.template_send = function (templateId, data, url, to) {
Object.keys(data).forEach(function (key) {
if (typeof data[key] === 'string') {
data[key] = { value: data[key] };
}
});
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/message/template/send', {
method: 'post',
query: { access_token },
body: {
touser: to,
template_id: templateId,
url: url,
data: data
}
}).then(res => res.json());
});
};
/**
* [wxopen_template_send description]
* @param {[type]} templateId [description]
* @param {[type]} page [description]
* @param {[type]} value [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明
*/
WeChat.prototype.wxopen_template_send = function (to, form_id, templateId, value, data, page, color, emphasis_keyword) {
return WeChat.request(`${WeChat.API_CORE}/message/wxopen/template/send`, {
method: 'post',
body: {
touser: to,
template_id: templateId,
page: page,
form_id: form_id,
value: value,
data: data,
color: color,
emphasis_keyword: emphasis_keyword
}
});
};
/**
* [jscode2session description]
* @param {[type]} appId [小程序唯一标识]
* @param {[type]} secret [小程序的 app secret]
* @param {[type]} code [登录时获取的 code]
* @param {[type]} grant_type [填写为 authorization_code]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wxopen/wawiki?action=dir_list&type=develop&lang=zh_CN&token=1925614965
*/
WeChat.prototype.jscode2session = function (appId, secret, code, grant_type) {
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_SNS + '/jscode2session', {
query: {
access_token,
appid: appId,
secret: secret,
js_code: code,
grant_type: grant_type || 'authorization_code'
}
}).then(res => res.json());
});
};
/**
* [function description]
* @param {[type]} id [description]
* @param {[type]} name [description]
* @param {[type]} password [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html
*/
WeChat.prototype.custom_add = function (id, name, password) {
// TODO:
};
/**
* [function description]
* @param {[type]} id [description]
* @param {[type]} name [description]
* @param {[type]} password [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html
*/
WeChat.prototype.custom_del = function (id, name, password) {
// TODO:
};
/**
* [function description]
* @param {[type]} id [description]
* @param {[type]} name [description]
* @param {[type]} password [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html
*/
WeChat.prototype.custom_set = function (id, name, password) {
// TODO:
};
/**
* [function description]
* @param {[type]} id [description]
* @param {[type]} name [description]
* @param {[type]} password [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html
*/
WeChat.prototype.custom_get = function (id, name, password) {
// TODO:
};
/**
* [function description]
* @param {[type]} id [description]
* @param {[type]} img [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html
*/
WeChat.prototype.custom_avatar = function (id, img) {
// TODO:
};
/**
* [function description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html
*/
WeChat.prototype.custom_list = function () {
// TODO:
};
/**
* [function description]
* @param {[type]} token [description]
* @param {[type]} openId [description]
* @param {[type]} msgType [description]
* @param {[type]} content [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/7/12a5a320ae96fecdf0e15cb06123de9f.html
*/
WeChat.prototype.custom_send = function (openId, msgType, content) {
const body = {};
body.touser = openId;
body.msgtype = msgType;
body[msgType] = content;
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/message/custom/send', {
method: 'post', body
}).then(res => res.json());
})
};
/**
* [function description]
* @param {[type]} action [description]
* @param {[type]} info [description]
* @param {[type]} expire [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html
*/
WeChat.prototype.qr = function (action, info, expire) {
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/qrcode/create', {
method: 'post',
query: { access_token },
body: {
action_name: action,
action_info: info,
expire_seconds: expire
}
})
.then(res => res.json())
.then(function (res) {
var url = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=';
res.img = url + encodeURIComponent(res.ticket);
return res;
});
})
};
/**
* [function description]
* @param {[type]} url [description]
* @param {[type]} action [description]
* @return {[type]} [description]
* @docs https://mp.weixin.qq.com/wiki/10/165c9b15eddcfbd8699ac12b0bd89ae6.html
*/
WeChat.prototype.short_url = function (url, action) {
action = action || 'long2short';
return this.requestToken().then(access_token => {
return WeChat.request(WeChat.API_CORE + '/shorturl', {
method: 'post',
query: { access_token },
body: {
action: action,
long_url: url
}
}).then(res => res.json());
})
};
/**
* [genSignature description]
* @param {[type]} ticket [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
*/
WeChat.prototype.genSignature = function (ticket) {
var self = this;
/**
* [signature description]
* @param {[type]} params [description]
* @return {[type]} [description]
*/
function signature(params) {
var shasum = crypto.createHash('sha1');
shasum.update(Object.keys(params).sort().map(function (key) {
return [key, params[key]].join('=');
}).join('&'));
params.appId = self.options.appId;
params.signature = shasum.digest('hex');
return params;
}
/**
* [function description]
* @param {[type]} url [description]
* @return {[type]} [description]
*/
return function (url) {
var nonce = Math.random().toString(36).substr(2);
var timestamp = parseInt(new Date / 1000);
return signature({
jsapi_ticket: ticket,
noncestr: nonce,
timestamp: timestamp,
url: url
});
}
};
/**
* [checkSignature description]
* @param {[type]} params [description]
* @param {[type]} signature [description]
* @return {[type]} [description]
* @docs http://mp.weixin.qq.com/wiki/4/2ccadaef44fe1e4b0322355c2312bfa8.html
*/
WeChat.prototype.checkSignature = function (token, timestamp, nonce, signature, echostr) {
var sha1 = crypto
.createHash('sha1')
.update([token, timestamp, nonce].sort().join(''))
.digest('hex');
return signature ? (sha1 == signature) && (echostr || true) : sha1;
};
/**
* [exports description]
* @type {[type]}
*/
module.exports = WeChat;
;