提交 aa8c7485 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

code clean & better tests

上级 1bec6e51
......@@ -17,8 +17,14 @@ const EventEmitter2 = require('eventemitter2')
const Wechaty = require('../src/wechaty')
//log.level = 'verbose'
log.level = 'silly'
// log.level = 'silly'
/**
*
* Apply Your Own Tuling123 Developer API_KEY at:
* http://www.tuling123.com
*
*/
const TULING123_API_KEY = '18f25157e0446df58ade098479f74b21'
const brain = new Tuling123(TULING123_API_KEY)
......@@ -27,6 +33,10 @@ const bot = new Wechaty({head: false})
console.log(`
Welcome to Tuling Wechaty Bot.
Tuling API: http://www.tuling123.com/html/doc/api.html
Notice: This bot will only active in the group whose name contains 'wechaty'.
/* if (m.group() && /Wechaty/i.test(m.group().name())) { */
Loading...
`)
......@@ -121,9 +131,11 @@ function talk(m) {
const talkerName = fromId + groupId
if (!Talkers[talkerName]) {
Talkers[talkerName] = new Talker(function(text) {
log.info('Tuling123', 'Talker is thinking how to reply: %s', text)
return brain.ask(text, {userid: talkerName})
.then(r => r.text)
.then(r => {
log.info('Tuling123', 'Talker reply:"%s" for "%s" ', r.text, text)
return r.text
})
})
Talkers[talkerName].on('say', reply => bot.reply(m, reply))
}
......
......@@ -19,7 +19,7 @@ class Bridge {
this.browser = options.browser
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
}
toString() { return `Class Bridge({browser: ${options.browser}, port: ${options.port}})` }
toString() { return `Class Bridge({browser: ${this.options.browser}, port: ${this.options.port}})` }
init() {
log.verbose('Bridge', 'init()')
......@@ -27,9 +27,15 @@ class Bridge {
}
logout() { return this.proxyWechaty('logout') }
quit() { return this.proxyWechaty('quit') }
getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') }
getLoginQrImgUrl() { return this.proxyWechaty('getLoginQrImgUrl') }
quit() {
log.verbose('Bridge', 'quit()')
return this.proxyWechaty('quit')
}
// @Deprecated
// getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') }
// getLoginQrImgUrl() { return this.proxyWechaty('getLoginQrImgUrl') }
getUserName() { return this.proxyWechaty('getUserName') }
getContact(id) {
......@@ -39,11 +45,11 @@ class Bridge {
}
/**
* Call a function repeatly untill it return a value
*
* @param <function> pfunc
* @param <number> timeout
* Call a function repeatly untill it return a resolved promise
*
* @param {Function} pfunc
* @param {Number} timeout
* @TODO: change waitData to retry-promise
*/
waitData(pfunc, timeout) {
log.silly('Bridge', 'waitData()')
......@@ -135,3 +141,12 @@ class Bridge {
}
module.exports = Bridge
/*
*
ac = Wechaty.glue.contactFactory.getAllContacts();
Object.keys(ac).filter(function(k) { return /李/.test(ac[k].NickName) }).map(function(k) { var c = ac[k]; return {NickName: c.NickName, Alias: c.Alias, Uin: c.Uin, MMInChatRoom: c.MMInChatRoom} })
Object.keys(window._chatContent).filter(function (k) { return window._chatContent[k].length > 0 }).map(function (k) { return window._chatContent[k].map(function (v) {return v.MMDigestTime}) })
*
*/
\ No newline at end of file
......@@ -23,23 +23,23 @@ class Browser {
init() {
log.verbose('Browser', 'init()')
return this.initDriver()
.then(this.open.bind(this))
.then(r => {
log.verbose('Browser', 'inited: ' + this.toString())
log.verbose('Browser', 'initDriver() done')
return this.open()
})
.then(r => {
log.verbose('Browser', 'open() done')
return true
})
}
open() {
const WX_URL = 'https://wx.qq.com'
log.verbose('Browser', `open() at ${WX_URL}`)
try {
return this.driver.get(WX_URL)
} catch (e) { // WebDriver exception
// TODO: try to fix this by re-open webdriver 3 times
log.error('Browser', 'open be rejected: %s', e)
return Promise.reject(e)
}
return this.driver.get(WX_URL)
}
initDriver() {
......@@ -85,16 +85,60 @@ class Browser {
quit() {
log.verbose('Browser', 'quit()')
if (!this.driver) {
log.verbose('Browser', 'no need to quite because no driver')
log.verbose('Browser', 'driver.quit() skipped because no driver')
return Promise.resolve('no driver')
} else if (!this.driver.getSession()) {
log.verbose('Browser', 'driver.quit() skipped because no driver session')
return Promise.resolve('no driver session')
}
log.verbose('Browser', 'driver.quit')
this.driver.close() // http://stackoverflow.com/a/32341885/1123955
this.driver.quit()
this.driver = null
return Promise.resolve()
return this.waitClean()
}
waitClean() {
const retry = require('retry-promise').default // https://github.com/olalonde/retry-promise
return retry({ max: 5, backoff: 300 }, function (attempt) {
log.verbose('Browser', `waitClean() retryPromise: Attempt ${attempt}`)
return driverProcessNum()
.then(n => {
if (n > 0) throw new Error('reject because there has driver process not exited')
log.verbose('Browser', 'waitClean hit')
return n
})
})
.catch(e => {
log.error('Browser', 'waitClean() retryPromise failed: %s', e)
})
///////////////////////////////////////
function driverProcessNum() {
return new Promise((resolve, reject) => {
require('ps-tree')(process.pid, (err, data) => {
if (err) { return reject(err) }
const num = data.filter(obj => /phantomjs/i.test(obj.COMMAND)).length
resolve(num)
})
/**
*
* raw ps & grep
*
const exec = require('child_process').exec
exec('ps axf | grep phantomjs | grep -v grep | wc -l', function(err, stdout, stderr) {
if (err) {
return reject(err)
}
return resolve(stdout[0])
})
*
**/
})
}
}
execute(script, ...args) {
//log.verbose('Browser', `Browser.execute(${script})`)
if (!this.driver) {
......
......@@ -8,16 +8,6 @@
* Licenst: MIT
* https://github.com/zixia/wechaty-lib
*
MMCgi.isLogin
loginScope.qrcodeUrl
loginScope.code:
0: 显示二维码
201: 扫描,未确认
200: 登录成功
408: 未确认
*/
if (typeof Wechaty !== 'undefined') {
return 'Wechaty already injected?'
......@@ -142,6 +132,11 @@ return (function(port) {
return
}
// loginScope.code:
// 0: 显示二维码
// 201: 扫描,未确认
// 200: 登录成功
// 408: 未确认
var code = +Wechaty.glue.loginScope.code
var url = Wechaty.glue.loginScope.qrcodeUrl
if (code !== Wechaty.vars.scanCode) {
......
......@@ -35,6 +35,9 @@ class Server extends EventEmitter {
, r => resolve(r), e => reject(e)
)
this.socketServer = this.createSocketIo(this.httpsServer)
}).then(r => {
log.verbose('Server', 'full init()-ed')
return true
})
}
......@@ -150,6 +153,7 @@ class Server extends EventEmitter {
this.httpsServer.close()
this.httpsServer = null
}
return Promise.resolve(true)
}
}
......
......@@ -41,20 +41,39 @@ class PuppetWeb extends Puppet {
init() {
log.verbose('PuppetWeb', 'init()')
return this.initAttach()
.then(this.initBrowser.bind(this))
.then(this.initBridge.bind(this))
.then(this.initServer.bind(this))
.catch(e => {
.then(r => {
log.verbose('PuppetWeb', 'initAttach done: %s', r)
return this.initBrowser()
})
.then(r => {
log.verbose('PuppetWeb', 'initBrowser done: %s', r)
return this.initBridge()
})
.then(r => {
log.verbose('PuppetWeb', 'initBridge done: %s', r)
return this.initServer()
})
.then(r => {
log.verbose('PuppetWeb', 'initServer done: %s', r)
return r
})
.catch(e => { // Reject
log.error('PuppetWeb', e)
throw e
})
.then(r => { // Finally
log.verbose('PuppetWeb', 'all initXXX done.')
return true
})
}
initAttach() {
log.verbose('PuppetWeb', 'initAttach()')
Contact.attach(this)
Group.attach(this)
return Promise.resolve()
return Promise.resolve(true)
}
initBrowser() {
log.verbose('PuppetWeb', 'initBrowser')
......@@ -77,12 +96,13 @@ class PuppetWeb extends Puppet {
server.on('logout', this.onServerLogout.bind(this))
server.on('message', this.onServerMessage.bind(this))
server.on('unload', this.onServerUnload.bind(this))
server.on('connection', this.onServerConnection.bind(this))
server.on('disconnect', this.onServerDisconnect.bind(this))
server.on('log', this.onServerLog.bind(this))
;[ // simple server events forwarding
'connection'
, 'disconnect'
;[ // Public events to end user
, 'scan'
, 'log'
, 'dong'
].map(e => {
server.on(e, data => {
......@@ -95,8 +115,22 @@ class PuppetWeb extends Puppet {
return this.server.init()
}
onServerConnection(data) {
log.verbose('PuppetWeb', 'onServerConnection: %s', data)
}
onServerDisconnect(data) {
log.verbose('PuppetWeb', 'onServerDisconnect: %s', data)
log.verbose('PuppetWeb', 'onServerDisconnect: unloaded? call onServerUnload to try to fix connection')
this.onServerUnload(data)
}
onServerLog(data) {
log.verbose('PuppetWeb', 'onServerLog: %s', data)
}
onServerLogin(data) {
co(function* () {
co.call(this, function* () {
// co.call to make `this` context work inside generator.
// See also: https://github.com/tj/co/issues/274
const userName = yield this.bridge.getUserName()
if (!userName) {
log.silly('PuppetWeb', 'onServerLogin: browser not full loaded, retry later.')
......@@ -107,7 +141,7 @@ class PuppetWeb extends Puppet {
this.user = yield Contact.load(userName).ready()
log.verbose('PuppetWeb', `user ${this.user.name()} logined`)
this.emit('login', this.user)
}.bind(this))
})
.catch(e => log.error('PuppetWeb', 'onServerLogin rejected: %s', e))
}
onServerLogout(data) {
......@@ -167,10 +201,20 @@ class PuppetWeb extends Puppet {
*/
quit() {
log.verbose('PuppetWeb', 'quit()')
if (this.server) { this.server.quit() }
if (this.bridge) { this.bridge.quit() }
if (this.browser) { this.browser.quit() }
let p = Promise.resolve(true)
if (this.server) { p.then(this.server.quit.bind(this)) }
else { log.warn('PuppetWeb', 'quit() without server') }
if (this.bridge) { p.then(this.bridge.quit.bind(this)) }
else { log.warn('PuppetWeb', 'quit() without bridge') }
if (this.browser) { p.then(this.browser.quit.bind(this)) }
else { log.warn('PuppetWeb', 'quit() without browser') }
return p // return Promise
}
isLogined() { return !!(this.user) }
}
......
......@@ -6,7 +6,8 @@
* https://github.com/zixia/wechaty
*
*/
const EventEmitter = require('events')
const log = require('npmlog')
const EventEmitter = require('events')
const Puppet = require('./puppet')
const PuppetWeb = require('./puppet-web')
......@@ -20,9 +21,12 @@ class Wechaty extends EventEmitter {
super()
this.options = options || {}
this.options.puppet = this.options.puppet || 'web'
this.VERSION = require('../package.json').version
}
toString() { return 'Class Wechaty(' + this.puppet + ')'}
init() {
log.verbose('Wechaty', 'init() with version: %s', this.VERSION)
this.initPuppet()
this.initEventHook()
return this.puppet.init()
......@@ -36,8 +40,7 @@ class Wechaty extends EventEmitter {
})
break
default:
throw new Error('Puppet unsupport(yet): ' + puppet)
break
throw new Error('Puppet unsupport(yet): ' + this.options.puppet)
}
return Promise.resolve(this.puppet)
}
......
......@@ -41,6 +41,7 @@ test('PuppetWebServer basic tests', function(t) {
test('PuppetWebServer smoke testing', function(t) {
const server = new PuppetWebServer({port: PORT})
t.ok(server, 'new server instance')
co(function* () {
const retInit = yield server.init()
......
const co = require('co')
const test = require('tap').test
const log = require('npmlog')
const co = require('co')
const test = require('tap').test
const log = require('npmlog')
log.level = 'verbose'
// log.level = 'silly'
const PuppetWeb = require('../src/puppet-web')
const PORT = 58788
false && test('PuppetWeb smoke testing', function(t) {
let pw
co(function* () {
pw = new PuppetWeb({port: PORT})
t.ok(pw, 'new PuppetWeb')
yield pw.init()
t.pass('pw full inited')
t.equal(pw.isLogined() , false , 'instance not logined')
// XXX find a better way to mock...
pw.bridge.getUserName = function () { return Promise.resolve('mockedUserName') }
const p1 = new Promise((resolve) => {
pw.once('login', r => {
t.equal(pw.isLogined() , true , 'logined after login event')
resolve()
})
})
pw.server.emit('login')
yield p1
const p2 = new Promise((resolve) => {
pw.once('logout', r => {
t.equal(pw.isLogined() , false , 'logouted after logout event')
resolve()
})
function dingSocket(server) {
const maxTime = 9000
const waitTime = 500
let totalTime = 0
return new Promise((resolve, reject) => {
log.verbose('TestingPuppetWeb', 'dingSocket()')
return testDing()
function testDing() {
// log.silly('TestingPuppetWeb', server.socketio)
if (!server.socketClient) {
totalTime += waitTime
if (totalTime > maxTime) {
return reject('timeout after ' + totalTime + 'ms')
}
log.silly('TestingPuppetWeb', 'waiting socketClient to connect for ' + totalTime + '/' + maxTime + ' ms...')
setTimeout(testDing, waitTime)
return
}
//log.silly('TestingPuppetWebServer', server.socketClient)
server.socketClient.once('dong', data => {
log.verbose('TestingPuppetWeb', 'socket recv event dong: ' + data)
return resolve(data)
})
pw.server.emit('logout')
yield p2
server.socketClient.emit('ding')
}
})
}
false && test('PuppetWeb smoke testing', function(t) {
let pw
co(function* () {
pw = new PuppetWeb({port: PORT})
t.ok(pw, 'new PuppetWeb')
yield pw.init()
t.pass('pw full inited')
t.equal(pw.isLogined() , false , 'instance not logined')
// XXX find a better way to mock...
pw.bridge.getUserName = function () { return Promise.resolve('mockedUserName') }
const p1 = new Promise((resolve) => {
pw.once('login', r => {
t.equal(pw.isLogined() , true , 'logined after login event')
resolve()
})
})
.catch(e => t.fail(e)) // Reject
.then(r => { // Finally
pw.quit()
t.end()
pw.server.emit('login')
yield p1
const p2 = new Promise((resolve) => {
pw.once('logout', r => {
t.equal(pw.isLogined() , false , 'logouted after logout event')
resolve()
})
})
.catch(e => t.fail(e)) // Exception
pw.server.emit('logout')
yield p2
})
.catch(e => t.fail(e)) // Reject
.then(r => { // Finally 1
log.warn('TestingPuppetWeb', 'finally()')
return pw.quit()
})
.then(r => { t.end() }) // Finally 2
.catch(e => t.fail(e)) // Exception
})
test('Puppet Web server/browser communication', function(t) {
// WTF?
false && test('Puppet Web server/browser communication', function(t) {
let pw2
co(function* () {
pw2 = new PuppetWeb({port: PORT})
t.ok(pw2, 'new PuppetWeb')
yield Promise.resolve()
yield pw2.init()
t.pass('pw2 inited')
const retSocket = yield dingSocket(pw2.server)
t.equal(retSocket, 'dong', 'dingSocket got dong')
})
.catch(e => t.fail(e)) // Reject
.catch(e => {
log.warn('TestingPuppetWeb', 'error: %s', e)
t.fail(e)
}) // Reject
.then(r => { // Finally
pw2.quit()
t.end()
})
.catch(e => { t.fail(e) }) // Exception
return // The following is help functions only
//////////////////////////////////////////////
function dingSocket(server) {
const maxTime = 9000
const waitTime = 500
let totalTime = 0
return new Promise((resolve, reject) => {
log.verbose('TestingPuppetWeb', 'dingSocket()')
return testDing()
function testDing() {
// log.silly('TestingPuppetWeb', server.socketio)
if (!server.socketClient) {
totalTime += waitTime
if (totalTime > maxTime) {
return reject('timeout after ' + totalTime + 'ms')
}
log.silly('TestingPuppetWeb', 'waiting socketClient to connect for ' + totalTime + '/' + maxTime + ' ms...')
setTimeout(testDing, waitTime)
return
}
//log.silly('TestingPuppetWebServer', server.socketClient)
server.socketClient.once('dong', data => {
log.verbose('TestingPuppetWeb', 'socket recv event dong: ' + data)
return resolve(data)
})
server.socketClient.emit('ding')
}
})
test('Puppet Web WTF server/browser communication', function(t) {
const pw = new PuppetWeb({port: PORT})
t.ok(pw, 'new PuppetWeb')
pw.init()
.then(r => {
t.pass('pw inited')
return dingSocket(pw.server)
})
.then(retSocket => {
t.equal(retSocket, 'dong', 'dingSocket got dong')
return true
})
.catch(e => { // Reject
log.warn('TestingPuppetWeb', 'error: %s', e)
t.fail(e)
throw e
})
.then(r => { // Finally 1
t.pass('dingSocket resolved')
return pw.quit()
})
.then(r => { // Finally 2
t.pass('pw.quit() resolved')
t.end()
})
}
.catch(e => {
t.fail(e)
throw e
}) // Exception
})
......@@ -2,7 +2,8 @@ const path = require('path')
const co = require('co')
const test = require('tap').test
const log = require('npmlog')
//log.level = 'silly'
log.level = 'verbose'
log.level = 'silly'
const WebDriver = require('selenium-webdriver')
const Browser = WebDriver.Browser
......@@ -12,24 +13,86 @@ const PuppetWebBrowser = require('../src/puppet-web-browser')
const PuppetWebBridge = require('../src/puppet-web-bridge')
const PORT = 58788
test('WebDriver smoke testing', function(t) {
const wb = new PuppetWebBrowser({port: PORT})
function driverProcessNum() {
return new Promise((resolve, reject) => {
// require('ps-tree')(process.pid, (err, data) => {
// if (err) { return reject(err) }
// data.forEach(c => console.log(c))
// const num = data.filter(obj => /phantomjs/i.test(obj.COMMAND)).length
// return resolve(num)
// })
const exec = require('child_process').exec
exec('ps axf >> /tmp/ps.log', r=>r)
exec('ps axf | grep phantomjs | grep -v grep | wc -l >> /tmp/ps.log', r=>r)
exec('ps axf | grep phantomjs | grep -v grep | wc -l', function(err, stdout, stderr) {
if (err) {
return reject(err)
}
return resolve(parseInt(stdout[0]))
})
})
}
test('WebDriver process create & quit test', function(t) {
co(function* () {
const b = new PuppetWebBrowser({port: PORT})
t.ok(b, 'Browser instnace')
yield b.init()
t.pass('inited')
let n = yield driverProcessNum()
t.ok(n > 0, 'driver process exist')
// console.log(b.driver.getSession())
yield b.quit()
t.pass('quited')
n = yield driverProcessNum()
t.equal(n, 0, 'no driver process after quit')
})
.catch(e => { t.fail(e) })
.then(t.end.bind(t))
return
})
// XXX WTF with co module???
false && test('WebDriver smoke testing', function(t) {
const wb = new PuppetWebBrowser()
t.ok(wb, 'Browser instnace')
const bridge = new PuppetWebBridge({browser: wb})
const bridge = new PuppetWebBridge({browser: wb, port: PORT})
t.ok(bridge, 'Bridge instnace')
var driver // for help function `execute`
co(function* () {
const m = yield driverProcessNum()
t.equal(m, 0, 'driver process not exist before get()')
driver = yield wb.initDriver()
t.ok(driver, 'driver inited')
const injectio = bridge.getInjectio()
t.ok(injectio.length > 10, 'got injectio')
// XXX: if get rid of this dummy,
// driver.get() will fail due to cant start phantomjs process
// yield Promise.resolve()
yield driver.get('https://wx.qq.com/')
t.pass('driver got url')
t.pass('driver url opened')
const n = yield driverProcessNum()
t.ok(n > 0, 'driver process exist after get()')
const retAdd = yield execute('return 1+1')
t.equal(retAdd, 2, 'execute js in browser')
......@@ -38,21 +101,73 @@ test('WebDriver smoke testing', function(t) {
t.equal(retInject, 'Wechaty', 'injected wechaty')
})
.catch(e => { // REJECTED
t.ok(false, 'promise rejected. e:' + e)
.catch(e => t.fail('promise rejected. e:' + e)) // Rejected
.then(r => wb.quit()) // Finally 1
.then(r => t.end()) // Finally 2
.catch(e => t.fail('exception got:' + e)) // Exception
return
//////////////////////////////////
function execute() {
return driver.executeScript.apply(driver, arguments)
}
})
test('WebDriver WTF testing', function(t) {
const wb = new PuppetWebBrowser()
t.ok(wb, 'Browser instnace')
const bridge = new PuppetWebBridge({browser: wb, port: PORT})
t.ok(bridge, 'Bridge instnace')
var driver // for help function `execute`
driverProcessNum()
.then(n => {
t.equal(n, 0, 'driver process not exist before get()')
return wb.initDriver()
})
.then(d => {
driver = d
t.ok(driver, 'driver inited')
return bridge.getInjectio()
})
.then(() => { // FINALLY
t.end()
driver.quit()
.then(r => {
injectio = r
t.ok(injectio.length > 10, 'got injectio')
return driver.get('https://wx.qq.com/')
})
.catch(e => { // EXCEPTION
t.fail('exception got:' + e)
.then(r => {
t.pass('driver url opened')
return driverProcessNum()
})
.then(n => {
t.ok(n > 0, 'driver process exist after get()')
return execute('return 1+1')
})
.then(retAdd => {
t.equal(retAdd, 2, 'execute js in browser')
return execute(injectio, PORT)
})
.then(retInject => {
t.equal(retInject, 'Wechaty', 'injected wechaty')
})
.catch(e => t.fail('promise rejected. e:' + e)) // Rejected
.then(r => wb.quit()) // Finally 1
.then(r => t.end()) // Finally 2
.catch(e => t.fail('exception got:' + e)) // Exception
return
//////////////////////////////////
function execute() {
return driver.executeScript.apply(driver, arguments)
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册