未验证 提交 83eb7d0d 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓) 提交者: GitHub

Merge branch 'master' into puppet-padchat

......@@ -109,7 +109,7 @@ class Talker extends EventEmitter {
text: string[],
time: number[],
}
private timer?: number
private timer?: NodeJS.Timer
constructor(
thinker: (text: string) => Promise<string>,
......
......@@ -43,12 +43,13 @@ bot
console.log(`${url}\n[${code}] Scan QR Code in above url to login: `)
})
.on('login' , user => console.log(`${user} logined`))
.on('message', msg => {
.on('message', async msg => {
console.log(`RECV: ${msg}`)
if (msg.type() !== Message.Type.Text) {
const file = msg.file()
const name = msg.file().name
const file = await msg.file()
const name = file.name
console.log('Save file to: ' + name)
file.save(name)
}
......
......@@ -17,7 +17,10 @@
*
*/
import { createWriteStream, createReadStream } from 'fs'
import {
// createWriteStream,
createReadStream,
} from 'fs'
import {
PassThrough,
Readable,
......@@ -61,13 +64,9 @@ bot
// const mp3Stream = await msg.readyStream()
const filename = msg.file().name
if (!filename) {
throw new Error('no filename for media message')
}
const file = createWriteStream(filename)
msg.file().pipe(file)
const msgFile = await msg.file()
const filename = msgFile.name
msgFile.save(filename)
const mp3Stream = createReadStream(filename)
const text = await speechToText(mp3Stream)
......
{
"name": "wechaty",
"version": "0.15.88",
"version": "0.15.90",
"description": "Wechat for Bot(Personal Account)",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
......@@ -98,7 +98,7 @@
"clone-class": "^0.6.11",
"cuid": "^2.1.1",
"express": "^4.16.3",
"file-box": "^0.6.2",
"file-box": "^0.6.9",
"hot-import": "^0.2.1",
"mime": "^2.2.0",
"normalize-package-data": "^2.4.0",
......
......@@ -605,13 +605,13 @@ export class Contact extends PuppetAccessory implements Sayable {
* const isSelf = contact.self()
*/
public self(): boolean {
const user = this.puppet.userSelf()
const userId = this.puppet.selfId()
if (!user) {
if (!userId) {
return false
}
return this.id === user.id
return this.id === userId
}
/**
......
......@@ -59,10 +59,22 @@ export class FriendRequest extends PuppetAccessory {
// tslint:disable-next-line:variable-name
public static Type = FriendRequestType
public static createSend(
/**
* Send a Friend Request to a `contact` with message `hello`.
* @param contact
* @param hello
*/
public static create(
contact : Contact,
hello : string,
): FriendRequest {
): FriendRequest {
return this.createSend(contact, hello)
}
private static createSend(
contact : Contact,
hello : string,
): FriendRequest {
log.verbose('PuppeteerFriendRequest', 'createSend(%s, %s)',
contact,
hello,
......@@ -113,6 +125,11 @@ export class FriendRequest extends PuppetAccessory {
return receivedRequest
}
public static fromJSON(payloadJsonStr: string): FriendRequest {
const payload: FriendRequestPayload = JSON.parse(payloadJsonStr)
return new this(payload)
}
/**
*
* Instance Properties
......@@ -136,6 +153,17 @@ export class FriendRequest extends PuppetAccessory {
}
}
public toString() {
return 'FriendRequest'
}
public toJSON() {
if (!this.payload) {
throw new Error('no payload')
}
return JSON.stringify(this.payload)
}
public async send(): Promise<void> {
if (!this.payload) {
throw new Error('no payload')
......
......@@ -21,11 +21,11 @@ import StateSwitch from 'state-switch'
import {
Message,
} from './message'
} from './message'
import {
ScanData,
} from './puppet/'
ScanPayload,
} from './puppet/'
import {
config,
......@@ -77,7 +77,7 @@ export class Io {
private onMessage: undefined | Function
private scanData: ScanData
private scanPayload?: ScanPayload
constructor(
private options: IoOptions,
......@@ -87,8 +87,6 @@ export class Io {
this.cuid = options.wechaty.cuid
this.scanData = {} as any
this.protocol = options.protocol + '|' + options.wechaty.cuid
log.verbose('Io', 'instantiated with apihost[%s], token[%s], protocol[%s], cuid[%s]',
options.apihost,
......@@ -115,8 +113,10 @@ export class Io {
await this.initEventHook()
this.ws = this.initWebSocket()
this.options.wechaty.on('scan', (url, code) => {
this.scanData.url = url
this.scanData.code = code
this.scanPayload = Object.assign(this.scanPayload || {}, {
url,
code,
})
})
this.state.on(true)
......@@ -300,23 +300,23 @@ export class Io {
case 'update':
log.verbose('Io', 'on(update): %s', ioEvent.payload)
const user = this.options.wechaty.puppet.userSelf()
const userId = this.options.wechaty.puppet.selfId()
if (user) {
if (userId) {
const loginEvent: IoEvent = {
name : 'login',
payload : {
id: user.id,
name: user.name(),
id: userId,
name: this.options.wechaty.Contact.load(userId).name(),
},
}
this.send(loginEvent)
}
if (this.scanData) {
if (this.scanPayload) {
const scanEvent: IoEvent = {
name: 'scan',
payload: this.scanData,
payload: this.scanPayload,
}
this.send(scanEvent)
}
......@@ -342,6 +342,9 @@ export class Io {
// @types/ws might has bug for `ws.on('error', e => this.wsOnError(e))`
private wsOnError(e?: Error) {
log.warn('Io', 'initWebSocket() error event[%s]', e && e.message)
if (!e) {
return
}
this.options.wechaty.emit('error', e)
// when `error`, there must have already a `close` event
......
......@@ -53,11 +53,10 @@ export enum MessageType {
export interface MessagePayload {
type : MessageType,
text? : string,
file? : FileBox,
// direction : MessageDirection,
fromId? : string,
date : Date,
toId? : null | string, // if to is not set, then room must be set
filename? : string,
timestamp : number, // milliseconds
toId? : null | string, // if to is not set, then room must be set
roomId? : null | string,
}
......@@ -77,8 +76,6 @@ export class Message extends PuppetAccessory implements Sayable {
// tslint:disable-next-line:variable-name
public static readonly Type = MessageType
// tslint:disable-next-line:variable-name
// public static readonly Direction = MessageDirection
/**
* @todo add function
......@@ -104,88 +101,6 @@ export class Message extends PuppetAccessory implements Sayable {
]
}
/**
* "mobile originated" or "mobile terminated"
* https://www.tatango.com/resources/video-lessons/video-mo-mt-sms-messaging/
*/
// private static createMO(
// options: MessageMOOptions,
// ): Message {
// log.verbose('Message', 'static createMobileOriginated()')
// /**
// * Must NOT use `Message` at here
// * MUST use `this` at here
// *
// * because the class will be `cloneClass`-ed
// */
// const msg = new this(cuid())
// const direction = MessageDirection.MO
// const to = options.to
// const room = options.room
// const text = (options as MessageMOOptionsText).text
// const date = new Date()
// const file = (options as MessageMOOptionsFile).file
// const roomId = room && room.id
// const toId = to && to.id
// if (text) {
// /**
// * 1. Text
// */
// msg.payload = {
// type: MessageType.Text,
// direction,
// toId,
// roomId,
// text,
// date,
// }
// } else if (file) {
// /**
// * 2. File
// */
// let type: MessageType
// const ext = path.extname(file.name)
// switch (ext.toLowerCase()) {
// case '.bmp':
// case '.jpg':
// case '.jpeg':
// case '.png':
// case '.gif': // type = WebMsgType.EMOTICON
// type = MessageType.Image
// break
// case '.mp4':
// type = MessageType.Video
// break
// case '.mp3':
// type = MessageType.Audio
// break
// default:
// throw new Error('unknown ext:' + ext)
// }
// msg.payload = {
// type,
// direction,
// toId,
// roomId,
// file,
// date,
// }
// } else {
// throw new Error('neither text nor file!?')
// }
// return msg
// }
/**
* Create a Mobile Terminated Message
*
......@@ -207,7 +122,6 @@ export class Message extends PuppetAccessory implements Sayable {
* because the class will be `cloneClass`-ed
*/
const msg = new this(id)
// msg.direction = MessageDirection.MT
if (payload) {
msg.payload = payload
......@@ -216,27 +130,12 @@ export class Message extends PuppetAccessory implements Sayable {
return msg
}
/**
* @alias createMT
* Create a Mobile Terminated Message
*/
// public static create(
// id : string,
// payload? : MessagePayload,
// ): Message {
// return this.createMT(
// id,
// payload,
// )
// }
/**
*
* Instance Properties
*
*/
private payload? : MessagePayload
// private direction : MessageDirection
/**
* @private
......@@ -260,10 +159,7 @@ export class Message extends PuppetAccessory implements Sayable {
if (!this.puppet) {
throw new Error('Message class can not be instanciated without a puppet!')
}
// default set to MT because there's a id param
// this.direction = MessageDirection.MT
}
}
/**
* @private
......@@ -291,29 +187,19 @@ export class Message extends PuppetAccessory implements Sayable {
if (!this.payload) {
throw new Error('no payload')
}
const file = this.payload.file
if (!file) {
const filename = this.payload.filename
if (!filename) {
throw new Error('no file')
}
msgStrList.push(`<${file.name}>`)
msgStrList.push(`<${filename}>`)
}
return msgStrList.join('')
}
// /**
// * @private
// */
// public from(contact: Contact): void
// /**
// * @private
// */
// public from(): Contact
// /**
// * Get the sender from a message.
// * @returns {Contact}
// */
// public from(contact?: Contact): void | Contact {
/**
* Get the sender from a message.
* @returns {Contact}
*/
public from(): Contact {
if (!this.payload) {
throw new Error('no payload')
......@@ -333,30 +219,16 @@ export class Message extends PuppetAccessory implements Sayable {
return from
}
// /**
// * @private
// */
// public to(contact: Contact): void
// /**
// * @private
// */
// public to(): Contact | null // if to is not set, then room must had set
// /**
// * Get the destination of the message
// * Message.to() will return null if a message is in a room, use Message.room() to get the room.
// * @returns {(Contact|null)}
// */
// public to(contact?: Contact): void | null | Contact {
/**
* Get the destination of the message
* Message.to() will return null if a message is in a room, use Message.room() to get the room.
* @returns {(Contact|null)}
*/
public to(): null | Contact {
if (!this.payload) {
throw new Error('no payload')
}
// if (contact) {
// this.payload.to = contact
// return
// }
const toId = this.payload.toId
if (!toId) {
return null
......@@ -366,30 +238,16 @@ export class Message extends PuppetAccessory implements Sayable {
return to
}
// /**
// * @private
// */
// public room(room: Room): void
// /**
// * @private
// */
// public room(): Room | null
// /**
// * Get the room from the message.
// * If the message is not in a room, then will return `null`
// *
// * @returns {(Room | null)}
// */
// public room(room?: Room): void | null | Room {
/**
* Get the room from the message.
* If the message is not in a room, then will return `null`
*
* @returns {(Room | null)}
*/
public room(): null | Room {
if (!this.payload) {
throw new Error('no payload')
}
// if (room) {
// this.payload.room = room
// return
// }
const roomId = this.payload.roomId
if (!roomId) {
return null
......@@ -399,43 +257,16 @@ export class Message extends PuppetAccessory implements Sayable {
return room
}
// /**
// * Get the text of the message
// *
// * @deprecated: use `text()` instead
// * @returns {string}
// */
// public content(text?: string): void | string {
// log.warn('Message', 'content() Deprecated. Use `text()` instead of `content()`. See https://github.com/Chatie/wechaty/issues/1163')
// return this.text(text!)
// }
// /**
// * Get the text content of the message
// *
// * @returns {string}
// */
// public text(): string
// /**
// * @private
// */
// public text(text: string): void
// /**
// * Get the text content of the message
// *
// * @returns {string}
// */
// public text(text?: string): void | string {
/**
* Get the text content of the message
*
* @returns {string}
*/
public text(): string {
if (!this.payload) {
throw new Error('no payload')
}
// if (text) {
// this.payload.text = text
// return
// }
return this.payload.text || ''
}
......@@ -522,15 +353,12 @@ export class Message extends PuppetAccessory implements Sayable {
}
}
public file(): FileBox {
if (!this.payload) {
throw new Error('no payload')
}
const file = this.payload.file
if (!file) {
throw new Error('no file')
public async file(): Promise<FileBox> {
if (this.type() === Message.Type.Text) {
throw new Error('text message no file')
}
return file
const fileBox = await this.puppet.messageFile(this.id)
return fileBox
}
/**
......@@ -616,10 +444,10 @@ export class Message extends PuppetAccessory implements Sayable {
* }
*/
public self(): boolean {
const user = this.puppet.userSelf()
const userId = this.puppet.selfId()
const from = this.from()
return from.id === user.id
return from.id === userId
}
/**
......
......@@ -81,14 +81,14 @@ export class PuppetMock extends Puppet {
this.userId = 'logined_user_id'
const user = this.Contact.load(this.userId)
this.emit('login', user)
this.emit('login', user.id)
const msg = this.Message.create('mock_id')
await msg.ready()
setInterval(() => {
log.verbose('PuppetMock', `start() setInterval() pretending received a new message: ${msg + ''}`)
this.emit('message', msg)
this.emit('message', msg.id)
}, 3000)
}
......@@ -110,11 +110,11 @@ export class PuppetMock extends Puppet {
public async logout(): Promise<void> {
log.verbose('PuppetMock', 'logout()')
if (!this.logonoff()) {
if (!this.userId) {
throw new Error('logout before login?')
}
this.emit('logout', this.userId!) // becore we will throw above by logonoff() when this.user===undefined
this.emit('logout', this.userId) // becore we will throw above by logonoff() when this.user===undefined
this.userId = undefined
// TODO: do the logout job
......@@ -173,6 +173,13 @@ export class PuppetMock extends Puppet {
* Message
*
*/
public async messageFile(id: string): Promise<FileBox> {
return FileBox.packBase64(
'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ==',
'mock-file.txt',
)
}
public async messageRawPayload(id: string): Promise<MockMessageRawPayload> {
log.verbose('PuppetMock', 'messageRawPayload(%s)', id)
const rawPayload: MockMessageRawPayload = {
......@@ -186,10 +193,10 @@ export class PuppetMock extends Puppet {
public async messageRawPayloadParser(rawPayload: MockMessageRawPayload): Promise<MessagePayload> {
log.verbose('PuppetMock', 'messagePayload(%s)', rawPayload)
const payload: MessagePayload = {
date : new Date(),
timestamp : Date.now(),
fromId : 'xxx',
text : 'mock message text',
toId : this.userSelf().id,
toId : this.selfId(),
type : this.Message.Type.Text,
}
return payload
......
......@@ -11,6 +11,10 @@ import {
PadchatRoomMemberRawPayload,
} from './padchat-schemas'
import {
ADDRESS,
} from './config'
export const resolverDict: {
[idx: string]: Function,
} = {}
......@@ -19,7 +23,7 @@ export interface BridgeOptions {
// head? : boolean,
userId: string,
// profile: Profile,
botWs: WebSocket,
// botWs: WebSocket,
// desperate in the future
autoData: AutoDataType,
}
......@@ -141,7 +145,9 @@ export class Bridge extends EventEmitter {
log.verbose('PuppetPadchatBridge', 'constructor()')
this.userId = options.userId
this.botWs = options.botWs
this.botWs = new WebSocket(ADDRESS, { perMessageDeflate: true })
this.autoData = options.autoData || {}
// this.state = new StateSwitch('PuppetPadchatBridge', log)
}
......@@ -175,6 +181,19 @@ export class Bridge extends EventEmitter {
})
}
public async initWs(): Promise<void> {
this.botWs.on('message', wsMsg => {
this.emit('ws', wsMsg)
})
this.botWs.on('open', () => {
this.emit('open')
})
}
public closeWs(): void {
this.botWs.close()
}
/**
* Init with WebSocket Server
*/
......
......@@ -69,7 +69,7 @@ async function onMessage(
}
await msg.ready()
this.emit('message', msg)
this.emit('message', msg.id)
} catch (e) {
log.error('PuppetPadchatEvent', 'onMessage() exception: %s', e.stack)
......
......@@ -227,7 +227,7 @@ async function checkRoomJoin(
try {
if (inviter === 'You' || inviter === '' || inviter === 'you') {
inviterContact = this.userSelf()
inviterContact = this.Contact.load(this.selfId())
}
const max = 20
......@@ -318,8 +318,8 @@ async function checkRoomJoin(
await inviterContact.ready()
await room.ready()
this.emit('room-join', room , inviteeContactList, inviterContact)
room.emit('join' , inviteeContactList, inviterContact)
this.emit('room-join', room.id, inviteeContactList.map(c => c.id), inviterContact.id)
room.emit('join' , inviteeContactList, inviterContact)
return true
} catch (e) {
......@@ -342,7 +342,7 @@ function parseRoomLeave(
if ((!foundByBot || !foundByBot.length) && (!foundByOther || !foundByOther.length)) {
throw new Error('checkRoomLeave() no matched re for ' + content)
}
const [leaver, remover] = foundByBot ? [ foundByBot[1], this.userSelf().id ] : [ this.userSelf().id, foundByOther[1] ]
const [leaver, remover] = foundByBot ? [ foundByBot[1], this.selfId() ] : [ this.selfId(), foundByOther[1] ]
return [leaver, remover]
}
......@@ -373,8 +373,8 @@ async function checkRoomLeave(
* @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 leaverContact: Contact | null, removerContact: Contact | null
if (leaver === this.userSelf().id) {
leaverContact = this.userSelf()
if (leaver === this.selfId()) {
leaverContact = this.Contact.load(this.selfId())
// not sure which is better
// removerContact = room.member({contactAlias: remover}) || room.member({name: remover})
......@@ -385,7 +385,7 @@ async function checkRoomLeave(
// }
} else {
removerContact = this.userSelf()
removerContact = this.Contact.load(this.selfId())
// not sure which is better
// leaverContact = room.member({contactAlias: remover}) || room.member({name: leaver})
......@@ -408,8 +408,8 @@ async function checkRoomLeave(
* 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.emit('room-leave', room, [leaverContact] /* , [removerContact] */)
room.emit('leave' , [leaverContact], removerContact || undefined)
this.emit('room-leave', room.id , [leaverContact.id] /* , [removerContact] */)
room.emit('leave' , [leaverContact], removerContact || undefined)
setTimeout(_ => { room.refresh() }, 10000) // reload the room data, especially for memberList
return true
......@@ -450,7 +450,7 @@ async function checkRoomTopic(
let changerContact: Contact | null
if (/^You$/.test(changer) || /^你$/.test(changer)) {
changerContact = this.userSelf()
changerContact = this.Contact.load(this.selfId())
} else {
changerContact = room.member(changer)
}
......@@ -463,8 +463,8 @@ async function checkRoomTopic(
try {
await changerContact.ready()
await room.ready()
this.emit('room-topic', room, topic, oldTopic, changerContact)
room.emit('topic' , topic, oldTopic, changerContact)
this.emit('room-topic', room.id , topic, oldTopic, changerContact.id)
room.emit('topic' , topic, oldTopic, changerContact)
room.refresh()
return true
} catch (e) {
......
......@@ -18,7 +18,8 @@
*/
import * as path from 'path'
// import * as fs from 'fs'
import * as fs from 'fs'
import * as cuid from 'cuid'
import {
FileBox,
......@@ -64,10 +65,6 @@ import {
// Profile,
// } from '../profile'
import {
ADDRESS,
} from './config'
import {
Bridge,
resolverDict,
......@@ -101,23 +98,20 @@ const TOKEN = 'padchattest'
export class PuppetPadchat extends Puppet {
public bridge: Bridge
public botWs: WebSocket
// public botWs: WebSocket
constructor(
public options: PuppetOptions,
) {
super(options)
this.botWs = new WebSocket(ADDRESS, { perMessageDeflate: true })
this.bridge = new Bridge({
userId : TOKEN,
botWs : this.botWs,
autoData : {},
// profile: profile, // should be profile in the future
})
this.botWs.on('message', data => this.wsOnMessage(data))
this.bridge.on('ws', data => this.wsOnMessage(data))
}
......@@ -129,49 +123,74 @@ export class PuppetPadchat extends Puppet {
return data
}
// public initWatchdog(): void {
// log.verbose('PuppetPadchat', 'initWatchdogForPuppet()')
public initWatchdog(): void {
log.verbose('PuppetPadchat', 'initWatchdogForPuppet()')
const puppet = this
// const puppet = this
// clean the dog because this could be re-inited
this.watchdog.removeAllListeners()
// // clean the dog because this could be re-inited
// this.watchdog.removeAllListeners()
puppet.on('watchdog', food => this.watchdog.feed(food))
this.watchdog.on('feed', async food => {
log.silly('PuppetPadchat', 'initWatchdogForPuppet() dog.on(feed, food={type=%s, data=%s})', food.type, food.data)
// feed the dog, heartbeat the puppet.
puppet.emit('heartbeat', food.data)
// puppet.on('watchdog', food => this.watchdog.feed(food))
// this.watchdog.on('feed', food => {
// log.silly('PuppetPadchat', 'initWatchdogForPuppet() dog.on(feed, food={type=%s, data=%s})', food.type, food.data)
// // feed the dog, heartbeat the puppet.
// puppet.emit('heartbeat', food.data)
// })
const feedAfterTenSeconds = async () => {
this.bridge.WXHeartBeat()
.then(() => {
this.emit('watchdog', {
data: 'WXHeartBeat()',
})
})
.catch(e => {
log.warn('PuppetPadchat', 'initWatchdogForPuppet() feedAfterTenSeconds rejected: %s', e && e.message || '')
})
}
setTimeout(feedAfterTenSeconds, 15 * 1000)
})
// this.watchdog.on('reset', async (food, timeout) => {
// log.warn('PuppetPadchat', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s',
// food.data, timeout)
// try {
// await this.stop()
// await this.start()
// } catch (e) {
// puppet.emit('error', e)
// }
// })
// }
this.watchdog.on('reset', async (food, timeout) => {
log.warn('PuppetPadchat', 'initWatchdogForPuppet() dog.on(reset) last food:%s, timeout:%s',
food.data, timeout)
try {
await this.stop()
await this.start()
} catch (e) {
puppet.emit('error', e)
}
})
}
public async start(): Promise<void> {
// Connect with websocket server
const botWs = this.botWs
const bridge = this.bridge = await this.initBridge()
if (!this.bridge) {
throw Error('cannot init bridge successfully!')
}
/**
* state has two main state: ON / OFF
* ON (pending)
* OFF (pending)
*/
this.state.on('pending')
const bridge = this.bridge = await this.initBridge()
this.bridge.loginSucceed = false
log.verbose('PuppetPadchat', `start() with ${this.options.profile}`)
this.state.on('pending')
botWs.on('open', async() => {
this.bridge.once('open', async() => {
this.emit('watchdog', {
data: 'start',
})
// await some tasks...
await bridge.init()
await bridge.WXInitialize()
......@@ -219,11 +238,14 @@ export class PuppetPadchat extends Puppet {
public async initBridge(): Promise<Bridge> {
log.verbose('PuppetPadchat', 'initBridge()')
// if (this.state.off()) {
// const e = new Error('initBridge() found targetState != live, no init anymore')
// log.warn('PuppetPadchat', e.message)
// throw e
// }
if (this.state.off()) {
const e = new Error('initBridge() found targetState != live, no init anymore')
log.warn('PuppetPadchat', e.message)
throw e
}
await this.bridge.initWs()
const autoData: AutoDataType = await this.options.profile.get('autoData')
log.silly('PuppetPadchat', 'initBridge, get autoData: %s', JSON.stringify(autoData))
......@@ -277,14 +299,14 @@ export class PuppetPadchat extends Puppet {
const msg = this.Message.create(msgRawPayload['msg_id'], await this.messageRawPayloadParser(msgRawPayload))
await msg.ready()
this.emit('message', msg)
this.emit('message', msg.id)
})
// Data Return From WebSocket Client
} else {
// check logout:
if (rawWebSocketData.type === -1) {
this.emit('logout', this.userSelf())
this.emit('logout', this.selfId())
}
log.silly('PuppetPadchat', 'return apiName: %s, msgId: %s', rawWebSocketData.apiName, rawWebSocketData.msgId)
......@@ -362,12 +384,15 @@ export class PuppetPadchat extends Puppet {
this.userId = this.bridge.autoData.user_name // Puppet userId different with WebSocket userId
const user = this.Contact.load(this.userId)
await user.ready()
this.emit('login', user)
this.emit('login', this.userId)
log.verbose('PuppetPadchatBridge', 'loginSucceed: Send login to the bot, user_name: %s', this.bridge.username)
await this.bridge.WXSendMsg(this.bridge.autoData.user_name, 'Bot on line!')
this.state.on(true)
this.emit('start')
this.initWatchdog()
return
} else {
log.verbose('PuppetPadchatBridge', 'no enough data, save again, %s', JSON.stringify(this.bridge.autoData))
......@@ -388,21 +413,30 @@ export class PuppetPadchat extends Puppet {
}
this.state.off('pending')
this.watchdog.sleep()
setImmediate(() => this.bridge.removeAllListeners())
await this.logout()
this.bridge.closeWs()
// await some tasks...
this.state.off(true)
this.emit('stop')
}
public async logout(): Promise<void> {
log.verbose('PuppetPadchat', 'logout()')
if (!this.logonoff()) {
if (!this.userId) {
throw new Error('logout before login?')
}
// this.emit('logout', this.user!) // becore we will throw above by logonoff() when this.user===undefined
// this.user = undefined
this.emit('logout', this.userId) // becore we will throw above by logonoff() when this.user===undefined
this.userId = undefined
// TODO: do the logout job
// TODO: this.bridge.logout
}
/**
......@@ -416,9 +450,14 @@ export class PuppetPadchat extends Puppet {
public async contactAlias(contactId: string, alias?: string|null): Promise<void | string> {
log.verbose('PuppetPadchat', 'contactAlias(%s, %s)', contactId, alias)
const payload = await this.contactPayload(contactId)
if (typeof alias === 'undefined') {
return 'padchat alias'
return payload.alias || ''
}
// TODO: modify alias in bridge
return
}
......@@ -438,9 +477,15 @@ export class PuppetPadchat extends Puppet {
public async contactAvatar(contactId: string): Promise<FileBox> {
log.verbose('PuppetPadchat', 'contactAvatar(%s)', contactId)
const WECHATY_ICON_PNG = path.resolve('../../docs/images/wechaty-icon.png')
// TODO
return FileBox.packBase64('', WECHATY_ICON_PNG)
const rawPayload = await this.contactRawPayload(contactId)
const payload = await this.contactRawPayloadParser(rawPayload)
if (!payload.avatar) {
throw new Error('no avatar')
}
const file = FileBox.packRemote(payload.avatar)
return file
}
public async contactRawPayload(id: string): Promise<PadchatContactRawPayload> {
......@@ -491,6 +536,21 @@ export class PuppetPadchat extends Puppet {
* Message
*
*/
public async messageFile(id: string): Promise<FileBox> {
// const rawPayload = await this.messageRawPayload(id)
const base64 = 'cRH9qeL3XyVnaXJkppBuH20tf5JlcG9uFX1lL2IvdHRRRS9kMMQxOPLKNYIzQQ=='
const filename = 'test.txt'
const file = FileBox.packBase64(
base64,
filename,
)
return file
}
public async messageRawPayload(id: string): Promise<PadchatMessageRawPayload> {
throw Error('should not call messageRawPayload')
// log.verbose('PuppetPadchat', 'messageRawPayload(%s)', id)
......@@ -543,7 +603,7 @@ export class PuppetPadchat extends Puppet {
}
const payload: MessagePayload = {
date : new Date(),
timestamp : Date.now(),
fromId : rawPayload.from_user,
text : rawPayload.content,
toId : rawPayload.to_user,
......@@ -591,6 +651,22 @@ export class PuppetPadchat extends Puppet {
file : FileBox,
): Promise<void> {
log.verbose('PuppetPadchat', 'messageSend(%s, %s)', receiver, file)
const id = receiver.contactId || receiver.roomId
if (!id) {
throw new Error('no id!')
}
const xxx = cuid()
const tmpFile = path.join('/tmp/' + xxx)
file.save(tmpFile)
const bitmap = fs.readFileSync(tmpFile)
const base64 = new Buffer(bitmap).toString('base64')
fs.unlinkSync(tmpFile)
await this.bridge.WXSendImage(id, base64)
}
public async messageForward(
......@@ -601,6 +677,21 @@ export class PuppetPadchat extends Puppet {
receiver,
messageId,
)
const msg = this.Message.create(messageId)
await msg.ready()
if (msg.type() === this.Message.Type.Text) {
await this.messageSendText(
receiver,
msg.text(),
)
} else {
await this.messageSendFile(
receiver,
await msg.file(),
)
}
}
/**
......@@ -695,6 +786,7 @@ export class PuppetPadchat extends Puppet {
contactId : string,
): Promise<void> {
log.verbose('PuppetPadchat', 'roomDel(%s, %s)', roomId, contactId)
}
public async roomAdd(
......@@ -710,9 +802,14 @@ export class PuppetPadchat extends Puppet {
): Promise<void | string> {
log.verbose('PuppetPadchat', 'roomTopic(%s, %s)', roomId, topic)
const payload = await this.roomPayload(roomId)
if (typeof topic === 'undefined') {
return 'padchat room topic'
return payload.topic
}
// TODO: modify
return
}
......
......@@ -733,7 +733,7 @@ export class Bridge extends EventEmitter {
public async innerHTML(): Promise<string> {
const html = await this.evaluate(() => {
return document.body.innerHTML
return window.document.body.innerHTML
})
return html
}
......@@ -860,7 +860,7 @@ export class Bridge extends EventEmitter {
}
try {
const hostname = await this.page.evaluate(() => location.hostname) as string
const hostname = await this.page.evaluate(() => window.location.hostname) as string
log.silly('PuppetPuppeteerBridge', 'hostname() got %s', hostname)
return hostname
} catch (e) {
......
......@@ -24,7 +24,7 @@ import {
log,
} from '../config'
import {
ScanData,
ScanPayload,
} from '../puppet/'
// import { Contact } from '../contact'
......@@ -60,18 +60,18 @@ function onDing(
}
async function onScan(
this: PuppetPuppeteer,
data: ScanData,
this : PuppetPuppeteer,
payload : ScanPayload,
): Promise<void> {
log.verbose('PuppetPuppeteerEvent', 'onScan({code: %d, url: %s})', data.code, data.url)
log.verbose('PuppetPuppeteerEvent', 'onScan({code: %d, url: %s})', payload.code, payload.url)
if (this.state.off()) {
log.verbose('PuppetPuppeteerEvent', 'onScan(%s) state.off()=%s, NOOP',
data, this.state.off())
payload, this.state.off())
return
}
this.scanInfo = data
this.scanPayload = payload
/**
* When wx.qq.com push a new QRCode to Scan, there will be cookie updates(?)
......@@ -85,11 +85,11 @@ async function onScan(
// feed watchDog a `scan` type of food
const food: WatchdogFood = {
data,
data: payload,
type: 'scan',
}
this.emit('watchdog', food)
this.emit('scan' , data.url, data.code)
this.emit('scan' , payload.url, payload.code)
}
function onLog(data: any): void {
......@@ -106,7 +106,7 @@ async function onLogin(
const TTL_WAIT_MILLISECONDS = 1 * 1000
if (ttl <= 0) {
log.verbose('PuppetPuppeteerEvent', 'onLogin(%s) TTL expired')
this.emit('error', new Error('TTL expired.'))
this.emit('error', 'onLogin() TTL expired.')
return
}
......@@ -117,11 +117,11 @@ async function onLogin(
}
if (this.logonoff()) {
throw new Error('onLogin() user had already logined: ' + this.userSelf())
throw new Error('onLogin() user had already logined: ' + this.selfId())
// await this.logout()
}
this.scanInfo = undefined
this.scanPayload = undefined
try {
/**
......@@ -181,8 +181,10 @@ async function onMessage(
this : PuppetPuppeteer,
rawPayload : WebMessageRawPayload,
): Promise<void> {
const msg = this.Message.create(rawPayload.MsgId)
await msg.ready()
const msg = this.Message.create(
rawPayload.MsgId,
await this.messageRawPayloadParser(rawPayload),
)
/**
* Fire Events if match message type & content
......@@ -208,7 +210,7 @@ async function onMessage(
break
}
this.emit('message', msg)
this.emit('message', msg.id)
}
async function onUnload(this: PuppetPuppeteer): Promise<void> {
......
......@@ -145,7 +145,7 @@ async function checkFriendRequest(
ticket,
)
this.emit('friend', receivedRequest)
this.emit('friend', receivedRequest.toJSON())
}
/**
......@@ -188,7 +188,7 @@ async function checkFriendConfirm(
log.warn('PuppetPuppeteerFirer', 'fireFriendConfirm() contact still not ready after `ready()` call')
}
this.emit('friend', confirmedRequest)
this.emit('friend', confirmedRequest.toJSON())
}
/**
......@@ -259,7 +259,7 @@ async function checkRoomJoin(
try {
if (/^You|你$/i.test(inviter)) { // === 'You' || inviter === '你' || inviter === 'you'
inviterContact = this.userSelf()
inviterContact = this.Contact.load(this.selfId())
}
// const max = 20
......@@ -344,8 +344,8 @@ async function checkRoomJoin(
await inviterContact.ready()
await room.ready()
this.emit('room-join', room , inviteeContactList, inviterContact)
room.emit('join' , inviteeContactList, inviterContact)
this.emit('room-join', room.id, inviteeContactList.map(c => c.id), inviterContact.id)
room.emit('join' , inviteeContactList, inviterContact)
return true
} catch (e) {
......@@ -368,7 +368,7 @@ function parseRoomLeave(
if ((!foundByBot || !foundByBot.length) && (!foundByOther || !foundByOther.length)) {
throw new Error('checkRoomLeave() no matched re for ' + content)
}
const [leaver, remover] = foundByBot ? [ foundByBot[1], this.userSelf().id ] : [ this.userSelf().id, foundByOther[1] ]
const [leaver, remover] = foundByBot ? [ foundByBot[1], this.selfId() ] : [ this.selfId(), foundByOther[1] ]
return [leaver, remover]
}
......@@ -399,8 +399,8 @@ async function checkRoomLeave(
* @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 leaverContact: Contact | null, removerContact: Contact | null
if (leaver === this.userSelf().id) {
leaverContact = this.userSelf()
if (leaver === this.selfId()) {
leaverContact = this.Contact.load(this.selfId())
// not sure which is better
// removerContact = room.member({contactAlias: remover}) || room.member({name: remover})
......@@ -411,7 +411,7 @@ async function checkRoomLeave(
// }
} else {
removerContact = this.userSelf()
removerContact = this.Contact.load(this.selfId())
// not sure which is better
// leaverContact = room.member({contactAlias: remover}) || room.member({name: leaver})
......@@ -434,8 +434,8 @@ async function checkRoomLeave(
* 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.emit('room-leave', room, [leaverContact] /* , [removerContact] */)
room.emit('leave' , [leaverContact], removerContact || undefined)
this.emit('room-leave', room.id , [leaverContact.id] /* , [removerContact] */)
room.emit('leave' , [leaverContact], removerContact || undefined)
setTimeout(_ => { room.refresh() }, 10000) // reload the room data, especially for memberList
return true
......@@ -476,7 +476,7 @@ async function checkRoomTopic(
let changerContact: Contact | null
if (/^You$/.test(changer) || /^你$/.test(changer)) {
changerContact = this.userSelf()
changerContact = this.Contact.load(this.selfId())
} else {
changerContact = room.member(changer)
}
......@@ -489,8 +489,8 @@ async function checkRoomTopic(
try {
await changerContact.ready()
await room.ready()
this.emit('room-topic', room, topic, oldTopic, changerContact)
room.emit('topic' , topic, oldTopic, changerContact)
this.emit('room-topic', room.id , topic, oldTopic, changerContact.id)
room.emit('topic' , topic, oldTopic, changerContact)
room.refresh()
return true
} catch (e) {
......
......@@ -40,7 +40,7 @@ import {
Puppet,
PuppetOptions,
Receiver,
ScanData,
ScanPayload,
} from '../puppet/'
import {
config,
......@@ -89,8 +89,8 @@ export type PuppetFoodType = 'scan' | 'ding'
export type ScanFoodType = 'scan' | 'login' | 'logout'
export class PuppetPuppeteer extends Puppet {
public bridge : Bridge
public scanInfo?: ScanData
public bridge : Bridge
public scanPayload? : ScanPayload
public scanWatchdog: Watchdog<ScanFoodType>
......@@ -149,6 +149,8 @@ export class PuppetPuppeteer extends Puppet {
})
log.verbose('PuppetPuppeteer', 'start() done')
this.emit('start')
return
} catch (e) {
......@@ -274,6 +276,8 @@ export class PuppetPuppeteer extends Puppet {
} finally {
this.state.off(true)
}
this.emit('stop')
}
private async initBridge(): Promise<Bridge> {
......@@ -318,19 +322,20 @@ export class PuppetPuppeteer extends Puppet {
): Promise<MessagePayload> {
log.verbose('PuppetPuppeteer', 'messageRawPayloadParser(%s) @ %s', rawPayload, this)
const from: Contact = this.Contact.load(rawPayload.MMActualSender) // MMPeerUserName
const text: string = rawPayload.MMActualContent // Content has @id prefix added by wx
const date: Date = new Date(rawPayload.MMDisplayTime) // Javascript timestamp of milliseconds
const fromId = rawPayload.MMActualSender // MMPeerUserName
const text: string = rawPayload.MMActualContent // Content has @id prefix added by wx
const timestamp: number = rawPayload.MMDisplayTime // Javascript timestamp of milliseconds
const filename: undefined | string = this.filename(rawPayload) || undefined
let room : undefined | Room
let to : undefined | Contact
let roomId : undefined | string
let toId : undefined | string
// FIXME: has there any better method to know the room ID?
if (rawPayload.MMIsChatRoom) {
if (/^@@/.test(rawPayload.FromUserName)) {
room = this.Room.load(rawPayload.FromUserName) // MMPeerUserName always eq FromUserName ?
roomId = rawPayload.FromUserName // MMPeerUserName always eq FromUserName ?
} else if (/^@@/.test(rawPayload.ToUserName)) {
room = this.Room.load(rawPayload.ToUserName)
roomId = rawPayload.ToUserName
} else {
throw new Error('parse found a room message, but neither FromUserName nor ToUserName is a room(/^@@/)')
}
......@@ -338,36 +343,31 @@ export class PuppetPuppeteer extends Puppet {
if (rawPayload.ToUserName) {
if (!/^@@/.test(rawPayload.ToUserName)) { // if a message in room without any specific receiver, then it will set to be `undefined`
to = this.Contact.load(rawPayload.ToUserName)
toId = rawPayload.ToUserName
}
}
const file: undefined | FileBox = undefined
const type: MessageType = this.messageTypeFromWeb(rawPayload.MsgType)
const fromId = from && from.id
const roomId = room && room.id
const toId = to && to.id
const payload: MessagePayload = {
// direction: MessageDirection.MT,
type,
fromId,
filename,
toId,
roomId,
text,
date,
file,
}
if (type !== MessageType.Text && type !== MessageType.Unknown) {
payload.file = await this.messageRawPayloadToFile(rawPayload)
timestamp,
}
return payload
}
public async messageFile(messageId: string): Promise<FileBox> {
const rawPayload = await this.messageRawPayload(messageId)
const fileBox = await this.messageRawPayloadToFile(rawPayload)
return fileBox
}
private async messageRawPayloadToFile(
rawPayload: WebMessageRawPayload,
): Promise<FileBox> {
......@@ -579,7 +579,7 @@ export class PuppetPuppeteer extends Puppet {
public async logout(): Promise<void> {
log.verbose('PuppetPuppeteer', 'logout()')
const user = this.userSelf()
const user = this.selfId()
if (!user) {
log.warn('PuppetPuppeteer', 'logout() without self()')
return
......@@ -1366,7 +1366,7 @@ export class PuppetPuppeteer extends Puppet {
const first = cookie.find(c => c.name === 'webwx_data_ticket')
const webwxDataTicket = first && first.value
const size = buffer.length
const fromUserName = this.userSelf()!.id
const fromUserName = this.selfId()
const id = 'WU_FILE_' + this.fileId
this.fileId++
......
......@@ -78,9 +78,9 @@ test('constructor()', async t => {
const sandbox = sinon.createSandbox()
sandbox.stub(puppet, 'messagePayload').callsFake((_: string) => {
const payload: MessagePayload = {
type : Message.Type.Text,
fromId : EXPECTED.from,
date : new Date(),
type : Message.Type.Text,
fromId : EXPECTED.from,
timestamp : Date.now(),
}
return payload
})
......@@ -210,10 +210,10 @@ test('self()', async t => {
function mockMessagePayload() {
const payload: MessagePayload = {
fromId : MOCK_CONTACT.id,
toId : 'to_id',
type : {} as any,
date : {} as any,
fromId : MOCK_CONTACT.id,
toId : 'to_id',
type : wechaty.Message.Type.Text,
timestamp : Date.now(),
}
return payload
}
......
......@@ -54,7 +54,7 @@ const puppet = new PuppetPuppeteer({
const MOCK_USER_ID = 'TEST-USER-ID'
// FIXME: use wechaty.Contact.load()
puppet.emit('login', wechaty.Contact.load(MOCK_USER_ID))
puppet.emit('login', MOCK_USER_ID)
// MyContact.puppet = MyMessage.puppet = MyRoom.puppet = puppet
......
......@@ -3,5 +3,5 @@ export {
PuppetEventName,
PuppetOptions,
Receiver,
ScanData,
ScanPayload,
} from './puppet'
......@@ -34,9 +34,6 @@ import {
Watchdog,
WatchdogFood,
} from 'watchdog'
// import {
// Constructor,
// } from 'clone-class'
import {
WECHATY_EVENT_DICT,
......@@ -55,6 +52,7 @@ import {
} from '../contact'
import {
FriendRequest,
// FriendRequestPayload,
} from '../friend-request'
import {
Message,
......@@ -66,11 +64,10 @@ import {
RoomQueryFilter,
} from '../room'
// XXX: Name??? ScanInfo? ScanEvent? ScanXXX?
export interface ScanData {
avatar: string, // Image Data URL
url: string, // QR Code URL
code: number, // Code
export interface ScanPayload {
code : number, // Code
data? : string, // Image Data URL
url : string, // QR Code URL
}
export const PUPPET_EVENT_DICT = {
......@@ -162,6 +159,8 @@ export abstract class Puppet extends EventEmitter implements Sayable {
*/
try {
const childClassPath = callerResolve('.', __filename)
log.verbose('Puppet', 'constructor() childClassPath=%s', childClassPath)
this.childPkg = readPkgUp.sync({ cwd: childClassPath }).pkg
} finally {
if (!this.childPkg) {
......@@ -175,17 +174,47 @@ export abstract class Puppet extends EventEmitter implements Sayable {
return `Puppet#${this.counter}<${this.constructor.name}>(${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
public emit(event: 'login', user: Contact) : boolean
public emit(event: 'logout', user: Contact | string) : boolean
public emit(event: 'message', message: Message) : boolean
public emit(event: 'room-join', room: Room, inviteeList: Contact[], inviter: Contact) : boolean
public emit(event: 'room-leave', room: Room, leaverList: Contact[]) : boolean
public emit(event: 'room-topic', room: Room, topic: string, oldTopic: string, changer: Contact) : boolean
public emit(event: 'scan', url: string, code: number) : boolean
public emit(event: 'watchdog', food: WatchdogFood) : boolean
// private abstract async emitError(err: string) : Promise<void>
// private abstract async emitFriend(payload: FriendRequestPayload) : Promise<void>
// private abstract async emitHeartbeat(data: string) : Promise<void>
// private abstract async emitLogin(contactId: string) : Promise<void>
// private abstract async emitLogout(contactId: string) : Promise<void>
// private abstract async emitMessage(messageId: string) : Promise<void>
// private abstract async emitRoomJoin(roomId: string, inviteeIdList: string[], inviterId: string) : Promise<void>
// private abstract async emitRoomLeave(roomId: string, leaverIdList: string[]) : Promise<void>
// private abstract async emitRoomTopic(roomId: string, topic: string, oldTopic: string, changerId: string) : Promise<void>
// private abstract async emitScan(url: string, code: number, data?: string) : Promise<void>
// private abstract async emitWatchdog(...) : Promise<void>
// public emit(event: 'error', e: Error) : boolean
// public emit(event: 'friend', request: FriendRequest) : boolean
// public emit(event: 'heartbeat', data: string) : boolean
// public emit(event: 'login', user: Contact) : boolean
// public emit(event: 'logout', user: Contact) : boolean
// public emit(event: 'message', message: Message) : boolean
// public emit(event: 'room-join', room: Room, inviteeList: Contact[], inviter: Contact) : boolean
// public emit(event: 'room-leave', room: Room, leaverList: Contact[]) : boolean
// public emit(event: 'room-topic', room: Room, topic: string, oldTopic: string, changer: Contact) : boolean
// public emit(event: 'scan', url: string, code: number, data?: string) : boolean
// public emit(event: 'watchdog', food: WatchdogFood) : boolean
public emit(event: 'error', error: string) : boolean
public emit(event: 'friend', payload: string) : boolean
public emit(event: 'heartbeat', data: string) : boolean
public emit(event: 'login', contactId: string) : boolean
public emit(event: 'logout', contactId: string) : boolean
public emit(event: 'message', messageId: string) : boolean
public emit(event: 'room-join', roomId: string, inviteeIdList: string[], inviterId: string) : boolean
public emit(event: 'room-leave', roomId: string, leaverIdList: string[]) : boolean
public emit(event: 'room-topic', roomId: string, topic: string, oldTopic: string, changerId: string) : boolean
public emit(event: 'scan', qrCode: string, code: number, data?: string) : boolean
public emit(event: 'start') : boolean
public emit(event: 'stop') : boolean
/**
* Internal Usage
*/
public emit(event: 'watchdog', food: WatchdogFood) : boolean
public emit(event: never, ...args: never[]): never
......@@ -196,18 +225,40 @@ export abstract class Puppet extends EventEmitter implements Sayable {
return super.emit(event, ...args)
}
public on(event: 'error', listener: (e: Error) => void) : this
public on(event: 'friend', listener: (request: FriendRequest) => void) : this
public on(event: 'heartbeat', listener: (data: any) => void) : this
public on(event: 'login', listener: (user: Contact) => void) : this
public on(event: 'logout', listener: (user: Contact) => void) : this
public on(event: 'message', listener: (message: Message) => void) : this
public on(event: 'room-join', listener: (room: Room, inviteeList: Contact[], inviter: Contact) => void) : this
public on(event: 'room-leave', listener: (room: Room, leaverList: Contact[]) => void) : this
public on(event: 'room-topic', listener: (room: Room, topic: string, oldTopic: string, changer: Contact) => void) : this
public on(event: 'scan', listener: (info: ScanData) => void) : this
public on(event: 'watchdog', listener: (data: WatchdogFood) => void) : this
public on(event: never, listener: never) : never
// public on(event: 'error', listener: (e: Error) => void) : this
// public on(event: 'friend', listener: (request: FriendRequest) => void) : this
// public on(event: 'heartbeat', listener: (data: string) => void) : this
// public on(event: 'login', listener: (user: Contact) => void) : this
// public on(event: 'logout', listener: (user: Contact) => void) : this
// public on(event: 'message', listener: (message: Message) => void) : this
// public on(event: 'room-join', listener: (room: Room, inviteeList: Contact[], inviter: Contact) => void) : this
// public on(event: 'room-leave', listener: (room: Room, leaverList: Contact[]) => void) : this
// public on(event: 'room-topic', listener: (room: Room, topic: string, oldTopic: string, changer: Contact) => void) : this
// public on(event: 'scan', listener: (info: ScanPayload) => void) : this
// /**
// * Internal Usage
// */
// public on(event: 'watchdog', listener: (data: WatchdogFood) => void) : this
public on(event: 'error', listener: (error: string) => void) : this
public on(event: 'friend', listener: (payload: string) => void) : this
public on(event: 'heartbeat', listener: (data: string) => void) : this
public on(event: 'login', listener: (contactId: string) => void) : this
public on(event: 'logout', listener: (contactId: string) => void) : this
public on(event: 'message', listener: (messageId: string) => void) : this
public on(event: 'room-join', listener: (roomId: string, inviteeIdList: string[], inviterId: string) => void) : this
public on(event: 'room-leave', listener: (roomId: string, leaverIdList: string[]) => void) : this
public on(event: 'room-topic', listener: (roomId: string, topic: string, oldTopic: string, changerId: string) => void) : this
public on(event: 'scan', listener: (qrCode: string, code: number, data?: string) => void) : this
public on(event: 'start', listener: () => void) : this
public on(event: 'stop', listener: () => void) : this
/**
* Internal Usage
*/
public on(event: 'watchdog', listener: (data: WatchdogFood) => void) : this
public on(event: never, listener: never): never
public on(
event: PuppetEventName,
......@@ -255,15 +306,14 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public abstract async start() : Promise<void>
public abstract async stop() : Promise<void>
public userSelf(): Contact {
public selfId(): string {
log.verbose('Puppet', 'self()')
if (!this.userId) {
throw new Error('not logged in, no userSelf yet.')
}
const user = this.Contact.load(this.userId)
return user
return this.userId
}
public async say(textOrFile: string | FileBox) : Promise<void> {
......@@ -273,11 +323,11 @@ export abstract class Puppet extends EventEmitter implements Sayable {
if (typeof textOrFile === 'string') {
await this.messageSendText({
contactId: this.userSelf().id,
contactId: this.selfId(),
}, textOrFile)
} else if (textOrFile instanceof FileBox) {
await this.messageSendFile({
contactId: this.userSelf().id,
contactId: this.selfId(),
}, textOrFile)
} else {
throw new Error('say() arg unknown')
......@@ -303,12 +353,11 @@ export abstract class Puppet extends EventEmitter implements Sayable {
throw new Error('can only login once!')
}
this.userId = userId
const userSelf = this.Contact.load(userId)
await userSelf.ready()
this.emit('login', userSelf)
this.userId = userId
this.emit('login', userId)
}
/**
......@@ -316,6 +365,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
* Message
*
*/
public abstract async messageFile(messageId: string) : Promise<FileBox>
public abstract async messageForward(to: Receiver, messageId: string) : Promise<void>
public abstract async messageSendText(to: Receiver, text: string) : Promise<void>
public abstract async messageSendFile(to: Receiver, file: FileBox) : Promise<void>
......
......@@ -2,6 +2,8 @@ declare module 'bl'
declare module 'blessed-contrib'
declare module 'qrcode-terminal'
declare var window
// Extend the `Window` from Browser
interface Window {
emit: Function, // from puppeteer
......
......@@ -133,7 +133,7 @@ test('on(event, Function)', async t => {
wechaty.on('error', spy)
const messageFuture = new Promise(resolve => wechaty.once('message', resolve))
wechaty.emit('message')
wechaty.emit('message', {} as any)
await messageFuture
await wechaty.stop()
......
......@@ -104,7 +104,7 @@ export class Wechaty extends PuppetAccessory implements Sayable {
* singleton globalInstance
* @private
*/
private static singletonInstance: Wechaty
private static globalInstance: Wechaty
private profile: Profile
......@@ -144,13 +144,13 @@ export class Wechaty extends PuppetAccessory implements Sayable {
public static instance(
options?: WechatyOptions,
) {
if (options && this.singletonInstance) {
if (options && this.globalInstance) {
throw new Error('instance can be only set once!')
}
if (!this.singletonInstance) {
this.singletonInstance = new Wechaty(options)
if (!this.globalInstance) {
this.globalInstance = new Wechaty(options)
}
return this.singletonInstance
return this.globalInstance
}
/**
......@@ -226,6 +226,29 @@ export class Wechaty extends PuppetAccessory implements Sayable {
return Wechaty.version(forceNpm)
}
public emit(event: 'error' , error: Error) : boolean
public emit(event: 'friend' , request: FriendRequest) : boolean
public emit(event: 'heartbeat' , data: any) : boolean
public emit(event: 'logout' , user: Contact) : boolean
public emit(event: 'login' , user: Contact) : boolean
public emit(event: 'message' , message: Message) : boolean
public emit(event: 'room-join' , room: Room, inviteeList : Contact[], inviter : Contact) : boolean
public emit(event: 'room-leave' , room: Room, leaverList : Contact[], remover? : Contact) : boolean
public emit(event: 'room-topic' , room: Room, topic: string, oldTopic: string, changer: Contact) : boolean
public emit(event: 'scan' , qrCode: string, code: number, data?: string) : boolean
public emit(event: 'start') : boolean
public emit(event: 'stop') : boolean
// guard for the above event: make sure it includes all the possible values
public emit(event: never, listener: never): never
public emit(
event: WechatyEventName,
...args: any[]
): boolean {
return super.emit(event, ...args)
}
public on(event: 'error' , listener: string | ((this: Wechaty, error: Error) => void)) : this
public on(event: 'friend' , listener: string | ((this: Wechaty, request: FriendRequest) => void)) : this
public on(event: 'heartbeat' , listener: string | ((this: Wechaty, data: any) => void)) : this
......@@ -235,11 +258,12 @@ export class Wechaty extends PuppetAccessory implements Sayable {
public on(event: 'room-join' , listener: string | ((this: Wechaty, room: Room, inviteeList: Contact[], inviter: Contact) => void)) : this
public on(event: 'room-leave' , listener: string | ((this: Wechaty, room: Room, leaverList: Contact[], remover?: Contact) => void)) : this
public on(event: 'room-topic' , listener: string | ((this: Wechaty, room: Room, topic: string, oldTopic: string, changer: Contact) => void)): this
public on(event: 'scan' , listener: string | ((this: Wechaty, url: string, code: number) => void)) : this
public on(event: 'scan' , listener: string | ((this: Wechaty, url: string, code: number) => void)) : this
public on(event: 'start' , listener: string | ((this: Wechaty) => void)) : this
public on(event: 'stop' , listener: string | ((this: Wechaty) => void)) : this
// guard for the above event: make sure it includes all the possible values
public on(event: never, listener: never): never
public on(event: never, listener: never): never
/**
* @desc Wechaty Class Event Type
......@@ -474,7 +498,7 @@ export class Wechaty extends PuppetAccessory implements Sayable {
* Plugin Version Range Check
*/
private initPuppetSemverSatisfy(versionRange: string) {
log.verbose('Wechaty', 'initPuppet(%s)', versionRange)
log.verbose('Wechaty', 'initPuppetSemverSatisfy(%s)', versionRange)
return semver.satisfies(
this.version(true),
versionRange,
......@@ -482,12 +506,131 @@ export class Wechaty extends PuppetAccessory implements Sayable {
}
private initPuppetEventBridge(puppet: Puppet) {
for (const event of Object.keys(WECHATY_EVENT_DICT)) {
log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', event)
/// e as any ??? Maybe this is a bug of TypeScript v2.5.3
puppet.on(event as any, (...args: any[]) => {
this.emit(event, ...args)
})
const eventNameList: WechatyEventName[] = Object.keys(WECHATY_EVENT_DICT) as any
for (const eventName of eventNameList) {
log.verbose('Wechaty', 'initPuppetEventBridge() puppet.on(%s) registered', eventName)
// /// e as any ??? Maybe this is a bug of TypeScript v2.5.3
// puppet.on(event as any, (...args: any[]) => {
// this.emit(event, ...args)
// })
switch (eventName) {
case 'error':
puppet.removeAllListeners('error')
puppet.on('error', error => {
this.emit('error', new Error(error))
})
break
case 'heartbeat':
puppet.removeAllListeners('heartbeat')
puppet.on('heartbeat', data => {
this.emit('heartbeat', data)
})
break
case 'start':
puppet.removeAllListeners('start')
puppet.on('start', () => {
this.emit('start')
} )
break
case 'stop':
puppet.removeAllListeners('stop')
puppet.on('stop', () => {
this.emit('stop')
} )
break
case 'friend':
puppet.removeAllListeners('friend')
puppet.on('friend', payload => {
const request = this.FriendRequest.fromJSON(payload)
this.emit('friend', request)
})
break
case 'login':
puppet.removeAllListeners('login')
puppet.on('login', async contactId => {
const contact = this.Contact.load(contactId)
await contact.ready()
this.emit('login', contact)
})
break
case 'logout':
puppet.removeAllListeners('logout')
puppet.on('logout', async contactId => {
const contact = this.Contact.load(contactId)
await contact.ready()
this.emit('logout', contact)
})
break
case 'message':
puppet.removeAllListeners('message')
puppet.on('message', async messageId => {
const msg = this.Message.create(messageId)
await msg.ready()
this.emit('message', msg)
})
break
case 'room-join':
puppet.removeAllListeners('room-join')
puppet.on('room-join', async (roomId, inviteeIdList, inviterId) => {
const room = this.Room.load(roomId)
await room.ready()
const inviteeList = inviteeIdList.map(id => this.Contact.load(id))
await Promise.all(inviteeList.map(c => c.ready()))
const inviter = this.Contact.load(inviterId)
await inviter.ready()
this.emit('room-join', room, inviteeList, inviter)
})
break
case 'room-leave':
puppet.removeAllListeners('room-leave')
puppet.on('room-leave', async (roomId, leaverIdList) => {
const room = this.Room.load(roomId)
await room.ready()
const leaverList = leaverIdList.map(id => this.Contact.load(id))
await Promise.all(leaverList.map(c => c.ready()))
this.emit('room-leave', room, leaverList)
})
break
case 'room-topic':
puppet.removeAllListeners('room-topic')
puppet.on('room-topic', async (roomId, topic, oldTopic, changerId) => {
const room = this.Room.load(roomId)
await room.ready()
const changer = this.Contact.load(changerId)
await changer.ready()
this.emit('room-topic', room, topic, oldTopic, changer)
})
break
case 'scan':
puppet.removeAllListeners('scan')
puppet.on('scan', async (qrCode, code, data) => {
this.emit('scan', qrCode, code, data)
})
break
default:
throw new Error('eventName ' + eventName + 'unsupported!')
}
}
}
......@@ -529,12 +672,10 @@ export class Wechaty extends PuppetAccessory implements Sayable {
await this.profile.load()
await this.initPuppet()
// set puppet instance to Wechaty Static variable, for using by Contact/Room/Message/FriendRequest etc.
// config.puppetInstance(puppet)
await this.puppet.start()
} catch (e) {
// console.log(e)
log.error('Wechaty', 'start() exception: %s', e && e.message)
Raven.captureException(e)
throw e
......@@ -575,13 +716,6 @@ export class Wechaty extends PuppetAccessory implements Sayable {
return
}
// this.puppet = null
// config.puppetInstance(null)
// this.Contact.puppet = undefined
// this.FriendRequest.puppet = undefined
// this.Message.puppet = undefined
// this.Room.puppet = undefined
try {
await puppet.stop()
} catch (e) {
......@@ -651,22 +785,11 @@ export class Wechaty extends PuppetAccessory implements Sayable {
* console.log(`Bot is ${contact.name()}`)
*/
public userSelf(): Contact {
return this.puppet.userSelf()
const userId = this.puppet.selfId()
const user = this.Contact.load(userId)
return user
}
/**
* @private
*/
// public async send(message: Message): Promise<void> {
// try {
// await this.puppet.messageSend(message)
// } catch (e) {
// log.error('Wechaty', 'send() exception: %s', e.message)
// Raven.captureException(e)
// throw e
// }
// }
/**
* Send message to filehelper
*
......
......@@ -15,8 +15,7 @@
, "noLib": false
, "skipLibCheck": true
, "lib": [
"esnext"
, "dom"
"esnext"
]
}
, "exclude": [
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册