wx
Version:
W(ei)X(in) -- (minimalist) WeChat Middleware for Express.js
1,256 lines (1,239 loc) • 51.1 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var _, _s, api_binary, api_common, api_qrcode, async, crypto, exec, express, fs, getRawBody, path, qs, redis, request, xml2js,
slice = [].slice;
fs = require('fs');
path = require('path');
crypto = require('crypto');
exec = require('child_process').exec;
redis = require('redis');
_ = require('underscore');
_s = require('underscore.string');
qs = require('qs');
request = require('request');
getRawBody = require('raw-body');
xml2js = require('xml2js');
express = require('express');
async = require('async');
api_common = 'https://api.weixin.qq.com/cgi-bin';
api_qrcode = 'https://mp.weixin.qq.com/cgi-bin';
api_binary = 'http://file.api.weixin.qq.com/cgi-bin';
module.exports = function(arg) {
var WXMsgCrypt, access_token, app_id, app_secret, click_handlers, debug, encoding_aes_key, fetch_access_token, host, identify_qrcode, json_2_markdown, key_access_token, lock_fetching, markdown_2_json, markdown_2_json_placeholder, options, populate_user, port, qrcode_long_poll_timeout, qrcode_permanent_channel, qrcode_permanent_maximum, qrcode_temporary_expires, redis_client, redis_options, redis_pubsub, ref, regex_media_id, render_admin, reply, router, scan_handlers, text_handlers, token, unsent_dt_responses, unsent_mb_responses, user_actions, wrap, wx, wx_msg_crypt;
token = arg.token, app_id = arg.app_id, app_secret = arg.app_secret, encoding_aes_key = arg.encoding_aes_key, redis_options = arg.redis_options, populate_user = arg.populate_user, debug = arg.debug;
ref = redis_options != null ? redis_options : {
port: null,
host: null,
options: null
}, port = ref.port, host = ref.host, options = ref.options;
redis_pubsub = redis.createClient(port, host, options);
redis_client = redis.createClient(port, host, options);
access_token = null;
if (encoding_aes_key) {
WXMsgCrypt = require('./wx-msg-crypt');
wx_msg_crypt = new WXMsgCrypt(token, encoding_aes_key, app_id);
}
if (populate_user == null) {
populate_user = true;
}
click_handlers = {};
scan_handlers = {};
text_handlers = [];
qrcode_permanent_maximum = 100000;
qrcode_temporary_expires = 1800;
qrcode_long_poll_timeout = 30;
qrcode_permanent_channel = 'permanent';
unsent_dt_responses = {};
unsent_mb_responses = {};
key_access_token = "WX:ACCESS_TOKEN:" + app_id;
redis_pubsub.subscribe(key_access_token, 'WX:SCAN:TEMPORARY', 'WX:SEND:MOBILE', 'WX:SEND:DESKTOP');
redis_pubsub.on('message', (function(_this) {
return function(channel, message) {
var desktop_callback, dt_res, i, len, mb_res, mobile, name, query, ref1, ref2, ref3, req, res, results, session;
message = JSON.parse("" + message);
switch (channel) {
case key_access_token:
return access_token = message;
case 'WX:SCAN:TEMPORARY':
if (!(dt_res = unsent_dt_responses[message.id])) {
return;
}
delete unsent_dt_responses[message.id];
ref1 = JSON.parse(message.id), session = ref1[0], name = ref1[1], query = ref1[2];
req = dt_res[0], res = dt_res[1];
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', true);
if (scan_handlers[name]) {
_(req).extend(message.content);
_(req.user != null ? req.user : req.user = {}).extend(message.user, user_actions);
mobile = reply(req, message.id);
desktop_callback = function() {
return res.send([message.msg_id].concat(Array.prototype.slice.call(arguments)));
};
return async.eachSeries(scan_handlers[name], function(handler, callback) {
return handler(req, mobile, desktop_callback, callback);
}, function(err) {
if (err) {
return console.error(err);
}
});
} else {
return res.send([message.user]);
}
break;
case 'WX:SEND:MOBILE':
if (!(mb_res = unsent_mb_responses[message.id])) {
return;
}
delete unsent_mb_responses[message.id];
return mb_res[message.type](message.content);
case 'WX:SEND:DESKTOP':
if (!(dt_res = unsent_dt_responses[message.id])) {
return;
}
delete unsent_dt_responses[message.id];
ref2 = JSON.parse(message.id), session = ref2[0], name = ref2[1], query = ref2[2];
if (name === qrcode_permanent_channel) {
results = [];
for (i = 0, len = dt_res.length; i < len; i++) {
ref3 = dt_res[i], req = ref3[0], res = ref3[1];
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', true);
results.push(res.send(message.content));
}
return results;
} else {
dt_res[1].header('Access-Control-Allow-Origin', dt_res[0].headers.origin);
dt_res[1].header('Access-Control-Allow-Credentials', true);
return dt_res[1].send(message.content);
}
}
};
})(this));
wx = router = express.Router();
lock_fetching = 5;
(fetch_access_token = (function(_this) {
return function() {
redis_client.watch(key_access_token);
return redis_client.ttl(key_access_token, function(err, ttl) {
return redis_client.get(key_access_token, function(err, _access_token) {
var ref1;
if (ttl > lock_fetching) {
return redis_client.multi().exec(function(err, multi_res) {
if (!multi_res) {
return fetch_access_token();
}
access_token = _access_token;
return setTimeout(fetch_access_token, (ttl - lock_fetching) * 1000);
});
} else if ((ref1 = "" + _access_token) !== 'FETCHING') {
return redis_client.multi().setex(key_access_token, lock_fetching, 'FETCHING').exec(function(err, multi_res) {
if (!multi_res) {
return fetch_access_token();
}
return request({
method: 'GET',
url: api_common + "/token?grant_type=client_credential&appid=" + app_id + "&secret=" + app_secret,
json: true
}, function(err, res) {
var expires_in, ref2;
if (!res) {
return fetch_access_token();
}
if (res.body.errcode) {
console.error('微信认证失败,登录微信公共平台获取开发者凭据 https://mp.weixin.qq.com');
return redis_client.del(key_access_token);
} else {
ref2 = res.body, _access_token = ref2.access_token, expires_in = ref2.expires_in;
redis_client.setex(key_access_token, expires_in, _access_token);
redis_client.publish(key_access_token, JSON.stringify(_access_token));
return setTimeout(fetch_access_token, (expires_in - lock_fetching) * 1000);
}
});
});
} else {
return setTimeout(fetch_access_token, ttl * 1000);
}
});
});
};
})(this))();
identify_qrcode = function(req) {
var name, query, ref1, session;
name = ((ref1 = req.params.name) != null ? ref1 : '').toLowerCase();
query = _(req.query).clone();
delete query.t;
if ((1 <= name && name <= qrcode_permanent_maximum)) {
session = '';
query = _({
scene_id: +name
}).extend(query);
name = qrcode_permanent_channel;
} else {
session = req.session.id;
}
return [session, name, query];
};
router.get('/qrcode/:name?', (function(_this) {
return function(req, res) {
var create, name, query, ref1, session;
ref1 = identify_qrcode(req), session = ref1[0], name = ref1[1], query = ref1[2];
create = function(scene_id) {
var json;
json = {
'action_name': name === qrcode_permanent_channel ? 'QR_LIMIT_SCENE' : 'QR_SCENE',
'action_info': {
'scene': {
'scene_id': scene_id
}
}
};
if (name !== qrcode_permanent_channel) {
json.expire_seconds = qrcode_temporary_expires;
}
return request({
method: 'POST',
url: api_common + "/qrcode/create?access_token=" + access_token,
json: json
}, function(err, result) {
var key, ticket, value;
if (err) {
console.error(err);
return res.status(500).end();
}
ticket = result.body.ticket;
key = "WX:TICKETS:" + ticket;
value = JSON.stringify([session, name, query]);
if (name === qrcode_permanent_channel) {
redis_client.set(key, value);
} else {
redis_client.setex(key, 1800, value);
}
return res.redirect(api_qrcode + "/showqrcode?ticket=" + (encodeURI(ticket)));
});
};
if (name === qrcode_permanent_channel) {
return create(query.scene_id);
} else {
return redis_client.incr('WX:SCENE_ID', function(err, scene_id) {
return create(scene_id + qrcode_permanent_maximum);
});
}
};
})(this));
router.get('/scan/:name?', function(req, res) {
var id, name, query, ref1, session;
ref1 = identify_qrcode(req), session = ref1[0], name = ref1[1], query = ref1[2];
id = JSON.stringify([session, name, query]);
if (name === qrcode_permanent_channel) {
if (unsent_dt_responses[id] == null) {
unsent_dt_responses[id] = [];
}
unsent_dt_responses[id].push([req, res]);
} else {
unsent_dt_responses[id] = [req, res];
redis_client.setex("WX:SCAN:QUERY:" + id, qrcode_long_poll_timeout, 1);
}
return setTimeout(function() {
var dt_res;
if (!(dt_res = unsent_dt_responses[id])) {
return;
}
if (name === qrcode_permanent_channel) {
if (dt_res.some(function(arg1) {
var _req;
_req = arg1[0];
return _req === req;
})) {
unsent_dt_responses[id] = unsent_dt_responses[id].filter(function(arg1) {
var _req;
_req = arg1[0];
return _req !== req;
});
if (!unsent_dt_responses[id].length) {
delete unsent_dt_responses[id];
}
return res.status(404).end();
}
} else {
if (dt_res[0] === req) {
delete unsent_dt_responses[id];
}
return res.status(404).end();
}
}, qrcode_long_poll_timeout * 1000);
});
router.get('/wx.js', function(req, res) {
return res.sendFile(__dirname + "/wx_client.js");
});
router.get('/ace/ace.js', function(req, res) {
return res.sendFile(__dirname + "/ace/ace.js");
});
router.get('/ace/mode-markdown.js', function(req, res) {
return res.sendFile(__dirname + "/ace/mode-markdown.js");
});
router.get('/ace/theme-terminal.js', function(req, res) {
return res.sendFile(__dirname + "/ace/theme-terminal.js");
});
markdown_2_json_placeholder = 'function markdown_2_json() {}';
render_admin = function(markdown) {
return ("" + (fs.readFileSync(__dirname + "/admin.html"))).replace('MARKDOWN', markdown).replace(markdown_2_json_placeholder, markdown_2_json.toString()).replace('APPSECRET', app_secret);
};
router.get('/admin', function(req, res) {
return res.sendFile(__dirname + "/login.html");
});
router.post('/admin', function(req, res) {
if (req.body.app_secret !== app_secret) {
return res.send(fs.readFileSync(__dirname + "/login.html").toString().replace('ERROR', 'show'));
} else if (req.body.action === '开始管理') {
return wx.get_menu(function(err, json) {
if (err) {
return res.status(500).send(err);
}
return res.send(render_admin(json_2_markdown(json)));
});
} else if (req.body.action === '更新菜单') {
return wx.create_menu(markdown_2_json(req.body.buttons), function(err) {
if (err) {
console.error(err);
if (err) {
return res.status(500).end();
}
}
return wx.get_menu(function(err, json) {
if (err) {
console.error(err);
return res.status(500).end();
}
return res.send(render_admin(json_2_markdown(json)));
});
});
}
});
router.use((function(_this) {
return function(arg1, res, next) {
var message, nonce, ref1, signature, timestamp;
ref1 = arg1.query, signature = ref1.signature, timestamp = ref1.timestamp, nonce = ref1.nonce;
message = _([token, timestamp, nonce]).sort().join('');
if (signature === crypto.createHash('sha1').update(message).digest('hex')) {
return next();
}
return res.status(401).end();
};
})(this));
router.get('/', function(arg1, res) {
var echostr;
echostr = arg1.query.echostr;
return res.send(echostr);
});
router.post('/', (function(_this) {
return function(req, res, next) {
return getRawBody(req, {
length: req.headers['content-length'],
limit: '1mb',
encoding: 'utf8'
}, function(err, string) {
var callback;
if (err) {
return res.status(400).end();
}
if (debug) {
console.info(string);
}
callback = function(err, result) {
var message, process_message, ref1, ref2;
if (err) {
return res.status(400).end();
}
message = Object.keys(result.xml).reduce(function(memo, key) {
memo[_s.underscored(key)] = result.xml[key][0];
return memo;
}, {});
if (message.msg_id == null) {
message.msg_id = message.from_user_name + "@" + (Date.now());
}
process_message = function(user) {
var handlers, msg_type, scan, subscribe;
_(req).extend(message);
_(req.user != null ? req.user : req.user = {}).extend(user);
_(res).extend(reply(req));
scan = function(params) {
var name, process_scan, query, session;
process_scan = function() {
var desktop_callback, id, mobile;
id = JSON.stringify([session, name, query]);
if (name === qrcode_permanent_channel) {
if (scan_handlers[name]) {
_(req).extend(message);
_(req.user != null ? req.user : req.user = {}).extend(user, user_actions);
req.params.scene_id = query.scene_id;
_(req.query).extend(query);
req.url += "&" + qs.stringify(query);
mobile = res;
desktop_callback = function() {
return redis_client.publish('WX:SEND:DESKTOP', JSON.stringify({
id: id,
content: [message.msg_id].concat(Array.prototype.slice.call(arguments))
}));
};
return async.eachSeries(scan_handlers[name], function(handler, callback) {
return handler(req, mobile, desktop_callback, callback);
}, function(err) {
if (err) {
console.error(err);
return mobile.status(500).end();
} else {
return mobile.ok();
}
});
} else {
res.ok();
return redis_client.publish('WX:SEND:DESKTOP', JSON.stringify({
id: id,
content: [message.msg_id, user]
}));
}
} else {
return redis_client.exists("WX:SCAN:QUERY:" + id, (function(_this) {
return function(err, querying) {
if (querying) {
redis_client.del("WX:SCAN:QUERY:" + id);
redis_client.publish('WX:SCAN:TEMPORARY', JSON.stringify({
id: id,
user: user,
content: message,
msg_id: message.msg_id
}));
if (scan_handlers[name]) {
return unsent_mb_responses[id] = res;
} else {
return res.ok();
}
} else if (scan_handlers[name]) {
_(req.query).extend(query);
req.url += "&" + qs.stringify(query);
return async.eachSeries(scan_handlers[name], function(scan_handler, callback) {
return scan_handler(req, res, (function() {}), callback);
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
} else {
return res.ok();
}
};
})(this));
}
};
if (params) {
session = params[0], name = params[1], query = params[2];
return process_scan();
} else {
return redis_client.get("WX:TICKETS:" + message.ticket, (function(_this) {
return function(err, result) {
var ref1, ref2, scene_id;
if (result) {
ref1 = JSON.parse("" + result), session = ref1[0], name = ref1[1], query = ref1[2];
} else if ((1 <= (ref2 = (scene_id = message.event_key.match(/\d+/)[0])) && ref2 <= qrcode_permanent_maximum)) {
session = '';
name = qrcode_permanent_channel;
query = {
scene_id: +scene_id
};
} else {
return res.ok();
}
return process_scan();
};
})(this));
}
};
switch (msg_type = req.msg_type.toLowerCase()) {
case 'text':
return async.eachSeries(text_handlers, function(arg1, callback) {
var handlers, match, pattern;
pattern = arg1[0], handlers = arg1[1];
if (match = message.content.trim().match(pattern)) {
_(req.params).extend(match);
return async.eachSeries(handlers, function(handler, callback) {
return handler(req, res, callback);
}, callback);
} else {
return callback();
}
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
case 'image':
case 'voice':
case 'video':
case 'location':
case 'link':
case 'device_text':
return async.eachSeries(this[msg_type + "_handlers"] || [], function(handler, callback) {
return handler(req, res, callback);
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
case 'event':
switch (req.event.toLowerCase()) {
case 'subscribe':
subscribe = function() {
return async.eachSeries(this.subscribe_handlers || [], function(handler, callback) {
return handler(req, res, callback);
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
};
if (!message.ticket) {
return subscribe();
}
return redis_client.get("WX:TICKETS:" + message.ticket, (function(_this) {
return function(err, result) {
var name, query, ref1, ref2, scene_id, session;
if (result) {
ref1 = JSON.parse("" + result), session = ref1[0], name = ref1[1], query = ref1[2];
} else if ((1 <= (ref2 = (scene_id = message.event_key.match(/\d+/)[0])) && ref2 <= qrcode_permanent_maximum)) {
session = '';
name = qrcode_permanent_channel;
query = {
scene_id: +scene_id
};
} else {
return subscribe();
}
if (scan_handlers[name]) {
return scan([session, name, query]);
} else {
subscribe();
return redis_client.publish('WX:SEND:DESKTOP', JSON.stringify({
id: JSON.stringify([session, name, query]),
content: [message.msg_id, user]
}));
}
};
})(this));
case 'unsubscribe':
return async.eachSeries(this.unsubscribe_handlers || [], function(handler, callback) {
return handler(req, res, callback);
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
case 'click':
if (handlers = click_handlers[req.event_key]) {
return async.eachSeries(handlers, function(handler, callback) {
return handler(req, res, callback);
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
} else {
return res.ok();
}
break;
case 'scan':
return scan();
case 'templatesendjobfinish':
return async.eachSeries(this.templatesendjobfinish_handlers || [], function(handler, callback) {
return handler(req, res, callback);
}, function(err) {
if (err) {
console.error(err);
return res.status(500).end();
} else {
return res.ok();
}
});
default:
return res.ok();
}
}
};
if (populate_user && ((ref1 = (ref2 = message.event) != null ? ref2.toLowerCase() : void 0) !== 'unsubscribe')) {
return wx.user(message.from_user_name, function(err, user) {
if (err) {
if (debug) {
console.error(err);
}
return process_message(wx.user(message.from_user_name));
}
return process_message(user);
});
} else {
return process_message(wx.user(message.from_user_name));
}
};
if (encoding_aes_key) {
return wx_msg_crypt.decrypt(string, callback);
} else {
return xml2js.parseString(string, callback);
}
});
};
})(this));
regex_media_id = /^[\w\_\-]{64}$/;
reply = function(req, id) {
var message;
if (id) {
return ['text', 'image', 'voice', 'video', 'music', 'news', 'transfer', 'ok'].reduce(function(memo, type) {
memo[type] = function(content) {
return redis_client.publish('WX:SEND:MOBILE', JSON.stringify({
id: id,
type: type,
content: content
}));
};
return memo;
}, {});
}
if (encoding_aes_key) {
message = function(message) {
return wx_msg_crypt.encrypt("<xml> <ToUserName><![CDATA[" + req.from_user_name + "]]></ToUserName> <FromUserName><![CDATA[" + req.to_user_name + "]]></FromUserName> <CreateTime>" + (~~(Date.now() / 1000)) + "</CreateTime> " + message + " </xml>", req.query.timestamp, req.query.nonce);
};
} else {
message = function(message) {
return "<xml> <ToUserName><![CDATA[" + req.from_user_name + "]]></ToUserName> <FromUserName><![CDATA[" + req.to_user_name + "]]></FromUserName> <CreateTime>" + (~~(Date.now() / 1000)) + "</CreateTime> " + message + " </xml>";
};
}
return {
text: function(text) {
return this.send(message("<MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[" + text + "]]></Content>"));
},
image: function(image) {
var send;
send = (function(_this) {
return function(image) {
return _this.send(message("<MsgType><![CDATA[image]]></MsgType> <Image><MediaId><![CDATA[" + image + "]]></MediaId></Image>"));
};
})(this);
if (typeof image === 'string' && image.match(regex_media_id)) {
return send(image);
} else {
return wx.upload('image', image, (function(_this) {
return function(err, res) {
if (image = res != null ? res.media_id : void 0) {
return send(image);
} else {
console.error(err || res);
return _this.status(500).end();
}
};
})(this));
}
},
voice: function(voice) {
var send;
send = (function(_this) {
return function(voice) {
return _this.send(message("<MsgType><![CDATA[voice]]></MsgType> <Voice><MediaId><![CDATA[" + voice + "]]></MediaId></Voice>"));
};
})(this);
if (voice.match(regex_media_id)) {
return send(voice);
} else {
return wx.upload('voice', voice, (function(_this) {
return function(err, res) {
if (!(voice = res != null ? res.media_id : void 0)) {
return _this.status(500).end();
}
return send(voice);
};
})(this));
}
},
video: function(video) {
var send;
send = (function(_this) {
return function(arg1) {
var description, title, video;
video = arg1.video, title = arg1.title, description = arg1.description;
return _this.send(message("<MsgType><![CDATA[video]]></MsgType> <Video> <MediaId><![CDATA[" + video + "]]></MediaId> <Title><![CDATA[" + title + "]]></Title> <Description><![CDATA[" + description + "]]></Description> </Video>"));
};
})(this);
if (video.video.match(regex_media_id)) {
return send(video);
} else {
return wx.upload('video', video.video, (function(_this) {
return function(err, res) {
if (!(video.video = res != null ? res.media_id : void 0)) {
return _this.status(500).end();
}
return send(video);
};
})(this));
}
},
music: function(music) {
var send;
send = (function(_this) {
return function(arg1) {
var description, hq_music_url, music_url, thumb_media, title;
title = arg1.title, description = arg1.description, music_url = arg1.music_url, hq_music_url = arg1.hq_music_url, thumb_media = arg1.thumb_media;
return _this.send(message("<MsgType><![CDATA[music]]></MsgType> <Music> <Title><![CDATA[" + title + "]]></Title> <Description><![CDATA[" + description + "]]></Description> <MusicUrl><![CDATA[" + music_url + "]]></MusicUrl> <HQMusicUrl><![CDATA[" + hq_music_url + "]]></HQMusicUrl> <ThumbMediaId><![CDATA[" + thumb_media + "]]></ThumbMediaId> </Music>"));
};
})(this);
if (music.thumb_media.match(regex_media_id)) {
return send(music);
} else {
return wx.upload('thumb', music.thumb_media, (function(_this) {
return function(err, res) {
if (!(music.thumb_media = res != null ? res.thumb_media_id : void 0)) {
return _this.status(500).end();
}
return send(music);
};
})(this));
}
},
news: function(articles) {
articles = [].concat(articles).map(function(arg1) {
var description, pic_url, title, url;
title = arg1.title, description = arg1.description, pic_url = arg1.pic_url, url = arg1.url;
return "<item> <Title><![CDATA[" + title + "]]></Title> <Description><![CDATA[" + description + "]]></Description> <PicUrl><![CDATA[" + pic_url + "]]></PicUrl> <Url><![CDATA[" + url + "]]></Url> </item>";
});
return this.send(message("<MsgType><![CDATA[news]]></MsgType> <ArticleCount>" + articles.length + "</ArticleCount> <Articles>" + (articles.join('')) + "</Articles>"));
},
transfer: function() {
return this.send(message("<MsgType><![CDATA[transfer_customer_service]]></MsgType>"));
},
device: function(content) {
content = (new Buffer(content)).toString('base64');
return this.send(message("<MsgType><![CDATA[device_text]]></MsgType> <DeviceType><![CDATA[" + req.device_type + "]]></DeviceType> <DeviceID><![CDATA[" + req.device_id + "]]></DeviceID> <SessionID>" + req.session_id + "</SessionID> <Content><![CDATA[" + content + "]]></Content>"));
},
ok: function() {
return this.status(200).end();
}
};
};
user_actions = {
image_url: function(size) {
if (!this.headimgurl) {
return null;
}
return this.headimgurl.replace(/\/0$/i, "/" + size);
},
text: function(text, callback) {
return request({
method: 'POST',
url: api_common + "/message/custom/send?access_token=" + access_token,
json: {
touser: this.openid,
msgtype: 'text',
text: {
content: text
}
}
}, wrap(callback));
},
image: function(image, callback) {
var send;
send = (function(_this) {
return function(image) {
return request({
method: 'POST',
url: api_common + "/message/custom/send?access_token=" + access_token,
json: {
touser: _this.openid,
msgtype: 'image',
image: {
media_id: image
}
}
}, wrap(callback));
};
})(this);
if (typeof image === 'string' && image.match(regex_media_id)) {
return send(image);
} else {
return wx.upload('image', image, function(err, res) {
if (!(image = res != null ? res.media_id : void 0)) {
return typeof callback === "function" ? callback(err || res) : void 0;
}
return send(image);
});
}
},
voice: function(voice, callback) {
var send;
send = (function(_this) {
return function(voice) {
return request({
method: 'POST',
url: api_common + "/message/custom/send?access_token=" + access_token,
json: {
touser: _this.openid,
msgtype: 'voice',
voice: {
media_id: voice
}
}
}, wrap(callback));
};
})(this);
if (voice.match(regex_media_id)) {
return send(voice);
} else {
return wx.upload('voice', voice, function(err, res) {
if (!(voice = res != null ? res.media_id : void 0)) {
return typeof callback === "function" ? callback(err || res) : void 0;
}
return send(voice);
});
}
},
video: function(video, callback) {
var send;
send = (function(_this) {
return function(arg1) {
var description, title, video;
title = arg1.title, description = arg1.description, video = arg1.video;
return request({
method: 'POST',
url: api_common + "/message/custom/send?access_token=" + access_token,
json: {
touser: _this.openid,
msgtype: 'video',
video: {
title: title,
description: description,
media_id: video
}
}
}, wrap(callback));
};
})(this);
if (video.video.match(regex_media_id)) {
return send(video);
} else {
return wx.upload('video', video.video, function(err, res) {
if (!(video.video = res != null ? res.media_id : void 0)) {
return typeof callback === "function" ? callback(err || res) : void 0;
}
return send(video);
});
}
},
music: function(music, callback) {
var send;
send = (function(_this) {
return function(arg1) {
var description, hq_music_url, music_url, thumb_media, title;
title = arg1.title, description = arg1.description, music_url = arg1.music_url, hq_music_url = arg1.hq_music_url, thumb_media = arg1.thumb_media;
return request({
method: 'POST',
url: api_common + "/message/custom/send?access_token=" + access_token,
json: {
touser: _this.openid,
msgtype: 'music',
music: {
title: title,
description: description,
musicurl: music_url,
hqmusicurl: hq_music_url,
thumb_media_id: thumb_media
}
}
}, wrap(callback));
};
})(this);
if (music.thumb_media.match(regex_media_id)) {
return send(music);
} else {
return wx.upload('thumb', music.thumb_media, function(err, res) {
if (!(music.thumb_media = res != null ? res.thumb_media_id : void 0)) {
return typeof callback === "function" ? callback(err || res) : void 0;
}
return send(music);
});
}
},
news: function(news, callback) {
return request({
method: 'POST',
url: api_common + "/message/custom/send?access_token=" + access_token,
json: {
touser: this.openid,
msgtype: 'news',
news: {
articles: news.map(function(arg1) {
var description, pic_url, title, url;
title = arg1.title, description = arg1.description, pic_url = arg1.pic_url, url = arg1.url;
return {
title: title,
description: description,
picurl: pic_url,
url: url
};
})
}
}
}, wrap(callback));
},
template: function(arg1, callback) {
var data, template_id, topcolor, url;
template_id = arg1.template_id, topcolor = arg1.topcolor, url = arg1.url, data = arg1.data;
return request({
method: 'POST',
url: api_common + "/message/template/send?access_token=" + access_token,
json: {
touser: this.openid,
template_id: template_id,
url: url,
topcolor: topcolor || '#FF0000',
data: data
}
}, wrap(callback));
}
};
wrap = function(callback) {
if (callback == null) {
callback = function() {};
}
return function(err, res) {
var ref1;
if (err || ((ref1 = res.body) != null ? ref1.errcode : void 0)) {
return callback(err || res.body);
}
return callback(null, res.body);
};
};
wx.markdown_2_json = markdown_2_json = function(markdown) {
var base, buttons, i, json, len, line, match, parse, ref1, sub_buttons;
parse = function(line) {
var match;
if (match = line.match(/\[(.*)\]\((.*)\)/)) {
return {
type: 'view',
name: match[1],
url: match[2]
};
} else if (match = line.match(/\[(.*)\]\((.*)/)) {
return {
type: 'click',
name: match[1],
key: match[1]
};
} else {
return {
type: 'click',
name: line,
key: line
};
}
};
buttons = [];
ref1 = markdown.split(/\n/);
for (i = 0, len = ref1.length; i < len; i++) {
line = ref1[i];
line = line.trim();
if (match = line.match(/^[\+\+](.+)/)) {
buttons.push(parse(match[1].trim()));
} else if (match = line.match(/^[\-\-](.+)/)) {
sub_buttons = (base = buttons[buttons.length - 1]).sub_button != null ? base.sub_button : base.sub_button = [];
sub_buttons.push(parse(match[1].trim()));
}
}
return json = buttons;
};
json_2_markdown = function(json) {
var button, buttons, i, j, len, len1, markdown, ref1, ref2, ref3, ref4, ref5, sub_button;
markdown = '';
ref1 = buttons = json;
for (i = 0, len = ref1.length; i < len; i++) {
button = ref1[i];
if ((ref2 = button.sub_button) != null ? ref2.length : void 0) {
markdown += "+ " + button.name + "\n";
ref3 = button.sub_button;
for (j = 0, len1 = ref3.length; j < len1; j++) {
sub_button = ref3[j];
if ((ref4 = sub_button.type) === 'view') {
markdown += " - [" + sub_button.name + "](" + sub_button.url + ")\n";
} else {
markdown += " - " + sub_button.name + "\n";
}
}
} else if ((ref5 = button.type) === 'view') {
markdown += "+ [" + button.name + "](" + button.url + ")\n";
} else {
markdown += "+ " + button.name + "\n";
}
}
return markdown;
};
return _.extend(router, {
access_token: function() {
return access_token;
},
text: (function(_this) {
return function() {
var handlers, pattern;
handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
pattern = _.first(handlers);
if (_.isRegExp(pattern)) {
pattern = handlers.shift();
} else if (_.isFunction(pattern)) {
pattern = /.*/;
} else if (_.isString(pattern)) {
pattern = new RegExp(handlers.shift(), 'i');
}
text_handlers = _.filter(text_handlers, function(arg1) {
var pattern_exist;
pattern_exist = arg1[0];
return pattern.toString() !== pattern_exist.toString();
});
text_handlers.push([pattern, handlers]);
return _this;
};
})(this),
image: (function(_this) {
return function() {
var image_handlers;
image_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.image_handlers = image_handlers;
return _this;
};
})(this),
voice: (function(_this) {
return function() {
var voice_handlers;
voice_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.voice_handlers = voice_handlers;
return _this;
};
})(this),
video: (function(_this) {
return function() {
var video_handlers;
video_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.video_handlers = video_handlers;
return _this;
};
})(this),
location: (function(_this) {
return function() {
var location_handlers;
location_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.location_handlers = location_handlers;
return _this;
};
})(this),
link: (function(_this) {
return function() {
var link_handlers;
link_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.link_handlers = link_handlers;
return _this;
};
})(this),
device: (function(_this) {
return function() {
var device_text_handlers;
device_text_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.device_text_handlers = device_text_handlers;
return _this;
};
})(this),
subscribe: (function(_this) {
return function() {
var subscribe_handlers;
subscribe_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.subscribe_handlers = subscribe_handlers;
return _this;
};
})(this),
unsubscribe: (function(_this) {
return function() {
var unsubscribe_handlers;
unsubscribe_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.unsubscribe_handlers = unsubscribe_handlers;
return _this;
};
})(this),
templatesendjobfinish: (function(_this) {
return function() {
var templatesendjobfinish_handlers;
templatesendjobfinish_handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
_this.templatesendjobfinish_handlers = templatesendjobfinish_handlers;
return _this;
};
})(this),
click: (function(_this) {
return function() {
var handlers, key;
key = arguments[0], handlers = 2 <= arguments.length ? slice.call(arguments, 1) : [];
click_handlers[key] = handlers;
return _this;
};
})(this),
scan: (function(_this) {
return function() {
var channel, handlers;
handlers = 1 <= arguments.length ? slice.call(arguments, 0) : [];
switch (typeof _.first(handlers)) {
case 'function':
channel = '';
break;
case 'string':
channel = handlers.shift().toLowerCase();
}
scan_handlers[channel] = handlers;
return _this;
};
})(this),
user: (function(_this) {
return function(openid, callback) {
if (callback) {
request({
method: 'GET',
url: api_common + "/user/info?access_token=" + access_token + "&openid=" + openid,
json: true
}, function(err, res) {
if (err || res.body.errcode) {
return callback(err || res.body);
}
return callback(null, _(res.body).extend(user_actions));
});
return _this;
} else {
return _({
openid: openid
}).extend(user_actions);
}
};
})(this),
upload: (function(_this) {
return function(type, media, callback) {
var form, r;
if (callback == null) {
callback = function() {};
}
r = request.post({
url: api_binary + "/media/upload?access_token=" + access_token + "&type=" + type,
json: true
}, function(err, res) {
if (err) {
return callback(err);
}
return callback(null, res.body);
});
form = r.form();
form.append('media', typeof media === 'string' ? fs.createReadStream(media) : media);
form.append('hack', '');
return _this;
};
})(this),
download: (function(_this) {
return function(media_id, callback) {
if (callback == null) {
callback = function() {};
}
return request({
url: api_binary + "/media/get?access_token=" + access_token + "&media_id=" + media_id,
encoding: null
}, function(err, res) {
if (err) {
return callback(err);
}
return callback(null, res.body);
});
};
})(this),
get_menu: function(callback) {
if (callback == null) {
callback = function() {};
}
request({
method: 'GET',
url: api_common + "/menu/get?access_token=" + access_token,
json: true
}, function(err, res) {
var ref1, ref2;
if (err || ((ref1 = res.body.errcode) !== (void 0) && ref1 !== 46003)) {
return callback(err || res.body);
}
return callback(null, ((ref2 = res.body.menu) != null ? ref2.button : void 0) || []);
});
return this;
},
create_menu: function(buttons, callback) {
request({
method: 'POST',
url: api_common + "/menu/create?access_token=" + access_token,
json: {
button: buttons
}
}, wrap(callback));
return this;
},
delete_menu: function(callback) {
request({
method: 'GET',
url: api_common + "/menu/delete?access_token=" + access_token,
json: true
}, wrap(callback));
return this;
},
subscribers: function(next_openid, callback) {
if (typeof next_openid === 'function') {
callback = next_openid;
next_openid = null;
}
request({
method: 'GET',
url: api_common + "/user/get?access_token=" + access_token + (next_openid ? "&next_openid=" + next_openid : ''),
json: true
}, wrap(callback));
return this;
},
populate_subscribers: function(start, end, callback) {
var i, index, index_tasks, next_openid, results, total;
index = ~~(end / 10000) + 1;
next_openid = null;
total = null;
index_tasks = (function() {
results = [];
for (var i = 0; 0 <= index ? i <= index : i >= index; 0 <= index ? i++ : i--){ results.push(i); }
return results;
}).apply(this).map(function() {
return function(callback) {
var _callback;
_callback = function(err, res) {
if (err) {
console.error(err);
return callback(err);
}
next_openid = res.data.next_openid;
total = res.total;
return callback(null, res);
};
return request({
method: 'GET',
url: api_common + "/user/get?access_token=" + access_token + (next