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

1. setCookies & getCookies support & unit tests

2. loadSession & saveSession support & unit tests
3. wechaty.reply() method support
上级 494f476e
......@@ -43,3 +43,4 @@ coverage
0
t.js
t
tmp-chatbot-name-for-unit-testing.wechaty
......@@ -43,8 +43,7 @@ bot
// logToFile(JSON.stringify(msg.rawObj))
if (/^(ding|ping|bing)$/i.test(m.get('content')) && !m.self()) {
const replyMsg = m.reply('dong')
bot.send(replyMsg)
bot.reply(m, 'dong')
.then(() => { log.warn('Bot', 'REPLY: dong') })
}
})
......
......@@ -6,7 +6,7 @@ bot.init()
console.log(`Use Wechat to Scan QR Code in url to login: ${code}\n${url}`)
})
.on('message', m => {
(!m.self()) && bot.send(m.reply('roger')) // 1. reply others' msg
(!m.self()) && bot.reply(m, 'roger') // 1. reply others' msg
.then(() => console.log(`RECV: ${m}, REPLY: "roger"`)) // 2. log message
.catch(e => console.error(e)) // 3. catch exception
})
......
......@@ -25,7 +25,7 @@ class Contact {
return !rawObj ? {} : {
id: rawObj.UserName
, uin: rawObj.Uin // stable id? 4763975
, weixin: rawObj.Alias
, weixin: rawObj.Alias // Wechat ID
, name: rawObj.NickName
, remark: rawObj.RemarkName
, sex: rawObj.Sex
......
......@@ -81,24 +81,6 @@ class Message {
return this.obj.self === this.obj.from
}
reply(replyContent) {
if (this.self()) {
throw new Error('dont reply message send by myself')
}
const m = new Message()
.set('content' , replyContent)
.set('from' , this.obj.to)
.set('to' , this.obj.from)
.set('room' , this.obj.room)
// FIXME: find a alternate way to check a message create by `self`
.set('self' , this.obj.self)
// console.log(m)
return m
}
ready() {
log.silly('Message', 'ready()')
......
......@@ -118,6 +118,7 @@ class Bridge {
log.silly('Bridge', 'proxyWechaty: ' + wechatyScript)
return this.execute(wechatyScript)
}
execute(script, ...args) { return this.browser.execute(script, ...args) }
}
......
......@@ -23,28 +23,16 @@ class Browser {
toString() { return `Class Browser({head:${this.head})` }
init() {
log.verbose('Browser', 'init()')
return this.initDriver()
.then(r => {
log.verbose('Browser', 'initDriver() done')
return this.open()
})
.then(r => {
log.verbose('Browser', 'open() done')
return true
.then(() => this)
.catch(e => { // XXX: must has a `.catch` here, or promise will hang! 2016/6/7
log.error('Browser', 'init() rejectec: %s', e)
throw e
})
}
open() {
const WX_URL = 'https://wx.qq.com'
log.verbose('Browser', `open()ing at ${WX_URL}`)
return this.driver.get(WX_URL)
}
initDriver() {
log.verbose('Browser', 'initDriver()')
log.verbose('Browser', 'init()')
if (this.head) {
this.driver = new WebDriver.Builder()
.setAlertBehavior('ignore')
......@@ -56,6 +44,13 @@ class Browser {
return Promise.resolve(this.driver)
}
open() {
const WX_URL = 'https://wx.qq.com'
log.verbose('Browser', `open()ing at ${WX_URL}`)
return this.driver.get(WX_URL)
}
getPhantomJsDriver() {
// https://github.com/SeleniumHQ/selenium/issues/2069
// setup custom phantomJS capability
......@@ -89,6 +84,7 @@ class Browser {
log.verbose('Browser', 'driver.quit() skipped because no driver')
return Promise.resolve('no driver')
} else if (!this.driver.getSession()) {
this.driver = null
log.verbose('Browser', 'driver.quit() skipped because no driver session')
return Promise.resolve('no driver session')
}
......@@ -148,6 +144,35 @@ class Browser {
return Promise.reject(e)
}
}
getCookies() {
log.silly('Browser', 'getCookies()')
// console.trace("#################")
if (!this.driver || !this.driver.getSession()) {
return Promise.reject('no driver or no session')
}
return this.driver.manage().getCookies()
.then(cookies => {
log.silly('Browser', 'getCookies() got %s cookies', cookies.length)
return cookies
})
.catch(e => {
log.error('Browser', 'getCookies %s', e)
throw e
})
}
setCookies(cookies) { return this.setCookie(cookies) }
setCookie(cookie) {
if (cookie.map) {
return cookie.map(c => {
return this.setCookie(c)
})
}
return this.driver.manage().addCookie(cookie.name, cookie.value, cookie.path, cookie.domain, cookie.secure, cookie.expiry)
}
}
module.exports = Browser
......@@ -17,6 +17,7 @@
*
***************************************/
const util = require('util')
const fs = require('fs')
const log = require('npmlog')
const co = require('co')
......@@ -35,6 +36,7 @@ class PuppetWeb extends Puppet {
options = options || {}
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.head = options.head
this.name = options.name // if not set name, then dont store session.
this.user = null // <Contact> of user self
}
......@@ -68,90 +70,37 @@ class PuppetWeb extends Puppet {
log.verbose('PuppetWeb', 'init() done')
return this // for Chaining
})
// return this.initAttach()
// .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 this // for Chaining
// })
}
quit() {
log.verbose('PuppetWeb', 'quit()')
return co.call(this, function* () {
if (this.bridge) {
yield this.bridge.quit()
this.bridge = null
} else { log.warn('PuppetWeb', 'quit() without bridge') }
if (this.browser) {
yield this.browser.quit()
this.browser = null
} else { log.warn('PuppetWeb', 'quit() without browser') }
if (this.server) {
yield this.server.quit()
this.server = null
} else { log.warn('PuppetWeb', 'quit() without server') }
if (this.browser) {
yield this.browser.quit()
this.browser = null
} else { log.warn('PuppetWeb', 'quit() without browser') }
yield this.initAttach(null)
})
.catch(e => { // Reject
log.error('PuppetWeb', e)
throw e
})
.then(() => { // Finally
.then(() => { // Finally, Fall Safe
log.verbose('PuppetWeb', 'quit() done')
return this // for Chaining
})
// let p = Promise.resolve(true)
// if (this.bridge) {
// p.then(this.bridge.quit.bind(this.bridge))
// this.bridge = null
// } else {
// log.warn('PuppetWeb', 'quit() without bridge')
// }
// if (this.browser) {
// p.then(this.browser.quit.bind(this.browser))
// this.browser = null
// } else {
// log.warn('PuppetWeb', 'quit() without browser')
// }
// if (this.server) {
// p.then(this.server.quit.bind(this.server))
// this.server = null
// } else {
// log.warn('PuppetWeb', 'quit() without server')
// }
// return p // return Promise
}
initAttach(puppet) {
......@@ -163,7 +112,14 @@ class PuppetWeb extends Puppet {
initBrowser() {
log.verbose('PuppetWeb', 'initBrowser')
this.browser = new Browser({ head: this.head })
return this.browser.init()
.then(() => this.loadSession()).catch(e => { /* fail safe */ })
.then(() => this.browser.open())
.catch(e => {
log.error('PuppetWeb', 'initBrowser rejected: %s', e)
throw e
})
}
initBridge() {
log.verbose('PuppetWeb', 'initBridge()')
......@@ -201,6 +157,7 @@ class PuppetWeb extends Puppet {
}
onServerConnection(data) {
this.saveSession()
log.verbose('PuppetWeb', 'onServerConnection: %s', data.constructor.name)
}
onServerDisconnect(data) {
......@@ -213,6 +170,8 @@ class PuppetWeb extends Puppet {
}
onServerLogin(data) {
this.saveSession()
co.call(this, function* () {
// co.call to make `this` context work inside generator.
// See also: https://github.com/tj/co/issues/274
......@@ -233,7 +192,7 @@ class PuppetWeb extends Puppet {
if (this.user) {
this.emit('logout', this.user)
this.user = null
} else { log.warn('PuppetWeb', 'onServerLogout without this.user. still not logined?') }
} else { log.verbose('PuppetWeb', 'onServerLogout without this.user. still not logined?') }
}
onServerMessage(data) {
const m = new Message(data)
......@@ -275,16 +234,34 @@ class PuppetWeb extends Puppet {
if (room) {
destination = room
if (to && to!==room) {
content = `@[${to}] ${content}`
}
// if (to && to!==room) {
// content = `@[${to}] ${content}`
// }
}
log.silly('PuppetWeb', `send(${destination}, ${content})`)
return this.bridge.send(destination, content)
}
reply(message, replyContent) {
if (message.self()) {
throw new Error('dont reply message send by myself')
}
const m = new Message()
.set('content' , replyContent)
.set('from' , message.obj.to)
.set('to' , message.obj.from)
.set('room' , message.obj.room)
// FIXME: find a alternate way to check a message create by `self`
.set('self' , this.user.id)
console.log(m)
return this.send(m)
}
logout() { return this.bridge.logout() }
logout() { return this.bridge.logout() }
getContact(id) { return this.bridge.getContact(id) }
getLoginQrImgUrl() {
if (!this.bridge) {
......@@ -294,6 +271,49 @@ class PuppetWeb extends Puppet {
return this.bridge.getLoginQrImgUrl()
}
logined() { return !!(this.user) }
saveSession() {
if (!this.name) { return Promise.reject('saveSession() no name') }
const filename = this.name
return new Promise((resolve, reject) => {
this.browser.getCookies()
.then(cookies => {
// console.log(cookies)
const jsonStr = JSON.stringify(cookies)
fs.writeFile(filename, jsonStr, function(err) {
if(err) {
log.error('PuppetWeb', 'saveSession() fail to write file %s: %s', filename, err.Error)
return reject(err)
}
return resolve(cookies)
})
})
})
}
loadSession() {
if (!this.name) { return Promise.reject('loadSession() no name') }
const filename = this.name
return new Promise((resolve, reject) => {
fs.readFile(filename, (err, jsonStr) => {
if (err) {
if (err.code == 'ENOENT') {
log.verbose('PuppetWeb', 'loadSession() skipped coz no saved session')
} else {
log.verbose('PuppetWeb', 'loadSession() fail: %s', err.Error)
}
return reject(err.Error)
}
const cookies = JSON.parse(jsonStr)
return Promise.all(this.browser.setCookies(cookies))
.then(() => resolve(cookies))
.catch(e => reject(e))
})
})
}
}
module.exports = PuppetWeb
......@@ -25,6 +25,7 @@ class Puppet extends EventEmitter {
* @return <Promise>
*/
send(message) { throw new Error('To Be Implemented') }
reply(message, reply) { throw new Error('To Be Implemented') }
logout() { throw new Error('To Be Implementsd') }
ding() { throw new Error('To Be Implementsd') }
......
......@@ -24,6 +24,7 @@ class Wechaty extends EventEmitter {
super()
this.options = options || {}
this.options.puppet = this.options.puppet || 'web'
this.options.name = this.options.name // no name, no session restore
this.VERSION = require('../package.json').version
}
......@@ -44,6 +45,7 @@ class Wechaty extends EventEmitter {
this.puppet = new Puppet.Web({
head: this.options.head
, port: this.options.port
, name: this.options.name
})
break
default:
......
......@@ -24,7 +24,7 @@ test('Bridge retry-promise testing', function(t) {
}
const retryPromise = require('retry-promise').default
var delay50 = delayedFactory(50)
yield retryPromise({max:1, backoff: 10}, function() {
return delay50()
......@@ -68,7 +68,8 @@ test('Bridge smoking test', function(t) {
co(function* () {
yield browser.init()
t.pass('inited')
yield browser.open()
t.pass('inited & opened')
yield b.inject()
t.pass('wechaty injected')
......@@ -91,7 +92,7 @@ test('Bridge smoking test', function(t) {
t.pass('b.quit()')
yield browser.quit()
t.pass('browser.quit()')
t.end()
})
})
......
const co = require('co')
const test = require('tap').test
const log = require('npmlog')
// log.level = 'silly'
const Browser = require('../src/puppet-web-browser')
const PORT = 58788
......@@ -9,14 +11,42 @@ test('Browser class smoking tests', function(t) {
t.ok(b, 'Browser instance created')
co(function* () {
yield b.initDriver()
t.pass('inited driver')
yield b.init()
t.pass('inited')
yield b.open()
t.pass('opened')
const two = yield b.execute('return 1+1')
t.equal(two, 2, 'execute script ok')
let cookies = yield b.getCookies()
t.ok(cookies.length, 'should got plenty of cookies')
const EXPECTED_COOKIES = [{
name: 'wechaty0'
, value: '8788-0'
, path: '/'
, domain: '.qq.com'
, secure: false
, expiry: 99999999999999
}
, {
name: 'wechaty1'
, value: '8788-1'
, path: '/'
, domain: '.qq.com'
, secure: false
, expiry: 99999999999999
}]
yield b.setCookie(EXPECTED_COOKIES)
cookies = yield b.getCookies()
const cookies0 = cookies.filter(c => { return RegExp(EXPECTED_COOKIES[0].name).test(c.name) })
t.equal(cookies0[0].name, EXPECTED_COOKIES[0].name, 'should filter out the cookie named wechaty0')
const cookies1 = cookies.filter(c => { return RegExp(EXPECTED_COOKIES[1].name).test(c.name) })
t.equal(cookies1[0].name, EXPECTED_COOKIES[1].name, 'should filter out the cookie named wechaty1')
})
.catch((e) => { // Rejected
t.fail('co promise rejected:' + e)
......
......@@ -6,6 +6,7 @@ const log = require('npmlog')
const PuppetWeb = require('../src/puppet-web')
const PORT = 58788
const NAME = 'tmp-chatbot-name-for-unit-testing.wechaty'
function dingSocket(server) {
const maxTime = 9000
......@@ -105,36 +106,83 @@ false && test('Puppet Web server/browser communication', function(t) {
})
test('Puppet Web WTF server/browser communication', function(t) {
const pw = new PuppetWeb({port: PORT})
t.ok(pw, 'new PuppetWeb')
const pw = new PuppetWeb({port: PORT})
t.ok(pw, 'new PuppetWeb')
pw.init()
.then(r => {
t.pass('pw inited')
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
})
test('Puppet Web browser session save & load', function(t) {
let pw
pw = new PuppetWeb({port: PORT, name: NAME})
t.ok(pw, 'create PuppetWeb')
co(function* () {
yield pw.init()
t.pass('pw inited')
const EXPECTED_COOKIE = {
name: 'wechaty'
, value: '8788'
, path: '/'
, domain: '.qq.com'
, secure: false
, expiry: 99999999999999
}
pw.browser.setCookies(EXPECTED_COOKIE)
const cookies = yield pw.saveSession()
t.ok(cookies, 'saveSession should resolve cookies')
t.ok(cookies.length, 'cookies length should more than 0')
const cookiesLoad = yield pw.loadSession()
const cookiesFiltered = cookiesLoad.filter(c => /wechaty/i.test(c.name))
t.ok(cookiesLoad.length, 'pw session loaded')
yield pw.quit()
pw = new PuppetWeb({port: PORT, name: NAME})
yield pw.init()
const cookiesAfterQuit = yield pw.loadSession()
const cookiesFiltered2 = cookiesLoad.filter(c => /wechaty/i.test(c.name))
t.ok(cookiesFiltered2.length, 'should get old cookie after browser re-opened')
})
.catch(e => { // Reject
log.warn('TestingPuppetWeb', 'error: %s', e)
t.fail(e)
})
.then(r => { // Finally
pw.quit()
t.end()
})
.catch(e => { t.fail(e) }) // Exception
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
})
......@@ -32,7 +32,8 @@ test('WebDriver process create & quit test', function(t) {
t.ok(b, 'Browser instnace')
yield b.init()
t.pass('inited')
yield b.open()
t.pass('inited & opened')
let n = yield driverProcessNum()
t.ok(n > 0, 'driver process exist')
......@@ -68,7 +69,6 @@ test('WebDriver smoke testing', function(t) {
driver = yield wb.initDriver()
t.ok(driver, 'driver inited')
const injectio = bridge.getInjectio()
t.ok(injectio.length > 10, 'got injectio')
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册