庞大的用户安装量和恐怖的用户使用时间,微信已成为国内移动互联网上基础设施级的应用。前端
以微信为平台的客服服务有不少方式,好比订阅号,服务号,小程序,但受到微信官方的限制,若是想作一个聊天群的自助客服, 或者私人订制的客服,那就没有了官方的自动化方案。例如场景一:折扣商品导购群里,只要@客服+产品名,就能够立刻反馈一条产品折扣信息。场景二:徒步群里参加一个活动报名,只须要@客服+活动名称 就能够自助报名参加活动。node
以Electron-nightmare一周时间开发的微信天气查询助手 ,就是一次技术验证性尝试。python
-
git
python的优势,python是很是棒的语言,有着广阔的舞台,在可预见的将来,python的热度还将大幅提高。python有大量的三方库支持,各类重量级的应用框架、前瞻性产品基本上都提供python接口,有大量的社区。就微信机器人来讲,有很是优秀的itchat。但就针对微信来讲,python不是最好的解决方案。由于python须要从底层web通信协议开始分析,须要用抓包、分解、组装,完彻底全的实现一个web协议的机器人,这其间的开发工做量过大。并且越陷入web底层,未来随着官方协议变动,维护版本的工做量也不容忽视。这背离了咱们的初衷:在松耦合基础上尽量的专一业务自己。并且python还面临2与3的不兼容的坑,也是须要考虑的问题。angularjs
phantomjs 一个headless的浏览器框架,基于webkit。第一次了解到phantomjs是用来作爬虫,phantomjs很是适合动态页面的内容爬取,并且phantomjs能够很方便的运行在linux服务器上,相对于普通的浏览器,phantomjs消耗的内存要少太多。但phantomjs也有一些问题。最大的问题就是phantomjs的headless,没法很简单的测试代码片断。而这几年浏览器前端发展迅速,各类js新的特性不断涌现,但phantomjs兼容性问题也凸显:例如phantomjs对set Object就不支持。并且就总体性能而言,没有V8引擎的支持的js,在执行效率上也要打折扣。因此咱们仍是要尽量站在巨人的肩膀上拓展业务。github
站在巨人的肩上,Electron。Electron是基于Google的Chromium框架,原本是专一快速开发跨平台桌面应用。但基于高性能浏览器的Electron是天生的web自动化框架。配合Node.js的express,能够很简单的实现一个RESTFul服务。而浏览器的可视化调试工具也大大提升了测试、调试效率。在Electron上作微信网页版自动化更像是作一个plugin,这将减小了大量的的开发工做。web
功能 | python | phantomjs | Electron | |
---|---|---|---|---|
三方库 | ★★★★★ | ★★★★ | ★★★★☆ | |
学习容易 | ★★★☆ | ★★★☆ | ★★★☆ | |
开发效率 | ★★ | ★★★☆ | ★★★★☆ | |
方便测试 | ★★☆ | ★★☆ | ★★★★☆ | |
运行效率 | ★★★★☆ | ★★☆ | ★★★☆ | |
维护简单 | ★★☆ | ★★★☆ | ★★★★☆ | |
硬件要求低 | ★★★★☆ | ★★★☆ | ★★☆ |
从模块分离的原则,在设计上,将微信机器人做为服务提供出来,这样便可以在之后按照其约定方式(RESTFul)提供新的对接方式,也隔离的自己业务与机器人服务之间的耦合性,并且分离后,基础服务能够远程部署。设计思路以下图chrome
当前(2017-11-30日)微信网页版是基于AngularJS v1.2.28开发的,采用MVC分离模式。经过分析最主要的index.js源码咱们能够大概知道各个功能模块的组合方式。express
在了解网页版模式后,接下来就是获取,并调用对应的功能模块,具体实现位于 wxinjector.js
var injector = angular.element(document.body).injector(); var F = { chatFactory:injector.get('chatFactory'), contactFactory:injector.get('contactFactory'), confFactory:injector.get('confFactory'), loginFactory:injector.get('loginFactory'), accountFactory:injector.get('accountFactory'), utilFactory:injector.get('utilFactory'), } var CTRLS = { appController:angular.element(document.body).scope(), loginController:angular.element(document.querySelector("body > div.login.ng-scope")).scope(), chatSenderController:angular.element(document.querySelector("#chatArea > div.box_ft.ng-scope")).scope(), }
chrome的dev-tools是很是好用的可视化测试工具,咱们能够将功能片断一点一点的在console中测试后,再添加到项目的代码中
angular.element(document.body).injector().get('contactFactory').getAllContacts();
function SendMessage(ToUserName, msg){ var a = angular.element(document.querySelector("#editArea")).scope(); var confFactory = angular.element(document.body).injector().get('confFactory') var chatFactory = angular.element(document.body).injector().get('chatFactory'); a.editAreaCtn = msg; var e = chatFactory.createMessage({ ToUserName:ToUserName, MsgType: confFactory.MSGTYPE_TEXT, Content: a.editAreaCtn }); chatFactory.appendMessage(e), chatFactory.sendMessage(e), // O[chatFactory.getCurrentUserName()] = "", a.editAreaCtn = ""; }
|____conf # 程序配置文件 | |____service.json | |____wxconf.js | |____cities.json # 城市气象编码表 |____lib # 模块代码 | |____weather.js | |____inject | | |____wxinjector.js | |____wxbot.js |____test # 测试代码目录 |____.gitignore |____package.json |____README.md |____sy-cli.js # 天气问答业务程序 |____syaya.js # 微信基础服务程序
$sudo apt-get install xvfb libgtk2.0-0 libnotify-bin libgconf-2-4 libnss3 libasound2 libcap2-bin libcups2 libxtst6 libxss1 $xvfb-run node --harmony syaya.js
page.engin.on('dom-ready', function () { console.log("DOM-READY for inject..."); page.engin.inject("js", WX_HELPER_JS); });
var bodyParser = require('body-parser'); var app = express(); app.use(bodyParser.json({ limit: '100kb', type: 'application/x-www-form-urlencoded' }));
async function sleep(ms) { return new Promise(resolve => { var tm = setTimeout(() => { console.log("clear", tm); clearTimeout(tm); resolve(0); }, ms); }); } async function test_sleep(){ var r = await sleep(2000); console.log(r); } test_sleep();
var co = require("co"); function process_one_message(msg){ co(function *(message) { var action = yield extract_action(message); var r = yield action.do_step1(); if(r.status == "success"){ r = yield action.do_step2(); } r = yield action.do_final(); r = yield make_response(action, message); }, msg); }