diff --git a/examples/ding-dong-bot.ts b/examples/ding-dong-bot.ts index 3fbc67b367e3be72283695f4ddc9f2d4f9a2871c..f90a103d8a6af1201b37a5be0bdae35d8d4be0a3 100644 --- a/examples/ding-dong-bot.ts +++ b/examples/ding-dong-bot.ts @@ -21,6 +21,7 @@ import * as path from 'path' /* tslint:disable:variable-name */ import * as QrcodeTerminal from 'qrcode-terminal' import finis from 'finis' +import { FileBox } from 'file-box' /** * Change `import { ... } from '../'` @@ -103,9 +104,10 @@ bot /** * 2. reply qrcode image */ - const imageMessage = bot.Message.create(BOT_QR_CODE_IMAGE_FILE) - log.info('Bot', 'REPLY: %s', imageMessage) - await m.say(imageMessage) + const fileBox = FileBox.fromLocal(BOT_QR_CODE_IMAGE_FILE) + + log.info('Bot', 'REPLY: %s', fileBox) + await m.say(fileBox) /** * 3. reply 'scan now!' diff --git a/examples/media-file-bot.ts b/examples/media-file-bot.ts index 6f7e1317e2f7b3b35474d19c98ac06c1ca4895f0..0f650daf0081e07df29de84a4776a458e342458d 100644 --- a/examples/media-file-bot.ts +++ b/examples/media-file-bot.ts @@ -50,8 +50,8 @@ bot console.log(`${url}\n[${code}] Scan QR Code in above url to login: `) }) .on('login' , user => console.log(`${user} logined`)) -.on('message', m => { - console.log(`RECV: ${m}`) +.on('message', msg => { + console.log(`RECV: ${msg}`) // console.log(inspect(m)) // saveRawObj(m.rawObj) @@ -64,8 +64,8 @@ bot // || m.type() === MsgType.APP // || (m.type() === MsgType.TEXT && m.typeSub() === MsgType.LOCATION) // LOCATION // ) { - if (m.type() !== Message.Type.TEXT) { - saveMediaFile(m) + if (msg.type() !== Message.Type.Text) { + saveMediaFile(msg) } // } }) @@ -73,7 +73,9 @@ bot .catch(e => console.error('bot.start() error: ' + e)) async function saveMediaFile(message: Message) { - const filename = message.filename() + const fileBox = message.file() + + const filename = fileBox.name console.log('IMAGE local filename: ' + filename) if (!filename) { @@ -84,8 +86,7 @@ async function saveMediaFile(message: Message) { console.log('start to readyStream()') try { - const netStream = await message.readyStream() - netStream + fileBox .pipe(fileStream) .on('close', () => { const stat = statSync(filename) diff --git a/src/puppet-mock/puppet-mock.ts b/src/puppet-mock/puppet-mock.ts index 5c6b6ff76301c436a62d71891b54b3a28ba07fcd..5e4d00f0f7b084c9aeca061b1db02f5344004838 100644 --- a/src/puppet-mock/puppet-mock.ts +++ b/src/puppet-mock/puppet-mock.ts @@ -25,6 +25,7 @@ import { import { Message, MessagePayload, + MessageDirection, } from '../puppet/message' import { Contact, @@ -49,28 +50,16 @@ import { import { log, } from '../config' -// import Profile from '../profile' -// import Wechaty from '../wechaty' export type PuppetFoodType = 'scan' | 'ding' export type ScanFoodType = 'scan' | 'login' | 'logout' export class PuppetMock extends Puppet { - private user?: Contact - constructor( public options: PuppetOptions, ) { - super( - options, - // { - // Contact: Contact, - // FriendRequest: FriendRequest, - // Message: Message, - // Room: Room, - // }, - ) + super(options) } public toString() { @@ -115,66 +104,6 @@ export class PuppetMock extends Puppet { this.state.off(true) } - public logonoff(): boolean { - if (this.user) { - return true - } else { - return false - } - } - - public userSelf(): Contact { - log.verbose('PuppetMock', 'self()') - - if (!this.user) { - throw new Error('not logged in, no userSelf yet.') - } - - return this.user - } - - public async messagePayload(message: Message): Promise { - log.verbose('PuppetMock', 'messagePayload(%s)', message) - const payload: MessagePayload = { - type : Message.Type.Text, - from : Contact.load('xxx'), - text : 'mock message text', - date : new Date(), - to: this.userSelf(), - } - return payload - } - - public async messageSend(message: Message): Promise { - log.verbose('PuppetMock', 'messageSend(%s)', message) - } - - public async messageForward(message: Message, to: Contact | Room): Promise { - log.verbose('PuppetMock', 'messageForward(%s, %s)', - message, - to, - ) - } - - public async send(message: Message): Promise { - log.verbose('PuppetMock', 'send(%s)', message) - // TODO - } - - public async say(text: string): Promise { - if (!this.logonoff()) { - throw new Error('can not say before login') - } - - const msg = Message.createMO({ - text, - to: this.userSelf(), - }) - msg.puppet = this - - await this.send(msg) - } - public async logout(): Promise { log.verbose('PuppetMock', 'logout()') @@ -184,8 +113,15 @@ export class PuppetMock extends Puppet { this.emit('logout', this.user!) // becore we will throw above by logonoff() when this.user===undefined this.user = undefined + + // TODO: do the logout job } + /** + * + * Contact + * + */ public contactAlias(contact: Contact) : Promise public contactAlias(contact: Contact, alias: string | null): Promise @@ -211,8 +147,8 @@ export class PuppetMock extends Puppet { return FileBox.fromLocal(WECHATY_ICON_PNG) } - public async contactPayload(contact: Contact): Promise { - log.verbose('PuppetMock', 'contactPayload(%s)', contact) + public async contactPayload(id: string): Promise { + log.verbose('PuppetMock', 'contactPayload(%s)', id) return { gender: Gender.Unknown, @@ -221,8 +157,42 @@ export class PuppetMock extends Puppet { } - public async roomPayload(room: Room): Promise { - log.verbose('PuppetMock', 'roomPayload(%s)', room) + /** + * + * Message + * + */ + public async messagePayload(id: string): Promise { + log.verbose('PuppetMock', 'messagePayload(%s)', id) + const payload: MessagePayload = { + date : new Date(), + direction : MessageDirection.MT, + from : Contact.load('xxx'), + text : 'mock message text', + to : this.userSelf(), + type : Message.Type.Text, + } + return payload + } + + public async messageSend(message: Message): Promise { + log.verbose('PuppetMock', 'messageSend(%s)', message) + } + + public async messageForward(message: Message, to: Contact | Room): Promise { + log.verbose('PuppetMock', 'messageForward(%s, %s)', + message, + to, + ) + } + + /** + * + * Room + * + */ + public async roomPayload(id: string): Promise { + log.verbose('PuppetMock', 'roomPayload(%s)', id) return { topic : 'mock topic', @@ -279,6 +249,12 @@ export class PuppetMock extends Puppet { log.verbose('PuppetMock', 'roomQuit(%s)', room) } + /** + * + * + * FriendRequest + * + */ public async friendRequestSend(contact: Contact, hello: string): Promise { log.verbose('PuppetMock', 'friendRequestSend(%s, %s)', contact, hello) } diff --git a/src/puppet-puppeteer/event.ts b/src/puppet-puppeteer/event.ts index c17b53c345410c481cd62ed8a738be10d3ff4162..d97e51ecc38a51fcf372442b29de7731d34f0a10 100644 --- a/src/puppet-puppeteer/event.ts +++ b/src/puppet-puppeteer/event.ts @@ -27,15 +27,15 @@ import { ScanData, } from '../puppet/' -import PuppeteerContact from './puppeteer-contact' -import PuppeteerMessage from './puppeteer-message' +import Contact from '../puppet/contact' +import Message from '../puppet/message' import Firer from './firer' import PuppetPuppeteer from './puppet-puppeteer' import { WebMsgType, WebMessageRawPayload, -} from '../puppet/schemas/' +} from './web-schemas' /* tslint:disable:variable-name */ export const Event = { @@ -141,7 +141,7 @@ async function onLogin( log.silly('PuppetPuppeteerEvent', 'bridge.getUserName: %s', userId) - const user = PuppeteerContact.load(userId) + const user = Contact.load(userId) user.puppet = this await user.ready() @@ -188,35 +188,35 @@ async function onLogout( } async function onMessage( - this: PuppetPuppeteer, - obj: WebMessageRawPayload, + this : PuppetPuppeteer, + rawPayload : WebMessageRawPayload, ): Promise { - let m = new PuppeteerMessage(obj) - m.puppet = this + let msg = Message.createMT(rawPayload.MsgId) + msg.puppet = this try { - await m.ready() + await msg.ready() /** * Fire Events if match message type & content */ - switch (m.type()) { + switch (rawPayload.MsgType) { case WebMsgType.VERIFYMSG: - Firer.checkFriendRequest.call(this, m) + Firer.checkFriendRequest.call(this, msg) break case WebMsgType.SYS: - if (m.room()) { - const joinResult = await Firer.checkRoomJoin.call(this , m) - const leaveResult = await Firer.checkRoomLeave.call(this , m) - const topicRestul = await Firer.checkRoomTopic.call(this , m) + if (msg.room()) { + const joinResult = await Firer.checkRoomJoin.call(this , msg) + const leaveResult = await Firer.checkRoomLeave.call(this , msg) + const topicRestul = await Firer.checkRoomTopic.call(this , msg) if (!joinResult && !leaveResult && !topicRestul) { - log.warn('PuppetPuppeteerEvent', `checkRoomSystem message: <${m.text()}> not found`) + log.warn('PuppetPuppeteerEvent', `checkRoomSystem message: <${msg.text()}> not found`) } } else { - Firer.checkFriendConfirm.call(this, m) + Firer.checkFriendConfirm.call(this, msg) } break } @@ -226,7 +226,7 @@ async function onMessage( * reload if needed */ - switch (m.type()) { + switch (rawPayload.MsgType) { case WebMsgType.EMOTICON: case WebMsgType.IMAGE: case WebMsgType.VIDEO: @@ -234,20 +234,20 @@ async function onMessage( case WebMsgType.MICROVIDEO: case WebMsgType.APP: log.verbose('PuppetPuppeteerEvent', 'onMessage() EMOTICON/IMAGE/VIDEO/VOICE/MICROVIDEO message') - m = new PuppeteerMessage(obj) - m.puppet = this + msg = Message.createMT(rawPayload.MsgId) + msg.puppet = this break case WebMsgType.TEXT: - if (m.typeSub() === WebMsgType.LOCATION) { + if (rawPayload.SubMsgType === WebMsgType.LOCATION) { log.verbose('PuppetPuppeteerEvent', 'onMessage() (TEXT&LOCATION) message') - m = new PuppeteerMessage(obj) + msg = Message.createMT(rawPayload.MsgId) } break } - await m.ready() - this.emit('message', m) + await msg.ready() + this.emit('message', msg) } catch (e) { log.error('PuppetPuppeteerEvent', 'onMessage() exception: %s', e.stack) diff --git a/src/puppet-puppeteer/firer.ts b/src/puppet-puppeteer/firer.ts index c4570d4b450cc7319b43564d00c0dcad00423420..fb14c4da62bf7e2e4a298e48b557af8ae77a5ca8 100644 --- a/src/puppet-puppeteer/firer.ts +++ b/src/puppet-puppeteer/firer.ts @@ -26,15 +26,18 @@ import { import { WebRecomendInfo, - FriendRequest, -} from '../puppet/' + WebMessageRawPayload, + // FriendRequest, +} from './web-schemas' import PuppetPuppeteer from './puppet-puppeteer' -import PuppeteerContact from './puppeteer-contact' +import PuppeteerContact from '../puppet/contact' +import { + FriendRequest, +} from '../puppet/friend-request' import { - PuppeteerFriendRequest, -} from './puppeteer-friend-request' -import PuppeteerMessage from './puppeteer-message' + Message, +} from '../puppet/message' /* tslint:disable:variable-name */ export const Firer = { @@ -109,26 +112,24 @@ const regexConfig = { } async function checkFriendRequest( - this: PuppetPuppeteer, - msg: PuppeteerMessage, + this : PuppetPuppeteer, + rawPayload : WebMessageRawPayload, ): Promise { - if (!msg.rawObj) { - throw new Error('no rawPayload') - } else if (!msg.rawObj.RecommendInfo) { + if (!rawPayload.RecommendInfo) { throw new Error('no RecommendInfo') } - const rawPayload: WebRecomendInfo = msg.rawObj.RecommendInfo - log.verbose('PuppetPuppeteerFirer', 'fireFriendRequest(%s)', rawPayload) + const recommendInfo: WebRecomendInfo = rawPayload.RecommendInfo + log.verbose('PuppetPuppeteerFirer', 'fireFriendRequest(%s)', recommendInfo) - if (!rawPayload) { - throw new Error('no rawPayload') + if (!recommendInfo) { + throw new Error('no recommendInfo') } - const contact = PuppeteerContact.load(rawPayload.UserName) - contact.puppet = msg.puppet + const contact = PuppeteerContact.load(recommendInfo.UserName) + contact.puppet = this - const hello = rawPayload.Content - const ticket = rawPayload.Ticket + const hello = recommendInfo.Content + const ticket = recommendInfo.Ticket await contact.ready() if (!contact.isReady()) { @@ -140,7 +141,7 @@ async function checkFriendRequest( hello, ticket, ) - receivedRequest.puppet = msg.puppet + receivedRequest.puppet = this this.emit('friend', receivedRequest) } @@ -165,7 +166,7 @@ function parseFriendConfirm( async function checkFriendConfirm( this: PuppetPuppeteer, - m: PuppeteerMessage, + m: Message, ) { const content = m.text() log.silly('PuppetPuppeteerFirer', 'fireFriendConfirm(%s)', content) @@ -176,7 +177,7 @@ async function checkFriendConfirm( const contact = m.from() - const confirmedRequest = PuppeteerFriendRequest.createConfirm( + const confirmedRequest = FriendRequest.createConfirm( contact, ) confirmedRequest.puppet = m.puppet @@ -227,7 +228,7 @@ function parseRoomJoin( async function checkRoomJoin( this: PuppetPuppeteer, - msg: PuppeteerMessage, + msg: Message, ): Promise { const room = msg.room() @@ -379,7 +380,7 @@ function parseRoomLeave( */ async function checkRoomLeave( this: PuppetPuppeteer, - m: PuppeteerMessage, + m: Message, ): Promise { log.verbose('PuppetPuppeteerFirer', 'fireRoomLeave(%s)', m.text()) @@ -461,7 +462,7 @@ function parseRoomTopic( async function checkRoomTopic( this: PuppetPuppeteer, - m: PuppeteerMessage): Promise { + m: Message): Promise { let topic, changer try { [topic, changer] = parseRoomTopic.call(this, m.text()) diff --git a/src/puppet-puppeteer/puppet-puppeteer.ts b/src/puppet-puppeteer/puppet-puppeteer.ts index 5d5d88b299defed52662945da2cb4b2b3a9caf04..9cb024746c94d8bff14f2bc9cac252d845959eac 100644 --- a/src/puppet-puppeteer/puppet-puppeteer.ts +++ b/src/puppet-puppeteer/puppet-puppeteer.ts @@ -70,7 +70,6 @@ import { MessagePayload, MessageType, } from '../puppet/message' -import PuppeteerMessage from './puppeteer-message' import { Room, RoomMemberQueryFilter, @@ -106,19 +105,11 @@ export class PuppetPuppeteer extends Puppet { public scanWatchdog: Watchdog // private fileId: number - private user? : Contact constructor( public options: PuppetOptions, ) { - super(options, - // { - // Contact: Contact, - // FriendRequest: FriendRequest, - // Message: PuppeteerMessage, - // Room: Room, - // } - ) + super(options) // this.fileId = 0 @@ -126,10 +117,6 @@ export class PuppetPuppeteer extends Puppet { this.scanWatchdog = new Watchdog(SCAN_TIMEOUT, 'Scan') } - public toString() { - return `PuppetPuppeteer<${this.options.profile.name}>` - } - public async start(): Promise { log.verbose('PuppetPuppeteer', `start() with ${this.options.profile}`) @@ -326,32 +313,17 @@ export class PuppetPuppeteer extends Puppet { return this.bridge } - /** - * get self contact - */ - public userSelf(): Contact { - log.verbose('PuppetPuppeteer', 'userSelf()') + public async messagePayload(id: string): Promise { + log.verbose('PuppetPuppeteer', 'messagePayload(%s)', id) - if (!this.user) { - throw new Error('not logged in, no userSelf yet.') - } - - return this.user - } - - public async messagePayload( - message: Message, - ): Promise { - log.verbose('PuppetPuppeteer', 'messagePayload(%s)', message) - - const rawPayload = await this.messageRawPayload(message) + const rawPayload = await this.messageRawPayload(id) const payload = this.messageParseRawPayload(rawPayload) return payload } - private messageRawPayload(message: Message) { - return this.bridge.getMessage(message.id) + private messageRawPayload(id: string) { + return this.bridge.getMessage(id) } private messageParseRawPayload( @@ -446,7 +418,7 @@ export class PuppetPuppeteer extends Puppet { */ // public async forward(baseData: MsgRawObj, patchData: MsgRawObj): Promise { public async messageForward( - message : PuppeteerMessage, + message : Message, to : Contact | Room, ): Promise { @@ -455,7 +427,7 @@ export class PuppetPuppeteer extends Puppet { to, ) - let rawPayload = await this.messageRawPayload(message) + let rawPayload = await this.messageRawPayload(message.id) // rawPayload = Object.assign({}, rawPayload) @@ -513,7 +485,7 @@ export class PuppetPuppeteer extends Puppet { } } - public async messageSend(message: PuppeteerMessage): Promise { + public async messageSend(message: Message): Promise { log.verbose('PuppetPuppeteer', 'send(%s)', message) const to = message.to() @@ -557,29 +529,6 @@ export class PuppetPuppeteer extends Puppet { } } - /** - * Bot say... - * send to `self` for notice / log - */ - public async say(text: string): Promise { - if (!this.logonoff()) { - throw new Error('can not say before login') - } - - if (!text) { - log.warn('PuppetPuppeteer', 'say(%s) can not say nothing', text) - return - } - - if (!this.user) { - log.warn('PuppetPuppeteer', 'say(%s) can not say because no user', text) - this.emit('error', new Error('no this.user for PuppetPuppeteer')) - return - } - - return await this.user.say(text) - } - public async login(user: Contact): Promise { log.warn('PuppetPuppeteer', 'login(%s)', user) this.user = user @@ -664,22 +613,22 @@ export class PuppetPuppeteer extends Puppet { } } - private async contactRawPayload(contact: Contact): Promise { - log.verbose('PuppetPuppeteer', 'contactRawPayload(%s)', contact) + private async contactRawPayload(id: string): Promise { + log.verbose('PuppetPuppeteer', 'contactRawPayload(%s)', id) try { - const rawPayload = await this.bridge.getContact(contact.id) as WebContactRawPayload + const rawPayload = await this.bridge.getContact(id) as WebContactRawPayload return rawPayload } catch (e) { - log.error('PuppetPuppeteer', 'contactRawPayload(%d) exception: %s', contact, e.message) + log.error('PuppetPuppeteer', 'contactRawPayload(%d) exception: %s', id, e.message) Raven.captureException(e) throw e } } - public async contactPayload(contact: Contact): Promise { - log.verbose('PuppetPuppeteer', 'contactPayload(%s)', contact) - const rawPayload = await this.contactRawPayload(contact) + public async contactPayload(id: string): Promise { + log.verbose('PuppetPuppeteer', 'contactPayload(%s)', id) + const rawPayload = await this.contactRawPayload(id) const payload = this.contactParseRawPayload(rawPayload) return payload } @@ -695,7 +644,7 @@ export class PuppetPuppeteer extends Puppet { } public async contactAvatar(contact: Contact): Promise { - const payload = await this.contactPayload(contact) + const payload = await this.contactPayload(contact.id) if (!payload.avatar) { throw new Error('Can not get avatar: no payload.avatar!') } @@ -819,8 +768,8 @@ export class PuppetPuppeteer extends Puppet { } } - private async roomRawPayload(room: Room): Promise { - log.verbose('PuppetPuppeteer', 'roomRawPayload(%s)', room) + private async roomRawPayload(id: string): Promise { + log.verbose('PuppetPuppeteer', 'roomRawPayload(%s)', id) try { let rawPayload: PuppeteerRoomRawPayload | undefined // = await this.bridge.getContact(room.id) as PuppeteerRoomRawPayload @@ -828,27 +777,27 @@ export class PuppetPuppeteer extends Puppet { // let currNum = rawPayload.MemberList && rawPayload.MemberList.length || 0 // let prevNum = room.memberList().length // rawPayload && rawPayload.MemberList && this.rawObj.MemberList.length || 0 + let prevNum = 0 + let ttl = 7 while (ttl--/* && currNum !== prevNum */) { - rawPayload = await this.bridge.getContact(room.id) as PuppeteerRoomRawPayload + rawPayload = await this.bridge.getContact(id) as PuppeteerRoomRawPayload const currNum = rawPayload.MemberList && rawPayload.MemberList.length || 0 - const prevNum = room.memberList().length // rawPayload && rawPayload.MemberList && this.rawObj.MemberList.length || 0 log.silly('PuppetPuppeteer', `roomPayload() this.bridge.getContact(%s) MemberList.length:%d at ttl:%d`, - room.id, + id, currNum, ttl, ) - if (currNum) { - if (prevNum === currNum) { - log.silly('PuppetPuppeteer', `roomPayload() puppet.getContact(${room.id}) done at ttl:%d`, ttl) - break - } + if (currNum > 0 && prevNum === currNum) { + log.silly('PuppetPuppeteer', `roomPayload() puppet.getContact(${id}) done at ttl:%d`, ttl) + break } + prevNum = currNum - log.silly('PuppetPuppeteer', `roomPayload() puppet.getContact(${room.id}) retry at ttl:%d`, ttl) + log.silly('PuppetPuppeteer', `roomPayload() puppet.getContact(${id}) retry at ttl:%d`, ttl) await new Promise(r => setTimeout(r, 1000)) // wait for 1 second } @@ -859,16 +808,16 @@ export class PuppetPuppeteer extends Puppet { return rawPayload } catch (e) { - log.error('PuppetPuppeteer', 'roomRawPayload(%s) exception: %s', room.id, e.message) + log.error('PuppetPuppeteer', 'roomRawPayload(%s) exception: %s', id, e.message) Raven.captureException(e) throw e } } - public async roomPayload(room: Room): Promise { - log.verbose('PuppetPuppeteer', 'roomPayload(%s)', room) + public async roomPayload(id: string): Promise { + log.verbose('PuppetPuppeteer', 'roomPayload(%s)', id) - const rawPayload = await this.roomRawPayload(room) + const rawPayload = await this.roomRawPayload(id) const payload = await this.roomParseRawPayload(rawPayload) return payload diff --git a/src/puppet-puppeteer/puppeteer-friend-request.spec.ts b/src/puppet-puppeteer/puppeteer-friend-request.spec.ts index 4f815235fa79e884b30dca587cf6d6e4dd0c25f9..7f5a32ebd61e5ef2716046a3214353f3336d887f 100755 --- a/src/puppet-puppeteer/puppeteer-friend-request.spec.ts +++ b/src/puppet-puppeteer/puppeteer-friend-request.spec.ts @@ -31,21 +31,21 @@ import { // Puppet, // FriendRequest, WebRecomendInfo, -} from '../puppet/' +} from './web-schemas' // import { // PuppetMock, // } from '../puppet-mock/' -import PuppeteerContact from './puppeteer-contact' -import PuppeteerMessage from './puppeteer-message' -import PuppeteerFriendRequest from './puppeteer-friend-request' +import Contact from '../puppet/contact' +import Message from '../puppet/message' +import FriendRequest from '../puppet/friend-request' import { PuppetPuppeteer } from './puppet-puppeteer' test('PuppetPuppeteerFriendRequest.receive smoke testing', async t => { // tslint:disable-next-line:variable-name - const MyFriendRequest = cloneClass(PuppeteerFriendRequest) + const MyFriendRequest = cloneClass(FriendRequest) // tslint:disable-next-line:variable-name - const MyContact = cloneClass(PuppeteerContact) + const MyContact = cloneClass(Contact) const puppet = new PuppetPuppeteer({ profile: new Profile(), @@ -73,17 +73,17 @@ test('PuppetPuppeteerFriendRequest.receive smoke testing', async t => { ) t.is(fr.hello(), '我是群聊"Wechaty"的李卓桓.PreAngel', 'should has right request message') - t.true(fr.contact() instanceof PuppeteerContact, 'should have a Contact instance') + t.true(fr.contact() instanceof Contact, 'should have a Contact instance') t.is(fr.type(), MyFriendRequest.Type.Receive, 'should be receive type') }) test('PuppetPuppeteerFriendRequest.confirm smoke testing', async t => { // tslint:disable-next-line:variable-name - const MyFriendRequest = cloneClass(PuppeteerFriendRequest) + const MyFriendRequest = cloneClass(FriendRequest) // tslint:disable-next-line:variable-name - const MyContact = cloneClass(PuppeteerContact) + const MyContact = cloneClass(Contact) // tslint:disable-next-line:variable-name - const MyMessage = cloneClass(PuppeteerMessage) + const MyMessage = cloneClass(Message) const puppet = new PuppetPuppeteer({ profile: new Profile(), @@ -105,6 +105,6 @@ test('PuppetPuppeteerFriendRequest.confirm smoke testing', async t => { const contact = m.from() const fr = MyFriendRequest.createConfirm(contact || new MyContact('xx')) - t.true(fr.contact() instanceof PuppeteerContact, 'should have a Contact instance') + t.true(fr.contact() instanceof Contact, 'should have a Contact instance') t.is(fr.type(), MyFriendRequest.Type.Confirm, 'should be confirm type') }) diff --git a/src/puppet-puppeteer/puppeteer-message.spec.ts b/src/puppet-puppeteer/puppeteer-message.spec.ts index f2ea34822cd4b93347f5c2a9a10954aa306fb03e..d846e9512806f22bec1d5dbcda3a91552765b162 100755 --- a/src/puppet-puppeteer/puppeteer-message.spec.ts +++ b/src/puppet-puppeteer/puppeteer-message.spec.ts @@ -33,9 +33,16 @@ import Wechaty from '../wechaty' // import { PuppetMock } from '../puppet-mock/' -import Contact from '../puppet/contact' -import Message from '../puppet/message' -import Room from '../puppet/room' +import { + Contact, +} from '../puppet/contact' +import { + Message, + MessagePayload, + } from '../puppet/message' +import { + Room, +} from '../puppet/room' import PuppetPuppeteer from './puppet-puppeteer' @@ -167,15 +174,29 @@ test('findAll()', async t => { test('self()', async t => { MyRoom.puppet = MyContact.puppet = MyMessage.puppet = puppet - const selfMsg = MyMessage.createMO({ - from: Contact.load(MOCK_USER_ID), + const MOCK_CONTACT = Contact.load(MOCK_USER_ID) + + const sandbox = sinon.createSandbox() + sandbox.stub(puppet, 'messagePayload').callsFake(() => { + const payload: MessagePayload = { + from : MOCK_CONTACT, + to : {} as any, + type : {} as any, + direction : {} as any, + date : {} as any, + } + return payload }) + sandbox.stub((puppet as any as { 'user': Contact }), 'user').value(MOCK_CONTACT) + + const selfMsg = MyMessage.createMT('xxx') t.true(selfMsg.self(), 'should identify self message true where message from userId') - const otherMsg = MyMessage.createMO({ - from: Contact.load('fdsafasfsfa'), - }) + sandbox.stub((puppet as any as { 'user': Contact }), 'user').value( + Contact.load('fsadfjas;dlkdjfl;asjflk;sjfl;as'), + ) + const otherMsg = MyMessage.createMT('xxx') t.false(otherMsg.self(), 'should identify self message false when from a different fromId') }) diff --git a/src/puppet-puppeteer/puppeteer-message.ts b/src/puppet-puppeteer/puppeteer-message.ts deleted file mode 100644 index 841de6558ccffacd4a0ed8f9fec9868dbe53e2af..0000000000000000000000000000000000000000 --- a/src/puppet-puppeteer/puppeteer-message.ts +++ /dev/null @@ -1,44 +0,0 @@ -// import * as fs from 'fs' -// import * as path from 'path' -// import * as mime from 'mime' -// import { -// Readable, -// } from 'stream' - -// import { -// Raven, -// log, -// } from '../config' -// import Misc from '../misc' - -import { - Message, -} from '../puppet/' - -// import PuppetPuppeteer from './puppet-puppeteer' -// import PuppeteerContact from './puppeteer-contact' -// import PuppeteerRoom from './puppeteer-room' - -// import { -// WebAppMsgType, -// WebMessageRawPayload, -// WebMsgType, -// } from '../puppet/schemas/' - -// export interface WebMsgPayload { -// id: string, -// type: WebMsgType, -// from: string, -// to?: string, // if to is not set, then room must be set -// room?: string, -// content: string, -// status: string, -// digest: string, -// date: number, - -// url?: string, // for MessageMedia class -// } - -export class PuppeteerMessage extends Message {} - -export default PuppeteerMessage diff --git a/src/puppet-puppeteer/web-schemas.ts b/src/puppet-puppeteer/web-schemas.ts index acbfd4e1fe50c37c9204a9ccb258f1706e4e0dbc..b2601576c41b215aba0919b33cd4d8f1a1800b12 100644 --- a/src/puppet-puppeteer/web-schemas.ts +++ b/src/puppet-puppeteer/web-schemas.ts @@ -16,17 +16,13 @@ * limitations under the License. * */ -import { - Gender, -} from '../contact' - export interface WebContactRawPayload { Alias: string, City: string, NickName: string, Province: string, RemarkName: string, - Sex: Gender, + Sex: number, Signature: string, StarFriend: string, Uin: string, diff --git a/src/puppet/contact.ts b/src/puppet/contact.ts index 16dc6ecdb051bd7a69ae2e6f22bf6fdc5bb62d52..f9648030d82f7f1f9387f0cdadd9aae506546acf 100644 --- a/src/puppet/contact.ts +++ b/src/puppet/contact.ts @@ -17,6 +17,8 @@ * * @ignore */ +import { FileBox } from 'file-box' + import { log, Raven, @@ -26,8 +28,6 @@ import PuppetAccessory from '../puppet-accessory' import Message from './message' -import PuppeteerMessage from '../puppet-puppeteer/puppeteer-message' - /** * Enum for Gender values. * @@ -235,29 +235,34 @@ export class Contact extends PuppetAccessory implements Sayable { * console.error(e) * } */ - public async say(message: Message): Promise + public async say(file: FileBox): Promise - public async say(textOrMessage: string | Message): Promise { - log.verbose('Contact', 'say(%s)', textOrMessage) + public async say(textOrFile: string | FileBox): Promise { + log.verbose('Contact', 'say(%s)', textOrFile) - let msg - if (textOrMessage instanceof Message) { - msg = textOrMessage + let msg: Message + if (typeof textOrFile === 'string') { + msg = Message.createMO({ + text : textOrFile, + to : this, + }) + } else if (textOrFile instanceof FileBox) { + msg = Message.createMO({ + to : this, + file : textOrFile, + }) } else { - msg = new PuppeteerMessage() - msg.puppet = this.puppet - msg.text(textOrMessage) + throw new Error('unsupported') } - msg.from(this.puppet.userSelf()) - msg.to(this) + msg.puppet = this.puppet log.silly('Contact', 'say() from: %s to: %s content: %s', this.puppet.userSelf(), this, msg, ) - await this.puppet.send(msg) + await this.puppet.messageSend(msg) } /** @@ -454,16 +459,16 @@ export class Contact extends PuppetAccessory implements Sayable { /** * Get avatar picture file stream * - * @returns {Promise} + * @returns {Promise} * @example * const avatarFileName = contact.name() + `.jpg` - * const avatarReadStream = await contact.avatar() + * const fileBox = await contact.avatar() * const avatarWriteStream = createWriteStream(avatarFileName) - * avatarReadStream.pipe(avatarWriteStream) + * fileBox.pipe(avatarWriteStream) * log.info('Bot', 'Contact: %s: %s with avatar file: %s', contact.weixin(), contact.name(), avatarFileName) */ // TODO: use File to replace ReadableStream - public async avatar(): Promise { + public async avatar(): Promise { log.verbose('Contact', 'avatar()') return this.puppet.contactAvatar(this) @@ -511,7 +516,7 @@ export class Contact extends PuppetAccessory implements Sayable { } try { - this.payload = await this.puppet.contactPayload(this) + this.payload = await this.puppet.contactPayload(this.id) log.silly('Contact', `ready() this.puppet.contactPayload(%s) resolved`, this) // console.log(this.payload) diff --git a/src/puppet/index.ts b/src/puppet/index.ts index 0f0e0b5fd2859133648dedaff5cc2732a0644411..1feb3dc6bca7dc6d2ddc14c963dc8ad02d69f566 100644 --- a/src/puppet/index.ts +++ b/src/puppet/index.ts @@ -26,5 +26,3 @@ export { PuppetOptions, ScanData, } from './puppet' - -export * from './schemas/' diff --git a/src/puppet/message.ts b/src/puppet/message.ts index 5ff53d7c22fbc802e20919d054e3ea435cdbce6b..1051e7d746316cb3c28c7bbc400c6d748d653fa5 100644 --- a/src/puppet/message.ts +++ b/src/puppet/message.ts @@ -38,9 +38,6 @@ import { MessageMOOptionsText, MessageMOOptionsFile, MessagePayload, - MessagePayloadText, - MessagePayloadFile, - MessagePayloadDirectionMT, MessageType, } from './message.type' @@ -85,11 +82,8 @@ export class Message extends PuppetAccessory implements Sayable { ] } - public static create( - this: T, - ...args: any[] - ): T['prototype'] { - return new (this as any)(...args) + public static create(options: MessageMOOptions): Message { + return this.createMO(options) } /** @@ -216,7 +210,7 @@ export class Message extends PuppetAccessory implements Sayable { if (!this.payload) { throw new Error('no payload') } - const file = (this.payload as MessagePayloadFile).file + const file = this.payload.file if (!file) { throw new Error('no file') } @@ -249,7 +243,7 @@ export class Message extends PuppetAccessory implements Sayable { // return // } - const from = (this.payload).from + const from = this.payload.from if (!from) { throw new Error('no from') } @@ -349,7 +343,7 @@ export class Message extends PuppetAccessory implements Sayable { // return // } - return (this.payload).text || '' + return this.payload.text || '' } public async say(text: string, mention?: Contact | Contact[]): Promise @@ -457,7 +451,7 @@ export class Message extends PuppetAccessory implements Sayable { if (!this.payload) { throw new Error('no payload') } - const file = ( this.payload).file + const file = this.payload.file if (!file) { throw new Error('no file') } @@ -645,7 +639,7 @@ export class Message extends PuppetAccessory implements Sayable { return } - this.payload = await this.puppet.messagePayload(this) + this.payload = await this.puppet.messagePayload(this.id) // TODO ... the rest } diff --git a/src/puppet/puppet.ts b/src/puppet/puppet.ts index bbd163ecd6c8901972841e464ed07a1084d7288d..3ff492b13b98d9febda59a04b61502875efc42df 100644 --- a/src/puppet/puppet.ts +++ b/src/puppet/puppet.ts @@ -108,6 +108,8 @@ export abstract class Puppet extends EventEmitter implements Sayable { protected readonly watchdog: Watchdog + protected user?: Contact + /** * childPkg stores the `package.json` that the NPM module who extends the `Puppet` */ @@ -159,6 +161,10 @@ export abstract class Puppet extends EventEmitter implements Sayable { normalize(this.childPkg) } + public toString() { + return `Puppet<${this.options.profile.name}>` + } + public emit(event: 'error', e: Error) : boolean public emit(event: 'friend', request: FriendRequest) : boolean public emit(event: 'heartbeat', data: any) : boolean @@ -235,16 +241,52 @@ export abstract class Puppet extends EventEmitter implements Sayable { public abstract async start() : Promise public abstract async stop() : Promise - public abstract userSelf(): Contact + public userSelf(): Contact { + log.verbose('Puppet', 'self()') + + if (!this.user) { + throw new Error('not logged in, no userSelf yet.') + } + + return this.user + } - // TODO: change Message to File - public abstract async say(textOrFile: string | File) : Promise - // public abstract async send(file: FileBox) : Promise + public async say(textOrFile: string | FileBox) : Promise { + if (!this.logonoff()) { + throw new Error('can not say before login') + } + + let msg: Message + + if (typeof textOrFile === 'string') { + msg = Message.createMO({ + text : textOrFile, + to : this.userSelf(), + }) + } else if (textOrFile instanceof FileBox) { + msg = Message.createMO({ + file: textOrFile, + to: this.userSelf(), + }) + } else { + throw new Error('say() arg unknown') + } + + msg.puppet = this + await this.messageSend(msg) + } /** * Login / Logout */ - public abstract logonoff() : boolean + public logonoff(): boolean { + if (this.user) { + return true + } else { + return false + } + } + // public abstract login(user: Contact): Promise public abstract async logout(): Promise @@ -254,7 +296,7 @@ export abstract class Puppet extends EventEmitter implements Sayable { * */ public abstract async messageForward(message: Message, to: Contact | Room) : Promise - public abstract async messagePayload(message: Message) : Promise + public abstract async messagePayload(id: string) : Promise public abstract async messageSend(message: Message) : Promise /** @@ -274,7 +316,7 @@ export abstract class Puppet extends EventEmitter implements Sayable { public abstract async roomCreate(contactList: Contact[], topic?: string) : Promise public abstract async roomDel(room: Room, contact: Contact) : Promise public abstract async roomFindAll(query?: RoomQueryFilter) : Promise - public abstract async roomPayload(room: Room) : Promise + public abstract async roomPayload(id: string) : Promise public abstract async roomQuit(room: Room) : Promise public abstract async roomTopic(room: Room, topic?: string) : Promise @@ -289,7 +331,7 @@ export abstract class Puppet extends EventEmitter implements Sayable { // TODO: change the return type from NodeJS.ReadableStream to File(vinyl) public abstract async contactAvatar(contact: Contact) : Promise - public abstract async contactPayload(contact: Contact) : Promise + public abstract async contactPayload(id: string) : Promise public abstract async contactFindAll(query?: ContactQueryFilter) : Promise diff --git a/src/puppet/room.ts b/src/puppet/room.ts index 471773df683bec04978e911c9fd8466b340cc1c1..39043eeebe5ef0ba81677d25b09e2d887fb2dc00 100644 --- a/src/puppet/room.ts +++ b/src/puppet/room.ts @@ -19,6 +19,8 @@ */ import * as util from 'util' +import { FileBox } from 'file-box' + import { // config, Raven, @@ -30,8 +32,6 @@ import PuppetAccessory from '../puppet-accessory' import Contact from './contact' import Message from './message' -import PuppeteerMessage from '../puppet-puppeteer/puppeteer-message' - export const ROOM_EVENT_DICT = { join: 'tbw', leave: 'tbw', @@ -224,7 +224,7 @@ export class Room extends PuppetAccessory implements Sayable { return } - const payload = await this.puppet.roomPayload(this) + const payload = await this.puppet.roomPayload(this.id) await Promise.all( payload.memberList.map( contact => contact.ready(), @@ -247,13 +247,13 @@ export class Room extends PuppetAccessory implements Sayable { public say(text: string) : Promise public say(text: string, mention: Contact) : Promise public say(text: string, mention: Contact[]) : Promise - public say(message: Message) : Promise + public say(file: FileBox) : Promise public say(text: never, ...args: never[]) : never /** * Send message inside Room, if set [replyTo], wechaty will mention the contact as well. * - * @param {(string | MediaMessage)} textOrMessage - Send `text` or `media file` inside Room. + * @param {(string | MediaMessage)} textOrFile - Send `text` or `media file` inside Room. * @param {(Contact | Contact[])} [replyTo] - Optional parameter, send content inside Room, and mention @replyTo contact or contactList. * @returns {Promise} * If bot send message successfully, it will return true. If the bot failed to send for blocking or any other reason, it will return false @@ -272,36 +272,46 @@ export class Room extends PuppetAccessory implements Sayable { * await room.say('Hello world!', contact) */ public async say( - textOrMessage : string | Message, + textOrFile : string | FileBox, mention? : Contact | Contact[], ): Promise { log.verbose('Room', 'say(%s, %s)', - textOrMessage, + textOrFile, Array.isArray(mention) ? mention.map(c => c.name()).join(', ') : mention ? mention.name() : '', ) let msg: Message - if (textOrMessage instanceof Message) { - msg = textOrMessage - } else { - msg = new PuppeteerMessage() // FIXME - msg.puppet = this.puppet + let text: string + + const replyToList: Contact[] = [].concat(mention as any || []) - const replyToList: Contact[] = [].concat(mention as any || []) + if (typeof textOrFile === 'string') { if (replyToList.length > 0) { const AT_SEPRATOR = String.fromCharCode(8197) const mentionList = replyToList.map(c => '@' + c.name()).join(AT_SEPRATOR) - msg.text(mentionList + ' ' + textOrMessage) + text = mentionList + ' ' + textOrFile } else { - msg.text(textOrMessage) + text = textOrFile } - // m.to(replyToList[0]) + msg = Message.createMO({ + room : this, + to : replyToList[0], // FIXME: is this right? + text, + }) + } else if (textOrFile instanceof FileBox) { + msg = Message.createMO({ + room: this, + to: replyToList[0], + file: textOrFile, + }) + } else { + throw new Error('arg unsupported') } - msg.room(this) - await this.puppet.send(msg) + msg.puppet = this.puppet + await this.puppet.messageSend(msg) } public emit(event: 'leave', leaver: Contact[], remover?: Contact) : boolean