├── .babelrc ├── .gitignore ├── README.md ├── app ├── app.js ├── cloud.js ├── dataTemplates │ └── textMsg.js ├── libs │ ├── common.js │ ├── getJsApiData.js │ ├── turingRobot.js │ ├── util.js │ ├── wxAuth.js │ ├── wxAutoReply.js │ ├── wxCustomeMenu.js │ └── wxGetUserinfo.js ├── routes │ ├── auth.js │ ├── userinfo.js │ └── weixin.js ├── spiders │ ├── douban │ │ ├── api.js │ │ └── douban.js │ ├── weibo │ │ ├── api.js │ │ └── weibo.js │ └── zhihu │ │ ├── api.js │ │ └── zhihu.js └── websdk │ ├── getWebToken.js │ └── getWebUserInfo.js ├── client ├── App.vue ├── components │ ├── Display.vue │ └── IncrementButton.vue ├── main.js └── vuex │ ├── actions.js │ ├── getters.js │ └── store.js ├── config.js ├── doc ├── 微博相关api.md └── 知乎相关api.md ├── package.json ├── public ├── dist │ ├── build.js │ └── commons.js └── scripts │ ├── index.js │ └── jweixin.js ├── server.js ├── token ├── views ├── base.html ├── index.html └── user.html └── webpackConfig ├── webpack.dev.config.js └── webpack.prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .avoscloud/ 2 | node_modules 3 | .DS_Store 4 | .idea 5 | npm-debug.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 不再更新: 可以移步 [基于koa2的微信开发](https://github.com/xiadd/miniweather) 2 | 3 | 如果你觉得这个代码有点帮助的话, 请点击下面搬瓦工链接注册一下, 我也收获点aff支撑服务器运营: 4 | 5 | #### [搬瓦工](https://bandwagonhost.com/aff.php?aff=30537) 6 | 7 | ### shorthand 8 | shorthand公众号开源,基于node,新手坑慎入 9 | `npm run dev`执行开发环境 10 | `npm run build`打包 11 | `lean up` or `node server.js` 生产环境 12 | 13 | ![微信开发交流群](https://ooo.0o0.ooo/2017/01/18/587f314e8ac5a.png) 14 | ### 不定期更新 15 | 16 | - [nodejs微信开发---接入指南](https://segmentfault.com/a/1190000005856154) 17 | - [nodejs微信开发---自动回复的实现](https://segmentfault.com/a/1190000005861026) 18 | - [nodejs 微信开发 --- 获取access_token+自定义菜单](https://segmentfault.com/a/1190000005906009) 19 | - [nodejs微信开发---授权登录+获取用户信息](https://segmentfault.com/a/1190000005921102) 20 | - [nodejs 微信开发 --- jssdk的使用](https://segmentfault.com/a/1190000005958495) 21 | 22 | #### shorthand任务清单 23 | 标注一些shorthand的公众号需要完成的任务 24 | 25 | ##### 教程部分 26 | - [x] 完成接入篇 27 | 28 | - [x] 完成简单的机器人聊天(示例,不是主要功能) 29 | 30 | - [x] 获取access_token+自定义菜单+授权登录+获取用户信息 31 | 32 | - [x] js sdk的使用 33 | 34 | - [ ] … … 待定 35 | 36 | ##### 公众号功能 37 | - [x] 知乎精选(非网页) 38 | 39 | - [ ] 微博状态(非网页) 40 | 41 | - [ ] 表白墙功能 (?待定是否用得着) 42 | 43 | - [ ] rss阅读(网页,是公众号还是网站待定) 44 | 45 | - [ ] 草榴抓取🔞🔞🔞? 46 | 47 | - [ ] … … 待定 48 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const nunjucks =require('nunjucks'); 3 | const AV = require('leanengine'); 4 | const path = require('path'); 5 | const bodyParser = require('body-parser'); 6 | require('body-parser-xml')(bodyParser); 7 | 8 | //引入token刷新 9 | const getToken = require('./libs/common'); 10 | getToken(); 11 | 12 | //创建菜单 13 | const createMenu = require('./libs/wxCustomeMenu'); 14 | createMenu(); 15 | 16 | 17 | //引入路由 18 | const weixin = require('./routes/weixin'); 19 | const auth = require('./routes/auth'); 20 | const userinfo = require('./routes/userinfo'); 21 | 22 | //app配置 23 | const app = express(); 24 | app.use(AV.express()); 25 | app.set('views', path.join(__dirname, '../views')); 26 | 27 | //解析xml 28 | app.use(bodyParser.xml({ 29 | limit: '1MB', 30 | xmlParseOptions: { 31 | normalize: true, 32 | normalizeTags: true, 33 | explicitArray: false 34 | } 35 | })); 36 | 37 | //启用nunjucks模板 38 | app.engine('html', nunjucks.render); 39 | app.set('view engine', 'html'); 40 | 41 | //启用路由 42 | app.use('/wechat', weixin); 43 | app.use(auth); 44 | app.use(userinfo); 45 | 46 | app.get('/', function (req, res) { 47 | console.log(1); 48 | res.render('index.html'); 49 | }); 50 | 51 | module.exports = app; 52 | -------------------------------------------------------------------------------- /app/cloud.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 6/30/16. 3 | */ 4 | var AV = require('leanengine'); 5 | 6 | /** 7 | * 一个简单的云代码方法 8 | */ 9 | AV.Cloud.define('hello', function (request, response) { 10 | response.success('Hello world!'); 11 | }); 12 | 13 | AV.Cloud.define('getUsers', function (req, res) { 14 | 15 | }); 16 | 17 | module.exports = AV.Cloud; 18 | -------------------------------------------------------------------------------- /app/dataTemplates/textMsg.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | '' + 3 | '' + 4 | '' + 5 | '%s' + 6 | '' + 7 | '' + 8 | '0' + 9 | ''; -------------------------------------------------------------------------------- /app/libs/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const request = require('request'); 3 | const qs = require('querystring'); 4 | const fs = require('fs'); 5 | const config = require('../../config'); 6 | const isExsitSync = require('./util').isExistSync; 7 | 8 | const getAccessToken = function () { 9 | let queryParams = { 10 | 'grant_type': 'client_credential', 11 | 'appid': config.appId, 12 | 'secret': config.appSecret 13 | }; 14 | 15 | let wxGetAccessTokenBaseUrl = 'https://api.weixin.qq.com/cgi-bin/token?'+qs.stringify(queryParams); 16 | let options = { 17 | method: 'GET', 18 | url: wxGetAccessTokenBaseUrl 19 | }; 20 | return new Promise((resolve, reject) => { 21 | request(options, function (err, res, body) { 22 | if (res) { 23 | resolve(JSON.parse(body)); 24 | } else { 25 | reject(err); 26 | } 27 | }); 28 | }) 29 | }; 30 | 31 | const saveToken = function () { 32 | getAccessToken().then(res => { 33 | let token = res['access_token']; 34 | fs.writeFile('./token', token, function (err) { 35 | 36 | }); 37 | }) 38 | }; 39 | 40 | const refreshToken = function () { 41 | saveToken(); 42 | setInterval(function () { 43 | saveToken(); 44 | }, 7000*1000); 45 | }; 46 | 47 | module.exports = refreshToken; -------------------------------------------------------------------------------- /app/libs/getJsApiData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const request = require('request'); 4 | const qs = require('querystring'); 5 | const config = require('../../config'); 6 | const token = fs.readFileSync('./token').toString(); 7 | const sha1 = require('./util').sha1; 8 | const reqUrl = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + token + '&type=jsapi'; 9 | 10 | function getJsApiTicket() { 11 | let options = { 12 | method: 'get', 13 | url: reqUrl 14 | }; 15 | 16 | return new Promise((resolve, reject) => { 17 | request(options, function (err, res, body) { 18 | if (res) { 19 | resolve(body); 20 | } else { 21 | reject(err); 22 | } 23 | }) 24 | }) 25 | } 26 | 27 | function getNonceStr () { 28 | var text = ""; 29 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 30 | for(var i = 0; i < 16; i++) { 31 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 32 | } 33 | return text; 34 | } 35 | 36 | function getTimestamp() { 37 | return new Date().valueOf(); 38 | } 39 | 40 | function getSign(jsApiTicket, noncestr, timestamp, url) { 41 | let data = { 42 | 'jsapi_ticket': jsApiTicket, 43 | 'noncestr': noncestr, 44 | 'timestamp': timestamp, 45 | 'url': 'http://wuyrsp3tma.proxy.qqbrowser.cc/auth' 46 | }; 47 | var sortData = "jsapi_ticket=" + jsApiTicket + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url; 48 | console.log(sortData); 49 | return sha1(sortData); 50 | } 51 | 52 | //返回数据分别为sign, timestamp, noncestr 53 | function getJsApiData(clientUrl) { 54 | let noncestr = getNonceStr(); 55 | let timestamp = getTimestamp(); 56 | return getJsApiTicket().then(data => { 57 | return [getSign(JSON.parse(data).ticket, noncestr, timestamp, clientUrl), timestamp, noncestr]; 58 | }) 59 | } 60 | 61 | module.exports = getJsApiData; -------------------------------------------------------------------------------- /app/libs/turingRobot.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const config = require('../../config'); 3 | 4 | function getTuringResponse(info) { 5 | if(typeof info !== 'string') { 6 | info = info.toString(); 7 | } 8 | var options = { 9 | method:'GET', 10 | url: 'http://apis.baidu.com/turing/turing/turing?key=879a6cb3afb84dbf4fc84a1df2ab7319&info='+info, 11 | headers: { 12 | 'apikey': config.turingKey 13 | } 14 | }; 15 | return new Promise((resolve, reject) => { 16 | request(options, function (err, res, body) { 17 | if (res) { 18 | resolve(body); 19 | } else { 20 | reject(err); 21 | } 22 | }); 23 | }) 24 | } 25 | 26 | module.exports = getTuringResponse; -------------------------------------------------------------------------------- /app/libs/util.js: -------------------------------------------------------------------------------- 1 | const parserString = require('xml2js').parseString; 2 | const fs = require('fs'); 3 | const crypto = require('crypto'); 4 | 5 | //将xml转为obj对象 6 | exports.convertXMLtoJSON = function (xml) { 7 | if (typeof xml !== 'string') { 8 | console.error('请输入合法的xml字符串'); 9 | return ; 10 | } 11 | 12 | return new Promise((resolve, reject) => { 13 | parserString(xml, function (err, results) { 14 | if(err) { 15 | reject(err); 16 | }else { 17 | resolve(results); 18 | } 19 | }) 20 | }) 21 | }; 22 | 23 | //判断文件是否存在 24 | 25 | exports.isExistSync = function (path) { 26 | try { 27 | return typeof fs.statSync(path) === 'object'; 28 | } catch (e) { 29 | return false; 30 | } 31 | }; 32 | 33 | exports.sha1 = function (str) { 34 | var shasum = crypto.createHash("sha1"); 35 | shasum.update(str); 36 | str = shasum.digest("hex"); 37 | return str; 38 | } 39 | -------------------------------------------------------------------------------- /app/libs/wxAuth.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const path = require('path'); 3 | const url = require('url'); 4 | 5 | //import config 6 | const config = require('../../config'); 7 | 8 | //进行sha1加密 9 | function sha1(str) { 10 | var shasum = crypto.createHash("sha1"); 11 | shasum.update(str); 12 | str = shasum.digest("hex"); 13 | return str; 14 | } 15 | 16 | function wechatAuth(req, res) { 17 | var query = url.parse(req.url, true).query; 18 | var signature = query.signature; 19 | var echostr = query.echostr; 20 | var timestamp = query['timestamp']; 21 | var nonce = query.nonce; 22 | 23 | var reqArray = [nonce, timestamp, config.token]; 24 | 25 | //对数组进行字典排序 26 | reqArray.sort(); 27 | var sortStr = reqArray.join(''); 28 | var sha1Str = sha1(sortStr); 29 | 30 | if (signature === sha1Str) { 31 | res.end(echostr); 32 | } else { 33 | res.end("false"); 34 | console.log("授权失败!"); 35 | } 36 | } 37 | 38 | 39 | module.exports = wechatAuth; -------------------------------------------------------------------------------- /app/libs/wxAutoReply.js: -------------------------------------------------------------------------------- 1 | //自动回复消息 2 | const request = require('request'); 3 | 4 | /** 5 | * 6 | * @param msgType {string} 收到的信息的内容 7 | * @param info {string} 返回消息的内容 8 | * @returns {string} 返回xml字符串用作消息内容 9 | */ 10 | 11 | function autoReply(msgType, requestData, info) { 12 | switch (msgType) { 13 | case 'text': 14 | var resMsg = '' + 15 | '' + 16 | '' + 17 | '' + parseInt(new Date().valueOf() / 1000) + '' + 18 | '' + 19 | '' + 20 | ''; 21 | break; 22 | //关注事件 23 | case 'subscribe': 24 | var resMsg = '' + 25 | '' + 26 | '' + 27 | '' + parseInt(new Date().valueOf() / 1000) + '' + 28 | '' + 29 | '' + 30 | ''; 31 | } 32 | 33 | return resMsg; 34 | } 35 | 36 | module.exports = autoReply; -------------------------------------------------------------------------------- /app/libs/wxCustomeMenu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const request = require('request'); 4 | 5 | //token 6 | const token = fs.readFileSync('./token').toString(); 7 | 8 | //常用type为view和click,分别为点击事件和链接 9 | var menus = { 10 | "button": [ 11 | { 12 | "name": "测试菜单", 13 | "sub_button": [ 14 | { 15 | "type": "view", 16 | "name": "授权登录", 17 | "url": "http://wuyrsp3tma.proxy.qqbrowser.cc/auth" 18 | }] 19 | }] 20 | }; 21 | 22 | function createMenu() { 23 | let options = { 24 | url: 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token=' + token, 25 | form: JSON.stringify(menus), 26 | headers: { 27 | 'Content-Type': 'application/x-www-form-urlencoded' 28 | } 29 | }; 30 | 31 | request.post(options, function (err, res, body) { 32 | if (err) { 33 | console.log(err) 34 | }else { 35 | console.log(body); 36 | } 37 | }) 38 | 39 | } 40 | 41 | module.exports = createMenu; 42 | 43 | -------------------------------------------------------------------------------- /app/libs/wxGetUserinfo.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | 3 | //static varible 4 | const url = 'xiadd.me'; 5 | 6 | function getUserinfo() { 7 | 8 | } 9 | 10 | module.exports = getUserinfo; -------------------------------------------------------------------------------- /app/routes/auth.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const getJsApiData = require('../libs/getJsApiData'); 3 | const config = require('../../config'); 4 | 5 | router.get('/auth', function (req, res) { 6 | var clientUrl = 'http://' + req.hostname + req.url; 7 | getJsApiData(clientUrl).then(data => { 8 | res.render('base.html', {signature: data[0], timestamp: data[1], nonceStr: data[2], appId: config.appId}); 9 | }); 10 | }); 11 | 12 | module.exports = router; -------------------------------------------------------------------------------- /app/routes/userinfo.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const getToken = require('../websdk/getWebToken'); 3 | const getUserInfo = require('../websdk/getWebUserInfo'); 4 | 5 | router.get('/user', function (req, res) { 6 | getToken(req.query.code) 7 | .then(function (data) { 8 | return JSON.parse(data); 9 | }) 10 | .then(function (data) { 11 | getUserInfo(data['access_token'], data['openid']).then(_ => { 12 | res.render('user.html', {userinfo: _}); 13 | }) 14 | }); 15 | }); 16 | 17 | module.exports = router; -------------------------------------------------------------------------------- /app/routes/weixin.js: -------------------------------------------------------------------------------- 1 | const router =require('express').Router(); 2 | const wxAuth = require('../libs/wxAuth'); 3 | const turingRobot = require('../libs/turingRobot'); 4 | const autoReply = require('../libs/wxAutoReply'); 5 | 6 | router.get('/', wxAuth); 7 | 8 | router.post('/', function (req, res) { 9 | //设置返回数据header 10 | res.writeHead(200, {'Content-Type': 'application/xml'}); 11 | //关注后回复 12 | if (req.body.xml.event === 'subscribe') { 13 | var resMsg = autoReply('text', req.body.xml, '欢迎关注'); 14 | res.end(resMsg); 15 | } else { 16 | var info = encodeURI(req.body.xml.content); 17 | turingRobot(info).then(function (data) { 18 | var response = JSON.parse(data); 19 | var resMsg = autoReply('text', req.body.xml, response.text); 20 | res.end(resMsg); 21 | }) 22 | } 23 | }); 24 | 25 | module.exports = router; -------------------------------------------------------------------------------- /app/spiders/douban/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 7/20/16. 3 | */ 4 | module.exports = { 5 | baseUrl: '' 6 | }; 7 | -------------------------------------------------------------------------------- /app/spiders/douban/douban.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 7/20/16. 3 | */ 4 | -------------------------------------------------------------------------------- /app/spiders/weibo/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 7/16/16. 3 | */ 4 | module.exports = { 5 | baseUrl: '' 6 | } -------------------------------------------------------------------------------- /app/spiders/weibo/weibo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 7/16/16. 3 | */ 4 | -------------------------------------------------------------------------------- /app/spiders/zhihu/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 7/14/16. 3 | */ 4 | module.exports = { 5 | followees: 'https://www.zhihu.com/node/ProfileFolloweesListV2' //获取粉丝列表 6 | }; -------------------------------------------------------------------------------- /app/spiders/zhihu/zhihu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Created by xiadd on 7/14/16. 4 | */ 5 | 6 | var AV = require('leanengine'); 7 | var $ = require('cheerio'); 8 | var cheerio = require('cheerio'); 9 | var config = require('../../../config'); 10 | var Promise = require('bluebird'); 11 | var request = Promise.promisifyAll(require('request')); 12 | var api = require('./api'); 13 | 14 | 15 | //数据库 16 | var Follwees = AV.Object.extend('Follees'); 17 | 18 | function getFollowees(offset) { 19 | var params = { 20 | 'offset': offset, 21 | 'order_by': 'created', 22 | 'hash_id': '8e54246e804fef8aa43434190f1c1870' 23 | }; 24 | var options = { 25 | url: api.followees, 26 | method: 'post', 27 | headers: { 28 | 'Cookie': config.zhihuCookie, 29 | 'X-Xsrftoken': config.zhihuXsrfToken 30 | }, 31 | form: { 32 | 'method': 'next', 33 | 'params': JSON.stringify(params) 34 | } 35 | }; 36 | 37 | return new Promise(function (resolve, reject) { 38 | request(options, function (err, res, body) { 39 | if(typeof JSON.parse(body) === 'object' && JSON.parse(body['msg']).length !== 0) { 40 | resolve(JSON.parse(body)); 41 | } else { 42 | reject(new TypeError('返回数据格式不正确').message); 43 | } 44 | }) 45 | }) 46 | } 47 | 48 | function saveResults() { 49 | if(typeof getFollowees !== 'function'){ 50 | throw new TypeError('传入正确的函数'); 51 | } 52 | 53 | getFollowees().then(function (data) { 54 | if(!data['msg']){ 55 | return new Error('数据格式不正确'); 56 | } 57 | var resData = data['msg']; 58 | resData.forEach(function (item) { 59 | var newFollee = new Follwees(); 60 | newFollee.set('info', item); 61 | newFollee.save().then(function () { 62 | console.log('success'); 63 | }, function (err) { 64 | console.log(err); 65 | }) 66 | }); 67 | }); 68 | } 69 | 70 | module.exports = saveResults; -------------------------------------------------------------------------------- /app/websdk/getWebToken.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const request = require('request'); 3 | const qs = require('querystring'); 4 | const config = require('../../config'); 5 | 6 | function getToken(code) { 7 | let reqUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token?'; 8 | let params = { 9 | appid: config.appId, 10 | secret: config.appSecret, 11 | code: code, 12 | grant_type: 'authorization_code' 13 | }; 14 | 15 | let options = { 16 | method: 'get', 17 | url: reqUrl+qs.stringify(params) 18 | }; 19 | console.log(options.url); 20 | return new Promise((resolve, reject) => { 21 | request(options, function (err, res, body) { 22 | if (res) { 23 | console.log(body) 24 | resolve(body); 25 | } else { 26 | reject(err); 27 | } 28 | }) 29 | }) 30 | } 31 | 32 | module.exports = getToken; -------------------------------------------------------------------------------- /app/websdk/getWebUserInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const request = require('request'); 3 | const qs = require('querystring'); 4 | 5 | function getUserInfo(AccessToken, openId) { 6 | let reqUrl = 'https://api.weixin.qq.com/sns/userinfo?'; 7 | let params = { 8 | access_token: AccessToken, 9 | openid: openId, 10 | lang: 'zh_CN' 11 | }; 12 | 13 | let options = { 14 | method: 'get', 15 | url: reqUrl+qs.stringify(params) 16 | }; 17 | 18 | return new Promise((resolve, reject) => { 19 | request(options, function (err, res, body) { 20 | if (res) { 21 | resolve(body); 22 | } else { 23 | reject(err); 24 | } 25 | }); 26 | }) 27 | } 28 | 29 | module.exports = getUserInfo; -------------------------------------------------------------------------------- /client/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | -------------------------------------------------------------------------------- /client/components/Display.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /client/components/IncrementButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /client/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | new Vue({ 5 | el: 'body', 6 | components: {App} 7 | }); -------------------------------------------------------------------------------- /client/vuex/actions.js: -------------------------------------------------------------------------------- 1 | export const incrementCounter = function ({dispatch, state}) { 2 | dispatch('INCREMENT', 1) 3 | }; 4 | -------------------------------------------------------------------------------- /client/vuex/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiadd on 7/13/16. 3 | */ 4 | export function getCount (state) { 5 | return state.count 6 | } -------------------------------------------------------------------------------- /client/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | // 告诉 vue “使用” vuex 5 | Vue.use(Vuex); 6 | 7 | // 创建一个对象来保存应用启动时的初始状态 8 | const state = { 9 | // 应用启动时,count 置为0 10 | count: 0 11 | }; 12 | 13 | const mutations = { 14 | // mutation 的第一个参数是当前的 state 15 | // 你可以在函数里修改 state 16 | INCREMENT (state, amount) { 17 | state.count = state.count + amount 18 | } 19 | }; 20 | 21 | export default new Vuex.Store({ 22 | state, 23 | mutations 24 | }) -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'token': 'qbtest', 3 | 'appId': 'wx1ce65521ad23e942', 4 | 'appSecret': 'd4624c36b6795d1d99dcf0547af5443d', 5 | 'turingKey': '94bb7a64cc567365d9046fc01716a3d5', 6 | 'zhihuCookie': 'd_c0="ACCAX7ZGvgmPTkoI6M1Y_pZMv4ulQz2oM0s=|1460168043"; _za=7fb46797-31a2-4d5b-b07d-18053e015be4; _zap=40aa0788-b6af-473d-a803-d3456a851afe; q_c1=5cd4f50dfe3f4bdfb84102b1b1b4beeb|1469495568000|1464228898000; _xsrf=7910d14bef55d5c996a197045f6d1088; _ga=GA1.2.228887280.1468222913; s-q=vczh; s-i=3; sid=0uv8hr3g; s-t=autocomplete; l_cap_id="ZTFlY2YxMTc5NmRlNDA3MmE5ZjY2ZDgzOGRhNTU4NjM=|1470658266|b45c3f5a4b4b218eeada0056248af6e7b6fe4f81"; cap_id="NTg4M2IwYjIxYjM5NDhmMWJiM2Y4YzdiNDlmOTFkMDU=|1470658266|39e84c4c2e65bedda03c6bd72bb1e6e6bf922edf"; login="NzYyYmY0MWU5NDM4NDI5ZTlmNmU3YzMwYjVkY2Q1OTY=|1470658280|f58e1f4c7f32b2862c2974bd5e050295d4f25f1d"; __utmt=1; n_c=1; __utma=51854390.228887280.1468222913.1470641625.1470657896.13; __utmb=51854390.39.9.1470658285295; __utmc=51854390; __utmz=51854390.1470378766.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmv=51854390.100-1|2=registration_date=20130821=1^3=entry_date=20130821=1; a_t="2.0AABAZUgdAAAXAAAAIAXQVwAAQGVIHQAAACCAX7ZGvgkXAAAAYQJVTRUF0FcAA9qRB-Jd0PN85jjcGYO4Ohgzjn52PcmWSQY4I8rfNWrXPB6FWQXNvg=="; z_c0=Mi4wQUFCQVpVZ2RBQUFBSUlCZnRrYS1DUmNBQUFCaEFsVk5GUVhRVndBRDJwRUg0bDNRODN6bU9Od1pnN2c2R0RPT2Zn|1470658592|161a5e5b60a1de96829eb2f5c2f01ccc96fb04ee', 7 | 'zhihuXsrfToken': '7910d14bef55d5c996a197045f6d1088' 8 | }; -------------------------------------------------------------------------------- /doc/微博相关api.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiadd/shorthand/bd8d53a71fb8b60394fa938d4f6a95a8f4c555e3/doc/微博相关api.md -------------------------------------------------------------------------------- /doc/知乎相关api.md: -------------------------------------------------------------------------------- 1 | # 知乎个人信息 2 | 3 | ### 粉丝列表 4 | 5 | 请求方式: `post` 6 | 7 | 接口地址: `https://www.zhihu.com/node/ProfileFolloweesListV2` 8 | 9 | 必须headers: `Cookie` 和 `X-Xsrftoken` (注:直接使用看到的即可,暂时未发现有问题) 10 | 11 | 必须参数: `body` 12 | 13 | ```json 14 | { 15 | method: 'next', 16 | params: '{"offset":40,"order_by":"created","hash_id":"8e54246e804fef8aa43434190f1c1870"}'//其中offset代表数据量, 17 | } 18 | ``` 19 | 20 | 返回数据: 21 | 22 | ```json 23 | { 24 | r: 0, 25 | msg: []//内部数据,需要自行解析,每次20个数据,都为html片段。 26 | } 27 | ``` 28 | 29 | ### 获得赞数以及关详细信息 30 | 31 | 这部分的前提是已经抓取到个人页面。 32 | 33 | 需要自行解析html。这里以jquery(cheerio)为例进行获取数据。 34 | 35 | `$('.zm-profile-header-user-agree>strong').text()` 获取获得的赞数 36 | 37 | `$('.zm-profile-header-user-thanks>strong').text()` 获取获得的感谢数 38 | 39 | `$('.profile-navbar .num').text(function foo (i, v) {console.log(v)})` 分别获取提问,回答数,文章,收藏,公共编辑 40 | 41 | `$('.zm-profile-side-following strong:first').text()` 关注数。 42 | 43 | # 知乎日报 44 | 45 | ### 最新推荐 46 | 47 | 请求方式: `get` 48 | 49 | 接口地址:`http://news-at.zhihu.com/api/4/news/latest` 50 | 51 | 返回数据: 52 | 53 | ```json 54 | { 55 | "date": "20160720", 56 | "stories": [ 57 | { 58 | "title": "微信观光指南:四川佛罗里达, 贵州劳伦斯,湖北布里斯托", 59 | "ga_prefix": "072007", 60 | "images": [ 61 | "http://pic3.zhimg.com/33b60a7216be8acc3fc46dc6f3856d3a.jpg" 62 | ], 63 | "multipic": true, 64 | "type": 0, 65 | "id": 8585811 66 | }, 67 | ... 68 | ], 69 | "top_stories": [ 70 | { 71 | "image": "http://pic2.zhimg.com/f111d2aa5b8154261bfe977fd7192e95.jpg", 72 | "type": 0, 73 | "id": 8587002, 74 | "ga_prefix": "072007", 75 | "title": "读读日报 24 小时热门 TOP 5 · 手机之王,不是英特尔,是 ARM" 76 | } 77 | ... 78 | ] 79 | } 80 | ``` 81 | 82 | ### 单条消息详细内容 83 | 84 | 请求方式: get 85 | 86 | 接口地址: `http://news-at.zhihu.com/api/4/news/:id` 87 | 88 | 返回数据: 89 | 90 | ```json 91 | { 92 | "body": "
...
", 93 | "image_source": "Angel Abril Ruiz / CC BY", 94 | "title": "卖衣服的新手段:把耐用品变成「不停买新的」", 95 | "image": "http://p4.zhimg.com/30/59/30594279d368534c6c2f91b2c00c7806.jpg", 96 | "share_url": "http://daily.zhihu.com/story/3892357", 97 | "js": [], 98 | 99 | "ga_prefix": "050615", 100 | "images": [ 101 | "http://p3.zhimg.com/69/d0/69d0ab1bde1988bd475bc7e0a25b713e.jpg" 102 | ], 103 | "type": 0, 104 | "id": 3892357, 105 | "css": [ 106 | "http://news-at.zhihu.com/css/news_qa.auto.css?v=4b3e3" 107 | ] 108 | } 109 | ``` 110 | # 话题广场 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shorthand", 3 | "version": "1.0.0", 4 | "description": "公众测试号", 5 | "main": "server.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=dev supervisor server", 8 | "build": "webpack --config webpackConfig/webpack.prod.config.js -p" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/xiadd/shorthand.git" 13 | }, 14 | "keywords": [ 15 | "wechat", 16 | "express" 17 | ], 18 | "author": "xiadd", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/xiadd/shorthand/issues" 22 | }, 23 | "homepage": "https://github.com/xiadd/shorthand#readme", 24 | "dependencies": { 25 | "bluebird": "^3.4.1", 26 | "body-parser": "^1.15.2", 27 | "body-parser-xml": "^1.1.0", 28 | "cheerio": "^0.20.0", 29 | "express": "^4.14.0", 30 | "iconv": "^2.2.1", 31 | "iconv-lite": "^0.4.13", 32 | "leanengine": "^1.0.0", 33 | "lodash": "^4.13.1", 34 | "mongodb": "^2.2.2", 35 | "nunjucks": "^2.4.2", 36 | "request": "^2.72.0", 37 | "superagent": "^2.0.0", 38 | "xml2js": "^0.4.16" 39 | }, 40 | "enginesapp": { 41 | "node": "4.x" 42 | }, 43 | "devDependencies": { 44 | "babel-core": "^6.0.0", 45 | "babel-loader": "^6.0.0", 46 | "babel-plugin-transform-runtime": "^6.0.0", 47 | "babel-preset-es2015": "^6.0.0", 48 | "babel-preset-stage-2": "^6.0.0", 49 | "babel-runtime": "^6.0.0", 50 | "cross-env": "^1.0.6", 51 | "css-loader": "^0.23.0", 52 | "debug": "^2.2.0", 53 | "file-loader": "^0.8.4", 54 | "json-loader": "^0.5.4", 55 | "log4js": "^0.6.37", 56 | "reload": "^1.0.0", 57 | "url-loader": "^0.5.7", 58 | "vue": "^1.0.26", 59 | "vue-hot-reload-api": "^1.2.0", 60 | "vue-html-loader": "^1.0.0", 61 | "vue-loader": "^8.2.1", 62 | "vue-resource": "^0.7.0", 63 | "vue-router": "^0.7.13", 64 | "vue-style-loader": "^1.0.0", 65 | "vuex": "^0.8.2", 66 | "vux": "^0.1.3-rc8", 67 | "webpack": "^1.13.1", 68 | "webpack-dev-middleware": "^1.6.1", 69 | "webpack-dev-server": "^1.12.0", 70 | "webpack-hot-middleware": "^2.10.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /public/dist/build.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([1,0],[function(t,e,n){"use strict";function i(t){return t&&t.__esModule?t:{"default":t}}var r=n(2),o=i(r),s=n(14),a=i(s);new o["default"]({el:"body",components:{App:a["default"]}})},function(t,e,n){e=t.exports=n(9)(),e.push([t.id,"h1{color:#00ffd2}",""])},function(t,e,n){(function(e,n){/*! 2 | * Vue.js v1.0.26 3 | * (c) 2016 Evan You 4 | * Released under the MIT License. 5 | */ 6 | "use strict";function i(t,e,n){if(o(t,e))return void(t[e]=n);if(t._isVue)return void i(t._data,e,n);var r=t.__ob__;if(!r)return void(t[e]=n);if(r.convert(e,n),r.dep.notify(),r.vms)for(var s=r.vms.length;s--;){var a=r.vms[s];a._proxy(e),a._digest()}return n}function r(t,e){if(o(t,e)){delete t[e];var n=t.__ob__;if(!n)return void(t._isVue&&(delete t._data[e],t._digest()));if(n.dep.notify(),n.vms)for(var i=n.vms.length;i--;){var r=n.vms[i];r._unproxy(e),r._digest()}}}function o(t,e){return Vn.call(t,e)}function s(t){return Sn.test(t)}function a(t){var e=(t+"").charCodeAt(0);return 36===e||95===e}function u(t){return null==t?"":t.toString()}function c(t){if("string"!=typeof t)return t;var e=Number(t);return isNaN(e)?t:e}function l(t){return"true"===t||"false"!==t&&t}function h(t){var e=t.charCodeAt(0),n=t.charCodeAt(t.length-1);return e!==n||34!==e&&39!==e?t:t.slice(1,-1)}function f(t){return t.replace(Mn,p)}function p(t,e){return e?e.toUpperCase():""}function d(t){return t.replace(Fn,"$1-$2").toLowerCase()}function v(t){return t.replace(Pn,p)}function m(t,e){return function(n){var i=arguments.length;return i?i>1?t.apply(e,arguments):t.call(e,n):t.call(e)}}function g(t,e){e=e||0;for(var n=t.length-e,i=new Array(n);n--;)i[n]=t[n+e];return i}function _(t,e){for(var n=Object.keys(e),i=n.length;i--;)t[n[i]]=e[n[i]];return t}function y(t){return null!==t&&"object"==typeof t}function b(t){return In.call(t)===Rn}function w(t,e,n,i){Object.defineProperty(t,e,{value:n,enumerable:!!i,writable:!0,configurable:!0})}function C(t,e){var n,i,r,o,s,a=function u(){var a=Date.now()-o;a=0?n=setTimeout(u,e-a):(n=null,s=t.apply(r,i),n||(r=i=null))};return function(){return r=this,i=arguments,o=Date.now(),n||(n=setTimeout(a,e)),s}}function E(t,e){for(var n=t.length;n--;)if(t[n]===e)return n;return-1}function N(t){var e=function n(){if(!n.cancelled)return t.apply(this,arguments)};return e.cancel=function(){e.cancelled=!0},e}function x(t,e){return t==e||!(!y(t)||!y(e))&&JSON.stringify(t)===JSON.stringify(e)}function $(t){this.size=0,this.limit=t,this.head=this.tail=void 0,this._keymap=Object.create(null)}function k(){var t,e=ai.slice(pi,hi).trim();if(e){t={};var n=e.match(bi);t.name=n[0],n.length>1&&(t.args=n.slice(1).map(O))}t&&(ui.filters=ui.filters||[]).push(t),pi=hi+1}function O(t){if(wi.test(t))return{value:c(t),dynamic:!1};var e=h(t),n=e===t;return{value:n?t:e,dynamic:n}}function A(t){var e=yi.get(t);if(e)return e;for(ai=t,di=vi=!1,mi=gi=_i=0,pi=0,ui={},hi=0,fi=ai.length;hic&&u.push({value:t.slice(c,i)}),r=$i.test(n[0]),o=r?n[1]:n[2],s=o.charCodeAt(0),a=42===s,o=a?o.slice(1):o,u.push({tag:!0,value:o.trim(),html:r,oneTime:a}),c=i+n[0].length;return c1?t.map(function(t){return S(t,e)}).join("+"):S(t[0],e,!0)}function S(t,e,n){return t.tag?t.oneTime&&e?'"'+e.$eval(t.value)+'"':M(t.value,n):'"'+t.value+'"'}function M(t,e){if(ki.test(t)){var n=A(t);return n.filters?"this._applyFilters("+n.expression+",null,"+JSON.stringify(n.filters)+",false)":"("+t+")"}return e?t:"("+t+")"}function F(t,e,n,i){R(t,1,function(){e.appendChild(t)},n,i)}function P(t,e,n,i){R(t,1,function(){z(t,e)},n,i)}function I(t,e,n){R(t,-1,function(){J(t)},e,n)}function R(t,e,n,i,r){var o=t.__v_trans;if(!o||!o.hooks&&!Kn||!i._isCompiled||i.$parent&&!i.$parent._isCompiled)return n(),void(r&&r());var s=e>0?"enter":"leave";o[s](n,r)}function L(t){if("string"==typeof t){var e=t;t=document.querySelector(t),t||"production"!==n.env.NODE_ENV&&ji("Cannot find element: "+e)}return t}function H(t){if(!t)return!1;var e=t.ownerDocument.documentElement,n=t.parentNode;return e===t||e===n||!(!n||1!==n.nodeType||!e.contains(n))}function U(t,e){var n=t.getAttribute(e);return null!==n&&t.removeAttribute(e),n}function W(t,e){var n=U(t,":"+e);return null===n&&(n=U(t,"v-bind:"+e)),n}function B(t,e){return t.hasAttribute(e)||t.hasAttribute(":"+e)||t.hasAttribute("v-bind:"+e)}function z(t,e){e.parentNode.insertBefore(t,e)}function q(t,e){e.nextSibling?z(t,e.nextSibling):e.parentNode.appendChild(t)}function J(t){t.parentNode.removeChild(t)}function Y(t,e){e.firstChild?z(t,e.firstChild):e.appendChild(t)}function Q(t,e){var n=t.parentNode;n&&n.replaceChild(e,t)}function Z(t,e,n,i){t.addEventListener(e,n,i)}function G(t,e,n){t.removeEventListener(e,n)}function X(t){var e=t.className;return"object"==typeof e&&(e=e.baseVal||""),e}function K(t,e){qn&&!/svg$/.test(t.namespaceURI)?t.className=e:t.setAttribute("class",e)}function tt(t,e){if(t.classList)t.classList.add(e);else{var n=" "+X(t)+" ";n.indexOf(" "+e+" ")<0&&K(t,(n+e).trim())}}function et(t,e){if(t.classList)t.classList.remove(e);else{for(var n=" "+X(t)+" ",i=" "+e+" ";n.indexOf(i)>=0;)n=n.replace(i," ");K(t,n.trim())}t.className||t.removeAttribute("class")}function nt(t,e){var n,i;if(ot(t)&<(t.content)&&(t=t.content),t.hasChildNodes())for(it(t),i=e?document.createDocumentFragment():document.createElement("div");n=t.firstChild;)i.appendChild(n);return i}function it(t){for(var e;e=t.firstChild,rt(e);)t.removeChild(e);for(;e=t.lastChild,rt(e);)t.removeChild(e)}function rt(t){return t&&(3===t.nodeType&&!t.data.trim()||8===t.nodeType)}function ot(t){return t.tagName&&"template"===t.tagName.toLowerCase()}function st(t,e){var n=Ti.debug?document.createComment(t):document.createTextNode(e?" ":"");return n.__v_anchor=!0,n}function at(t){if(t.hasAttributes())for(var e=t.attributes,n=0,i=e.length;n=u.length){for(var t=0;t - did you mean <"+s+">? HTML is case-insensitive, remember to use kebab-case in templates."):Ii(t,i)&&ji("Unknown custom element: <"+i+'> - did you register the component correctly? For recursive components, make sure to provide the "name" option.')}}}function pt(t,e){var n=t.getAttribute("is");if(null!=n){if(bt(e,"components",n))return t.removeAttribute("is"),{id:n}}else if(n=W(t,"is"),null!=n)return{id:n,dynamic:!0}}function dt(t,e){var n,r,s;for(n in e)r=t[n],s=e[n],o(t,n)?y(r)&&y(s)&&dt(r,s):i(t,n,s);return t}function vt(t,e){var n=Object.create(t||null);return e?_(n,_t(e)):n}function mt(t){if(t.components){var e,i=t.components=_t(t.components),r=Object.keys(i);if("production"!==n.env.NODE_ENV)var o=t._componentNameMap={};for(var s=0,a=r.length;s=97&&e<=122||e>=65&&e<=90?"ident":e>=49&&e<=57?"number":"else"}function Dt(t){var e=t.trim();return("0"!==t.charAt(0)||!isNaN(t))&&(s(e)?h(e):"*"+e)}function Tt(t){function e(){var e=t[l+1];if(h===rr&&"'"===e||h===or&&'"'===e)return l++,i="\\"+e,p[Qi](),!0}var n,i,r,o,s,a,u,c=[],l=-1,h=Ki,f=0,p=[];for(p[Zi]=function(){void 0!==r&&(c.push(r),r=void 0)},p[Qi]=function(){void 0===r?r=i:r+=i},p[Gi]=function(){p[Qi](),f++},p[Xi]=function(){if(f>0)f--,h=ir,p[Qi]();else{if(f=0,r=Dt(r),r===!1)return!1;p[Zi]()}};null!=h;)if(l++,n=t[l],"\\"!==n||!e()){if(o=At(n),u=ur[h],s=u[o]||u["else"]||ar,s===ar)return;if(h=s[0],a=p[s[1]],a&&(i=s[2],i=void 0===i?n:i,a()===!1))return;if(h===sr)return c.raw=t,c}}function jt(t){var e=Yi.get(t);return e||(e=Tt(t),e&&Yi.put(t,e)),e}function Vt(t,e){return Ut(e).get(t)}function St(t,e,r){var o=t;if("string"==typeof e&&(e=Tt(e)),!e||!y(t))return!1;for(var s,a,u=0,c=e.length;u-1?n.replace(yr,It):n,e+"scope."+n)}function It(t,e){return Er[e]}function Rt(t){vr.test(t)&&"production"!==n.env.NODE_ENV&&ji("Avoid using reserved keywords in expression: "+t),Er.length=0;var e=t.replace(_r,Ft).replace(mr,"");return e=(" "+e).replace(wr,Pt).replace(yr,It),Lt(e)}function Lt(t){try{return new Function("scope","return "+t+";")}catch(e){return"production"!==n.env.NODE_ENV&&ji(e.toString().match(/unsafe-eval|CSP/)?"It seems you are using the default build of Vue.js in an environment with Content Security Policy that prohibits unsafe-eval. Use the CSP-compliant build instead: http://vuejs.org/guide/installation.html#CSP-compliant-build":"Invalid expression. Generated function body: "+t),Mt}}function Ht(t){var e=jt(t);return e?function(t,n){St(t,e,n)}:void("production"!==n.env.NODE_ENV&&ji("Invalid setter expression: "+t))}function Ut(t,e){t=t.trim();var n=hr.get(t);if(n)return e&&!n.set&&(n.set=Ht(n.exp)),n;var i={exp:t};return i.get=Wt(t)&&t.indexOf("[")<0?Lt("scope."+t):Rt(t),e&&(i.set=Ht(t)),hr.put(t,i),i}function Wt(t){return br.test(t)&&!Cr.test(t)&&"Math."!==t.slice(0,5)}function Bt(){xr.length=0,$r.length=0,kr={},Or={},Ar=!1}function zt(){for(var t=!0;t;)t=!1,qt(xr),qt($r),xr.length?t=!0:(Wn&&Ti.devtools&&Wn.emit("flush"),Bt())}function qt(t){for(var e=0;eTi._maxUpdateCount)){ji('You may have an infinite update loop for watcher with expression "'+i.expression+'"',i.vm);break}}t.length=0}function Jt(t){var e=t.id;if(null==kr[e]){var n=t.user?$r:xr;kr[e]=n.length,n.push(t),Ar||(Ar=!0,ri(zt))}}function Yt(t,e,n,i){i&&_(this,i);var r="function"==typeof e;if(this.vm=t,t._watchers.push(this),this.expression=e,this.cb=n,this.id=++Dr,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new oi,this.newDepIds=new oi,this.prevError=null,r)this.getter=e,this.setter=void 0;else{var o=Ut(e,this.twoWay);this.getter=o.get,this.setter=o.set}this.value=this.lazy?void 0:this.get(),this.queued=this.shallow=!1}function Qt(t,e){var n=void 0,i=void 0;e||(e=Tr,e.clear());var r=Ln(t),o=y(t);if((r||o)&&Object.isExtensible(t)){if(t.__ob__){var s=t.__ob__.dep.id;if(e.has(s))return;e.add(s)}if(r)for(n=t.length;n--;)Qt(t[n],e);else if(o)for(i=Object.keys(t),n=i.length;n--;)Qt(t[i[n]],e)}}function Zt(t){return ot(t)&<(t.content)}function Gt(t,e){var n=e?t:t.trim(),i=Vr.get(n);if(i)return i;var r=document.createDocumentFragment(),o=t.match(Fr),s=Pr.test(t),a=Ir.test(t);if(o||s||a){var u=o&&o[1],c=Mr[u]||Mr.efault,l=c[0],h=c[1],f=c[2],p=document.createElement("div");for(p.innerHTML=h+t+f;l--;)p=p.lastChild;for(var d;d=p.firstChild;)r.appendChild(d)}else r.appendChild(document.createTextNode(t));return e||it(r),Vr.put(n,r),r}function Xt(t){if(Zt(t))return Gt(t.innerHTML);if("SCRIPT"===t.tagName)return Gt(t.textContent);for(var e,n=Kt(t),i=document.createDocumentFragment();e=n.firstChild;)i.appendChild(e);return it(i),i}function Kt(t){if(!t.querySelectorAll)return t.cloneNode();var e,n,i,r=t.cloneNode(!0);if(Rr){var o=r;if(Zt(t)&&(t=t.content,o=r.content),n=t.querySelectorAll("template"),n.length)for(i=o.querySelectorAll("template"),e=i.length;e--;)i[e].parentNode.replaceChild(Kt(n[e]),i[e])}if(Lr)if("TEXTAREA"===t.tagName)r.value=t.value;else if(n=t.querySelectorAll("textarea"),n.length)for(i=r.querySelectorAll("textarea"),e=i.length;e--;)i[e].value=n[e].value;return r}function te(t,e,n){var i,r;return lt(t)?(it(t),e?Kt(t):t):("string"==typeof t?n||"#"!==t.charAt(0)?r=Gt(t,n):(r=Sr.get(t),r||(i=document.getElementById(t.slice(1)),i&&(r=Xt(i),Sr.put(t,r)))):t.nodeType&&(r=Xt(t)),r&&e?Kt(r):r)}function ee(t,e,n,i,r,o){this.children=[],this.childFrags=[],this.vm=e,this.scope=r,this.inserted=!1,this.parentFrag=o,o&&o.childFrags.push(this),this.unlink=t(e,n,i,r,this);var s=this.single=1===n.childNodes.length&&!n.childNodes[0].__v_anchor;s?(this.node=n.childNodes[0],this.before=ne,this.remove=ie):(this.node=st("fragment-start"),this.end=st("fragment-end"),this.frag=n,Y(this.node,n),n.appendChild(this.end),this.before=re,this.remove=oe),this.node.__v_frag=this}function ne(t,e){this.inserted=!0;var n=e!==!1?P:z;n(this.node,t,this.vm),H(this.node)&&this.callHook(se)}function ie(){this.inserted=!1;var t=H(this.node),e=this;this.beforeRemove(),I(this.node,this.vm,function(){t&&e.callHook(ae),e.destroy()})}function re(t,e){this.inserted=!0;var n=this.vm,i=e!==!1?P:z;ut(this.node,this.end,function(e){i(e,t,n)}),H(this.node)&&this.callHook(se)}function oe(){this.inserted=!1;var t=this,e=H(this.node);this.beforeRemove(),ct(this.node,this.end,this.vm,this.frag,function(){e&&t.callHook(ae),t.destroy()})}function se(t){!t._isAttached&&H(t.$el)&&t._callHook("attached")}function ae(t){t._isAttached&&!H(t.$el)&&t._callHook("detached")}function ue(t,e){this.vm=t;var n,i="string"==typeof e;i||ot(e)&&!e.hasAttribute("v-if")?n=te(e,!0):(n=document.createDocumentFragment(),n.appendChild(e)),this.template=n;var r,o=t.constructor.cid;if(o>0){var s=o+(i?e:ht(e));r=Wr.get(s),r||(r=Re(n,t.$options,!0),Wr.put(s,r))}else r=Re(n,t.$options,!0);this.linker=r}function ce(t,e,n){var i=t.node.previousSibling;if(i){for(t=i.__v_frag;!(t&&t.forId===n&&t.inserted||i===e);){if(i=i.previousSibling,!i)return;t=i.__v_frag}return t}}function le(t){var e=t.node;if(t.end)for(;!e.__vue__&&e!==t.end&&e.nextSibling;)e=e.nextSibling;return e.__vue__}function he(t){for(var e=-1,n=new Array(Math.floor(t));++e47&&e<58?parseInt(t,10):1===t.length&&(e=t.toUpperCase().charCodeAt(0),e>64&&e<91)?e:lo[t]});return n=[].concat.apply([],n),function(e){if(n.indexOf(e.keyCode)>-1)return t.call(this,e)}}function me(t){return function(e){return e.stopPropagation(),t.call(this,e)}}function ge(t){return function(e){return e.preventDefault(),t.call(this,e)}}function _e(t){return function(e){if(e.target===e.currentTarget)return t.call(this,e)}}function ye(t){if(mo[t])return mo[t];var e=be(t);return mo[t]=mo[e]=e,e}function be(t){t=d(t);var e=f(t),n=e.charAt(0).toUpperCase()+e.slice(1);go||(go=document.createElement("div"));var i,r=fo.length;if("filter"!==e&&e in go.style)return{kebab:t,camel:e};for(;r--;)if(i=po[r]+n,i in go.style)return{kebab:fo[r]+t,camel:i}}function we(t){var e=[];if(Ln(t))for(var n=0,i=t.length;n=r?n():t[o].call(e,i)}var r=t.length,o=0;t[0].call(e,i)}function Ne(t,e,i){for(var r,o,a,u,c,l,h,p=[],v=Object.keys(e),m=v.length;m--;)if(o=v[m],r=e[o]||Vo,"production"===n.env.NODE_ENV||"$data"!==o)if(c=f(o),So.test(c)){if(h={name:o,path:c,options:r,mode:jo.ONE_WAY,raw:null},a=d(o),null===(u=W(t,a))&&(null!==(u=W(t,a+".sync"))?h.mode=jo.TWO_WAY:null!==(u=W(t,a+".once"))&&(h.mode=jo.ONE_TIME)),null!==u)h.raw=u,l=A(u),u=l.expression,h.filters=l.filters,s(u)&&!l.filters?h.optimizedLiteral=!0:(h.dynamic=!0,"production"===n.env.NODE_ENV||h.mode!==jo.TWO_WAY||Mo.test(u)||(h.mode=jo.ONE_WAY,ji("Cannot bind two-way prop with non-settable parent path: "+u,i))),h.parentPath=u,"production"!==n.env.NODE_ENV&&r.twoWay&&h.mode!==jo.TWO_WAY&&ji('Prop "'+o+'" expects a two-way binding type.',i);else if(null!==(u=U(t,a)))h.raw=u;else if("production"!==n.env.NODE_ENV){var g=c.toLowerCase();u=/[A-Z\-]/.test(o)&&(t.getAttribute(g)||t.getAttribute(":"+g)||t.getAttribute("v-bind:"+g)||t.getAttribute(":"+g+".once")||t.getAttribute("v-bind:"+g+".once")||t.getAttribute(":"+g+".sync")||t.getAttribute("v-bind:"+g+".sync")),u?ji("Possible usage error for prop `"+g+"` - did you mean `"+a+"`? HTML is case-insensitive, remember to use kebab-case for props in templates.",i):r.required&&ji("Missing required prop: "+o,i)}p.push(h)}else"production"!==n.env.NODE_ENV&&ji('Invalid prop key: "'+o+'". Prop keys must be valid identifiers.',i);else ji("Do not use $data as prop.",i);return xe(p)}function xe(t){return function(e,n){e._props={};for(var i,r,s,a,u,f=e.$options.propsData,p=t.length;p--;)if(i=t[p],u=i.raw,r=i.path,s=i.options,e._props[r]=i,f&&o(f,r)&&ke(e,i,f[r]),null===u)ke(e,i,void 0);else if(i.dynamic)i.mode===jo.ONE_TIME?(a=(n||e._context||e).$get(i.parentPath),ke(e,i,a)):e._context?e._bindDir({name:"prop",def:Po,prop:i},null,null,n):ke(e,i,e.$get(i.parentPath));else if(i.optimizedLiteral){var v=h(u);a=v===u?l(c(u)):v,ke(e,i,a)}else a=s.type===Boolean&&(""===u||u===d(i.name))||u,ke(e,i,a)}}function $e(t,e,n,i){var r=e.dynamic&&Wt(e.parentPath),o=n;void 0===o&&(o=Ae(t,e)),o=Te(e,o,t);var s=o!==n;De(e,o,t)||(o=void 0),r&&!s?Ct(function(){i(o)}):i(o)}function ke(t,e,n){$e(t,e,n,function(n){kt(t,e.path,n)})}function Oe(t,e,n){$e(t,e,n,function(n){t[e.path]=n})}function Ae(t,e){var i=e.options;if(!o(i,"default"))return i.type!==Boolean&&void 0;var r=i["default"];return y(r)&&"production"!==n.env.NODE_ENV&&ji('Invalid default value for prop "'+e.name+'": Props with type Object/Array must use a factory function to return the default value.',t),"function"==typeof r&&i.type!==Function?r.call(t):r}function De(t,e,i){if(!t.options.required&&(null===t.raw||null==e))return!0;var r=t.options,o=r.type,s=!o,a=[];if(o){Ln(o)||(o=[o]);for(var u=0;ue?-1:t===e?0:1}function Ue(t,e,n,i){function r(r){We(t,e,r),n&&i&&We(n,i)}return r.dirs=e,r}function We(t,e,i){for(var r=e.length;r--;)e[r]._teardown(),"production"===n.env.NODE_ENV||i||t._directives.$remove(e[r])}function Be(t,e,n,i){var r=Ne(e,n,t),o=Le(function(){r(t,i)},t);return Ue(t,o)}function ze(t,e,i){var r,o,s=e._containerAttrs,a=e._replacerAttrs;if(11!==t.nodeType)e._asComponent?(s&&i&&(r=sn(s,i)),a&&(o=sn(a,e))):o=sn(t.attributes,e);else if("production"!==n.env.NODE_ENV&&s){var u=s.filter(function(t){return t.name.indexOf("_v-")<0&&!Zo.test(t.name)&&"slot"!==t.name}).map(function(t){return'"'+t.name+'"'});if(u.length){var c=u.length>1;ji("Attribute"+(c?"s ":" ")+u.join(", ")+(c?" are":" is")+" ignored on component <"+e.el.tagName.toLowerCase()+"> because the component is a fragment instance: http://vuejs.org/guide/components.html#Fragment-Instance")}}return e._containerAttrs=e._replacerAttrs=null,function(t,e,n){var i,s=t._context;s&&r&&(i=Le(function(){r(s,e,null,n)},s));var a=Le(function(){o&&o(t,e)},t);return Ue(t,a,s,i)}}function qe(t,e){var n=t.nodeType;return 1!==n||ln(t)?3===n&&t.data.trim()?Ye(t,e):null:Je(t,e)}function Je(t,e){if("TEXTAREA"===t.tagName){var n=j(t.value);n&&(t.setAttribute(":value",V(n)),t.value="")}var i,r=t.hasAttributes(),o=r&&g(t.attributes);return r&&(i=nn(t,o,e)),i||(i=tn(t,e)),i||(i=en(t,e)),!i&&r&&(i=sn(o,e)),i}function Ye(t,e){if(t._skip)return Qe;var n=j(t.wholeText);if(!n)return null;for(var i=t.nextSibling;i&&3===i.nodeType;)i._skip=!0,i=i.nextSibling;for(var r,o,s=document.createDocumentFragment(),a=0,u=n.length;ap.priority)&&(p=f,l=r.name,a=an(r.name),s=r.value,c=u[1],h=u[2]));return p?on(t,c,s,n,p,l,h,a):void 0}function rn(){}function on(t,e,n,i,r,o,s,a){var u=A(n),c={name:e,arg:s,expression:u.expression,filters:u.filters,raw:n,attr:o,modifiers:a,def:r};"for"!==e&&"router-view"!==e||(c.ref=at(t));var l=function(t,e,n,i,r){c.ref&&kt((i||t).$refs,c.ref,null),t._bindDir(c,e,n,i,r)};return l.terminal=!0,l}function sn(t,e){function i(t,e,n){var i=n&&cn(n),r=!i&&A(s);m.push({name:t,attr:a,raw:u,def:e,arg:l,modifiers:h,expression:r&&r.expression,filters:r&&r.filters,interp:n,hasOneTime:i})}for(var r,o,s,a,u,c,l,h,f,p,d,v=t.length,m=[];v--;)if(r=t[v],o=a=r.name,s=u=r.value,p=j(s),l=null,h=an(o),o=o.replace(Xo,""),p)s=V(p),l=o,i("bind",Ao.bind,p),"production"!==n.env.NODE_ENV&&"class"===o&&Array.prototype.some.call(t,function(t){return":class"===t.name||"v-bind:class"===t.name})&&ji('class="'+u+'": Do not mix mustache interpolation and v-bind for "class" on the same element. Use one or the other.',e);else if(Ko.test(o))h.literal=!Qo.test(o),i("transition",Yo.transition);else if(Zo.test(o))l=o.replace(Zo,""),i("on",Ao.on);else if(Qo.test(o))c=o.replace(Qo,""),"style"===c||"class"===c?i(c,Yo[c]):(l=c,i("bind",Ao.bind));else if(d=o.match(Go)){if(c=d[1],l=d[2],"else"===c)continue;f=bt(e,"directives",c,!0),f&&i(c,f)}if(m.length)return un(m)}function an(t){var e=Object.create(null),n=t.match(Xo);if(n)for(var i=n.length;i--;)e[n[i].slice(1)]=!0;return e}function un(t){return function(e,n,i,r,o){for(var s=t.length;s--;)e._bindDir(t[s],n,i,r,o)}}function cn(t){for(var e=t.length;e--;)if(t[e].oneTime)return!0}function ln(t){return"SCRIPT"===t.tagName&&(!t.hasAttribute("type")||"text/javascript"===t.getAttribute("type"))}function hn(t,e){return e&&(e._containerAttrs=pn(t)),ot(t)&&(t=te(t)),e&&(e._asComponent&&!e.template&&(e.template=""),e.template&&(e._content=nt(t),t=fn(t,e))),lt(t)&&(Y(st("v-start",!0),t),t.appendChild(st("v-end",!0))),t}function fn(t,e){var i=e.template,r=te(i,!0);if(r){var o=r.firstChild,s=o.tagName&&o.tagName.toLowerCase();return e.replace?(t===document.body&&"production"!==n.env.NODE_ENV&&ji("You are mounting an instance with a template to . This will replace entirely. You should probably use `replace: false` here."),r.childNodes.length>1||1!==o.nodeType||"component"===s||bt(e,"components",s)||B(o,"is")||bt(e,"elementDirectives",s)||o.hasAttribute("v-for")||o.hasAttribute("v-if")?r:(e._replacerAttrs=pn(o),dn(t,o),o)):(t.appendChild(r),t)}"production"!==n.env.NODE_ENV&&ji("Invalid template option: "+i)}function pn(t){if(1===t.nodeType&&t.hasAttributes())return g(t.attributes)}function dn(t,e){for(var n,i,r=t.attributes,o=r.length;o--;)n=r[o].name,i=r[o].value,e.hasAttribute(n)||ns.test(n)?"class"===n&&!j(i)&&(i=i.trim())&&i.split(/\s+/).forEach(function(t){tt(e,t)}):e.setAttribute(n,i)}function vn(t,e){if(e){for(var i,r,o=t._slotContents=Object.create(null),s=0,a=e.children.length;s1?g(n):n;var r=e&&n.some(function(t){return t._fromParent});r&&(i=!1);for(var o=g(arguments,1),s=0,a=n.length;se?o:-o}var n=null,i=void 0;t=cs(t);var r=g(arguments,1),o=r[r.length-1];"number"==typeof o?(o=o<0?-1:1,r=r.length>1?r.slice(0,-1):r):o=1;var s=r[0];return s?("function"==typeof s?n=function(t,e){return s(t,e)*o}:(i=Array.prototype.concat.apply([],r),n=function(t,r,o){return o=o||0,o>=i.length-1?e(t,r,o):e(t,r,o)||n(t,r,o+1)}),t.slice().sort(n)):t}function Tn(t,e){var n;if(b(t)){var i=Object.keys(t);for(n=i.length;n--;)if(Tn(t[i[n]],e))return!0}else if(Ln(t)){for(n=t.length;n--;)if(Tn(t[n],e))return!0}else if(null!=t)return t.toString().toLowerCase().indexOf(e)>-1}function jn(t){function e(t){return new Function("return function "+v(t)+" (options) { this._init(options) }")()}t.options={directives:Ao,elementDirectives:us,filters:hs,transitions:{},components:{},partials:{},replace:!0},t.util=qi,t.config=Ti,t.set=i,t["delete"]=r,t.nextTick=ri,t.compiler=is,t.FragmentFactory=ue,t.internalDirectives=Yo,t.parsers={path:lr,text:Oi,template:Hr,directive:Ci,expression:Nr},t.cid=0;var o=1;t.extend=function(t){t=t||{};var i=this,r=0===i.cid;if(r&&t._Ctor)return t._Ctor;var s=t.name||i.options.name;"production"!==n.env.NODE_ENV&&(/^[a-zA-Z][\w-]*$/.test(s)||(ji('Invalid component name: "'+s+'". Component names can only contain alphanumeric characaters and the hyphen.'),s=null));var a=e(s||"VueComponent");return a.prototype=Object.create(i.prototype),a.prototype.constructor=a,a.cid=o++,a.options=yt(i.options,t),a["super"]=i,a.extend=i.extend,Ti._assetTypes.forEach(function(t){a[t]=i[t]}),s&&(a.options.components[s]=a),r&&(t._Ctor=a),a},t.use=function(t){if(!t.installed){var e=g(arguments,1);return e.unshift(this),"function"==typeof t.install?t.install.apply(t,e):t.apply(null,e),t.installed=!0,this}},t.mixin=function(e){t.options=yt(t.options,e)},Ti._assetTypes.forEach(function(e){t[e]=function(i,r){return r?("production"!==n.env.NODE_ENV&&"component"===e&&(Fi.test(i)||Pi.test(i))&&ji("Do not use built-in or reserved HTML elements as component id: "+i),"component"===e&&b(r)&&(r.name||(r.name=i),r=t.extend(r)),this.options[e+"s"][i]=r,r):this.options[e+"s"][i]}}),_(t.transition,Si)}var Vn=Object.prototype.hasOwnProperty,Sn=/^\s?(true|false|-?[\d\.]+|'[^']*'|"[^"]*")\s?$/,Mn=/-(\w)/g,Fn=/([a-z\d])([A-Z])/g,Pn=/(?:^|[-_\/])(\w)/g,In=Object.prototype.toString,Rn="[object Object]",Ln=Array.isArray,Hn="__proto__"in{},Un="undefined"!=typeof window&&"[object Object]"!==Object.prototype.toString.call(window),Wn=Un&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,Bn=Un&&window.navigator.userAgent.toLowerCase(),zn=Bn&&Bn.indexOf("trident")>0,qn=Bn&&Bn.indexOf("msie 9.0")>0,Jn=Bn&&Bn.indexOf("android")>0,Yn=Bn&&/(iphone|ipad|ipod|ios)/i.test(Bn),Qn=Yn&&Bn.match(/os ([\d_]+)/),Zn=Qn&&Qn[1].split("_"),Gn=Zn&&Number(Zn[0])>=9&&Number(Zn[1])>=3&&!window.indexedDB,Xn=void 0,Kn=void 0,ti=void 0,ei=void 0;if(Un&&!qn){var ni=void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend,ii=void 0===window.onanimationend&&void 0!==window.onwebkitanimationend;Xn=ni?"WebkitTransition":"transition",Kn=ni?"webkitTransitionEnd":"transitionend",ti=ii?"WebkitAnimation":"animation",ei=ii?"webkitAnimationEnd":"animationend"}var ri=function(){function t(){r=!1;var t=i.slice(0);i=[];for(var e=0;e)":""}}();var Si=Object.freeze({appendWithTransition:F,beforeWithTransition:P,removeWithTransition:I,applyTransition:R}),Mi=/^v-ref:/,Fi=/^(div|p|span|img|a|b|i|br|ul|ol|li|h1|h2|h3|h4|h5|h6|code|pre|table|th|td|tr|form|label|input|select|option|nav|article|section|header|footer)$/i,Pi=/^(slot|partial|component)$/i,Ii=void 0;"production"!==n.env.NODE_ENV&&(Ii=function(t,e){return e.indexOf("-")>-1?t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:/HTMLUnknownElement/.test(t.toString())&&!/^(data|time|rtc|rb|details|dialog|summary)$/.test(e)});var Ri=Ti.optionMergeStrategies=Object.create(null);Ri.data=function(t,e,i){return i?t||e?function(){var n="function"==typeof e?e.call(i):e,r="function"==typeof t?t.call(i):void 0;return n?dt(n,r):r}:void 0:e?"function"!=typeof e?("production"!==n.env.NODE_ENV&&ji('The "data" option should be a function that returns a per-instance value in component definitions.',i),t):t?function(){return dt(e.call(this),t.call(this))}:e:t},Ri.el=function(t,e,i){if(!i&&e&&"function"!=typeof e)return void("production"!==n.env.NODE_ENV&&ji('The "el" option should be a function that returns a per-instance value in component definitions.',i));var r=e||t;return i&&"function"==typeof r?r.call(i):r},Ri.init=Ri.created=Ri.ready=Ri.attached=Ri.detached=Ri.beforeCompile=Ri.compiled=Ri.beforeDestroy=Ri.destroyed=Ri.activate=function(t,e){return e?t?t.concat(e):Ln(e)?e:[e]:t},Ti._assetTypes.forEach(function(t){Ri[t+"s"]=vt}),Ri.watch=Ri.events=function(t,e){if(!e)return t;if(!t)return e;var n={};_(n,t);for(var i in e){var r=n[i],o=e[i];r&&!Ln(r)&&(r=[r]),n[i]=r?r.concat(o):[o]}return n},Ri.props=Ri.methods=Ri.computed=function(t,e){if(!e)return t;if(!t)return e;var n=Object.create(null);return _(n,t),_(n,e),n};var Li=function(t,e){return void 0===e?t:e},Hi=0;wt.target=null,wt.prototype.addSub=function(t){this.subs.push(t)},wt.prototype.removeSub=function(t){this.subs.$remove(t)},wt.prototype.depend=function(){wt.target.addDep(this)},wt.prototype.notify=function(){for(var t=g(this.subs),e=0,n=t.length;e=this.length&&(this.length=Number(t)+1),this.splice(t,1,e)[0]}),w(Ui,"$remove",function(t){if(this.length){var e=E(this,t);return e>-1?this.splice(e,1):void 0}});var Bi=Object.getOwnPropertyNames(Wi),zi=!0;Et.prototype.walk=function(t){for(var e=Object.keys(t),n=0,i=e.length;n",""],tr:[2,"","
"],col:[2,"","
"]};Mr.td=Mr.th=[3,"","
"],Mr.option=Mr.optgroup=[1,'"],Mr.thead=Mr.tbody=Mr.colgroup=Mr.caption=Mr.tfoot=[1,"","
"],Mr.g=Mr.defs=Mr.symbol=Mr.use=Mr.image=Mr.text=Mr.circle=Mr.ellipse=Mr.line=Mr.path=Mr.polygon=Mr.polyline=Mr.rect=[1,'',""];var Fr=/<([\w:-]+)/,Pr=/&#?\w+?;/,Ir=/