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

re-structed code design, server/browser now are flat

上级 aaadb190
......@@ -27,7 +27,7 @@ Please wait... I'm trying to login in...
`
console.log(welcome)
const bot = new Wechaty({browser: 'chrome'})
const bot = new Wechaty({head: true})
bot.init()
.then(login)
......
const log = require('npmlog')
log.level = 'silly'
const Wechaty = require('../src/wechaty')
const bot = new Wechaty({browser: 'chrome'})
const bot = new Wechaty({head: true})
bot.init()
.then(bot.getLoginQrImgUrl.bind(bot))
......
{
"name": "wechaty",
"version": "0.0.5",
"description": "Wechat for Bot",
"version": "0.0.6",
"description": "Wechat for Bot. (Personal Account, NOT Official Account)",
"main": "index.js",
"scripts": {
"start": "node example/ding-dong-bot.js",
......
......@@ -10,7 +10,9 @@ const log = require('npmlog')
class Contact {
constructor(id) {
if (!Contact.puppet) throw new Error('no puppet attached to Contact');
if (!Contact.puppet) {
throw new Error('no puppet attached to Contact')
}
log.silly('Contact', `constructor(${id})`)
this.id = id
......@@ -19,7 +21,7 @@ class Contact {
}
ready(contactGetter) {
log.silly('Contact', `ready(${contactGetter})`)
log.silly('Contact', 'ready(' + typeof contactGetter + ')')
if (this.obj.id) {
return Promise.resolve(this)
}
......@@ -30,9 +32,9 @@ class Contact {
this.rawObj = data
this.obj = this.parse(data)
return this
}).catch(e => {
}).catch(e => {
log.error('Contact', `contactGetter(${this.id}) rejected: ` + e)
throw new Error('contactGetter: ' + e)
throw new Error('contactGetter: ' + e)
})
}
......@@ -49,13 +51,13 @@ class Contact {
}
}
dumpRaw() {
dumpRaw() {
console.error('======= dump raw contact =======')
Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
}
dump() {
dump() {
console.error('======= dump contact =======')
Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
}
toString() {
......@@ -66,7 +68,6 @@ class Contact {
get(prop) { return this.obj[prop] }
send(message) {
}
......@@ -78,15 +79,15 @@ class Contact {
}
}
Contact.init = function () { Contact.pool = {} }
Contact.init = function() { Contact.pool = {} }
Contact.init()
Contact.load = function (id) {
Contact.load = function(id) {
if (id in Contact.pool) {
return Contact.pool[id]
}
return Contact.pool[id] = new Contact(id)
}
Contact.attach = function (puppet) { Contact.puppet = puppet }
Contact.attach = function(puppet) { Contact.puppet = puppet }
module.exports = Contact
......@@ -18,48 +18,43 @@ class Browser {
constructor(options) {
options = options || {}
this.browser = options.browser || 'phantomjs'
this.port = options.port || 8788 // 'W' 'X' Ascii Code
this.head = options.head || false // default to headless
this.port = options.port || 8788 // 'W' 'X' Ascii Code
}
toString() { return `Class Wechaty.Puppet.Browser({browser:${this.browser}, port:${this.port}})` }
toString() { return `Class Wechaty.Puppet.Web.Browser({head:${this.head}, port:${this.port}})` }
init() {
this.driver = this.getDriver()
return this.open()
.then(this.inject.bind(this))
.then(r => log.verbose('Browser', 'inited: ' + this.toString()))
}
open() {
const WX_URL = 'https://wx.qq.com'
log.verbose('Browser', `init ${this.browser}(${this.port})`)
this.driver = this.getDriver()
log.verbose('Browser', `open: ${WX_URL}`)
return this.driver.get(WX_URL)
}
getDriver() {
var driver
switch (this.browser) {
case 'chrome':
driver = new WebDriver.Builder().forBrowser(this.browser).build()
break
default:
case 'phantomjs':
driver = Browser.getPhantomJsDriver()
break
if (this.head) {
return new WebDriver.Builder().forBrowser('chrome').build()
}
return driver
return Browser.getPhantomJsDriver()
}
static getPhantomJsDriver() {
// https://github.com/SeleniumHQ/selenium/issues/2069
//setup custom phantomJS capability
// setup custom phantomJS capability
const phantomjsExe = require('phantomjs-prebuilt').path
// const phantomjsExe = require('phantomjs2').path
const customPhantom = WebDriver.Capabilities.phantomjs()
.set('phantomjs.binary.path', phantomjsExe)
log.verbose('Browser', 'phantomjs path:' + phantomjsExe)
//build custom phantomJS driver
return new WebDriver.Builder()
.withCapabilities(customPhantom)
......@@ -90,16 +85,15 @@ class Browser {
}
quit() {
log.verbose('Browser', 'Browser.quit')
log.verbose('Browser', 'quit()')
if (!this.driver) {
log.verbose('Browser', 'no need to quite because no driver')
return new Promise((resolve, reject) => resolve('no driver'))
return Promise.resolve('no driver')
}
log.verbose('Browser', 'Browser.driver.quit')
return this.execute('return (typeof Wechaty)!=="undefined" && Wechaty.quit()').then(() => {
log.verbose('Browser', 'Browser.driver.quit')
this.driver.quit()
this.driver = null
return new Promise((resolve, reject) => resolve())
})
}
......
......@@ -23,7 +23,7 @@ if (typeof Wechaty !== 'undefined') {
return 'Wechaty already injected?'
}
;return (function(port) {
return (function(port) {
port = port || 8788
/**
......
......@@ -17,57 +17,25 @@ const log = require('npmlog')
const Express = require('express')
const EventEmitter = require('events')
const Browser = require('./puppet-web-browser')
class Server extends EventEmitter {
constructor(options) {
super()
options = options || {}
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.browser = options.browser
this.logined = false
this.on('login' , () => this.logined = true)
this.on('logout', () => this.logined = false)
}
init() {
return new Promise((resolve, reject) => {
this.express = this.createExpress()
this.server = this.createHttpsServer(this.express, this.port)
this.socketio = this.createSocketIo(this.server)
this.browser = this.createBrowser()
this.browser.init()
.then(() => {
log.verbose('Server',`browser init finished with port: ${this.port}`)
resolve(true)
})
.catch(e => reject(e))
})
}
createBrowser(options) {
const b = new Browser({browser: this.browser, port: this.port})
toString() { return `Class Wechaty.Puppet.Web.Server({port:${this.port}})` }
/**
* `unload` event is sent from js@browser to webserver via socketio
* after received `unload`, we re-inject the Wechaty js code into browser.
*/
this.on('unload', () => {
log.verbose('Server', 'server received unload event')
this.browser.inject()
.then(() => log.verbose('Server', 're-injected'))
.catch((e) => log.error('Server', 'inject err: ' + e))
})
return b
init() {
this.express = this.createExpress()
this.httpsServer = this.createHttpsServer(this.express, this.port)
this.socketio = this.createSocketIo(this.httpsServer)
log.verbose('Server', 'inited: ' + this)
return Promise.resolve(this)
}
/**
*
* Https Server
*
*/
createHttpsServer(express) {
return https.createServer({
......@@ -79,45 +47,40 @@ class Server extends EventEmitter {
}
/**
*
* Express Middleware
*
*/
createExpress() {
const app = new Express()
app.use(bodyParser.json())
app.use(function(req, res, next) {
const e = new Express()
e.use(bodyParser.json())
e.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
next()
})
app.get('/ding', function(req, res) {
e.get('/ding', function(req, res) {
log.silly('Server', '%s GET /ding', new Date())
res.end('dong')
})
return app
return e
}
/**
*
* Socket IO
*
*/
createSocketIo(server) {
const socketServer = io.listen(server, {
log: true
createSocketIo(httpsServer) {
const socketServer = io.listen(httpsServer, {
// log: true
})
socketServer.sockets.on('connection', (s) => {
log.verbose('Server', 'got connection from browser')
// kick off the old one
if (this.socketClient) { this.socketClient.destroy() }
if (this.socketClient) {
// this.socketClient.destroy()
this.socketClient = null
}
// save to instance: socketClient
this.socketClient = s
s.on('disconnect', function() {
log.verbose('Server', 'socket.io disconnected')
/**
......@@ -128,7 +91,6 @@ class Server extends EventEmitter {
*/
this.socketClient = null
})
// Events from Wechaty@Broswer --to--> Server
;[
'message'
......@@ -141,64 +103,29 @@ class Server extends EventEmitter {
this.emit(e, data)
})
})
s.on('error', e => log.error('Server', 'socket error: %s', e))
/**
* prevent lost event: buffer new event received when socket disconnected
while (buff.length) {
let e = buff.shift()
socket.emit(e.event, e.data)
}
*/
})
return socketServer
}
isLogined() {
return this.logined
}
quit() {
if (this.browser) {
this.browser.quit()
delete this.browser
}
log.verbose('Server', 'quit()')
if (this.socketServer) {
log.verbose('Server', 'close socketServer')
socketServer.httpsServer.close()
socketServer.close()
delete this.socketServer
this.socketServer = null
}
if (this.socketClient) {
this.socketClient.distroy()
delete this.socketClient
log.verbose('Server', 'close socketClient')
this.socketClient = null
}
if (this.server) {
this.server.close()
delete this.server
if (this.httpsServer) {
log.verbose('Server', 'close httpsServer')
this.httpsServer.close()
this.httpsServer = null
}
}
/**
*
* Proxy Call to Wechaty in Browser
*
*/
browserExecute(script) {
if (!this.browser) {
throw new Error('no browser!')
}
return this.browser.execute(script)
}
proxyWechaty(wechatyFunc, ...args) {
//const args = Array.prototype.slice.call(arguments, 1)
const argsJson = JSON.stringify(args)
const wechatyScript = `return (Wechaty && Wechaty.${wechatyFunc}.apply(undefined, JSON.parse('${argsJson}')))`
log.silly('Server', 'proxyWechaty: ' + wechatyScript)
return this.browserExecute(wechatyScript)
}
}
module.exports = Server
......@@ -10,32 +10,42 @@
*
*/
/**************************************
*
* Class PuppetWeb
*
***************************************/
const log = require('npmlog')
const Puppet = require('./puppet')
const Message = require('./message')
const WebServer = require('./puppet-web-server')
const Server = require('./puppet-web-server')
const Browser = require('./puppet-web-browser')
class PuppetWeb extends Puppet {
constructor(options) {
super()
options = options || {}
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.browser = options.browser
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.head = options.head
}
toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }
init() {
this.server = new WebServer({
browser: this.browser
, port: this.port
})
this.logined = false
this.on('login' , () => this.logined = true)
this.on('logout', () => this.logined = false)
return Promise.all([
this.initServer()
, this.initBrowser()
])
}
initServer() {
this.server = new Server({port: this.port})
;[// events. ';' for seprate from the last code line
'login'
......@@ -43,66 +53,94 @@ class PuppetWeb extends Puppet {
].map(event =>
this.server.on(event, data => this.emit(event, data))
)
this.server.on('message', data => {
const m = new Message(data)
this.emit('message', m)
})
/**
* `unload` event is sent from js@browser to webserver via socketio
* after received `unload`, we re-inject the Wechaty js code into browser.
*/
this.server.on('unload', () => {
log.verbose('PuppetWeb', 'server received unload event')
this.browser.inject()
.then(() => log.verbose('PuppetWeb', 're-injected'))
.catch((e) => log.error('PuppetWeb', 'inject err: ' + e))
})
return this.server.init()
}
initBrowser() {
this.browser = new Browser({
head: this.head
, port: this.port
})
return this.browser.init()
}
send(message) {
const toContact = message.get('to')
const content = message.get('content')
return this.server.proxyWechaty('send', toContact.getId(), content)
return this.proxyWechaty('send', toContact.getId(), content)
}
logout() {
return this.server.proxyWechaty('logout')
}
getLoginStatusCode() {
log.verbose('PuppetWeb', 'getLoginStatusCode')
return this.server.proxyWechaty('getLoginStatusCode')
return this.proxyWechaty('logout')
}
getLoginQrImgUrl() { return this.server.proxyWechaty('getLoginQrImgUrl') }
debugLoop() {
// XXX
this.getLoginStatusCode().then((c) => {
log.verbose('PuppetWeb', `login status code: ${c}`)
setTimeout(this.debugLoop.bind(this), 3000)
})
}
/*
sendByServer(message) {
this.server.send(message)
}
*/
/**
*
* Interface Methods
*
*/
alive() { return this.server && this.server.isLogined() }
destroy() {
quit() {
log.verbose('PuppetWeb', 'quit()')
if (this.server) {
log.verbose('PuppetWeb', 'server.quit()')
this.server.quit()
delete this.server
this.server = null
}
if (this.browser) {
log.verbose('PuppetWeb', 'browser.quit()')
this.browser.quit()
this.browser = null
}
}
/**
*
* Proxy Call to Wechaty in Browser
*
*/
proxyWechaty(wechatyFunc, ...args) {
//const args = Array.prototype.slice.call(arguments, 1)
const argsJson = JSON.stringify(args)
const wechatyScript = `return (Wechaty && Wechaty.${wechatyFunc}.apply(undefined, JSON.parse('${argsJson}')))`
log.silly('PuppetWeb', 'proxyWechaty: ' + wechatyScript)
return this.browser.execute(wechatyScript)
}
/**
*
* Public Methods
*
*/
getLoginQrImgUrl() { return this.server.proxyWechaty('getLoginQrImgUrl') }
getLoginStatusCode() { return this.server.proxyWechaty('getLoginStatusCode') }
getContact(id) { return this.server.proxyWechaty('getContact', id) }
getLoginQrImgUrl() { return this.proxyWechaty('getLoginQrImgUrl') }
getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') }
getContact(id) { return this.proxyWechaty('getContact', id) }
isLogined() { return !!(this.logined) }
}
module.exports = PuppetWeb
......@@ -24,7 +24,7 @@ class Wechaty extends EventEmitter {
switch (options.puppet) {
case 'web':
this.puppet = new Puppet.Web({
browser: options.browser
head: options.head
, port: options.port
})
break
......
const co = require('co')
const test = require('tape')
const Browser = require('../src/puppet-web-browser')
const PORT = 58788
test('Browser class smoking tests', function (t) {
//t.plan(5)
const PORT = 58788
const b = new Browser({browser: 'phantomjs', port: PORT})
test('Browser class smoking tests', function(t) {
const b = new Browser({head: true, port: PORT})
t.ok(b, 'Browser instance created')
b.open()
.then(() => {
t.ok(true, 'url opened')
b.inject()
.then(() => {
t.ok(true, 'wechaty injected')
co(function* () {
yield b.init()
t.pass('inited')
yield b.open()
t.pass('opened')
Promise.all([
b.execute('return Wechaty && Wechaty.ding()') // ret_ding
, b.execute('return Wechaty && Wechaty.isReady()') // ret_ready
]).then(([
ret_ding
, ret_ready
]) => {
t.equal(ret_ding , 'dong', 'Wechaty.ding() returns dong' )
t.equal(typeof ret_ready, 'boolean', 'Wechaty.isReady() returns boollean' )
yield b.inject()
t.pass('wechaty injected')
b.quit() + t.end()
}).catch((e) => { // Promise.all
t.ok(false, 'Promise.all promise rejected:' + e)
b.quit() + t.end()
})
const retDing = yield b.execute('return Wechaty && Wechaty.ding()')
t.equal(retDing, 'dong', 'execute Wechaty.ding()')
}).catch((e) => { // b.inject
t.ok(false, 'b.inject promise rejected:' + e)
b.quit() + t.end()
})
}).catch((e) => { // b.open
t.ok(false, 'open promise rejected:' + e)
t.end() + b.quit()
const retReady = yield b.execute('return Wechaty && Wechaty.isReady()')
t.equal(typeof retReady, 'boolean', 'execute Wechaty.isReady()')
})
.catch((e) => { // Catch
t.fail('co promise rejected:' + e)
})
.then(r => { // Finally
b.quit()
t.end()
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
})
......@@ -5,9 +5,9 @@ const log = require('npmlog')
log.level = 'silly'
const PuppetWebServer = require('../src/puppet-web-server')
const PORT = 58788
test('PuppetWebServer basic tests', function(t) {
const PORT = 58788
const s = new PuppetWebServer({port: PORT})
t.equal(typeof s, 'object', 'PuppetWebServer instance created')
......@@ -26,24 +26,20 @@ test('PuppetWebServer basic tests', function(t) {
const socketio = s.createSocketIo()
t.equal(typeof socketio, 'object', 'create socket io')
t.equal(s.isLogined() , false , 'instance not logined')
s.emit('login')
t.equal(s.isLogined() , true , 'logined after login event')
s.emit('logout')
t.equal(s.isLogined() , false , 'logouted after logout event')
}).catch(e => { // Reject
})
.catch(e => { // Reject
t.fail('co promise rejected:' + e)
})
.then(() => { // Finally
.then(() => { // Finally
s.quit()
t.end()
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
})
test.only('PuppetWebServer smoke testing', function(t) {
const PORT = 58788
test('PuppetWebServer smoke testing', function(t) {
const server = new PuppetWebServer({port: PORT})
co(function* () {
......@@ -52,26 +48,15 @@ test.only('PuppetWebServer smoke testing', function(t) {
const retHttps = yield dingHttps()
t.equal(retHttps , 'dong', 'ding https got dong')
const retSocket = yield dingSocket()
t.equal(retSocket, 'dong', 'ding socket got dong')
/*
const retBrowser = yield dingBrowser()
t.equal(retBrowser, 'dong', 'ding browser got dong')
const retProxy = yield dingProxy()
t.equal(retProxy, 'dong', 'ding proxy got dong')
const retStatus = yield getLoginStatusCode()
t.equal(typeof retStatus, 'number', 'status is number') // XXX sometimes object? 201605
*/
}).catch(e => { // Catch
}).catch(e => { // Reject
t.fail('co rejected:' + e)
}).then(() => { // Finally
server.quit()
t.end()
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
return // The following is help functions only
......@@ -90,45 +75,4 @@ test.only('PuppetWebServer smoke testing', function(t) {
}).on('error', e => reject('https get error:' + e))
})
}
function dingSocket() {
const maxTime = 9000
const waitTime = 500
let totalTime = 0
return new Promise((resolve, reject) => {
setTimeout(testDing, waitTime)
function testDing() {
//log.silly('TestingPuppetWebServer', server.socketio)
if (!server.socketClient) {
totalTime += waitTime
if (totalTime > maxTime) {
return reject('timeout after ' + totalTime + 'ms')
}
log.verbose('TestingPuppetWebServer', 'waiting socketClient to connect for ' + totalTime + '/' + maxTime + ' ms...')
setTimeout(testDing, waitTime)
return
}
//log.silly('TestingPuppetWebServer', server.socketClient)
server.socketClient.on('dong', data => {
log.verbose('TestingPuppetWebServer', 'socket on dong got: ' + data)
return resolve(data)
})
server.socketClient.emit('ding')
}
})
}
function dingBrowser() {
return server.browserExecute('return Wechaty.ding()')
}
function dingProxy() {
return server.proxyWechaty('ding')
}
function getLoginStatusCode() {
return server.proxyWechaty('getLoginStatusCode')
}
})
const co = require('co')
const log = require('npmlog')
const test = require('tape')
const PuppetWeb = require('../src/puppet-web')
const PORT = 58788
const HEAD = true
test('PuppetWeb smoke testing', function (t) {
t.plan(1)
test('PuppetWeb smoke testing', function(t) {
const pw = new PuppetWeb({head: HEAD, port: PORT})
t.pass('test tbw')
co(function* () {
yield pw.init()
t.pass('pw full inited')
t.equal(pw.isLogined() , false , 'instance not logined')
pw.emit('login')
t.equal(pw.isLogined() , true , 'logined after login event')
pw.emit('logout')
t.equal(pw.isLogined() , false , 'logouted after logout event')
const retDing = yield pw.proxyWechaty('ding')
t.equal(retDing, 'dong', 'Wechaty.ding()')
const retCode = yield pw.proxyWechaty('getLoginStatusCode')
t.equal(typeof retCode, 'number', 'getLoginStatusCode')
})
.catch(e => t.fail(e))
.then(r => {
pw.quit()
t.end()
})
})
test('Puppet Web server/browser communication', function(t) {
const pw = new PuppetWeb({head: HEAD, port: PORT})
co(function* () {
yield pw.init()
t.pass('pw full inited')
const retSocket = yield dingSocket(pw.server)
t.equal(retSocket, 'dong', 'dingSocket got dong')
})
.catch(e => { // Reject
t.fail('co promise rejected:' + e)
})
.then(r => { // Finally
pw.quit()
t.end()
})
.catch(e => { // Exception
log.error('TestingPuppetWeb', 'Exception:' + e)
})
return // The following is help functions only
//////////////////////////////////////////
function dingSocket(server) {
const maxTime = 9000
const waitTime = 500
let totalTime = 0
return new Promise((resolve, reject) => {
setTimeout(testDing, waitTime)
return
function testDing() {
//log.silly('TestingPuppetWebServer', server.socketio)
if (!server.socketClient) {
totalTime += waitTime
if (totalTime > maxTime) {
return reject('timeout after ' + totalTime + 'ms')
}
log.verbose('TestingPuppetWeb', 'waiting socketClient to connect for ' + totalTime + '/' + maxTime + ' ms...')
setTimeout(testDing, waitTime)
return
}
//log.silly('TestingPuppetWebServer', server.socketClient)
server.socketClient.on('dong', data => {
log.verbose('TestingPuppetWeb', 'socket recv event dong: ' + data)
return resolve(data)
})
server.socketClient.emit('ding')
}
})
}
})
const path = require('path')
const test = require('tape')
const path = require('path')
const test = require('tape')
const log = require('npmlog')
log.level = 'silly'
const WebDriver = require('selenium-webdriver')
const Browser = WebDriver.Browser
const By = WebDriver.By
const WebBrowser = require('../src/puppet-web-browser')
const PuppetWebBrowser = require('../src/puppet-web-browser')
test('WebDriver smoke testing', function(t) {
/*
const driver = new WebDriver.Builder()
.withCapabilities(WebDriver.Capabilities.chrome()).build()
*/
const wb = new PuppetWebBrowser({head: true})
const driver = wb.getDriver()
const driver = WebBrowser.getPhantomJsDriver()
/*
.withCapabilities(
WebDriver.Capabilities.phantomjs()
//.set('phantomjs.binary.path', 'D:\\cygwin64\\home\\zixia\\git\\wechaty\\node_modules\\phantomjs-prebuilt\\lib\\phantom\\bin\\phantomjs.exe')
).build()
*/
// .set('webdriver.load.strategy', 'unstable')
// https://stackoverflow.com/questions/37071807/how-to-executescript-before-page-load-by-webdriver-in-selenium
const injectio = WebBrowser.getInjectio()
const injectio = PuppetWebBrowser.getInjectio()
driver.get('https://wx.qq.com/')
.then(() => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册