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

hudge improvements: new bridge class, new event proxy system, qrcode login use...

hudge improvements: new bridge class, new event proxy system, qrcode login use event `scan`, and more.
......@@ -32,11 +32,14 @@ node_modules
# Optional REPL history
.node_repl_history
cert.pem
key.pem
t.js
t
socket.io.min.js
.*.swp
# TAP Coverage
coverage
.nyc_output
# Other staff
0
t.js
t
......@@ -65,7 +65,7 @@ Use NPM is recommended to install Wechaty for you:
npm install --save wechaty
```
## Start from strach
## Start from scratch
In case that you do not know anything about nodejs, the follow instructions would help you to run Wechaty bot on your machine.
## 1. Install NodeJS
......@@ -75,20 +75,16 @@ NodeJS Version 6.0 & above is required.
1. Download NodeJS Installer(i.e. "v6.2.0 Current")
1. Run Installer to install NodeJS to your machine
## 2. Checkout Wechaty
Use `git` to checkout Wechaty source code from [Github.com](https://github.com)
## 2. Install Wechaty
Use `npm` to install Wechaty
```shell
git clone https://github.com/zixia/wechaty.git
npm install wechaty
```
This will install the last version of wechaty and all the dependents(dev).
## 3. Install Dependents
```shell
cd wechaty
npm install
```
## 4. Run Demo Bot
## 3. Run Demo Bot
```shell
cd node_modules/wechaty
npm start
```
This will run `node example/ding-dong-bot.js`
......
......@@ -436,7 +436,27 @@ angular.module("Controllers", []),
}(),
!function() {
"use strict";
angular.module("Controllers").controller("contentChatController", ["$scope", "$timeout", "$state", "$log", "$document", "$compile", "chatFactory", "accountFactory", "contactFactory", "appFactory", "confFactory", "utilFactory", "chatroomFactory", "mmpop", "ngDialog", "preview", "reportService", "mmHttp", "emojiFactory", function(e, t, o, n, r, a, i, c, s, l, u, f, d, g, m, p, h, M, y) {
angular.module("Controllers").controller("contentChatController", ["$scope", "$timeout", "$state", "$log", "$document", "$compile", "chatFactory", "accountFactory", "contactFactory", "appFactory", "confFactory", "utilFactory", "chatroomFactory", "mmpop", "ngDialog", "preview", "reportService", "mmHttp", "emojiFactory", function(
e // $scope of contentChatController
, t // $timeout
, o // $state
, n // $log
, r // $document
, a // $compile
, i // chatFactory
, c // accountFactory
, s // contactFactory
, l // appFactory
, u // confFactory
, f // utilFactory
, d // chatroomFactory
, g // mmpop
, m // ngDialog
, p // preview
, h // reportService
, M // mmHttp
, y // emojiFactory
) {
function C(o) {
var n = e.currentContact = s.getContact(o);
if (e.currentUser = o,
......@@ -3247,7 +3267,19 @@ angular.module("Services", []),
}(),
!function() {
"use strict";
angular.module("Services").factory("contactFactory", ["$rootScope", "$http", "$q", "$timeout", "confFactory", "accountFactory", "emojiFactory", "utilFactory", "resourceService", "reportService", "mmHttp", function(e, t, o, n, r, a, i, c, s, l, u) {
angular.module("Services").factory("contactFactory", ["$rootScope", "$http", "$q", "$timeout", "confFactory", "accountFactory", "emojiFactory", "utilFactory", "resourceService", "reportService", "mmHttp", function(
e // $rootScope
, t // $http
, o // $q
, n // $timeout
, r // confFactory
, a // accountFactory
, i // emojiFactory
, c // utilFactory
, s // resourceService
, l // reportService
, u // mmHttp
) {
function f(e) {
return e = angular.extend({
RemarkPYQuanPin: "",
......@@ -7455,7 +7487,20 @@ angular.module("Directives").directive("searchListDirective", [function() {
}
}
]),
angular.module("Directives").directive("navChatDirective", ["$timeout", "$log", "$document", "$stateParams", "$rootScope", "chatFactory", "accountFactory", "contactFactory", "appFactory", "confFactory", "utilFactory", "stateManageService", function(e, t, o, n, r, a, i, c, s, l, u, f) {
angular.module("Directives").directive("navChatDirective", ["$timeout", "$log", "$document", "$stateParams", "$rootScope", "chatFactory", "accountFactory", "contactFactory", "appFactory", "confFactory", "utilFactory", "stateManageService", function(
e // $timeout
, t // $log
, o // $document
, n // $stateParams
, r // $rootScope
, a // chatFactory
, i // accountFactory
, c // contactFactory
, s // appFactory
, l // confFactory
, u // utilFactory
, f // stateManageService
) {
return {
restrict: "EA",
scope: !0,
......
const log = require('npmlog')
//log.level = 'verbose'
log.level = 'silly'
log.level = 'verbose'
// log.level = 'silly'
const Wechaty = require('../src/wechaty')
......@@ -30,31 +30,33 @@ Please wait... I'm trying to login in...
console.log(welcome)
const bot = new Wechaty({head: true})
bot.init()
.then(bot.getLoginQrImgUrl.bind(bot))
.then(url => console.log(`Scan qrcode in url to login: \n${url}`))
.catch(e => {
log.error('Bot', 'init() fail:' + e)
bot.quit()
process.exit(-1)
bot.on('scan', ({url, code}) => {
console.log(`Scan qrcode in url to login: \n${url}`)
console.log(code)
})
bot.on('message', m => {
bot
.on('login' , () => log.info('Bot', 'logined'))
.on('logout' , () => log.info('Bot', 'logouted'))
.on('message', m => {
m.ready()
.then(msg => {
log.info('Bot', 'recv: %s' , msg)
log.info('Bot', 'recv: %s', msg)
})
.catch(e => log.error('Bot', 'ready: %s' , e))
if (/^(ding|ping|bing)$/i.test(m.get('content'))) {
const r = new Wechaty.Message()
r.set('to', m.inGroup() ? m.get('group') : m.get('from'))
r.set('content', 'dong')
bot.send(r)
const reply = new Wechaty.Message()
reply.set('to', m.group() ? m.get('group') : m.get('from'))
reply.set('content', 'dong')
bot.send(reply)
.then(() => { log.warn('Bot', 'REPLY: dong') })
}
})
bot.on('login' , () => npm.info('Bot', 'logined'))
bot.on('logout' , () => npm.info('Bot', 'logouted'))
bot.init()
.catch(e => {
log.error('Bot', 'init() fail: %s', e)
bot.quit()
process.exit(-1)
})
const log = require('npmlog')
const Wechaty = require('..')
const bot = new Wechaty({head: true})
bot.init()
.then(bot.getLoginQrImgUrl.bind(bot))
.then(url => console.log(`Scan qrcode in url to login: \n${url}`))
.catch(e => console.error(e))
bot.on('message', m => {
bot
.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
......@@ -17,3 +16,6 @@ bot.on('message', m => {
.then(() => console.log('REPLY: roger.')) // 4. print reply message
.catch(e => console.error(e))
})
bot.init()
.catch(e => console.error(e))
......@@ -29,36 +29,39 @@ Tuling API: http://www.tuling123.com/html/doc/api.html
Loading...
`)
bot.init()
.then(bot.getLoginQrImgUrl.bind(bot))
.then(url => console.log(`Scan to login:\n${url}`))
.catch(e => {
log.error('Bot', 'init() fail:' + e)
bot.quit()
process.exit(-1)
bot
.on('login' , e => log.info('Bot', 'bot login.'))
.on('logout' , e => log.info('Bot', 'bot logout.'))
.on('scan', ({url, code}) => {
console.log(`Scan qrcode in url to login: \n${url}`)
console.log(code)
})
bot.on('message', m => {
.on('message', m => {
co(function* () {
const msg = yield m.ready()
log.info('Bot', 'recv: %s' , msg)
if (m.inGroup()) {
if (m.group()) {
return
}
const r = new Wechaty.Message()
const reply = new Wechaty.Message()
.set('to', m.get('from'))
const content = m.get('content')
const {code, text} = yield brain.ask(content, {userid: msg.get('from')})
r.set('content', text)
reply.set('content', text)
yield bot.send(r)
yield bot.send(reply)
log.info('Bot', `REPLY: {code:${code}, text:${text}}`)
})
.catch(e => log.error('Bot', 'on message rejected: %s' , e))
})
bot.on('login' , e => log.info('Bot', 'bot login.'))
bot.on('logout' , e => log.info('Bot', 'bot logout.'))
bot.init()
.catch(e => {
log.error('Bot', 'init() fail:' + e)
bot.quit()
process.exit(-1)
})
__ __ _ _
\ \ / /__ ___| |__ __ _| |_ _ _
\ \ /\ / / _ \/ __| '_ \ / _` | __| | | |
\ V V / __/ (__| | | | (_| | |_| |_| |
\_/\_/ \___|\___|_| |_|\__,_|\__|\__, |
|___/
{
"name": "wechaty",
"version": "0.0.7",
"version": "0.0.8",
"description": "Wechat for Bot. (Personal Account, NOT Official Account)",
"main": "index.js",
"scripts": {
"start": "node example/ding-dong-bot.js",
"test": "tape test/*.js",
"test": "TAP_TIMEOUT=120 tap test/*-spec.js",
"lint": "eslint src"
},
"repository": {
......@@ -53,7 +53,6 @@
},
"devDependencies": {
"eslint": "^2.9.0",
"tape": "^4.5.1",
"tuling123-client": "0.0.1"
}
}
......@@ -17,7 +17,6 @@ class Contact {
this.id = id
this.obj = {}
}
ready(contactGetter) {
......@@ -25,7 +24,11 @@ class Contact {
if (this.obj.id) {
return Promise.resolve(this)
}
contactGetter = contactGetter || Contact.puppet.getContact.bind(Contact.puppet)
if (!contactGetter) {
log.silly('Contact', 'get contact via ' + Contact.puppet.constructor.name)
contactGetter = Contact.puppet.getContact.bind(Contact.puppet)
}
return contactGetter(this.id)
.then(data => {
log.silly('Contact', `contactGetter(${this.id}) resolved`)
......@@ -33,7 +36,7 @@ class Contact {
this.obj = this.parse(data)
return this
}).catch(e => {
log.error('Contact', `contactGetter(${this.id}) rejected: ` + e)
log.error('Contact', `contactGetter(${this.id}) rejected: `, e)
throw new Error('contactGetter: ' + e)
})
}
......@@ -41,6 +44,7 @@ class Contact {
parse(rawObj) {
return !rawObj ? {} : {
id: rawObj.UserName
, uin: rawObj.Uin // stable id? 4763975
, weixin: rawObj.Alias
, name: rawObj.NickName
, remark: rawObj.RemarkName
......@@ -50,6 +54,7 @@ class Contact {
, signature: rawObj.Signature
}
}
name() { return this.obj.name }
dumpRaw() {
console.error('======= dump raw contact =======')
......@@ -64,8 +69,6 @@ class Contact {
return `Contact(${this.id})`
}
getId() { return this.id }
get(prop) { return this.obj[prop] }
send(message) {
......
......@@ -20,7 +20,6 @@ class Group {
}
toString() { return this.obj.name ? this.obj.name : this.id }
getId() { return this.id }
ready(contactGetter) {
log.silly('Group', `ready(${contactGetter})`)
......@@ -70,7 +69,6 @@ class Group {
Object.keys(this.obj).forEach(k => console.error(`${k}: ${this.obj[k]}`))
}
getId() { return this.id }
get(prop) { return this.obj[prop] }
static find() {
......@@ -79,6 +77,7 @@ class Group {
static findAll() {
}
}
Group.init = function() { Group.pool = {} }
Group.init()
Group.load = function(id) {
......
/**
*
* wechaty: Wechat for Bot. and for human who talk to bot/robot
*
* Licenst: ISC
* https://github.com/zixia/wechaty
*
*/
const Message = require('./message')
class ImageMessage extends Message {
constructor(rawObj) {
super(rawObj)
}
}
module.exports = ImageMessage
......@@ -13,64 +13,80 @@ const log = require('npmlog')
class Message {
constructor(rawObj) {
Message.counter++;
Message.counter++
this.logToFile(JSON.stringify(rawObj))
this.rawObj = rawObj = rawObj || {}
this.obj = this.parse(rawObj)
this.id = this.obj.id
}
logToFile(data) {
require('fs').appendFile('message.log', data + '\n\n#############################\n\n', err => {
if (err) { log.error('Message', 'logToFile: ' + err) }
})
}
// Transform rawObj to local m
parse(rawObj) {
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) // MMPeerUserName always eq FromUserName ?
, content: rawObj.MMActualContent // Content has @id prefix added by wx
, status: rawObj.Status
, digest: rawObj.MMDigest
, from: Contact.load(rawObj.MMActualSender)
, to: Contact.load(rawObj.ToUserName)
, group: rawObj.MMIsChatRoom ? Group.load(rawObj.FromUserName) : null
, date: new Date(rawObj.MMDisplayTime*1000)
, fromContact: Contact.load(rawObj.MMActualSender)
, toContact: Contact.load(rawObj.ToUserName)
, fromGroup: rawObj.MMIsChatRoom ? Group.load(rawObj.FromUserName) : null
}
}
toString() {
const name = html2str(this.obj.from.get('name'))
const group = this.obj.fromGroup
let content = html2str(this.obj.content)
if (content.length > 20) content = content.substring(0,17) + '...';
let groupStr = group ? html2str(group) : ''
let fromStr = '<' + name + (groupStr ? `@[${groupStr}]` : '') + '>'
return `Message#${Message.counter}(${fromStr}: ${content})`
function html2str(html) {
return String(html)
.replace(/(<([^>]+)>)/ig,'')
.replace(/&apos;/g, "'")
.replace(/&quot;/g, '"')
.replace(/&gt;/g, '>')
.replace(/&lt;/g, '<')
.replace(/&amp;/g, '&')
}
}
var s = `${this.constructor.name}#${Message.counter}`
s += '(' + this.getSenderString()
s += ':' + this.getContentString() + ')'
return s
}
getSenderString() {
const name = this.obj.from.get('name')
const group = this.obj.group
return '<' + name + (group ? `@[${group}]` : '') + '>'
}
getContentString() {
let content = this.unescapeHtml(this.stripHtml(this.obj.content))
if (content.length > 20) { content = content.substring(0,17) + '...' }
return '{' + this.type() + '}' + content
}
stripHtml(str) { return String(str).replace(/(<([^>]+)>)/ig,'') }
unescapeHtml(str) {
return String(str)
.replace(/&apos;/g, "'")
.replace(/&quot;/g, '"')
.replace(/&gt;/g, '>')
.replace(/&lt;/g, '<')
.replace(/&amp;/g, '&')
}
from() { return this.obj.from }
to() { return this.obj.to }
content() { return this.obj.content }
ready() {
return new Promise((resolve, reject) => {
this.obj.fromContact.ready() // Contact from
.then(r => this.obj.toContact.ready()) // Contact to
.then(r => this.obj.fromGroup && this.obj.fromGroup.ready()) // Group member list
.then(r => resolve(this)) // RESOLVE
.catch(e => { // REJECT
log.error('Message', 'ready() rejected:' + e)
reject(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)
.catch(e => { // REJECT
log.error('Message', 'ready() rejected:' + e)
throw new Error(e)
})
}
fromGroup() { return !!(this.obj.fromGroup) }
group() { return !!(this.obj.group) }
get(prop) {
if (!prop || !(prop in this.obj)) {
......@@ -84,6 +100,7 @@ class Message {
this.obj[prop] = value
return this
}
type () { return Message.Type[this.obj.type] }
dump() {
console.error('======= dump message =======')
......@@ -94,7 +111,7 @@ class Message {
Object.keys(this.rawObj).forEach(k => console.error(`${k}: ${this.rawObj[k]}`))
}
getCount() { return Message.counter }
count() { return Message.counter }
static find(selector, option) {
return new Message({MsgId: '-1'})
......@@ -109,5 +126,29 @@ class Message {
}
Message.counter = 0
Message.Type = {
TEXT: 1,
IMAGE: 3,
VOICE: 34,
VIDEO: 43,
MICROVIDEO: 62,
EMOTICON: 47,
APP: 49,
VOIPMSG: 50,
VOIPNOTIFY: 52,
VOIPINVITE: 53,
LOCATION: 48,
STATUSNOTIFY: 51,
SYSNOTICE: 9999,
POSSIBLEFRIEND_MSG: 40,
VERIFYMSG: 37,
SHARECARD: 42,
SYS: 1e4,
RECALLED: 10002
}
Object.keys(Message.Type).forEach(k => {
const v = Message.Type[k]
Message.Type[v] = k // Message.Type[1] = 'TEXT'
})
module.exports = Message
/**
*
* Wechaty - Wechat for Bot, and human who talk to bot.
*
* Inject this js code to browser,
* in order to interactive with wechat web program.
*
* Licenst: MIT
* https://github.com/zixia/wechaty-lib
*
*/
const log = require('npmlog')
class Bridge {
constructor(options) {
if (!options || !options.browser) { throw new Error('Bridge need a browser')}
log.verbose('Bridge', `new Bridge({browser: ${options.browser}, port: ${options.port}})`)
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}})` }
init() {
log.verbose('Bridge', 'init()')
return this.inject()
}
logout() { return this.proxyWechaty('logout') }
quit() { return this.proxyWechaty('quit') }
getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') }
getLoginQrImgUrl() { return this.proxyWechaty('getLoginQrImgUrl') }
getUserName() { return this.proxyWechaty('getUserName') }
getContact(id) { return this.proxyWechaty('getContact', id) }
send(toUserName, content) { return this.proxyWechaty('send', toUserName, content) }
getInjectio() {
const fs = require('fs')
const path = require('path')
return fs.readFileSync(
path.join(path.dirname(__filename), 'puppet-web-injectio.js')
, 'utf8'
)
}
inject() {
log.verbose('Bridge', 'inject()')
const injectio = this.getInjectio()
try {
return this.execute(injectio, this.port)
.then(r => {
log.verbose('Bridge', 'injected. 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') }
return r
})
} catch (e) {
return Promise.reject('inject exception: ' + e)
}
throw new Error('should not run to here')
}
/**
* Proxy Call to Wechaty in Bridge
*/
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('Bridge', 'proxyWechaty: ' + wechatyScript)
return this.execute(wechatyScript)
}
execute(script, ...args) { return this.browser.execute(script, ...args) }
}
module.exports = Bridge
......@@ -16,36 +16,44 @@ const log = require('npmlog')
class Browser {
constructor(options) {
options = options || {}
options = options || {}
this.head = options.head || false // default to headless
this.port = options.port || 8788 // 'W' 'X' Ascii Code
}
toString() { return `Class Wechaty.Puppet.Web.Browser({head:${this.head}, port:${this.port}})` }
toString() { return `Class Browser({head:${this.head})` }
init() {
this.driver = this.getDriver()
return this.open()
.then(this.inject.bind(this))
.then(r => log.verbose('Browser', 'inited: ' + this.toString()))
log.verbose('Browser', 'init()')
return this.initDriver()
.then(this.open.bind(this))
.then(r => {
log.verbose('Browser', 'inited: ' + this.toString())
})
}
open() {
const WX_URL = 'https://wx.qq.com'
log.verbose('Browser', `open: ${WX_URL}`)
return this.driver.get(WX_URL)
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)
}
}
getDriver() {
initDriver() {
log.verbose('Browser', 'initDriver()')
if (this.head) {
return new WebDriver.Builder().forBrowser('chrome').build()
this.driver = new WebDriver.Builder().forBrowser('chrome').build()
} else {
this.driver = this.getPhantomJsDriver()
}
return Browser.getPhantomJsDriver()
return Promise.resolve(this.driver)
}
static getPhantomJsDriver() {
getPhantomJsDriver() {
// https://github.com/SeleniumHQ/selenium/issues/2069
// setup custom phantomJS capability
const phantomjsExe = require('phantomjs-prebuilt').path
......@@ -55,40 +63,21 @@ class Browser {
.set('phantomjs.cli.args', [
'--ignore-ssl-errors=true' // this help socket.io connect with localhost
, '--load-images=false'
, '--remote-debugger-port=9000'
// , '--webdriver-logfile=/tmp/wd.log'
// , '--webdriver-loglevel=DEBUG'
// , '--remote-debugger-port=9000'
])
log.silly('Browser', 'phantomjs path:' + phantomjsExe)
log.silly('Browser', 'phantomjs binary: ' + phantomjsExe)
//build custom phantomJS driver
return new WebDriver.Builder()
.withCapabilities(customPhantom)
.build()
}
static getInjectio() {
return fs.readFileSync(
path.join(path.dirname(__filename), 'puppet-web-injectio.js')
, 'utf8'
)
}
inject() {
const injectio = Browser.getInjectio()
log.verbose('Browser', 'inject()')
try {
return this.execute(injectio, this.port)
.then(r => {
log.verbose('Browser', 'init() after inject()')
return this.execute('return (typeof Wechaty)==="undefined" ? false : Wechaty.init()')
})
.then(r => {
log.verbose('Browser', 'Wechaty.init() return: ' + r)
return r
})
} catch (e) {
return Promise.reject('execute exception: ' + e)
}
}
// selenium-webdriver/lib/capabilities.js
// 66 BROWSER_NAME: 'browserName',
// name() { return this.driver.getCapabilities().get('browserName') }
quit() {
log.verbose('Browser', 'quit()')
......@@ -96,11 +85,11 @@ class Browser {
log.verbose('Browser', 'no need to quite because no driver')
return Promise.resolve('no driver')
}
return this.execute('return (typeof Wechaty)!=="undefined" && Wechaty.quit()').then(() => {
log.verbose('Browser', 'Browser.driver.quit')
this.driver.quit()
this.driver = null
})
log.verbose('Browser', 'driver.quit')
this.driver.close() // http://stackoverflow.com/a/32341885/1123955
this.driver.quit()
this.driver = null
return Promise.resolve()
}
execute(script, ...args) {
......
......@@ -46,27 +46,42 @@ return (function(port) {
// glue funcs
, getLoginStatusCode: function() { return Wechaty.glue.loginScope.code }
, getLoginQrImgUrl: function() { return Wechaty.glue.loginScope.qrcodeUrl }
, isLogin: function() { return !!(window.MMCgi && window.MMCgi.isLogin) }
, isReady: isReady
// variable
, socket: null
, eventsBuf: []
, vars: {
login: false
, socket: null
, eventsBuf: []
, scanCode: null
}
// funcs
, init: init
, send: send
, clog: clog // Console log
, slog: slog // log throw Socket IO
, log: log
, ding: ding
, quit: quit
, emit: emit
, getContact: getContact
, getUserName: getUserName
}
/**
*
* Functions that Glued with AngularJS
*
*/
function isWxLogin() { return !!(window.MMCgi && window.MMCgi.isLogin) }
function isReady() {
return !!((typeof angular) !== 'undefined' && angular.element && angular.element('body'))
return !!(
(typeof angular) !== 'undefined'
&& angular.element
&& angular.element('body')
)
}
function init() {
if (!isReady()) {
......@@ -78,8 +93,9 @@ return (function(port) {
clog('init on port:' + port)
glueAngular()
connectSocket()
hookUnload()
hookMessage()
hookEvents()
checkScan()
clog('inited!. ;-D')
return true
......@@ -114,14 +130,56 @@ return (function(port) {
}
}
function quit() {
if (Wechaty.socket) {
Wechaty.socket.close()
Wechaty.socket = null
function checkScan() {
clog('checkScan()')
if (isLogin()) {
log('checkScan() - already login, no more check')
return
}
if (!Wechaty.glue.loginScope) {
log('checkScan() - loginScope disappeared, no more check')
login('loginScope disappeared')
return
}
var code = +Wechaty.glue.loginScope.code
var url = Wechaty.glue.loginScope.qrcodeUrl
if (code !== Wechaty.vars.scanCode) {
log('checkScan() - code change detected. from '
+ Wechaty.vars.scanCode
+ ' to '
+ code
)
Wechaty.emit('scan', {
code: code
, url: url
})
Wechaty.vars.scanCode = code
}
setTimeout(checkScan, 100)
return
}
function isLogin() { return !!Wechaty.vars.login }
function login(data) {
clog('login()')
Wechaty.vars.login = true
Wechaty.emit('login', data)
}
function logout(data) {
clog('logout()')
Wechaty.vars.login = false
Wechaty.emit('logout', data)
}
function quit() {
clog('quit()')
if (Wechaty.vars.socket) {
Wechaty.vars.socket.close()
Wechaty.vars.socket = null
}
}
function slog(msg) { return Wechaty.socket && Wechaty.socket.emit('log', msg) }
function log(s) { clog(s); slog(s) }
function slog(msg) { return Wechaty.vars.socket && Wechaty.vars.socket.emit('log', msg) }
function ding() { return 'dong' }
function send(ToUserName, Content) {
var chat = Wechaty.glue.chatFactory
......@@ -133,19 +191,29 @@ return (function(port) {
chat.appendMessage(m)
return chat.sendMessage(m)
}
function getContact(id) { return Wechaty.glue.contactFactory.getContact(id) }
function hookMessage() {
function getContact(id) {
return Wechaty.glue.contactFactory
? Wechaty.glue.contactFactory.getContact(id)
: null
}
function getUserName() {
return Wechaty.glue.accountFactory
? Wechaty.glue.accountFactory.getUserName()
: null
}
function hookEvents() {
Wechaty.glue.rootScope.$on('message:add:success', function(event, data) {
if (!isLogin()) { // in case of we missed the pageInit event
login('by event[message:add:success]')
}
Wechaty.emit('message', data)
})
Wechaty.glue.appScope.$on("newLoginPage", function(event, data) {
Wechaty.emit('login', data)
login('by event[newLoginPage]')
})
Wechaty.glue.rootScope.$on('root:pageInit:success'), function (event, data) {
Wechaty.emit('login', data)
login('by event[root:pageInit:success]')
}
}
function hookUnload() {
window.addEventListener('unload', function(e) {
// XXX only 1 event can be emitted here???
Wechaty.emit('unload', e)
......@@ -157,24 +225,24 @@ 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 (!Wechaty.socket) {
clog('Wechaty.socket not ready')
if (!Wechaty.vars.socket) {
clog('Wechaty.vars.socket not ready')
if (event) {
Wechaty.eventsBuf.push([event, data])
Wechaty.vars.eventsBuf.push([event, data])
}
setTimeout(emit, 1000) // resent eventsBuf after 1000ms
return
}
if (Wechaty.eventsBuf.length) {
clog('Wechaty.eventsBuf has ' + Wechaty.eventsBuf.length + ' unsend events')
if (Wechaty.vars.eventsBuf.length) {
clog('Wechaty.vars.eventsBuf has ' + Wechaty.vars.eventsBuf.length + ' unsend events')
var eventData
while (eventData = Wechaty.eventsBuf.pop()) {
Wechaty.socket.emit(eventData[0], eventData[1])
while (eventData = Wechaty.vars.eventsBuf.pop()) {
Wechaty.vars.socket.emit(eventData[0], eventData[1])
}
clog('Wechaty.eventsBuf all sent')
clog('Wechaty.vars.eventsBuf all sent')
}
if (event) {
Wechaty.socket.emit(event, data)
Wechaty.vars.socket.emit(event, data)
}
}
function connectSocket() {
......@@ -193,7 +261,7 @@ return (function(port) {
}
// Wechaty global variable: socket
var socket = Wechaty.socket = io.connect('https://127.0.0.1:' + port)
var socket = Wechaty.vars.socket = io.connect('https://127.0.0.1:' + port)
// ding -> dong. for test & live check purpose
// ping/pong are reserved by socket.io https://github.com/socketio/socket.io/issues/2414
......@@ -215,8 +283,8 @@ return (function(port) {
window.Wechaty = Wechaty
if (Wechaty.isLogin()) {
Wechaty.emit('login', 'page refresh')
if (isWxLogin()) {
login('page refresh')
}
var callback = arguments[arguments.length - 1]
if (typeof callback === 'function') {
......
......@@ -12,7 +12,7 @@ const io = require('socket.io')
const path = require('path')
const https = require('https')
const bodyParser = require('body-parser')
const log = require('npmlog')
const log = require('npmlog')
const Express = require('express')
const EventEmitter = require('events')
......@@ -27,13 +27,14 @@ class Server extends EventEmitter {
toString() { return `Class Wechaty.Puppet.Web.Server({port:${this.port}})` }
init() {
log.verbose('Server', 'initing: ' + this)
log.verbose('Server', 'init()')
this.initEventsToClient()
return new Promise((resolve, reject) => {
this.express = this.createExpress()
this.httpsServer = this.createHttpsServer(this.express
, r => resolve(r), e => reject(e)
)
this.socketio = this.createSocketIo(this.httpsServer)
this.socketServer = this.createSocketIo(this.httpsServer)
})
}
......@@ -46,7 +47,7 @@ class Server extends EventEmitter {
, cert: require('./ssl-pem').cert
}, express)
.listen(this.port, () => {
log.verbose('Server', `createHttpsServer port ${this.port}`)
log.verbose('Server', `createHttpsServer listen on port ${this.port}`)
if (typeof resolve === 'function') {
resolve(this)
}
......@@ -84,50 +85,62 @@ class Server extends EventEmitter {
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()
this.socketClient = null
}
// save to instance: socketClient
if (this.socketClient) { this.socketClient = null } // close() ???
this.socketClient = s
s.on('disconnect', function() {
log.verbose('Server', 'socket.io disconnected')
/**
* Possible conditions:
* 1. Browser reload
* 2. Lost connection(Bad network
* 3.
*/
this.socketClient = null
})
// Events from Wechaty@Broswer --to--> Server
;[
'message'
, 'login'
, 'logout'
, 'log'
, 'unload'
].map(e => {
s.on(e, data => {
log.silly('Server', `recv event[${e}] from browser`)
this.emit(e, data)
})
})
s.on('error', e => log.error('Server', 'socket error: %s', e))
this.initEventsFromClient(s)
})
return socketServer
}
initEventsFromClient(client) {
log.verbose('Server', 'initEventFromClient()')
this.emit('connection', client)
client.on('disconnect', e => {
log.verbose('Server', 'socket.io disconnected: ' + e)
/**
* 1. Browser reload / 2. Lost connection(Bad network)
*/
this.socketClient = null
this.emit('disconnect', 'server re-emit from socketio')
})
client.on('error', e => log.error('Server', 'socketio client error: %s', e))
// Events from Wechaty@Broswer --to--> Server
;[
'message'
, 'scan'
, 'login'
, 'logout'
, 'log'
, 'unload'
, 'dong'
].map(e => {
client.on(e, data => {
log.silly('Server', `recv event[${e}](${data}) from browser`)
this.emit(e, data)
})
})
}
initEventsToClient() {
log.verbose('Server', 'initEventToClient()')
this.on('ding', data => {
log.silly('Server', `recv event[ding](${data}), sending to client`)
if (this.socketClient) { this.socketClient.emit('ding', data) }
else { log.warn('Server', 'this.socketClient not exist')}
})
}
quit() {
log.verbose('Server', 'quit()')
if (this.socketServer) {
log.verbose('Server', 'close socketServer')
socketServer.httpsServer.close()
socketServer.close()
this.socketServer.close()
this.socketServer = null
}
if (this.socketClient) {
......
......@@ -16,12 +16,16 @@
*
***************************************/
const log = require('npmlog')
const co = require('co')
const Puppet = require('./puppet')
const Message = require('./message')
const Contact = require('./contact')
const Group = require('./group')
const Server = require('./puppet-web-server')
const Browser = require('./puppet-web-browser')
const Bridge = require('./puppet-web-bridge')
class PuppetWeb extends Puppet {
constructor(options) {
......@@ -30,141 +34,143 @@ class PuppetWeb extends Puppet {
this.port = options.port || 8788 // W(87) X(88), ascii char code ;-]
this.head = options.head
this.logined = false
this.user = null
this.user = null // <Contact> currentUser
}
toString() { return `Class PuppetWeb({browser:${this.browser},port:${this.port}})` }
init() {
this.on('login' , e => {
this.logined = true
// TODO: save currentUser to this.user as a Contact
log.verbose('PuppetWeb', 'init()')
return this.initAttach()
.then(this.initBrowser.bind(this))
.then(this.initBridge.bind(this))
.then(this.initServer.bind(this))
.catch(e => {
log.error('PuppetWeb', e)
throw e
})
this.on('logout', e => this.logined = false)
this.userId = null
return Promise.all([
this.initServer()
, this.initBrowser()
])
}
initAttach() {
log.verbose('PuppetWeb', 'initAttach()')
Contact.attach(this)
Group.attach(this)
return Promise.resolve()
}
initBrowser() {
log.verbose('PuppetWeb', 'initBrowser')
this.browser = new Browser({ head: this.head })
return this.browser.init()
}
initBridge() {
log.verbose('PuppetWeb', 'initBridge()')
this.bridge = new Bridge({
browser: this.browser
, port: this.port
})
return this.bridge.init()
}
initServer() {
this.server = new Server({port: this.port})
;[// events. ';' for seprate from the last code line
'login'
, 'logout'
].map(event =>
this.server.on(event, data => this.emit(event, data))
)
this.server.on('message', data => this.recvMessage(data))
/**
* `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', data => {
log.verbose('PuppetWeb', 'server received unload event')
this.emit('logout', data)
this.browser.inject()
.then(() => log.verbose('PuppetWeb', 're-injected'))
.catch((e) => log.error('PuppetWeb', 'inject err: ' + e))
log.verbose('PuppetWeb', 'initServer()')
const server = new Server({port: this.port})
server.on('login', this.onServerLogin.bind(this))
server.on('logout', this.onServerLogout.bind(this))
server.on('message', this.onServerMessage.bind(this))
server.on('unload', this.onServerUnload.bind(this))
;[ // simple server events forwarding
'connection'
, 'disconnect'
, 'scan'
, 'log'
, 'dong'
].map(e => {
server.on(e, data => {
log.verbose('PuppetWeb', 'Server event[%s]: %s', e, data)
this.emit(e, data)
})
})
this.server.on('log', s => log.verbose('PuppetWeb', 'log event:' + s))
this.server = server
return this.server.init()
}
initBrowser() {
this.browser = new Browser({
head: this.head
, port: this.port
})
return this.browser.init()
onServerLogin(data) {
co(function* () {
const userName = yield this.bridge.getUserName()
if (!userName) {
log.silly('PuppetWeb', 'onServerLogin: browser not full loaded, retry later.')
setTimeout(this.onServerLogin.bind(this), 100)
return
}
log.silly('PuppetWeb', 'userName: %s', userName)
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))
}
recvMessage(data) {
onServerLogout(data) {
this.user = null
this.emit('logout', data)
}
onServerMessage(data) {
const m = new Message(data)
if (!this.user) {
log.warn('PuppetWeb', 'recvMessage() without this.user')
return
}
const fromId = m.get('from')
if (fromId==this.user.id) {
log.warn('PuppetWeb', 'onServerMessage() without this.user')
} else if (this.user.id==m.from().id) {
log.silly('PuppetWeb', 'onServerMessage skip msg send by self')
return
}
this.emit('message', m)
}
onServerUnload(data) {
/**
* `unload` event is sent from js@browser to webserver via socketio
* after received `unload`, we re-inject the Wechaty js code into browser.
*/
log.verbose('PuppetWeb', 'server received unload event')
this.emit('logout', data) // XXX: should emit event[logout] from browser
this.bridge.inject()
.then(r => log.verbose('PuppetWeb', 're-injected:' + r))
.catch(e => log.error('PuppetWeb', 'inject err: ' + e))
}
send(message) {
const toContact = message.get('to')
const toContactId = toContact.getId()
const content = message.get('content')
const userName = message.get('to').id
const content = message.content()
log.silly('PuppetWeb', `send(${toContactId}, ${content})`)
return this.proxyWechaty('send', toContactId, content)
log.silly('PuppetWeb', `send(${userName}, ${content})`)
return this.bridge.send(userName, content)
}
logout() {
return this.proxyWechaty('logout')
logout() { return this.bridge.logout() }
getContact(id) { return this.bridge.getContact(id) }
getLoginQrImgUrl() {
if (!this.bridge) {
log.error('PuppetWeb', 'bridge not found')
return
}
return this.bridge.getLoginQrImgUrl()
}
debugLoop() {
// XXX
this.getLoginStatusCode().then((c) => {
this.bridge.getLoginStatusCode().then((c) => {
log.verbose('PuppetWeb', `login status code: ${c}`)
setTimeout(this.debugLoop.bind(this), 3000)
})
}
/**
*
* Interface Methods
*
*/
quit() {
log.verbose('PuppetWeb', 'quit()')
if (this.server) {
log.verbose('PuppetWeb', 'server.quit()')
this.server.quit()
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)
if (this.server) { this.server.quit() }
if (this.bridge) { this.bridge.quit() }
if (this.browser) { this.browser.quit() }
}
/**
*
* Public Methods
*
*/
getLoginQrImgUrl() {
log.silly('PuppetWeb', 'getLoginQrImgUrl()')
return this.proxyWechaty('getLoginQrImgUrl')
}
getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') }
getContact(id) { return this.proxyWechaty('getContact', id) }
isLogined() { return !!(this.logined) }
isLogined() { return !!(this.user) }
}
module.exports = PuppetWeb
......@@ -18,23 +18,34 @@ const Group = require('./group')
class Wechaty extends EventEmitter {
constructor(options) {
super()
options = options || {}
options.puppet = options.puppet || 'web'
switch (options.puppet) {
this.options = options || {}
this.options.puppet = this.options.puppet || 'web'
}
toString() { return 'Class Wechaty(' + this.puppet + ')'}
init() {
this.initPuppet()
this.initEventHook()
return this.puppet.init()
}
initPuppet() {
switch (this.options.puppet) {
case 'web':
this.puppet = new Puppet.Web({
head: options.head
, port: options.port
head: this.options.head
, port: this.options.port
})
break
default:
throw new Error('Puppet unsupport(yet): ' + puppet)
break
}
Contact.attach(this.puppet)
Group.attach(this.puppet)
return Promise.resolve(this.puppet)
}
initEventHook() {
// scan qrCode
this.puppet.on('scan', (e) => {
this.emit('scan', e)
})
this.puppet.on('message', (e) => {
this.emit('message', e)
})
......@@ -44,9 +55,9 @@ class Wechaty extends EventEmitter {
this.puppet.on('logout', (e) => {
this.emit('logout', e)
})
return Promise.resolve()
}
init() { return this.puppet.init() }
currentUser() { return this.puppet.currentUser() }
send(message) { return this.puppet.send(message) }
quit() { return this.puppet.quit() }
......
const test = require('tape')
const test = require('tap').test
const Contact = require('../src/contact')
const Puppet = require('../src/puppet')
const log = require('npmlog')
......@@ -15,8 +15,8 @@ test('Contact smoke testing', t => {
return new Promise((resolve,reject) => {
if (id!=UserName) return resolve({});
setTimeout(() => {
return resolve({
UserName: UserName
return resolve({
UserName: UserName
, NickName: NickName
})
}, 200)
......@@ -25,11 +25,14 @@ test('Contact smoke testing', t => {
const c = new Contact(UserName)
t.equal(c.getId(), UserName, 'id/UserName right')
t.equal(c.id, UserName, 'id/UserName right')
c.ready(mockContactGetter)
.then(r => {
t.equal(c.get('id') , UserName, 'UserName set')
t.equal(c.get('name') , NickName, 'NickName set')
t.equal(r.get('id') , UserName, 'UserName set')
t.equal(r.get('name') , NickName, 'NickName set')
const s = r.toString()
t.equal(typeof s, 'string', 'toString()')
})
.catch(e => t.fail('ready() rejected: ' + e))
.then(t.end) // test end
......
const test = require('tape')
const test = require('tap').test
const Message = require('../src/message')
const Group = require('../src/group')
const Puppet = require('../src/puppet')
......@@ -8,20 +8,58 @@ const log = require('npmlog')
Group.attach(new Puppet())
false && test('Group constructor parser test', t => {
const rawData = JSON.parse('{"MsgId":"1120003476579027592","FromUserName":"@@4aa0ae1e1ebc568b613fa43ce93b478df0339f73340d87083822c2016d2e53d9","ToUserName":"@94e4b0db79ccc844d7bb4a2b1efac3ff","MsgType":1,"Content":"@9ad4ba13fac52c55d323521b67f7cc39:<br/>[Strong]","Status":3,"ImgStatus":1,"CreateTime":1462889712,"VoiceLength":0,"PlayLength":0,"FileName":"","FileSize":"","MediaId":"","Url":"","AppMsgType":0,"StatusNotifyCode":0,"StatusNotifyUserName":"","RecommendInfo":{"UserName":"","NickName":"","QQNum":0,"Province":"","City":"","Content":"","Signature":"","Alias":"","Scene":0,"VerifyFlag":0,"AttrStatus":0,"Sex":0,"Ticket":"","OpCode":0},"ForwardFlag":0,"AppInfo":{"AppID":"","Type":0},"HasProductId":0,"Ticket":"","ImgHeight":0,"ImgWidth":0,"SubMsgType":0,"NewMsgId":1120003476579027600,"MMPeerUserName":"@@4aa0ae1e1ebc568b613fa43ce93b478df0339f73340d87083822c2016d2e53d9","MMDigest":"感恩的心 <img class=\"emoji emoji1f42e\" text=\"_web\" src=\"/zh_CN/htmledition/v2/images/spacer.gif\" />:<img class=\"qqemoji qqemoji79\" text=\"[Strong]_web\" src=\"/zh_CN/htmledition/v2/images/spacer.gif\" />","MMIsSend":false,"MMIsChatRoom":true,"MMUnread":false,"LocalID":"1120003476579027592","ClientMsgId":"1120003476579027592","MMActualContent":"<img class=\"qqemoji qqemoji79\" text=\"[Strong]_web\" src=\"/zh_CN/htmledition/v2/images/spacer.gif\" />","MMActualSender":"@9ad4ba13fac52c55d323521b67f7cc39","MMDigestTime":"22:15","MMDisplayTime":1462889712,"MMTime":"22:15","_h":126,"_index":0,"_offsetTop":0,"$$hashKey":"0QK", "MemberList": [{"Uin":0,"UserName":"@94e4b0db79ccc844d7bb4a2b1efac3ff","NickName":"李卓桓","AttrStatus":37996631,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"北京阿布","KeyWord":"liz"},{"Uin":0,"UserName":"@34887973779b7dd827366a31772cd83df223e6f71d9a79e44fe619aafe2901a4","NickName":"Tiger","AttrStatus":4292711,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"DisplayNameTiger","KeyWord":"","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@34887973779b7dd827366a31772cd83df223e6f71d9a79e44fe619aafe2901a4&skey=@crypt_f9cec94b_8517b7f9fec85f5a78a804c4f45f5536&chatroomid=@7b3dcd218431d79045cda3493c3179ae"}]}')
test('Group smoke testing', t => {
const UserName = '@0bb3e4dd746fdbd4a80546aef66f4085'
const NickName = 'Nick Name Test'
const EncryChatRoomId = '123456abcdef'
// Mock
const mockContactGetter = function (id) {
return new Promise((resolve,reject) => {
if (id!=UserName) return resolve({});
setTimeout(() => {
return resolve({
UserName: UserName
, NickName: NickName
, EncryChatRoomId: EncryChatRoomId
})
}, 200)
})
}
const g = new Group(UserName)
t.equal(g.id, UserName, 'id/UserName right')
g.ready(mockContactGetter)
.then(r => {
t.equal(r.get('id') , UserName, 'UserName set')
t.equal(r.get('name') , NickName, 'NickName set')
t.equal(r.get('encryId') , EncryChatRoomId, 'EncryChatRoomId set')
const s = r.toString()
t.equal(typeof s, 'string', 'toString()')
})
.catch(e => t.fail('ready() rejected: ' + e))
.then(t.end) // test end
})
/*
const rawData = JSON.parse('{"MsgId":"1120003476579027592","FromUserName":"@@4aa0ae1e1ebc568b613fa43ce93b478df0339f73340d87083822c2016d2e53d9","ToUserName":"@94e4b0db79ccc844d7bb4a2b1efac3ff","MsgType":1,"Content":"@9ad4ba13fac52c55d323521b67f7cc39:<br/>[Strong]","Status":3,"ImgStatus":1,"CreateTime":1462889712,"VoiceLength":0,"PlayLength":0,"FileName":"","FileSize":"","MediaId":"","Url":"","AppMsgType":0,"StatusNotifyCode":0,"StatusNotifyUserName":"","RecommendInfo":{"UserName":"","NickName":"","QQNum":0,"Province":"","City":"","Content":"","Signature":"","Alias":"","Scene":0,"VerifyFlag":0,"AttrStatus":0,"Sex":0,"Ticket":"","OpCode":0},"ForwardFlag":0,"AppInfo":{"AppID":"","Type":0},"HasProductId":0,"Ticket":"","ImgHeight":0,"ImgWidth":0,"SubMsgType":0,"NewMsgId":1120003476579027600,"MMPeerUserName":"@@4aa0ae1e1ebc568b613fa43ce93b478df0339f73340d87083822c2016d2e53d9","MMDigest":"感恩的心 ","MMIsSend":false,"MMIsChatRoom":true,"MMUnread":false,"LocalID":"1120003476579027592","ClientMsgId":"1120003476579027592","MMActualContent":"HTML-CODE","MMActualSender":"@9ad4ba13fac52c55d323521b67f7cc39","MMDigestTime":"22:15","MMDisplayTime":1462889712,"MMTime":"22:15","_h":126,"_index":0,"_offsetTop":0,"$$hashKey":"0QK", "MemberList": [{"Uin":0,"UserName":"@94e4b0db79ccc844d7bb4a2b1efac3ff","NickName":"李卓桓","AttrStatus":37996631,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"北京阿布","KeyWord":"liz"},{"Uin":0,"UserName":"@34887973779b7dd827366a31772cd83df223e6f71d9a79e44fe619aafe2901a4","NickName":"Tiger","AttrStatus":4292711,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"DisplayNameTiger","KeyWord":"","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@34887973779b7dd827366a31772cd83df223e6f71d9a79e44fe619aafe2901a4&skey=@crypt_f9cec94b_8517b7f9fec85f5a78a804c4f45f5536&chatroomid=@7b3dcd218431d79045cda3493c3179ae"}]}')
const EXPECTED = {
id: '179242112323992762'
id: '1120003476579027592'
, from: '@0bb3e4dd746fdbd4a80546aef66f4085'
}
const g = new Group(rawData)
t.equal(m.get('id') , EXPECTED.id , 'id right')
t.equal(m.get('from').getId() , EXPECTED.from , 'from right')
t.equal(g.id , EXPECTED.id , 'id right')
t.equal(g.from.id , EXPECTED.from , 'from right')
const s = g.toString()
t.equal(typeof s, 'string', 'toString()')
t.end()
})
*/
false && test('Message ready() promise testing', t => {
// must different with other rawData, because Contact class with load() will cache the result. or use Contact.resetPool()
......
const test = require('tape')
const test = require('tap').test
const Message = require('../src/message')
const Contact = require('../src/contact')
const Puppet = require('../src/puppet')
......@@ -17,8 +17,11 @@ test('Message constructor parser test', t => {
}
const m = new Message(rawData)
t.equal(m.get('id') , EXPECTED.id , 'id right')
t.equal(m.get('from').getId() , EXPECTED.from , 'from right')
t.equal(m.id , EXPECTED.id , 'id right')
t.equal(m.from().id , EXPECTED.from , 'from right')
const s = m.toString()
t.equal(typeof s, 'string', 'toString()')
t.end()
})
......@@ -26,7 +29,7 @@ test('Message constructor parser test', t => {
test('Message ready() promise testing', t => {
// must different with other rawData, because Contact class with load() will cache the result. or use Contact.resetPool()
const rawData = JSON.parse('{"MsgId":"3009511950433684462","FromUserName":"@0748ee480711bf20af91c298a0d7dcc77c30a680c1004157386b81cf13474823","ToUserName":"@b58f91e0c5c9e841e290d862ddb63c14","MsgType":1,"Content":"哈哈","Status":3,"ImgStatus":1,"CreateTime":1462887888,"VoiceLength":0,"PlayLength":0,"FileName":"","FileSize":"","MediaId":"","Url":"","AppMsgType":0,"StatusNotifyCode":0,"StatusNotifyUserName":"","RecommendInfo":{"UserName":"","NickName":"","QQNum":0,"Province":"","City":"","Content":"","Signature":"","Alias":"","Scene":0,"VerifyFlag":0,"AttrStatus":0,"Sex":0,"Ticket":"","OpCode":0},"ForwardFlag":0,"AppInfo":{"AppID":"","Type":0},"HasProductId":0,"Ticket":"","ImgHeight":0,"ImgWidth":0,"SubMsgType":0,"NewMsgId":3009511950433684500,"MMPeerUserName":"@0748ee480711bf20af91c298a0d7dcc77c30a680c1004157386b81cf13474823","MMDigest":"哈哈","MMIsSend":false,"MMIsChatRoom":false,"MMUnread":false,"LocalID":"3009511950433684462","ClientMsgId":"3009511950433684462","MMActualContent":"哈哈","MMActualSender":"@0748ee480711bf20af91c298a0d7dcc77c30a680c1004157386b81cf13474823","MMDigestTime":"21:44","MMDisplayTime":1462887888,"MMTime":"21:44","_h":104,"_index":0,"_offsetTop":0,"$$hashKey":"098"}')
const expectedFromUserName = '@0748ee480711bf20af91c298a0d7dcc77c30a680c1004157386b81cf13474823'
const expectedToUserName = '@b58f91e0c5c9e841e290d862ddb63c14'
const expectedFromNickName = 'From Nick Name Test'
......@@ -41,13 +44,13 @@ test('Message ready() promise testing', t => {
return new Promise((resolve,reject) => {
let obj = {}
switch (id) {
case expectedFromUserName:
case expectedFromUserName:
obj = {
UserName: expectedFromUserName
, NickName: expectedFromNickName
}
break
case expectedToUserName:
case expectedToUserName:
obj = {
UserName: expectedToUserName
, NickName: expectedToNickName
......@@ -67,10 +70,10 @@ test('Message ready() promise testing', t => {
const m = new Message(rawData)
t.equal(m.get('id'), expectedMsgId, 'id/MsgId right')
t.equal(m.id, expectedMsgId, 'id/MsgId right')
m.ready()
.then(r => {
const p = m.ready()
p.then(r => {
/*
const fromC = m.get('from')
const toC = m.get('to')
......
const co = require('co')
const test = require('tap').test
const Browser = require('../src/puppet-web-browser')
const Bridge = require('../src/puppet-web-bridge')
const PORT = 58788
test('Bridge class smoking tests', function(t) {
const browser = new Browser({port: PORT})
t.ok(browser, 'Browser instance created')
const b = new Bridge({browser: browser})
t.ok(b, 'Bridge instance creted')
co(function* () {
yield browser.init()
t.pass('inited')
yield b.inject()
t.pass('wechaty injected')
const retDing = yield b.execute('return Wechaty && Wechaty.ding()')
t.equal(retDing, 'dong', 'execute Wechaty.ding()')
const retReady = yield b.execute('return Wechaty && Wechaty.isReady()')
t.equal(typeof retReady, 'boolean', 'execute Wechaty.isReady()')
const retCode = yield b.proxyWechaty('getLoginStatusCode')
t.equal(typeof retCode, 'number', 'getLoginStatusCode')
})
.catch((e) => { // Rejected
t.fail('co promise rejected:' + e)
})
.then(r => { // Finally
b.quit()
browser.quit()
t.end()
})
.catch(e => { // Exception
t.fail('Exception:' + e)
})
})
const co = require('co')
const test = require('tape')
const test = require('tap').test
const Browser = require('../src/puppet-web-browser')
const PORT = 58788
......@@ -9,20 +9,14 @@ test('Browser class smoking tests', function(t) {
t.ok(b, 'Browser instance created')
co(function* () {
yield b.init()
t.pass('inited')
yield b.initDriver()
t.pass('inited driver')
yield b.open()
t.pass('opened')
yield b.inject()
t.pass('wechaty injected')
const retDing = yield b.execute('return Wechaty && Wechaty.ding()')
t.equal(retDing, 'dong', 'execute Wechaty.ding()')
const retReady = yield b.execute('return Wechaty && Wechaty.isReady()')
t.equal(typeof retReady, 'boolean', 'execute Wechaty.isReady()')
const two = yield b.execute('return 1+1')
t.equal(two, 2, 'execute script ok')
})
.catch((e) => { // Rejected
t.fail('co promise rejected:' + e)
......
const https = require('https')
const test = require('tape')
const test = require('tap').test
const co = require('co')
const log = require('npmlog')
//log.level = 'silly'
......
const co = require('co')
const test = require('tape')
const test = require('tap').test
const log = require('npmlog')
log.level = 'silly'
// log.level = 'silly'
const PuppetWeb = require('../src/puppet-web')
const PORT = 58788
test('PuppetWeb smoke testing', function(t) {
const pw = new PuppetWeb({port: PORT})
false && test('PuppetWeb smoke testing', function(t) {
let pw
co(function* () {
pw = new PuppetWeb({port: PORT})
t.ok(pw, 'new PuppetWeb')
co(function* () {
yield pw.init()
t.pass('pw full inited')
yield pw.init()
t.pass('pw full inited')
t.equal(pw.isLogined() , false , 'instance not logined')
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')
// XXX find a better way to mock...
pw.bridge.getUserName = function () { return Promise.resolve('mockedUserName') }
const retDing = yield pw.proxyWechaty('ding')
t.equal(retDing, 'dong', 'Wechaty.ding()')
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 retCode = yield pw.proxyWechaty('getLoginStatusCode')
t.equal(typeof retCode, 'number', 'getLoginStatusCode')
})
.catch(e => t.fail(e)) // Reject
.then(r => { // Finally
pw.quit()
t.end()
})
const p2 = new Promise((resolve) => {
pw.once('logout', r => {
t.equal(pw.isLogined() , false , 'logouted after logout event')
resolve()
})
})
pw.server.emit('logout')
yield p2
})
.catch(e => t.fail(e)) // Reject
.then(r => { // Finally
pw.quit()
t.end()
})
.catch(e => t.fail(e)) // Exception
})
test('Puppet Web server/browser communication', function(t) {
const pw = new PuppetWeb({port: PORT})
let pw2
co(function* () {
yield pw.init()
t.pass('pw full inited')
pw2 = new PuppetWeb({port: PORT})
t.ok(pw2, 'new PuppetWeb')
const retSocket = yield dingSocket(pw.server)
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
.then(r => { // Finally
pw.quit()
.catch(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) => {
setTimeout(testDing, waitTime)
return
log.verbose('TestingPuppetWeb', 'dingSocket()')
return testDing()
function testDing() {
//log.silly('TestingPuppetWebServer', server.socketio)
// log.silly('TestingPuppetWeb', 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...')
log.silly('TestingPuppetWeb', 'waiting socketClient to connect for ' + totalTime + '/' + maxTime + ' ms...')
setTimeout(testDing, waitTime)
return
}
//log.silly('TestingPuppetWebServer', server.socketClient)
server.socketClient.on('dong', data => {
server.socketClient.once('dong', data => {
log.verbose('TestingPuppetWeb', 'socket recv event dong: ' + data)
return resolve(data)
})
......
const path = require('path')
const co = require('co')
const test = require('tape')
const test = require('tap').test
const log = require('npmlog')
//log.level = 'silly'
......@@ -8,21 +8,33 @@ const WebDriver = require('selenium-webdriver')
const Browser = WebDriver.Browser
const By = WebDriver.By
const PuppetWebBrowser = require('../src/puppet-web-browser')
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})
const driver = wb.getDriver()
t.ok(wb, 'Browser instnace')
const bridge = new PuppetWebBridge({browser: wb})
t.ok(bridge, 'Bridge instnace')
var driver // for help function `execute`
co(function* () {
const injectio = PuppetWebBrowser.getInjectio()
driver = yield wb.initDriver()
t.ok(driver, 'driver inited')
const injectio = bridge.getInjectio()
t.ok(injectio.length > 10, 'got injectio')
yield driver.get('https://wx.qq.com/')
t.pass('driver got url')
const retAdd = yield execute('return 1+1')
t.equal(retAdd, 2, 'execute js in browser')
const retInject = yield execute(injectio, 8788)
const retInject = yield execute(injectio, PORT)
t.equal(retInject, 'Wechaty', 'injected wechaty')
})
......@@ -33,6 +45,9 @@ test('WebDriver smoke testing', function(t) {
t.end()
driver.quit()
})
.catch(e => { // EXCEPTION
t.fail('exception got:' + e)
})
return
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册