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

Split PuppetPuppeteer Out (#1371)

上级 7cc0a006
......@@ -4,7 +4,3 @@
/docs/ @lijiarui @hczhcz @TingYinHelen @ax4
/examples/ @Gcaufy @hczhcz
/src/puppet-puppeteer/bridge.*.ts @mukaiu @xjchengo
/src/puppet-puppeteer/event.ts @binsee
/src/puppet-puppeteer/firer.*.ts @xinbenlv
/src/puppet-puppeteer/wechaty-bro.js @binsee @mukaiu @hczhcz @cherry-geqi @zhenyong
......@@ -7,7 +7,7 @@
"wechaty": {
"DEFAULT_HEAD": 0,
"DEFAULT_PORT": 8080,
"DEFAULT_PUPPET": "puppeteer",
"DEFAULT_PUPPET": "wechat4u",
"DEFAULT_PROFILE": "default",
"DEFAULT_PROTOCOL": "io|0.0.1",
"DEFAULT_TOKEN": "WECHATY_IO_TOKEN",
......@@ -15,7 +15,7 @@
},
"scripts": {
"clean": "shx rm -fr dist/*",
"dist": "npm run clean && tsc && shx cp src/puppet-puppeteer/*.js dist/src/puppet-puppeteer/",
"dist": "npm run clean && tsc",
"pack": "npm pack",
"doc": "bash -x scripts/generate-docs.sh",
"coverage": "nyc report --reporter=text-lcov | coveralls",
......@@ -124,7 +124,7 @@
"watchdog": "^0.8.1",
"wechat4u": "^0.7.6",
"wechaty-puppet": "^0.2.3",
"wechaty-puppet-wechat4u": "^0.2.2",
"wechaty-puppet-wechat4u": "^0.2.6",
"ws": "^5.2.0",
"xml2json": "^0.11.2"
},
......@@ -184,7 +184,8 @@
"tuling123-client": "^0.0.2",
"typedoc": "^0.11.1",
"typescript": "^2.9.2",
"wechaty-puppet-mock": "^0.2.1"
"wechaty-puppet-mock": "^0.2.1",
"wechaty-puppet-puppeteer": "^0.2.1"
},
"files_comment__whitelist_npm_publish": "http://stackoverflow.com/a/8617868/1123955",
"files": [
......
......@@ -34,10 +34,6 @@ export {
export { IoClient } from './io-client'
export { Misc } from './misc'
export {
PuppetPuppeteer,
} from './puppet-puppeteer/'
export {
Wechaty,
}
......
// import { PuppetMock } from 'wechaty-puppet-mock'
import { PuppetWechat4u } from 'wechaty-puppet-wechat4u'
// import { PuppetPuppeteer } from 'wechaty-puppet-puppeteer'
import { PuppetPuppeteer } from './puppet-puppeteer/'
import { PuppetPadchat } from './puppet-padchat'
/**
......@@ -12,7 +12,7 @@ export const PUPPET_DICT = {
//////////////////////////
// mock: PuppetMock,
padchat: PuppetPadchat,
puppeteer: PuppetPuppeteer,
// puppeteer: PuppetPuppeteer,
wechat4u: PuppetWechat4u,
}
......
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import test from 'blue-tape'
// tslint:disable:no-shadowed-variable
// import sinon from 'sinon'
// const sinonTest = require('sinon-test')(sinon)
import {
launch,
} from 'puppeteer'
// import { spy } from 'sinon'
import {
MemoryCard,
} from 'memory-card'
import Bridge from './bridge'
const PUPPETEER_LAUNCH_OPTIONS = {
headless: true,
args: [
'--disable-gpu',
'--disable-setuid-sandbox',
'--no-sandbox',
],
}
test('PuppetPuppeteerBridge', async t => {
const profile = new MemoryCard()
const bridge = new Bridge({ profile })
try {
await bridge.init()
await bridge.quit()
t.pass('Bridge instnace')
} catch (e) {
t.fail('Bridge instance: ' + e)
}
})
test('preHtmlToXml()', async t => {
const BLOCKED_HTML_ZH = [
'<pre style="word-wrap: break-word; white-space: pre-wrap;">',
'&lt;error&gt;',
'&lt;ret&gt;1203&lt;/ret&gt;',
'&lt;message&gt;当前登录环境异常。为了你的帐号安全,暂时不能登录web微信。你可以通过Windows微信、Mac微信或者手机客户端微信登录。&lt;/message&gt;',
'&lt;/error&gt;',
'</pre>',
].join('')
const BLOCKED_XML_ZH = [
'<error>',
'<ret>1203</ret>',
'<message>当前登录环境异常。为了你的帐号安全,暂时不能登录web微信。你可以通过Windows微信、Mac微信或者手机客户端微信登录。</message>',
'</error>',
].join('')
const profile = new MemoryCard()
const bridge = new Bridge({ profile })
const xml = bridge.preHtmlToXml(BLOCKED_HTML_ZH)
t.equal(xml, BLOCKED_XML_ZH, 'should parse html to xml')
})
test('testBlockedMessage()', async t => {
const BLOCKED_HTML_ZH = [
'<pre style="word-wrap: break-word; white-space: pre-wrap;">',
'&lt;error&gt;',
'&lt;ret&gt;1203&lt;/ret&gt;',
'&lt;message&gt;当前登录环境异常。为了你的帐号安全,暂时不能登录web微信。你可以通过手机客户端或者windows微信登录。&lt;/message&gt;',
'&lt;/error&gt;',
'</pre>',
].join('')
const BLOCKED_XML_ZH = `
<error>
<ret>1203</ret>
<message>当前登录环境异常。为了你的帐号安全,暂时不能登录web微信。你可以通过手机客户端或者windows微信登录。</message>
</error>
`
const BLOCKED_TEXT_ZH = [
'当前登录环境异常。为了你的帐号安全,暂时不能登录web微信。',
'你可以通过手机客户端或者windows微信登录。',
].join('')
// tslint:disable:max-line-length
const BLOCKED_XML_EN = `
<error>
<ret>1203</ret>
<message>For account security, newly registered WeChat accounts are unable to log in to Web WeChat. To use WeChat on a computer, use Windows WeChat or Mac WeChat at http://wechat.com</message>
</error>
`
const BLOCKED_TEXT_EN = [
'For account security, newly registered WeChat accounts are unable to log in to Web WeChat.',
' To use WeChat on a computer, use Windows WeChat or Mac WeChat at http://wechat.com',
].join('')
t.test('not blocked', async t => {
const profile = new MemoryCard()
const bridge = new Bridge({ profile })
const msg = await bridge.testBlockedMessage('this is not xml')
t.equal(msg, false, 'should return false when no block message')
})
t.test('html', async t => {
const profile = new MemoryCard()
const bridge = new Bridge({ profile })
const msg = await bridge.testBlockedMessage(BLOCKED_HTML_ZH)
t.equal(msg, BLOCKED_TEXT_ZH, 'should get zh blocked message')
})
t.test('zh', async t => {
const profile = new MemoryCard()
const bridge = new Bridge({ profile })
const msg = await bridge.testBlockedMessage(BLOCKED_XML_ZH)
t.equal(msg, BLOCKED_TEXT_ZH, 'should get zh blocked message')
})
test('en', async t => {
const profile = new MemoryCard()
const bridge = new Bridge({ profile })
const msg = await bridge.testBlockedMessage(BLOCKED_XML_EN)
t.equal(msg, BLOCKED_TEXT_EN, 'should get en blocked message')
})
})
test('clickSwitchAccount()', async t => {
const SWITCH_ACCOUNT_HTML = `
<div class="association show" ng-class="{show: isAssociationLogin &amp;&amp; !isBrokenNetwork}">
<img class="img" mm-src="" alt="" src="//res.wx.qq.com/a/wx_fed/webwx/res/static/img/2KriyDK.png">
<p ng-show="isWaitingAsConfirm" class="waiting_confirm ng-hide">Confirm login on mobile WeChat</p>
<a href="javascript:;" ng-show="!isWaitingAsConfirm" ng-click="associationLogin()" class="button button_primary">Log in</a>
<a href="javascript:;" ng-click="qrcodeLogin()" class="button button_default">Switch Account</a>
</div>
`
const profile = new MemoryCard()
const bridge = new Bridge({ profile} )
t.test('switch account needed', async t => {
const browser = await launch(PUPPETEER_LAUNCH_OPTIONS)
const page = await browser.newPage()
await page.setContent(SWITCH_ACCOUNT_HTML)
const clicked = await bridge.clickSwitchAccount(page)
await page.close()
await browser.close()
t.equal(clicked, true, 'should click the switch account button')
})
t.test('switch account not needed', async t => {
const browser = await launch(PUPPETEER_LAUNCH_OPTIONS)
const page = await browser.newPage()
await page.setContent('<h1>ok</h1>')
const clicked = await bridge.clickSwitchAccount(page)
await page.close()
await browser.close()
t.equal(clicked, false, 'should no button found')
})
})
test('WechatyBro.ding()', async t => {
const profile = new MemoryCard(Math.random().toString(36).substr(2, 5))
const bridge = new Bridge({
profile,
})
t.ok(bridge, 'should instanciated a bridge')
try {
await bridge.init()
t.pass('should init Bridge')
const retDing = await bridge.evaluate(() => {
return WechatyBro.ding()
}) as any as string
t.is(retDing, 'dong', 'should got dong after execute WechatyBro.ding()')
const retCode = await bridge.proxyWechaty('loginState')
t.is(typeof retCode, 'boolean', 'should got a boolean after call proxyWechaty(loginState)')
await bridge.quit()
t.pass('b.quit()')
} catch (err) {
t.fail('exception: ' + err.message)
} finally {
profile.destroy()
}
})
此差异已折叠。
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import test from 'blue-tape'
// tslint:disable:no-shadowed-variable
// import sinon from 'sinon'
import {
MemoryCard,
} from 'memory-card'
// import Wechaty from '../wechaty'
import {
// Event,
PuppetPuppeteer,
} from './puppet-puppeteer'
test('Puppet Puppeteer Event smoke testing', async t => {
const puppet = new PuppetPuppeteer({
memory: new MemoryCard(),
})
try {
await puppet.start()
t.pass('should be inited')
await puppet.stop()
t.pass('should be quited')
} catch (e) {
t.fail('exception: ' + e.message)
}
})
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
WatchdogFood,
} from 'watchdog'
import {
log,
} from '../config'
// import {
// PuppetScanEvent,
// } from 'wechaty-puppet'
import {
Firer,
} from './firer'
import {
PuppetPuppeteer,
} from './puppet-puppeteer'
import {
WebMessageType,
WebMessageRawPayload,
} from './web-schemas'
/* tslint:disable:variable-name */
export const Event = {
onDing,
onLog,
onLogin,
onLogout,
onMessage,
onScan,
onUnload,
}
function onDing(
this: PuppetPuppeteer,
data: any,
): void {
log.silly('PuppetPuppeteerEvent', 'onDing(%s)', data)
this.emit('watchdog', { data })
}
async function onScan(
this : PuppetPuppeteer,
// Do not use PuppetScanPayload at here, use { code: number, url: string } instead,
// because this is related with Browser Hook Code:
// wechaty-bro.js
payloadFromBrowser : { code: number, url: string },
): Promise<void> {
log.verbose('PuppetPuppeteerEvent', 'onScan({code: %d, url: %s})', payloadFromBrowser.code, payloadFromBrowser.url)
// if (this.state.off()) {
// log.verbose('PuppetPuppeteerEvent', 'onScan(%s) state.off()=%s, NOOP',
// payload, this.state.off())
// return
// }
this.scanPayload = {
qrcode: payloadFromBrowser.url,
status: payloadFromBrowser.code,
}
/**
* When wx.qq.com push a new QRCode to Scan, there will be cookie updates(?)
*/
await this.saveCookie()
if (this.logonoff()) {
log.verbose('PuppetPuppeteerEvent', 'onScan() there has user when got a scan event. emit logout and set it to null')
await this.logout()
}
// feed watchDog a `scan` type of food
const food: WatchdogFood = {
data: payloadFromBrowser,
type: 'scan',
}
this.emit('watchdog', food)
const qrcode = payloadFromBrowser.url.replace(/\/qrcode\//, '/l/')
const status = payloadFromBrowser.code
this.emit('scan', qrcode, status)
}
function onLog(data: any): void {
log.silly('PuppetPuppeteerEvent', 'onLog(%s)', data)
}
async function onLogin(
this: PuppetPuppeteer,
note: string,
ttl = 30,
): Promise<void> {
log.verbose('PuppetPuppeteerEvent', 'onLogin(%s, %d)', note, ttl)
const TTL_WAIT_MILLISECONDS = 1 * 1000
if (ttl <= 0) {
log.verbose('PuppetPuppeteerEvent', 'onLogin(%s) TTL expired')
this.emit('error', new Error('onLogin() TTL expired.'))
return
}
// if (this.state.off()) {
// log.verbose('PuppetPuppeteerEvent', 'onLogin(%s, %d) state.off()=%s, NOOP',
// note, ttl, this.state.off())
// return
// }
if (this.logonoff()) {
throw new Error('onLogin() user had already logined: ' + this.selfId())
// await this.logout()
}
this.scanPayload = undefined
try {
/**
* save login user id to this.userId
*
* issue #772: this.bridge might not inited if the 'login' event fired too fast(because of auto login)
*/
const userId = await this.bridge.getUserName()
if (!userId) {
log.verbose('PuppetPuppeteerEvent', 'onLogin() browser not fully loaded(ttl=%d), retry later', ttl)
const html = await this.bridge.innerHTML()
log.silly('PuppetPuppeteerEvent', 'onLogin() innerHTML: %s', html.substr(0, 500))
setTimeout(onLogin.bind(this, note, ttl - 1), TTL_WAIT_MILLISECONDS)
return
}
log.silly('PuppetPuppeteerEvent', 'bridge.getUserName: %s', userId)
// const user = this.Contact.load(userId)
// await user.ready()
log.silly('PuppetPuppeteerEvent', `onLogin() user ${userId} logined`)
// if (this.state.on() === true) {
await this.saveCookie()
// }
// fix issue #668
await this.waitStable()
await this.login(userId)
} catch (e) {
log.error('PuppetPuppeteerEvent', 'onLogin() exception: %s', e)
throw e
}
return
}
async function onLogout(
this: PuppetPuppeteer,
data: any,
): Promise<void> {
log.verbose('PuppetPuppeteerEvent', 'onLogout(%s)', data)
if (this.logonoff()) {
await this.logout()
} else {
// not logged-in???
log.error('PuppetPuppeteerEvent', 'onLogout() without self-user')
}
}
async function onMessage(
this : PuppetPuppeteer,
rawPayload : WebMessageRawPayload,
): Promise<void> {
// const msg = this.Message.create(
// rawPayload.MsgId,
// await this.messagePayload(rawPayload.MsgId),
// )
const firer = new Firer(this)
/**
* Fire Events if match message type & content
*/
switch (rawPayload.MsgType) {
case WebMessageType.VERIFYMSG:
this.emit('friendship', rawPayload.MsgId)
// firer.checkFriendRequest(rawPayload)
break
case WebMessageType.SYS:
/**
* /^@@/.test() return true means it's a room
*/
if (/^@@/.test(rawPayload.FromUserName)) {
const joinResult = await firer.checkRoomJoin(rawPayload)
const leaveResult = await firer.checkRoomLeave(rawPayload)
const topicRestul = await firer.checkRoomTopic(rawPayload)
if (!joinResult && !leaveResult && !topicRestul) {
log.warn('PuppetPuppeteerEvent', `checkRoomSystem message: <${rawPayload.Content}> not found`)
}
} else {
await firer.checkFriendConfirm(rawPayload)
}
break
}
this.emit('message', rawPayload.MsgId)
}
async function onUnload(this: PuppetPuppeteer): Promise<void> {
log.silly('PuppetPuppeteerEvent', 'onUnload()')
/*
try {
await this.quit()
await this.init()
} catch (e) {
log.error('PuppetPuppeteerEvent', 'onUnload() exception: %s', e)
this.emit('error', e)
throw e
}
*/
}
export default Event
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* Process the Message to find which event to FIRE
*/
// tslint:disable:no-shadowed-variable
import test from 'blue-tape'
// import sinon from 'sinon'
// const sinonTest = require('sinon-test')(sinon)
import { PuppetPuppeteer } from './puppet-puppeteer'
import { Firer } from './firer'
const SELF_ID = 'self-id'
const mockPuppetPuppeteer = {
selfId: () => SELF_ID,
} as any as PuppetPuppeteer
test('parseFriendConfirm()', async t => {
const contentList = [
[
'You have added 李卓桓 as your WeChat contact. Start chatting!',
'李卓桓',
],
[
'你已添加了李卓桓,现在可以开始聊天了。',
'李卓桓',
],
[
`johnbassserver@gmail.com just added you to his/her contacts list. Send a message to him/her now!`,
`johnbassserver@gmail.com`,
],
[
`johnbassserver@gmail.com刚刚把你添加到通讯录,现在可以开始聊天了。`,
`johnbassserver@gmail.com`,
],
]
let result: boolean
const firer = new Firer(mockPuppetPuppeteer)
contentList.forEach(([content]) => {
result = (firer as any).parseFriendConfirm(content)
t.true(result, 'should be truthy for confirm msg: ' + content)
})
result = (firer as any).parseFriendConfirm('fsdfsdfasdfasdfadsa')
t.false(result, 'should be falsy for other msg')
})
test('parseRoomJoin()', async t => {
const contentList: [string, string, string[]][] = [
[
`You invited 管理员 to the group chat. `,
`You`,
[`管理员`],
],
[
`You invited 李卓桓.PreAngel、Bruce LEE to the group chat. `,
`You`,
[`李卓桓.PreAngel`, `Bruce LEE`],
],
[
`管理员 invited 小桔建群助手 to the group chat`,
`管理员`,
[`小桔建群助手`],
],
[
`管理员 invited 庆次、小桔妹 to the group chat`,
`管理员`,
['庆次', '小桔妹'],
],
[
`你邀请"管理员"加入了群聊 `,
`你`,
['管理员'],
],
[
`"管理员"邀请"宁锐锋"加入了群聊`,
`管理员`,
['宁锐锋'],
],
[
`"管理员"通过扫描你分享的二维码加入群聊 `,
`你`,
['管理员'],
],
[
`" 桔小秘"通过扫描"李佳芮"分享的二维码加入群聊`,
`李佳芮`,
['桔小秘'],
],
[
`"管理员" joined group chat via the QR code you shared. `,
`you`,
['管理员'],
],
[
`"宁锐锋" joined the group chat via the QR Code shared by "管理员".`,
`管理员`,
['宁锐锋'],
],
]
const firer = new Firer(mockPuppetPuppeteer)
let result
contentList.forEach(([content, inviter, inviteeList]) => {
result = (firer as any).parseRoomJoin(content)
t.ok(result, 'should check room join message right for ' + content)
t.deepEqual(result[0], inviteeList, 'should get inviteeList right')
t.is(result[1], inviter, 'should get inviter right')
})
t.throws(() => {
(firer as any).parseRoomJoin('fsadfsadfsdfsdfs')
}, Error, 'should throws if message is not expected')
})
test('parseRoomLeave()', async t => {
const contentLeaverList = [
[
`You removed "Bruce LEE" from the group chat`,
`Bruce LEE`,
],
[
'你将"李佳芮"移出了群聊',
'李佳芮',
],
]
const contentRemoverList = [
[
`You were removed from the group chat by "桔小秘"`,
`桔小秘`,
],
[
'你被"李佳芮"移出群聊',
'李佳芮',
],
]
const firer = new Firer(mockPuppetPuppeteer)
contentLeaverList.forEach(([content, leaver]) => {
const resultLeaver = (firer as any).parseRoomLeave(content)[0]
t.ok(resultLeaver, 'should get leaver for leave message: ' + content)
t.is(resultLeaver, leaver, 'should get leaver name right')
})
contentRemoverList.forEach(([content, remover]) => {
const resultRemover = (firer as any).parseRoomLeave(content)[1]
t.ok(resultRemover, 'should get remover for leave message: ' + content)
t.is(resultRemover, remover, 'should get leaver name right')
})
t.throws(() => {
(firer as any).parseRoomLeave('fafdsfsdfafa')
}, Error, 'should throw if message is not expected')
})
test('parseRoomTopic()', async t => {
const contentList = [
[
`"李卓桓.PreAngel" changed the group name to "ding"`,
`李卓桓.PreAngel`,
`ding`,
],
[
'"李佳芮"修改群名为“dong”',
'李佳芮',
'dong',
],
]
const firer = new Firer(mockPuppetPuppeteer)
let result
contentList.forEach(([content, changer, topic]) => {
result = (firer as any).parseRoomTopic(content)
t.ok(result, 'should check topic right for content: ' + content)
t.is(topic , result[0], 'should get right topic')
t.is(changer, result[1], 'should get right changer')
})
t.throws(() => {
(firer as any).parseRoomTopic('fafdsfsdfafa')
}, Error, 'should throw if message is not expected')
})
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/* tslint:disable:no-var-requires */
// const retryPromise = require('retry-promise').default
// import cuid from 'cuid'
import {
log,
} from '../config'
import {
// WebRecomendInfo,
WebMessageRawPayload,
} from './web-schemas'
import PuppetPuppeteer from './puppet-puppeteer'
// import {
// // FriendRequestPayload,
// FriendRequestType,
// FriendRequestPayloadReceive,
// FriendRequestPayloadConfirm,
// } from 'wechaty-puppet'
const REGEX_CONFIG = {
friendConfirm: [
/^You have added (.+) as your WeChat contact. Start chatting!$/,
/^你已添加了(.+),现在可以开始聊天了。$/,
/^(.+) just added you to his\/her contacts list. Send a message to him\/her now!$/,
/^(.+)刚刚把你添加到通讯录,现在可以开始聊天了。$/,
],
roomJoinInvite: [
// There are 3 blank(charCode is 32) here. eg: You invited 管理员 to the group chat.
/^(.+?) invited (.+) to the group chat.\s+$/,
// There no no blank or punctuation here. eg: 管理员 invited 小桔建群助手 to the group chat
/^(.+?) invited (.+) to the group chat$/,
// There are 2 blank(charCode is 32) here. eg: 你邀请"管理员"加入了群聊
/^(.+?)邀请"(.+)"加入了群聊\s+$/,
// There no no blank or punctuation here. eg: "管理员"邀请"宁锐锋"加入了群聊
/^"(.+?)"邀请"(.+)"加入了群聊$/,
],
roomJoinQrcode: [
// Wechat change this, should desperate. See more in pr#651
// /^" (.+)" joined the group chat via the QR Code shared by "?(.+?)".$/,
// There are 2 blank(charCode is 32) here. Qrcode is shared by bot. eg: "管理员" joined group chat via the QR code you shared.
/^"(.+)" joined group chat via the QR code "?(.+?)"? shared.\s+$/,
// There are no blank(charCode is 32) here. Qrcode isn't shared by bot. eg: "宁锐锋" joined the group chat via the QR Code shared by "管理员".
/^"(.+)" joined the group chat via the QR Code shared by "?(.+?)".$/,
// There are 2 blank(charCode is 32) here. Qrcode is shared by bot. eg: "管理员"通过扫描你分享的二维码加入群聊
/^"(.+)"通过扫描(.+?)分享的二维码加入群聊\s+$/,
// There are 1 blank(charCode is 32) here. Qrode isn't shared by bot. eg: " 苏轼"通过扫描"管理员"分享的二维码加入群聊
/^"\s+(.+)"通过扫描"(.+?)"分享的二维码加入群聊$/,
],
// no list
roomLeaveIKickOther: [
/^(You) removed "(.+)" from the group chat$/,
/^()将"(.+)"移出了群聊$/,
],
roomLeaveOtherKickMe: [
/^(You) were removed from the group chat by "(.+)"$/,
/^()被"(.+)"移出群聊$/,
],
roomTopic: [
/^"?(.+?)"? changed the group name to "(.+)"$/,
/^"?(.+?)"?修改群名为“(.+)”$/,
],
}
export class Firer {
constructor(
public puppet: PuppetPuppeteer,
) {
//
}
// public async checkFriendRequest(
// rawPayload : WebMessageRawPayload,
// ): Promise<void> {
// if (!rawPayload.RecommendInfo) {
// throw new Error('no RecommendInfo')
// }
// const recommendInfo: WebRecomendInfo = rawPayload.RecommendInfo
// log.verbose('PuppetPuppeteerFirer', 'fireFriendRequest(%s)', recommendInfo)
// if (!recommendInfo) {
// throw new Error('no recommendInfo')
// }
// const contactId = recommendInfo.UserName
// const hello = recommendInfo.Content
// const ticket = recommendInfo.Ticket
// const type = FriendRequestType.Receive
// const id = cuid()
// const payloadReceive: FriendRequestPayloadReceive = {
// id,
// contactId,
// hello,
// ticket,
// type,
// }
// this.puppet.cacheFriendRequestPayload.set(id, payloadReceive)
// this.puppet.emit('friend', id)
// }
public async checkFriendConfirm(
rawPayload : WebMessageRawPayload,
) {
const content = rawPayload.Content
log.silly('PuppetPuppeteerFirer', 'fireFriendConfirm(%s)', content)
if (!this.parseFriendConfirm(content)) {
return
}
// const contactId = rawPayload.FromUserName
// const type = FriendRequestType.Confirm
// const id = cuid()
// const payloadConfirm: FriendRequestPayloadConfirm = {
// id,
// contactId,
// type,
// }
// this.puppet.cacheFriendRequestPayload.set(id, payloadConfirm)
this.puppet.emit('friendship', rawPayload.MsgId)
}
public async checkRoomJoin(
rawPayload : WebMessageRawPayload,
): Promise<boolean> {
const text = rawPayload.Content
const roomId = rawPayload.FromUserName
/**
* Get the display names of invitee & inviter
*/
let inviteeNameList : string[]
let inviterName : string
try {
[inviteeNameList, inviterName] = this.parseRoomJoin(text)
} catch (e) {
log.silly('PuppetPuppeteerFirer', 'checkRoomJoin() "%s" is not a join message', text)
return false // not a room join message
}
log.silly('PuppetPuppeteerFirer', 'checkRoomJoin() inviteeList: %s, inviter: %s',
inviteeNameList.join(','),
inviterName,
)
/**
* Convert the display name to Contact ID
*/
let inviterContactId: undefined | string = undefined
const inviteeContactIdList: string[] = []
if (/^You|你$/i.test(inviterName)) { // === 'You' || inviter === '你' || inviter === 'you'
inviterContactId = this.puppet.selfId()
}
const sleep = 1000
const timeout = 60 * 1000
let ttl = timeout / sleep
let ready = true
while (ttl-- > 0) {
log.silly('PuppetPuppeteerFirer', 'fireRoomJoin() retry() ttl %d', ttl)
if (!ready) {
await new Promise(r => setTimeout(r, timeout))
ready = true
}
/**
* loop inviteeNameList
* set inviteeContactIdList
*/
for (const i in inviteeNameList) {
const inviteeName = inviteeNameList[i]
const inviteeContactId = inviteeContactIdList[i]
if (inviteeContactId) {
/**
* had already got ContactId for Room Member
* try to resolve the ContactPayload
*/
try {
await this.puppet.contactPayload(inviteeContactId)
} catch (e) {
log.warn('PuppetPuppeteerFirer', 'fireRoomJoin() contactPayload(%s) exception: %s', inviteeContactId, e.message)
ready = false
}
} else {
/**
* only had Name of RoomMember
* try to resolve the ContactId & ContactPayload
*/
const memberIdList = await this.puppet.roomMemberSearch(roomId, inviteeName)
if (memberIdList.length <= 0) {
ready = false
}
const contactId = memberIdList[0]
// XXX: Take out the first one if we have matched many contact.
inviteeContactIdList[i] = contactId
try {
await this.puppet.contactPayload(contactId)
} catch (e) {
ready = false
}
}
}
if (!inviterContactId) {
const contactIdList = await this.puppet.roomMemberSearch(roomId, inviterName)
if (contactIdList.length > 0) {
inviterContactId = contactIdList[0]
} else {
ready = false
}
}
if (ready) {
log.silly('PuppetPuppeteerFirer', 'fireRoomJoin() resolve() inviteeContactIdList: %s, inviterContactId: %s',
inviteeContactIdList.join(','),
inviterContactId,
)
/**
* Resolve All Payload again to make sure the data is ready.
*/
await Promise.all(
inviteeContactIdList.map(
id => this.puppet.contactPayload(id),
),
)
if (!inviterContactId) {
throw new Error('no inviterContactId')
}
await this.puppet.contactPayload(inviterContactId)
await this.puppet.roomPayload(roomId)
this.puppet.emit('room-join', roomId, inviteeContactIdList, inviterContactId)
return true
}
}
log.warn('PuppetPuppeteerFier', 'fireRoomJoin() resolve payload fail.')
return false
}
/**
* You removed "Bruce LEE" from the group chat
*/
public async checkRoomLeave(
rawPayload : WebMessageRawPayload,
): Promise<boolean> {
log.verbose('PuppetPuppeteerFirer', 'fireRoomLeave(%s)', rawPayload.Content)
const roomId = rawPayload.FromUserName
let leaverName : string
let removerName : string
try {
[leaverName, removerName] = this.parseRoomLeave(rawPayload.Content)
} catch (e) {
log.silly('PuppetPuppeteerFirer', 'fireRoomLeave() %s', e.message)
return false
}
log.silly('PuppetPuppeteerFirer', 'fireRoomLeave() got leaverName: %s', leaverName)
/**
* FIXME: leaver maybe is a list
* @lijiarui: I have checked, leaver will never be a list. If the bot remove 2 leavers at the same time, it will be 2 sys message, instead of 1 sys message contains 2 leavers.
*/
let leaverContactId : undefined | string
let removerContactId : undefined | string
if (/^(You|你)$/i.test(leaverName)) {
leaverContactId = this.puppet.selfId()
} else if (/^(You|你)$/i.test(removerName)) {
removerContactId = this.puppet.selfId()
}
if (!leaverContactId) {
const idList = await this.puppet.roomMemberSearch(roomId, leaverName)
leaverContactId = idList[0]
}
if (!removerContactId) {
const idList = await this.puppet.roomMemberSearch(roomId, removerName)
removerContactId = idList[0]
}
if (!leaverContactId || !removerContactId) {
throw new Error('no id')
}
/**
* FIXME: leaver maybe is a list
* @lijiarui 2017: I have checked, leaver will never be a list. If the bot remove 2 leavers at the same time,
* it will be 2 sys message, instead of 1 sys message contains 2 leavers.
* @huan 2018 May: we need to generilize the pattern for future usage.
*/
this.puppet.emit('room-leave', roomId , [leaverContactId], removerContactId)
setTimeout(async _ => {
await this.puppet.roomPayloadDirty(roomId)
await this.puppet.roomPayload(roomId)
}, 10 * 1000) // reload the room data, especially for memberList
return true
}
public async checkRoomTopic(
rawPayload : WebMessageRawPayload,
): Promise<boolean> {
let topic : string
let changer : string
try {
[topic, changer] = this.parseRoomTopic(rawPayload.Content)
} catch (e) { // not found
return false
}
const roomId = rawPayload.ToUserName
const roomPayload = await this.puppet.roomPayload(roomId)
const oldTopic = roomPayload.topic
let changerContactId: undefined | string
if (/^(You|你)$/.test(changer)) {
changerContactId = this.puppet.selfId()
} else {
changerContactId = (await this.puppet.roomMemberSearch(roomId, changer))[0]
}
if (!changerContactId) {
log.error('PuppetPuppeteerFirer', 'fireRoomTopic() changer contact not found for %s', changer)
return false
}
try {
this.puppet.emit('room-topic', roomId , topic, oldTopic, changerContactId)
return true
} catch (e) {
log.error('PuppetPuppeteerFirer', 'fireRoomTopic() co exception: %s', e.stack)
return false
}
}
/**
* try to find FriendRequest Confirmation Message
*/
private parseFriendConfirm(
content: string,
): boolean {
const reList = REGEX_CONFIG.friendConfirm
let found = false
reList.some(re => !!(found = re.test(content)))
if (found) {
return true
} else {
return false
}
}
/**
* try to find 'join' event for Room
*
* 1.
* You invited 管理员 to the group chat.
* You invited 李卓桓.PreAngel、Bruce LEE to the group chat.
* 2.
* 管理员 invited 小桔建群助手 to the group chat
* 管理员 invited 庆次、小桔妹 to the group chat
*/
private parseRoomJoin(
content: string,
): [string[], string] {
log.verbose('PuppetPuppeteerFirer', 'parseRoomJoin(%s)', content)
const reListInvite = REGEX_CONFIG.roomJoinInvite
const reListQrcode = REGEX_CONFIG.roomJoinQrcode
let foundInvite: string[]|null = []
reListInvite.some(re => !!(foundInvite = content.match(re)))
let foundQrcode: string[]|null = []
reListQrcode.some(re => !!(foundQrcode = content.match(re)))
if ((!foundInvite || !foundInvite.length) && (!foundQrcode || !foundQrcode.length)) {
throw new Error('parseRoomJoin() not found matched re of ' + content)
}
/**
* 管理员 invited 庆次、小桔妹 to the group chat
* "管理员"通过扫描你分享的二维码加入群聊
*/
const [inviter, inviteeStr] = foundInvite ? [ foundInvite[1], foundInvite[2] ] : [ foundQrcode[2], foundQrcode[1] ]
const inviteeList = inviteeStr.split(/、/)
return [inviteeList, inviter] // put invitee at first place
}
private parseRoomLeave(
content: string,
): [string, string] {
let matchIKickOther: null | string[] = []
REGEX_CONFIG.roomLeaveIKickOther.some(
regex => !!(
matchIKickOther = content.match(regex)
),
)
let matchOtherKickMe: null | string[] = []
REGEX_CONFIG.roomLeaveOtherKickMe.some(
re => !!(
matchOtherKickMe = content.match(re)
),
)
let leaverName : undefined | string
let removerName : undefined | string
if (matchIKickOther && matchIKickOther.length) {
leaverName = matchIKickOther[2]
removerName = matchIKickOther[1]
} else if (matchOtherKickMe && matchOtherKickMe.length) {
leaverName = matchOtherKickMe[1]
removerName = matchOtherKickMe[2]
} else {
throw new Error('no match')
}
return [leaverName, removerName]
}
private parseRoomTopic(
content: string,
): [string, string] {
const reList = REGEX_CONFIG.roomTopic
let found: string[]|null = []
reList.some(re => !!(found = content.match(re)))
if (!found || !found.length) {
throw new Error('checkRoomTopic() not found')
}
const [, changer, topic] = found
return [topic, changer]
}
}
export default Firer
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-shadowed-variable
import test from 'blue-tape'
// import sinon from 'sinon'
import {
PuppetPuppeteer,
} from './'
test('PuppetPuppeteer Module Exports', async t => {
t.ok(PuppetPuppeteer , 'should export PuppetPuppeteer')
})
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { PuppetPuppeteer } from './puppet-puppeteer'
export {
PuppetPuppeteer,
}
export default PuppetPuppeteer
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-shadowed-variable
import test from 'blue-tape'
import sinon from 'sinon'
const sinonTest = require('sinon-test')(sinon, {
useFakeTimers: { // https://github.com/sinonjs/lolex
shouldAdvanceTime : true,
advanceTimeDelta : 10,
},
})
// import { log } from '../../src/config'
// log.level('silly')
import { MemoryCard } from 'memory-card'
import { Wechaty } from '../wechaty'
import { PuppetPuppeteer } from './puppet-puppeteer'
import { Bridge } from './bridge'
import { Event } from './event'
class WechatyTest extends Wechaty {
public initPuppetAccessory(puppet: PuppetPuppeteer) {
super.initPuppetAccessory(puppet)
}
public initPuppetEventBridge(puppet: PuppetPuppeteer) {
super.initPuppetEventBridge(puppet)
}
}
class PuppetTest extends PuppetPuppeteer {
public contactRawPayload(id: string) {
return super.contactRawPayload(id)
}
public roomRawPayload(id: string) {
return super.roomRawPayload(id)
}
public messageRawPayload(id: string) {
return super.messageRawPayload(id)
}
}
// test('Puppet smoke testing', async t => {
// const puppet = new PuppetTest({ memory: new MemoryCard() })
// const wechaty = new WechatyTest({ puppet })
// wechaty.initPuppetAccessory(puppet)
// t.ok(puppet.state.off(), 'should be OFF state after instanciate')
// puppet.state.on('pending')
// t.ok(puppet.state.on(), 'should be ON state after set')
// t.ok(puppet.state.pending(), 'should be pending state after set')
// })
test('login/logout events', sinonTest(async function (t: test.Test) {
const sandbox = sinon.createSandbox()
try {
const puppet = new PuppetTest({ memory: new MemoryCard() })
const wechaty = new WechatyTest({ puppet })
wechaty.initPuppetAccessory(puppet)
wechaty.initPuppetEventBridge(puppet)
sandbox.stub(Event, 'onScan') // block the scan event to prevent reset logined user
sandbox.stub(Bridge.prototype, 'getUserName').resolves('mockedUserName')
sandbox.stub(Bridge.prototype, 'contactList')
.onFirstCall().resolves([])
.onSecondCall().resolves([1])
.resolves([1, 2])
sandbox.stub(puppet, 'contactRawPayload').resolves({
NickName: 'mockedNickName',
UserName: 'mockedUserName',
})
// sandbox.stub(puppet, 'waitStable').resolves()
await puppet.start()
t.pass('should be inited')
t.is(puppet.logonoff() , false , 'should be not logined')
const future = new Promise(r => wechaty.once('login', r))
.catch(e => t.fail(e))
puppet.bridge.emit('login', 'TestPuppetPuppeteer')
await future
t.is(puppet.logonoff(), true, 'should be logined')
t.ok((puppet.bridge.getUserName as any).called, 'bridge.getUserName should be called')
t.ok((puppet.contactRawPayload as any).called, 'puppet.contactRawPayload should be called')
t.ok((Bridge.prototype.contactList as any).called, 'contactList stub should be called')
t.is((Bridge.prototype.contactList as any).callCount, 4, 'should call stubContacList 4 times')
const logoutPromise = new Promise(resolve => puppet.once('logout', _ => resolve('logoutFired')))
puppet.bridge.emit('logout')
t.is(await logoutPromise, 'logoutFired', 'should fire logout event')
t.is(puppet.logonoff(), false, 'should be logouted')
await puppet.stop()
} catch (e) {
t.fail(e)
} finally {
sandbox.restore()
}
}))
此差异已折叠。
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-shadowed-variable
import test from 'blue-tape'
import sinon from 'sinon'
import {
cloneClass,
} from 'clone-class'
import {
MemoryCard,
} from 'memory-card'
import {
log,
} from '../config'
// import Wechaty from '../wechaty'
import { Contact } from '../user'
import PuppetPuppeteer from './puppet-puppeteer'
test('Contact smoke testing', async t => {
/* tslint:disable:variable-name */
const UserName = '@0bb3e4dd746fdbd4a80546aef66f4085'
const NickName = 'NickNameTest'
const RemarkName = 'AliasTest'
const sandbox = sinon.createSandbox()
function mockContactPayload(id: string) {
log.verbose('PuppeteerContactTest', 'mockContactPayload(%s)', id)
return new Promise<any>(resolve => {
if (id !== UserName) return resolve({})
setImmediate(() => resolve({
UserName: UserName,
NickName: NickName,
RemarkName: RemarkName,
}))
})
}
const puppet = new PuppetPuppeteer({
memory: new MemoryCard(),
// wechaty: new Wechaty(),
})
sandbox.stub(puppet as any, 'contactRawPayload').callsFake(mockContactPayload)
// tslint:disable-next-line:variable-name
const MyContact = cloneClass(Contact)
MyContact.puppet = puppet
const c = new MyContact(UserName)
t.is(c.id, UserName, 'id/UserName right')
await c.ready()
t.is(c.name(), NickName, 'NickName set')
t.is(c.alias(), RemarkName, 'should get the right alias from Contact')
sandbox.restore()
// const contact1 = await Contact.find({name: 'NickNameTest'})
// t.is(contact1.id, UserName, 'should find contact by name')
// const contact2 = await Contact.find({alias: 'AliasTest'})
// t.is(contact2.id, UserName, 'should find contact by alias')
})
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-shadowed-variable
import test from 'blue-tape'
import sinon from 'sinon'
// const sinonTest = require('sinon-test')(sinon)
import {
MemoryCard,
} from 'memory-card'
import {
Contact,
} from '../user'
import {
Wechaty,
} from '../wechaty'
import {
FriendshipPayload,
FriendshipType,
} from 'wechaty-puppet'
import {
PuppetPuppeteer,
} from './puppet-puppeteer'
import {
WebMessageRawPayload,
} from './web-schemas'
class WechatyTest extends Wechaty {
public initPuppetAccessory(puppet: PuppetPuppeteer) {
super.initPuppetAccessory(puppet)
}
}
class PuppetTest extends PuppetPuppeteer {
public contactRawPayload(id: string) {
return super.contactRawPayload(id)
}
public roomRawPayload(id: string) {
return super.roomRawPayload(id)
}
public messageRawPayload(id: string) {
return super.messageRawPayload(id)
}
}
test('PuppetPuppeteerFriendship.receive smoke testing', async t => {
const puppet = new PuppetTest({ memory: new MemoryCard() })
const wechaty = new WechatyTest({ puppet })
wechaty.initPuppetAccessory(puppet)
/* tslint:disable:max-line-length */
const rawMessagePayload: WebMessageRawPayload = JSON.parse(`
{"MsgId":"3225371967511173931","FromUserName":"fmessage","ToUserName":"@f7321198e0349f1b38c9f2ef158f70eb","MsgType":37,"Content":"&lt;msg fromusername=\\"wxid_a8d806dzznm822\\" encryptusername=\\"v1_c1e03a32c60dd9a9e14f1092132808a2de0ad363f79b303693654282954fbe4d3e12481166f4b841f28de3dd58b0bd54@stranger\\" fromnickname=\\"李卓桓.PreAngel\\" content=\\"我是群聊&amp;quot;Wechaty&amp;quot;的李卓桓.PreAngel\\" shortpy=\\"LZHPREANGEL\\" imagestatus=\\"3\\" scene=\\"14\\" country=\\"CN\\" province=\\"Beijing\\" city=\\"Haidian\\" sign=\\"投资人中最会飞的程序员。好友请加 918999 ,因为本号好友已满。\\" percard=\\"1\\" sex=\\"1\\" alias=\\"zixia008\\" weibo=\\"\\" weibonickname=\\"\\" albumflag=\\"0\\" albumstyle=\\"0\\" albumbgimgid=\\"911623988445184_911623988445184\\" snsflag=\\"49\\" snsbgimgid=\\"http://mmsns.qpic.cn/mmsns/zZSYtpeVianSQYekFNbuiajROicLficBzzeGuvQjnWdGDZ4budZovamibQnoKWba7D2LeuQRPffS8aeE/0\\" snsbgobjectid=\\"12183966160653848744\\" mhash=\\"\\" mfullhash=\\"\\" bigheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/0\\" smallheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/132\\" ticket=\\"v2_ba70dfbdb1b10168d61c1ab491be19e219db11ed5c28701f605efb4dccbf132f664d8a4c9ef6e852b2a4e8d8638be81d125c2e641f01903669539c53f1e582b2@stranger\\" opcode=\\"2\\" googlecontact=\\"\\" qrticket=\\"\\" chatroomusername=\\"2332413729@chatroom\\" sourceusername=\\"\\" sourcenickname=\\"\\"&gt;&lt;brandlist count=\\"0\\" ver=\\"670564024\\"&gt;&lt;/brandlist&gt;&lt;/msg&gt;","Status":3,"ImgStatus":1,"CreateTime":1475567560,"VoiceLength":0,"PlayLength":0,"FileName":"","FileSize":"","MediaId":"","Url":"","AppMsgType":0,"StatusNotifyCode":0,"StatusNotifyUserName":"","RecommendInfo":{"UserName":"@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49","NickName":"李卓桓.PreAngel","Province":"北京","City":"海淀","Content":"我是群聊\\"Wechaty\\"的李卓桓.PreAngel","Signature":"投资人中最会飞的程序员。好友请加 918999 ,因为本号好友已满。","Alias":"zixia008","Scene":14,"AttrStatus":233251,"Sex":1,"Ticket":"v2_ba70dfbdb1b10168d61c1ab491be19e219db11ed5c28701f605efb4dccbf132f664d8a4c9ef6e852b2a4e8d8638be81d125c2e641f01903669539c53f1e582b2@stranger","OpCode":2,"HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49&skey=@crypt_f9cec94b_5b073dca472bd5e41771d309bb8c37bd&msgid=3225371967511173931","MMFromVerifyMsg":true},"ForwardFlag":0,"AppInfo":{"AppID":"","Type":0},"HasProductId":0,"Ticket":"","ImgHeight":0,"ImgWidth":0,"SubMsgType":0,"NewMsgId":3225371967511174000,"MMPeerUserName":"fmessage","MMDigest":"李卓桓.PreAngel想要将你加为朋友","MMIsSend":false,"MMIsChatRoom":false,"MMUnread":true,"LocalID":"3225371967511173931","ClientMsgId":"3225371967511173931","MMActualContent":"&lt;msg fromusername=\\"wxid_a8d806dzznm822\\" encryptusername=\\"v1_c1e03a32c60dd9a9e14f1092132808a2de0ad363f79b303693654282954fbe4d3e12481166f4b841f28de3dd58b0bd54@stranger\\" fromnickname=\\"李卓桓.PreAngel\\" content=\\"我是群聊&amp;quot;Wechaty&amp;quot;的李卓桓.PreAngel\\" shortpy=\\"LZHPREANGEL\\" imagestatus=\\"3\\" scene=\\"14\\" country=\\"CN\\" province=\\"Beijing\\" city=\\"Haidian\\" sign=\\"投资人中最会飞的程序员。好友请加 918999 ,因为本号好友已满。\\" percard=\\"1\\" sex=\\"1\\" alias=\\"zixia008\\" weibo=\\"\\" weibonickname=\\"\\" albumflag=\\"0\\" albumstyle=\\"0\\" albumbgimgid=\\"911623988445184_911623988445184\\" snsflag=\\"49\\" snsbgimgid=\\"http://mmsns.qpic.cn/mmsns/zZSYtpeVianSQYekFNbuiajROicLficBzzeGuvQjnWdGDZ4budZovamibQnoKWba7D2LeuQRPffS8aeE/0\\" snsbgobjectid=\\"12183966160653848744\\" mhash=\\"\\" mfullhash=\\"\\" bigheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/0\\" smallheadimgurl=\\"http://wx.qlogo.cn/mmhead/ver_1/xct7OPTbuU6iaS8gTaK2VibhRs3rATwnU1rCUwWu8ic89EGOynaic2Y4MUdKr66khhAplcfFlm7xbXhum5reania3fXDXH6CI9c3Bb4BODmYAh04/132\\" ticket=\\"v2_ba70dfbdb1b10168d61c1ab491be19e219db11ed5c28701f605efb4dccbf132f664d8a4c9ef6e852b2a4e8d8638be81d125c2e641f01903669539c53f1e582b2@stranger\\" opcode=\\"2\\" googlecontact=\\"\\" qrticket=\\"\\" chatroomusername=\\"2332413729@chatroom\\" sourceusername=\\"\\" sourcenickname=\\"\\"&gt;&lt;brandlist count=\\"0\\" ver=\\"670564024\\"&gt;&lt;/brandlist&gt;&lt;/msg&gt;","MMActualSender":"fmessage","MMDigestTime":"15:52","MMDisplayTime":1475567560,"MMTime":"15:52"}
`)
const info = rawMessagePayload.RecommendInfo!
const contact = wechaty.Contact.load(info.UserName)
const hello = info.Content
const ticket = info.Ticket
const id = 'id'
const type = FriendshipType.Receive
const payload: FriendshipPayload = {
id,
type,
contactId: contact.id,
hello,
ticket,
}
const sandbox = sinon.createSandbox()
sandbox.stub(puppet, 'friendshipPayload').resolves(payload)
sandbox.stub(puppet, 'friendshipPayloadCache').returns(payload)
const fr = wechaty.Friendship.load(id)
await fr.ready()
t.is(fr.hello(), '我是群聊"Wechaty"的李卓桓.PreAngel', 'should has right request message')
t.true(fr.contact() instanceof Contact, 'should have a Contact instance')
t.is(fr.type(), wechaty.Friendship.Type.Receive, 'should be receive type')
sandbox.restore()
})
test('PuppetPuppeteerFriendship.confirm smoke testing', async t => {
const puppet = new PuppetTest({ memory: new MemoryCard() })
const wechaty = new WechatyTest({ puppet })
wechaty.initPuppetAccessory(puppet)
/* tslint:disable:max-line-length */
const rawMessagePayload: WebMessageRawPayload = JSON.parse(`
{"MsgId":"3382012679535022763","FromUserName":"@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49","ToUserName":"@f7321198e0349f1b38c9f2ef158f70eb","MsgType":10000,"Content":"You have added 李卓桓.PreAngel as your WeChat contact. Start chatting!","Status":4,"ImgStatus":1,"CreateTime":1475569920,"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":3382012679535022600,"MMPeerUserName":"@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49","MMDigest":"You have added 李卓桓.PreAngel as your WeChat contact. Start chatting!","MMIsSend":false,"MMIsChatRoom":false,"LocalID":"3382012679535022763","ClientMsgId":"3382012679535022763","MMActualContent":"You have added 李卓桓.PreAngel as your WeChat contact. Start chatting!","MMActualSender":"@04a0fa314d0d8d50dc54e2ec908744ebf46b87404d143fd9a6692182dd90bd49","MMDigestTime":"16:32","MMDisplayTime":1475569920,"MMTime":"16:32"}
`)
const friendshipPayload: FriendshipPayload = {
id : 'id',
type : FriendshipType.Confirm,
contactId : 'xxx',
}
const sandbox = sinon.createSandbox()
sandbox.stub(puppet, 'messageRawPayload') .resolves(rawMessagePayload)
sandbox.stub(puppet, 'contactPayload') .resolves({})
sandbox.stub(puppet, 'contactPayloadCache') .returns({})
sandbox.stub(puppet, 'friendshipPayload') .resolves(friendshipPayload)
sandbox.stub(puppet, 'friendshipPayloadCache') .returns(friendshipPayload)
const msg = wechaty.Message.create(rawMessagePayload.MsgId)
await msg.ready()
t.true(/^You have added (.+) as your WeChat contact. Start chatting!$/.test(msg.text()), 'should match confirm message')
const fr = wechaty.Friendship.load('xx')
await fr.ready()
t.true(fr.contact() instanceof Contact, 'should have a Contact instance')
t.is(fr.type(), wechaty.Friendship.Type.Confirm, 'should be confirm type')
sandbox.restore()
})
#!/usr/bin/env ts-node
/**
* Wechaty - https://github.com/chatie/wechaty
*
* @copyright 2016-2018 Huan LI <zixia@zixia.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-shadowed-variable
import test from 'blue-tape'
import sinon from 'sinon'
// import cloneClass from 'clone-class'
import {
MemoryCard,
} from 'memory-card'
import {
log,
} from '../config'
import {
Wechaty,
} from '../wechaty'
import {
PuppetPuppeteer,
} from './puppet-puppeteer'
import {
WebRoomRawPayload,
} from './web-schemas'
class WechatyTest extends Wechaty {
public initPuppetAccessory(puppet: PuppetPuppeteer) {
super.initPuppetAccessory(puppet)
}
}
class PuppetPuppeteerTest extends PuppetPuppeteer {
public id?: string = undefined
}
// tslint:disable:max-line-length
const ROOM_RAW_PAYLOAD: WebRoomRawPayload = JSON.parse(`{"RemarkPYQuanPin":"","RemarkPYInitial":"","PYInitial":"TZZGQNTSHGFJ","PYQuanPin":"tongzhizhongguoqingniantianshihuiguanfangjia","Uin":0,"UserName":"@@e2355db381dc46a77c0b95516d05e7486135cb6370d8a6af66925d89d50ec278","NickName":"(通知)中国青年天使会官方家","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=670397504&username=@@e2355db381dc46a77c0b95516d05e7486135cb6370d8a6af66925d89d50ec278&skey=","ContactFlag":2,"MemberCount":146,"MemberList":[{"Uin":0,"UserName":"@ecff4a7a86f23455dc42317269aa36ab","NickName":"童玮亮","AttrStatus":103423,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"dap","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@ecff4a7a86f23455dc42317269aa36ab&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@eac4377ecfd59e4321262f892177169f","NickName":"麦刚","AttrStatus":33674247,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"mai","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@eac4377ecfd59e4321262f892177169f&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@ad85207730aa94e006ddce28f74e6878","NickName":"田美坤Maggie","AttrStatus":112679,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"田美坤","KeyWord":"tia","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@ad85207730aa94e006ddce28f74e6878&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":2351423900,"UserName":"@33cc239d22b20d56395bbbd0967b28b9","NickName":"周宏光","AttrStatus":327869,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"周宏光","KeyWord":"acc","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@33cc239d22b20d56395bbbd0967b28b9&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@5e77381e1e3b5641ddcee44670b6e83a","NickName":"牛文文","AttrStatus":100349,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"niu","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@5e77381e1e3b5641ddcee44670b6e83a&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@56941ef97f3e9c70af88667fdd613b44","NickName":"羊东 东方红酒窖","AttrStatus":33675367,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"Yan","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@56941ef97f3e9c70af88667fdd613b44&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@72c4767ce32db488871fdd1c27173b81","NickName":"李竹~英诺天使(此号已满)","AttrStatus":235261,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"liz","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@72c4767ce32db488871fdd1c27173b81&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@0b0e2eb9501ab2d84f9f800f6a0b4216","NickName":"周静彤 杨宁助理","AttrStatus":230885,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"zlo","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@0b0e2eb9501ab2d84f9f800f6a0b4216&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@4bfa767be0cd3fb78409b9735d1dcc57","NickName":"周哲 Jeremy","AttrStatus":33791995,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"zho","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@4bfa767be0cd3fb78409b9735d1dcc57&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"},{"Uin":0,"UserName":"@ad954bf2159a572b7743a5bc134739f4","NickName":"vicky张","AttrStatus":100477,"PYInitial":"","PYQuanPin":"","RemarkPYInitial":"","RemarkPYQuanPin":"","MemberStatus":0,"DisplayName":"","KeyWord":"hua","HeadImgUrl":"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@ad954bf2159a572b7743a5bc134739f4&skey=@crypt_f9cec94b_f23a307a23231cfb5098faf91ff759ca&chatroomid=@4b8baa99bdfc354443711412126d2aaf"}],"RemarkName":"","HideInputBarFlag":0,"Sex":0,"Signature":"","VerifyFlag":0,"OwnerUin":2351423900,"StarFriend":0,"AppAccountFlag":0,"Statues":0,"AttrStatus":0,"Province":"","City":"","Alias":"","SnsFlag":0,"UniFriend":0,"DisplayName":"","ChatRoomId":0,"KeyWord":"","EncryChatRoomId":"@4b8baa99bdfc354443711412126d2aaf","MMFromBatchGet":true,"MMOrderSymbol":"TONGZHIZHONGGUOQINGNIANTIANSHIHUIGUANFANGJIA","MMFromBatchget":true,"MMInChatroom":true}`)
const CONTACT_RAW_PAYLOAD_DICT = JSON.parse(`{"@ad85207730aa94e006ddce28f74e6878":{ "UserName": "@ad85207730aa94e006ddce28f74e6878","NickName": "田美坤Maggie","RemarkName": "" },"@72c4767ce32db488871fdd1c27173b81":{ "UserName": "@72c4767ce32db488871fdd1c27173b81","NickName": "李竹~英诺天使(此号已满)","RemarkName": "" },"@ecff4a7a86f23455dc42317269aa36ab":{ "UserName": "@ecff4a7a86f23455dc42317269aa36ab","NickName": "童玮亮","RemarkName": "童玮亮备注" }}`)
const ROOM_EXPECTED = {
id: '@@e2355db381dc46a77c0b95516d05e7486135cb6370d8a6af66925d89d50ec278',
topic: '(通知)中国青年天使会官方家',
encryId: '@4b8baa99bdfc354443711412126d2aaf',
memberId1: '@ad85207730aa94e006ddce28f74e6878',
memberNick1: '田美坤',
memberId2: '@72c4767ce32db488871fdd1c27173b81',
memberNick2: '李竹~英诺天使(此号已满)',
memberId3: '@ecff4a7a86f23455dc42317269aa36ab',
memberNick3: '童玮亮备注',
ownerId: '@33cc239d22b20d56395bbbd0967b28b9',
}
test('Room smok testing', async t => {
// Mock
const mockContactRoomRawPayload = function (id: string) {
log.verbose('PuppeteerRoomTest', 'mockContactRawPayload(%s)', id)
return new Promise(resolve => {
if (id === ROOM_EXPECTED.id) {
setImmediate(() => resolve(ROOM_RAW_PAYLOAD))
} else if (id in CONTACT_RAW_PAYLOAD_DICT) {
setImmediate(() => resolve(CONTACT_RAW_PAYLOAD_DICT[id]))
} else {
// ignore other ids
setImmediate(() => resolve({id}))
}
})
}
const sandbox = sinon.createSandbox()
const puppet = new PuppetPuppeteerTest({
memory: new MemoryCard(),
})
const wechaty = new WechatyTest({ puppet })
wechaty.initPuppetAccessory(puppet)
sandbox.stub(puppet, 'contactRawPayload').callsFake(mockContactRoomRawPayload)
sandbox.stub(puppet, 'roomRawPayload').callsFake(mockContactRoomRawPayload)
sandbox.stub(puppet, 'id').value('pretend-to-be-logined')
const room = wechaty.Room.load(ROOM_EXPECTED.id)
await room.ready()
t.is(room.id, ROOM_EXPECTED.id, 'should set id/UserName right')
// t.is((r as any).payload[.('encryId') , EXPECTED.encryId, 'should set EncryChatRoomId')
t.is(await room.topic(), ROOM_EXPECTED.topic, 'should set topic/NickName')
const contact1 = new wechaty.Contact(ROOM_EXPECTED.memberId1)
const alias1 = await room.alias(contact1)
t.is(alias1, ROOM_EXPECTED.memberNick1, 'should get roomAlias')
// const name1 = r.alias(contact1)
// t.is(name1, EXPECTED.memberNick1, 'should get roomAlias')
const contact2 = wechaty.Contact.load(ROOM_EXPECTED.memberId2)
const alias2 = await room.alias(contact2)
t.is(alias2, null, 'should return null if not set roomAlias')
// const name2 = r.alias(contact2)
// t.is(name2, null, 'should return null if not set roomAlias')
t.equal(await room.has(contact1), true, 'should has contact1')
const noSuchContact = wechaty.Contact.load('not exist id')
t.equal(await room.has(noSuchContact), false, 'should has no this member')
const owner = room.owner()
t.true(owner === null || owner instanceof wechaty.Contact, 'should get Contact instance for owner, or null')
// wxApp hide uin for all contacts.
// t.is(r.owner().id, EXPECTED.ownerId, 'should get owner right by OwnerUin & Uin')
const contactA = await room.member(ROOM_EXPECTED.memberNick1)
if (!contactA) {
throw new Error(`member(${ROOM_EXPECTED.memberNick1}) should get member by roomAlias by default`)
}
const contactB = await room.member(ROOM_EXPECTED.memberNick2)
const contactC = await room.member(ROOM_EXPECTED.memberNick3)
const contactD = await room.member({roomAlias: ROOM_EXPECTED.memberNick1})
if (!contactB) {
throw new Error(`member(${ROOM_EXPECTED.memberNick2}) should get member by name by default`)
}
if (!contactC) {
throw new Error(`member(${ROOM_EXPECTED.memberNick3}) should get member by name by default`)
}
if (!contactD) {
throw new Error(`member({alias: ${ROOM_EXPECTED.memberNick3}}) should get member by roomAlias`)
}
t.is(contactA.id, ROOM_EXPECTED.memberId1, `should get the right id from ${ROOM_EXPECTED.memberId1}, find member by default`)
t.is(contactB.id, ROOM_EXPECTED.memberId2, `should get the right id from ${ROOM_EXPECTED.memberId2}, find member by default`)
t.is(contactC.id, ROOM_EXPECTED.memberId3, `should get the right id from ${ROOM_EXPECTED.memberId3}, find member by default`)
t.is(contactD.id, ROOM_EXPECTED.memberId1, `should get the right id from ${ROOM_EXPECTED.memberId1}, find member by roomAlias`)
const s = room.toString()
t.is(typeof s, 'string', 'toString()')
sandbox.restore()
})
// test('Room static method', async t => {
// const puppet = new PuppetPuppeteer({
// memory: new MemoryCard(),
// })
// const wechaty = new WechatyTest({ puppet })
// wechaty.initPuppetAccessory(puppet)
// try {
// const result = await wechaty.Room.find({ topic: 'xxx' })
// t.is(result, null, `should return null if cannot find the room`)
// } catch (e) {
// t.pass('should throw before login or not found')
// }
// const roomList = await wechaty.Room.findAll({
// topic: 'yyy',
// })
// t.is(roomList.length, 0, 'should return empty array before login')
// })
test('Room iterator for contact in it', async t => {
// Mock
const mockContactRoomRawPayload = function (id: string) {
log.verbose('PuppeteerRoomTest', 'mockContactRawPayload(%s)', id)
return new Promise(resolve => {
if (id === ROOM_EXPECTED.id) {
setImmediate(() => resolve(ROOM_RAW_PAYLOAD))
} else if (id in CONTACT_RAW_PAYLOAD_DICT) {
setImmediate(() => resolve(CONTACT_RAW_PAYLOAD_DICT[id]))
} else {
// ignore other ids
setImmediate(() => resolve({id}))
}
})
}
const sandbox = sinon.createSandbox()
const puppet = new PuppetPuppeteer({
memory: new MemoryCard(),
})
const wechaty = new WechatyTest({ puppet })
wechaty.initPuppetAccessory(puppet)
sandbox.stub(puppet, 'contactRawPayload').callsFake(mockContactRoomRawPayload)
sandbox.stub(puppet, 'roomRawPayload').callsFake(mockContactRoomRawPayload)
const room = wechaty.Room.load(ROOM_EXPECTED.id)
await room.ready()
const MEMBER_CONTACT_ID_LIST = ROOM_RAW_PAYLOAD.MemberList!.map(rawMember => rawMember.UserName)
let n = 0
for await (const memberContact of room) {
t.ok(MEMBER_CONTACT_ID_LIST.includes(memberContact.id), 'should get one of the room member: ' + memberContact.id)
n++
}
const memberList = await room.memberList()
t.equal(n, memberList.length, 'should iterate all the members of the room')
})
export * from './message-extname'
export * from './message-filename'
export * from './is-type'
export * from './message-raw-payload-parser'
export * from './web-message-type'
export function isRoomId(id: string): boolean {
return /^@@/.test(id)
}
export function isContactId(id: string): boolean {
return !isRoomId(id)
}
此差异已折叠。
此差异已折叠。
......@@ -2,12 +2,3 @@ declare module 'bl'
declare module 'blessed-contrib'
declare module 'qrcode-terminal'
declare module 'json-rpc-peer'
declare var window
// Extend the `Window` from Browser
interface Window {
emit: Function, // from puppeteer
}
declare const WechatyBro: any
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册