龐大的用戶安裝量和恐怖的用戶使用時間,微信已成爲國內移動互聯網上基礎設施級的應用。前端
以微信爲平臺的客服服務有不少方式,好比訂閱號,服務號,小程序,但受到微信官方的限制,若是想作一個聊天羣的自助客服, 或者私人訂製的客服,那就沒有了官方的自動化方案。例如場景一:折扣商品導購羣裏,只要@客服+產品名,就能夠立刻反饋一條產品折扣信息。場景二:徒步羣裏參加一個活動報名,只須要@客服+活動名稱 就能夠自助報名參加活動。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); }