diff --git a/.travis.yml b/.travis.yml index 83e9449580fe6d6c03e9e55e4395b786a7a66e29..d73820c93d7d263b9fcf5720411760a50b53d3bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,7 @@ node_js: - "6" os: - linux - - centos - macosx - - windows dist: trusty before_install: - export DISPLAY=:99.0 diff --git a/README.md b/README.md index 82a3c241c455d193d7d5c629d7b1455919c4e4b5..ac6e503a131757bb42308fdb9c8a1db166be70bd 100644 --- a/README.md +++ b/README.md @@ -26,26 +26,18 @@ So a tireless bot working for me 24x7 on wechat, moniting/filtering the most imp # Examples Wechaty is super easy to use: 10 lines of javascript is enough for your first wechat robot. -## 1. Basic: 10 lines -The following 10 lines of code implement a bot who reply "roger" for every message received: +## 1. Basic: 9 lines +The following 9 lines of code implement a bot who reply "roger" for every message received: ```javascript const Wechaty = require('wechaty') const bot = new Wechaty() - -bot.init() -.on('scan', ({url, code}) => { - console.log(`Scan qrcode in url to login: ${code}\n${url}`) -}) -.on('message', m => { - console.log('RECV: ' + m.get('content')) // 1. print received message - - const reply = new Wechaty.Message() // 2. create reply message - .set('to', m.get('from')) // 1) set receipt - .set('content', 'roger.') // 2) set content - - bot.send(reply) // 3. do reply! - .then(() => console.log('REPLY: roger.')) // 4. print reply message +bot.init().on('scan', ({url, code}) => { + console.log(`Use Wechat to scan qrcode in url to login: ${code}\n${url}`) +}).on('message', m => { + bot.send(m.reply('roger')) // 1. reply + .then(() => console.log(`RECV: ${m}, REPLY: "roger"`)) // 2. log message + .catch(e => console.error(e)) // 3. catch exception }) ``` @@ -133,9 +125,9 @@ bot.on('scan', ({code, url}) => { 1. url: {String} the qrcode image url 2. code: {Number} the scan status code. some known status of the code list here is: 1. 0 initial - 2. 408 wait for scan - 3. 201 scaned, wait for confirm - 4. 200 login confirmed + 1. 200 login confirmed + 1. 201 scaned, wait for confirm + 1. 408 wait for scan `scan` event will be emit when it will detect a new code status change. @@ -215,6 +207,14 @@ message.ready() }) ``` +### Message.reply() +Create a new Message instance that reply the original one. which means: the `to` field of the reply message is the `from` of origin message. + +```javascript +const replyMessage = message.reply('roger!') +bot.send(replyMessage) +``` + ## Class Contact ### Contact.get(prop) @@ -324,7 +324,7 @@ Know more about TAP: [Why I use Tape Instead of Mocha & So Should You](https://m - [ ] Message - [ ] Send/Reply image message - [ ] Session save/load - + Everybody is welcome to issue your needs. # Known Issues & Support diff --git a/example/ding-dong-bot.js b/example/ding-dong-bot.js index 8bc43cc70986082842a27d5ace838b69d4ea1df1..9cea39d95315a6c96b4c4e1ba9a784f1291ca4bd 100644 --- a/example/ding-dong-bot.js +++ b/example/ding-dong-bot.js @@ -1,6 +1,6 @@ const log = require('npmlog') log.level = 'verbose' -// log.level = 'silly' +log.level = 'silly' const Wechaty = require('../src/wechaty') @@ -39,16 +39,14 @@ bot .on('message', m => { m.ready() .then(msg => { - log.info('Bot', 'recv: %s', msg) - logToFile(JSON.stringify(msg.rawObj)) + log.info('Bot', 'recv: %s', msg.toStringEx()) + // logToFile(JSON.stringify(msg.rawObj)) }) .catch(e => log.error('Bot', 'ready: %s' , e)) if (/^(ding|ping|bing)$/i.test(m.get('content'))) { - const reply = new Wechaty.Message() - reply.set('to', m.group() ? m.get('group') : m.get('from')) - reply.set('content', 'dong') - bot.send(reply) + const replyMsg = m.reply('dong') + bot.send(replyMsg) .then(() => { log.warn('Bot', 'REPLY: dong') }) } }) diff --git a/example/roger-bot.js b/example/roger-bot.js index dd0dc2bcdb38f36ab0fa39abb4d8c60b8d56adfa..dd49630d1cc2f39a84ba4cf23b5ed8367e5809fa 100644 --- a/example/roger-bot.js +++ b/example/roger-bot.js @@ -1,21 +1,13 @@ const Wechaty = require('..') -const bot = new Wechaty({head: true}) +const bot = new Wechaty() -bot +bot.init() .on('scan', ({url, code}) => { - console.log(`Scan qrcode in url to login: ${code}\n${url}`) + console.log(`Use Wechat to scan qrcode in url to login: ${code}\n${url}`) }) .on('message', m => { - console.log('RECV: ' + m.get('content')) // 1. print received message - - const reply = new Wechaty.Message() // 2. create reply message - .set('to', m.get('from')) // 1) set receipt - .set('content', 'roger.') // 2) set content - - bot.send(reply) // 3. do reply! - .then(() => console.log('REPLY: roger.')) // 4. print reply message - .catch(e => console.error(e)) + bot.send(m.reply('roger')) // 1. reply + .then(() => console.log(`RECV: ${m}, REPLY: "roger"`)) // 2. log message + .catch(e => console.error(e)) // 3. catch exception }) - -bot.init() .catch(e => console.error(e)) diff --git a/src/contact.js b/src/contact.js index 5a35459c1085439da813cd8f51637cc17f6671d2..e978a3069a6c45b1f7c870e05afeacc91155a19b 100644 --- a/src/contact.js +++ b/src/contact.js @@ -13,11 +13,13 @@ class Contact { log.silly('Contact', `constructor(${id})`) if (!Contact.puppet) { throw new Error('no puppet attached to Contact') } - this.id = id - this.obj = {} + if (id && typeof id !== 'string') { throw new Error('id must be string if provided. we got: ' + typeof id) } + this.id = id + this.obj = {} } - toString() { return `Contact(${this.obj.name}[${this.id}])` } + toString() { return this.id } + toStringEx() { return `Contact(${this.obj.name}[${this.id}])` } parse(rawObj) { return !rawObj ? {} : { @@ -31,15 +33,24 @@ class Contact { , city: rawObj.City , signature: rawObj.Signature + , address: rawObj.Alias // XXX: need a stable address for user + , star: !!rawObj.StarFriend , stranger: !!rawObj.stranger // assign by injectio.js } } - name() { return this.obj.name } + + name() { return this.obj.name } + remark() { return this.obj.remark } ready(contactGetter) { log.silly('Contact', 'ready(' + typeof contactGetter + ')') - if (this.obj.id) { return Promise.resolve(this) } + if (!this.id) { + log.warn('Contact', 'ready() call on an un-inited contact') + return Promise.resolve(this) + } else if (this.obj.id) { + return Promise.resolve(this) + } if (!contactGetter) { log.silly('Contact', 'get contact via ' + Contact.puppet.constructor.name) @@ -68,16 +79,17 @@ class Contact { get(prop) { return this.obj[prop] } - send(message) { - const msg = new Contact.puppet.Message() // create a empty message - msg.set('from', this) - msg.set('content', message) - - return Contact.puppet.send(message) - } + // KISS: don't cross reference + // send(message) { + // const msg = new Contact.puppet.Message() // create a empty message + // msg.set('from', this) + // msg.set('content', message) + + // return Contact.puppet.send(message) + // } stranger() { return this.obj.stranger } star() { return this.obj.star } - + static find() { } static findAll() { } } diff --git a/src/group.js b/src/group.js index 51249b66e170c1c07f3aea844083a7e963368be3..34bb00242b06be6d2fd96db1f59b9623194fc9b3 100644 --- a/src/group.js +++ b/src/group.js @@ -11,18 +11,25 @@ const Contact = require('./contact') class Group { constructor(id) { + log.silly('Group', `constructor(${id})`) this.id = id this.obj = {} - log.silly('Group', `constructor(${id})`) if (!Group.puppet) { throw new Error('no puppet attached to Group') } } - toString() { return `Group(${this.obj.name}[${this.id}])` } + + toString() { return this.id } + toStringEx() { return `Group(${this.obj.name}[${this.id}])` } ready(contactGetter) { log.silly('Group', `ready(${contactGetter})`) - if (this.obj.id) { return Promise.resolve(this) } + if (!this.id) { + log.warn('Group', 'ready() on a un-inited group') + return Promise.resolve(this) + } else if (this.obj.id) { + return Promise.resolve(this) + } contactGetter = contactGetter || Group.puppet.getContact.bind(Group.puppet) return contactGetter(this.id) diff --git a/src/message.js b/src/message.js index 52507448de3d95a7f30af1e7b23ffa1efe66781c..6a50a9ff6a3f578fa2b77e1e3410d3c170419bf9 100644 --- a/src/message.js +++ b/src/message.js @@ -25,28 +25,30 @@ class Message { return { id: rawObj.MsgId , type: rawObj.MsgType - // , from: rawObj.MMActualSender - // , to: rawObj.ToUserName - // , group: !!(rawObj.MMIsChatRoom) // MMPeerUserName always eq FromUserName ? + , from: rawObj.MMActualSender + , to: rawObj.ToUserName + , group: rawObj.MMIsChatRoom ? rawObj.FromUserName : null // MMPeerUserName always eq FromUserName ? , content: rawObj.MMActualContent // Content has @id prefix added by wx , status: rawObj.Status , digest: rawObj.MMDigest + , date: rawObj.MMDisplayTime // Javascript timestamp of milliseconds - , from: Contact.load(rawObj.MMActualSender) - , to: Contact.load(rawObj.ToUserName) - , group: rawObj.MMIsChatRoom ? Group.load(rawObj.FromUserName) : null - , date: new Date(rawObj.MMDisplayTime*1000) + // , from: Contact.load(rawObj.MMActualSender) + // , to: Contact.load(rawObj.ToUserName) + // , group: rawObj.MMIsChatRoom ? Group.load(rawObj.FromUserName) : null + // , date: new Date(rawObj.MMDisplayTime*1000) } } - toString() { + toString() { return this.obj.content } + toStringEx() { var s = `${this.constructor.name}#${Message.counter}` s += '(' + this.getSenderString() s += ':' + this.getContentString() + ')' return s } getSenderString() { - const name = this.obj.from.get('remark') || this.obj.from.get('name') - const group = this.obj.group + const name = Contact.load(this.obj.from).toStringEx() + const group = this.obj.group ? Contact.load(this.obj.group).toStringEx() : null return '<' + name + (group ? `@${group}` : '') + '>' } getContentString() { @@ -69,24 +71,36 @@ class Message { content() { return this.obj.content } group() { return this.obj.group } - reply(content) { - const from = this.from() - + reply(replyContent) { return new Message() - .set('to', from) - .set('content', content) + .set('to' , this.group() ? this.group() : this.from()) + .set('content', replyContent) } - + ready() { log.silly('Message', 'ready()') - return this.obj.from.ready() // Contact from - .then(r => this.obj.to.ready()) // Contact to - .then(r => this.obj.group && this.obj.group.ready()) // Group member list - .then(r => this) // ready return this for chain + + const f = Contact.load(this.obj.from) + const t = Contact.load(this.obj.to) + const g = this.obj.group ? Contact.load(this.obj.group) : null + + return f.ready() // Contact from + .then(r => t.ready()) // Contact to + .then(r => g && g.ready()) // Group member list + .then(r => this) // return this for chain .catch(e => { // REJECTED log.error('Message', 'ready() rejected: %s', e) - throw new Error(e) + throw e }) + + // return this.obj.from.ready() // Contact from + // .then(r => this.obj.to.ready()) // Contact to + // .then(r => this.obj.group && this.obj.group.ready()) // Group member list + // .then(r => this) // return this for chain + // .catch(e => { // REJECTED + // log.error('Message', 'ready() rejected: %s', e) + // throw new Error(e) + // }) } get(prop) { diff --git a/src/puppet-web-bridge.js b/src/puppet-web-bridge.js index 212b7620f4e5cd2bda08f1894596df389d521304..c77cc14a799da777d46d209b717be3a9ec5a6d4e 100644 --- a/src/puppet-web-bridge.js +++ b/src/puppet-web-bridge.js @@ -53,30 +53,19 @@ class Bridge { const timeout = max * (backoff * max) / 2 return retryPromise({ max: max, backoff: backoff }, function (attempt) { - log.verbose('Bridge', 'getContact() retryPromise: attampt %s/%s time for timeout %s' + log.silly('Bridge', 'getContact() retryPromise: attampt %s/%s time for timeout %s' , attempt, max, timeout) - /** - * This promise is MUST have here, - * the reason is as the following NOTICE explained - */ - return new Promise((resolve, reject) => { - this.proxyWechaty('getContact', id) - .then(r => { - if (r) { - resolve(r) - } - /** - * NOTICE: the promise that this.proxyWechaty returned will be resolved but be `undefined` - * which should be treat as `rejected` - */ - return reject('got empty') - }).catch(e => { - reject(e) - }) + + return this.proxyWechaty('getContact', id) + .then(r => { + if (!r) { + throw ('got empty return') + } + return r }) }.bind(this)) .catch(e => { - log.error('Bridge', 'getContact() retryPromise FAIL: %s', e) + log.error('Bridge', 'getContact() retryPromise finally FAIL: %s', e) throw e }) ///////////////////////////////// @@ -98,12 +87,12 @@ class Bridge { try { return this.execute(injectio, this.port) .then(r => { - log.verbose('Bridge', 'injected. initing...') + log.verbose('Bridge', `injected, got [${r}]. now initing...`) return this.proxyWechaty('init') }) .then(r => { - if (true===r) { log.verbose('Bridge', 'Wechaty.init() return: ' + r) } - else { throw new Error('Wechaty.init() return not true: ' + r) } + if (true!==r) { throw new Error('Wechaty.init() failed: ' + r) } + log.verbose('Bridge', 'Wechaty.init() successful') return r }) } catch (e) { @@ -142,8 +131,10 @@ class Bridge { module.exports = Bridge -/* -* +/** + * + * some handy browser javascript snips + * 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} }) @@ -151,5 +142,5 @@ Object.keys(window._chatContent).filter(function (k) { return window._chatConten .web_wechat_tab_add .web_wechat_tab_launch-chat -* -*/ \ No newline at end of file + * + */ \ No newline at end of file diff --git a/src/puppet-web-browser.js b/src/puppet-web-browser.js index df2c32cc0da996b78cda958cf4ef925ef18f3664..28cd78d42abff028c7437cca99999cda032c20a1 100644 --- a/src/puppet-web-browser.js +++ b/src/puppet-web-browser.js @@ -24,7 +24,7 @@ class Browser { init() { log.verbose('Browser', 'init()') - + return this.initDriver() .then(r => { log.verbose('Browser', 'initDriver() done') @@ -38,8 +38,8 @@ class Browser { open() { const WX_URL = 'https://wx.qq.com' - log.verbose('Browser', `open() at ${WX_URL}`) - + log.verbose('Browser', `open()ing at ${WX_URL}`) + return this.driver.get(WX_URL) } @@ -93,50 +93,45 @@ class Browser { 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 this.clean() + return this.driver.close() // http://stackoverflow.com/a/32341885/1123955 + .then(r => this.driver.quit()) + .then(r => this.driver = null) + .then(r => this.clean()) } - + clean() { - const max = 5 + const max = 15 const backoff = 100 - + // max = (2*totalTime/backoff) ^ (1/2) + // timeout = 11250 for {max: 15, backoff: 100} + // timeout = 45000 for {max: 30, backoff: 100} const timeout = max * (backoff * max) / 2 - return retryPromise({ max: max, backoff: backoff }, resolveAfterClean.bind(this)) - .catch(e => { - log.error('Browser', 'waitClean() retryPromise failed: %s', e) - throw e - }) - //////////////////////////////////////////////// - function resolveAfterClean(attempt) { - log.verbose('Browser', 'clean() retryPromise: attampt %s time for timeout %s' + return retryPromise({ max: max, backoff: backoff }, attempt => { + log.silly('Browser', 'clean() retryPromise: attampt %s time for timeout %s' , attempt, timeout) - return this.numProcess() - .then(n => { - if (n > 0) throw new Error('reject because there has driver process not exited') - log.verbose('Browser', 'waitClean hit') - return n - }) - } - } - - numProcess() { - return new Promise((resolve, reject) => { - require('ps-tree')(process.pid, (err, children) => { - if (err) { - return reject(err) - } - const num = children.filter(child => /phantomjs/i.test(child.COMMAND)).length - return resolve(num) + + return new Promise((resolve, reject) => { + require('ps-tree')(process.pid, (err, children) => { + if (err) { + return reject(err) + } + const num = children.filter(child => /phantomjs/i.test(child.COMMAND)).length + if (num==0) { + return resolve('clean') + } else { + return reject('dirty') + } + }) }) }) + .catch(e => { + log.error('Browser', 'retryPromise failed: %s', e) + throw e + }) } - + execute(script, ...args) { //log.verbose('Browser', `Browser.execute(${script})`) if (!this.driver) { diff --git a/src/puppet-web-injectio.js b/src/puppet-web-injectio.js index 3a686c6572cab42454da858b7258a1694fc3474e..a2c3f1b2db0a2b240f19f2e641a9c58d5e147758 100644 --- a/src/puppet-web-injectio.js +++ b/src/puppet-web-injectio.js @@ -3,7 +3,7 @@ * Wechaty - Wechat for Bot, and human who talk to bot. * * Class PuppetWebInjectio - * + * * Inject this js code to browser, * in order to interactive with wechat web program. * @@ -14,30 +14,17 @@ /*global angular*/ -if (typeof Wechaty !== 'undefined') { - return 'Wechaty already injected?' -} - return (function(port) { port = port || 8788 - /** - * Log to console - * http://stackoverflow.com/a/7089553/1123955 - */ - function clog(s) { - var d = new Date() - s = d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + ' ' + s - - var i = document.createElement('iframe') - i.style.display = 'none' - document.body.appendChild(i) - i.contentWindow.console.log(s) - i.parentNode.removeChild(i) + if (typeof Wechaty !== 'undefined') { + return 'Wechaty already injected?' } var Wechaty = { - glue: {} // will be initialized by glueAngular() function + glue: { + } // will be initialized by glueAngular() function + // glue funcs , getLoginStatusCode: function() { return Wechaty.glue.loginScope.code } , getLoginQrImgUrl: function() { return Wechaty.glue.loginScope.qrcodeUrl } @@ -45,7 +32,9 @@ return (function(port) { // variable , vars: { - login: false + logined: false + , inited: false + , socket: null , eventsBuf: [] , scanCode: null @@ -65,6 +54,28 @@ return (function(port) { , getUserName: getUserName } + window.Wechaty = Wechaty + + if (isWxLogin()) { + login('page refresh') + } + + /** + * Two return mode of WebDriver (should be one of them at a time) + * 1. a callback. return a value by call callback with args + * 2. direct return + */ + var callback = arguments[arguments.length - 1] + if (typeof callback === 'function') { + return callback('Wechaty') + } else { + return 'Wechaty' + } + + return 'Should not run to here' + + ///////////////////////////////////////////////////////////////////////////// + /** * * Functions that Glued with AngularJS @@ -79,6 +90,11 @@ return (function(port) { ) } function init() { + if (Wechaty.vars.inited === true) { + log('Wechaty.init() called twice: already inited') + return + } + if (!isReady()) { clog('angular not ready. wait 500ms...') setTimeout(init, 500) @@ -92,8 +108,15 @@ return (function(port) { checkScan() + heartBeat() + clog('inited!. ;-D') - return true + return Wechaty.vars.inited = true + } + + function heartBeat() { + Wechaty.emit('ding', 'heartbeat in browser') + setTimeout(heartBeat, 15000) } function glueAngular() { @@ -160,16 +183,17 @@ return (function(port) { return } - function isLogin() { return !!Wechaty.vars.login } + function isLogin() { return !!Wechaty.vars.logined } function login(data) { clog('login()') - Wechaty.vars.login = true + Wechaty.vars.logined = true Wechaty.emit('login', data) } function logout(data) { clog('logout()') - Wechaty.vars.login = false + Wechaty.vars.logined = false Wechaty.emit('logout', data) + checkScan() } function quit() { clog('quit()') @@ -231,13 +255,12 @@ return (function(port) { } // Wechaty.emit, will save event & data when there's no socket io connection to prevent event lost function emit(event, data) { + if (event) { + Wechaty.vars.eventsBuf.push([event, data]) + } if (!Wechaty.vars.socket) { clog('Wechaty.vars.socket not ready') - if (event) { - Wechaty.vars.eventsBuf.push([event, data]) - } - setTimeout(emit, 1000) // resent eventsBuf after 1000ms - return + return setTimeout(emit, 1000) // resent eventsBuf after 1000ms } if (Wechaty.vars.eventsBuf.length) { clog('Wechaty.vars.eventsBuf has ' + Wechaty.vars.eventsBuf.length + ' unsend events') @@ -247,9 +270,9 @@ return (function(port) { } clog('Wechaty.vars.eventsBuf all sent') } - if (event) { - Wechaty.vars.socket.emit(event, data) - } + // if (event) { + // Wechaty.vars.socket.emit(event, data) + // } } function connectSocket() { clog('connectSocket()') @@ -287,16 +310,20 @@ return (function(port) { // }) } - window.Wechaty = Wechaty - if (isWxLogin()) { - login('page refresh') - } - var callback = arguments[arguments.length - 1] - if (typeof callback === 'function') { - return callback('Wechaty') - } + /** + * Log to console + * http://stackoverflow.com/a/7089553/1123955 + */ + function clog(s) { + var d = new Date() + s = d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + ' ' + s - return 'Wechaty' + var i = document.createElement('iframe') + i.style.display = 'none' + document.body.appendChild(i) + i.contentWindow.console.log(s) + i.parentNode.removeChild(i) + } }.apply(window, arguments)) diff --git a/src/puppet-web-server.js b/src/puppet-web-server.js index 80e6150703795cae9a0bdcd077cc554645c409bd..8ba609850d37a2b931e02e1ce32e7a2f6f8bd4d8 100644 --- a/src/puppet-web-server.js +++ b/src/puppet-web-server.js @@ -4,7 +4,7 @@ * Web Server for puppet * * Class PuppetWebServer - * + * * Licenst: ISC * https://github.com/zixia/wechaty * @@ -111,7 +111,8 @@ class Server extends EventEmitter { this.emit('disconnect', e) }) - client.on('error', e => log.error('Server', 'socketio client error: %s', e)) + client.on('error' , e => log.error('Server', 'socketio client error: %s', e)) + client.on('ding' , e => log.silly('Server', 'got ding: %s', e)) // Events from Wechaty@Broswer --to--> Server ;[ diff --git a/src/puppet-web.js b/src/puppet-web.js index 4f83368c434f054420c60cb1a56f08813df6f6a7..0c031116f85ff0cc3080418a7b4233fdebbe4b35 100644 --- a/src/puppet-web.js +++ b/src/puppet-web.js @@ -3,7 +3,7 @@ * wechaty: Wechat for Bot. and for human who talk to bot/robot * * Class PuppetWeb - * + * * use to control wechat web. * * Licenst: ISC @@ -16,6 +16,7 @@ * Class PuppetWeb * ***************************************/ +const util = require('util') const log = require('npmlog') const co = require('co') @@ -35,14 +36,14 @@ class PuppetWeb extends Puppet { this.port = options.port || 8788 // W(87) X(88), ascii char code ;-] this.head = options.head - this.user = null // currentUser + this.user = null // } toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` } init() { log.verbose('PuppetWeb', 'init()') - + return this.initAttach() .then(r => { log.verbose('PuppetWeb', 'initAttach done: %s', r) @@ -50,7 +51,7 @@ class PuppetWeb extends Puppet { }) .then(r => { log.verbose('PuppetWeb', 'initBrowser done: %s', r) - return this.initBridge() + return this.initBridge() }) .then(r => { log.verbose('PuppetWeb', 'initBridge done: %s', r) @@ -66,10 +67,10 @@ class PuppetWeb extends Puppet { }) .then(r => { // Finally log.verbose('PuppetWeb', 'all initXXX done.') - return true + return this // for Chaining }) } - + initAttach() { log.verbose('PuppetWeb', 'initAttach()') Contact.attach(this) @@ -97,7 +98,7 @@ 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)) @@ -117,7 +118,7 @@ class PuppetWeb extends Puppet { } onServerConnection(data) { - log.verbose('PuppetWeb', 'onServerConnection: %s', data) + log.verbose('PuppetWeb', 'onServerConnection: %s', data.constructor.name) } onServerDisconnect(data) { log.verbose('PuppetWeb', 'onServerDisconnect: %s', data) @@ -127,9 +128,9 @@ class PuppetWeb extends Puppet { onServerLog(data) { log.verbose('PuppetWeb', 'onServerLog: %s', data) } - + onServerLogin(data) { - co.call(this, 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() @@ -166,14 +167,19 @@ class PuppetWeb extends Puppet { */ log.verbose('PuppetWeb', 'server received unload event') this.emit('logout', data) // XXX: should emit event[logout] from browser - - if (this.bridge) { - this.bridge.inject() - .then(r => log.verbose('PuppetWeb', 're-injected:' + r)) - .catch(e => log.error('PuppetWeb', 'inject err: ' + e)) - } else { - log.verbose('PuppetWeb', 'bridge gone, should be quiting now') + + if (!this.browser || !this.bridge) { + log.warn('PuppetWeb', 'bridge gone, should be quiting now') + return } + + return this.browser.quit() + .then(r => log.verbose('PuppetWeb', 'browser.quit()ed:' + r)) + .then(r => this.browser.init()) + .then(r => log.verbose('PuppetWeb', 'browser.re-init()ed:' + r)) + .then(r => this.bridge.init()) + .then(r => log.verbose('PuppetWeb', 'bridge.re-init()ed:' + r)) + .catch(e => log.error('PuppetWeb', 'onServerUnload() err: ' + e)) } send(message) { @@ -209,9 +215,9 @@ class PuppetWeb extends Puppet { quit() { log.verbose('PuppetWeb', 'quit()') let p = Promise.resolve(true) - - if (this.bridge) { - p.then(this.bridge.quit.bind(this.bridge)) + + if (this.bridge) { + p.then(this.bridge.quit.bind(this.bridge)) this.bridge = null } else { log.warn('PuppetWeb', 'quit() without bridge') @@ -230,7 +236,7 @@ class PuppetWeb extends Puppet { } else { log.warn('PuppetWeb', 'quit() without server') } - + return p // return Promise } } diff --git a/src/puppet.js b/src/puppet.js index 6961d8360e61df7d53c15557e4c5a3d82e6efc2b..3b05407cb3dc93bf54921eac8f023b0b8bf12810 100644 --- a/src/puppet.js +++ b/src/puppet.js @@ -18,12 +18,6 @@ class Puppet extends EventEmitter { super() } - /** - * Get current logined user - * @return - */ - currentUser() { throw new Error('To Be Implemented') } - /** * let puppet send message * @@ -33,32 +27,14 @@ class Puppet extends EventEmitter { send(message) { throw new Error('To Be Implemented') } logout() { throw new Error('To Be Implementsd') } - alive() { throw new Error('To Be Implementsd') } + ding() { throw new Error('To Be Implementsd') } getContact(id) { // for unit testing - log.silly('Puppet', `Interface method getContact(${id})`) + log.verbose('Puppet', `Interface method getContact(${id})`) return Promise.resolve({UserName: 'WeChaty', NickName: 'Puppet'}) } // () { throw new Error('To Be Implemented') } - - /** - * - * Events .on(...) - * - * login - - * logout - - * - * - */ - debug(cb) { - // List of all events - [ 'message' // event data should carry a instance of Message - , 'login' - , 'logout' - ].map(e => { this.on(e, cb) }) - } - } Object.assign(Puppet, { diff --git a/src/wechaty.js b/src/wechaty.js index 2f42ff71c0e0611495598d2c0318ff3c281ffb33..1c4feb5cd70d659c502f0d1fa57a72f861ea714c 100644 --- a/src/wechaty.js +++ b/src/wechaty.js @@ -3,7 +3,7 @@ * wechaty: Wechat for Bot. and for human who talk to bot/robot * * Class Wechaty - * + * * Licenst: ISC * https://github.com/zixia/wechaty * @@ -24,7 +24,7 @@ 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 + ')'} @@ -32,7 +32,11 @@ class Wechaty extends EventEmitter { log.info('Wechaty', 'init() with version: %s', this.VERSION) this.initPuppet() this.initEventHook() + return this.puppet.init() + .then(r => { + return this // for chaining + }) } initPuppet() { switch (this.options.puppet) { @@ -61,12 +65,21 @@ class Wechaty extends EventEmitter { this.puppet.on('logout', (e) => { this.emit('logout', e) }) + + /** + * TODO: support more events: + * 1. error + * 2. send + * 3. reply + * 4. quit + * 5. ... + */ + return Promise.resolve() } - currentUser() { return this.puppet.currentUser() } - quit() { return this.puppet.quit() } - + quit() { return this.puppet.quit() } + send(message) { return this.puppet.send(message) } reply(message, reply) { return this.puppet.reply(message, reply) }