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

#40 more types, bug fixes, async/await

上级 2c1e4d3b
......@@ -16,7 +16,7 @@ Wechaty is a Bot Framework for Wechat **Personal** Account that help you easy cr
# ATTENTION
## Wechaty is just converted from Javascript to Typescript.
## Wechaty is just converted to Typescript from Javascript.
Details: https://github.com/wechaty/wechaty/issues/40
......@@ -25,7 +25,7 @@ Details: https://github.com/wechaty/wechaty/issues/40
- Please do not clone/pull until you are really want to play with typescript wechaty in alpha stage.
```
The last javascript version is: [v0.4.0](https://github.com/wechaty/wechaty/releases/tag/v0.4.0) (2016/10/9)
The last Javascript version is: [v0.4.0](https://github.com/wechaty/wechaty/releases/tag/v0.4.0) (2016/10/9)
Or install v0.4 via `npm install wechaty`
......
......@@ -60,7 +60,7 @@ console.log(welcome)
const bot = Wechaty.instance({ profile: Config.DEFAULT_PROFILE })
bot
.on('scan', ({url, code}) => {
.on('scan', (url, code) => {
console.log(`Use Wechat to Scan QR Code in url to login: ${code}\n${url}`)
})
.on('logout' , user => log.info('Bot', `${user.name()} logouted`))
......@@ -87,7 +87,7 @@ bot
, room.topic()
, Array.isArray(invitee)
? invitee.map(c => c.name()).join(',')
: invitee
: invitee.name()
, inviter.name()
)
})
......@@ -232,7 +232,12 @@ function manageDingRoom() {
* Event: Join
*/
room.on('join', (invitee: Contact|Contact[], inviter: Contact) => {
log.verbose('Bot', 'Room EVENT: join - %s, %s', typeof invitee, typeof inviter)
log.verbose('Bot', 'Room EVENT: join - %s, %s'
, Array.isArray(invitee)
? invitee.map(c => c.name()).join(', ')
: invitee.name()
, inviter.name()
)
checkRoomJoin.call(this, room, invitee, inviter)
})
......@@ -318,7 +323,7 @@ function checkRoomJoin(room: Room, invitee: Contact|Contact[] , inviter: Contact
}
} catch (e) {
log.error('room join event exception: %s', e.stack)
log.error('Bot', 'checkRoomJoin() exception: %s', e.stack)
}
}
......
......@@ -121,13 +121,22 @@ Config.puppetInstance = function(instance?: Puppet): Puppet {
type WatchdogFood = {
data: any
, type?: 'HEARTBEAT' | 'POISON' | 'SCAN'
, timeout?: number // millisecond
, type?: 'HEARTBEAT'
| 'POISON'
| 'SCAN'
}
type WechatyEventType = 'login' | 'logout' | 'friend'
| 'message' | 'error' | 'heartbeat' | 'scan'
| 'room-join' | 'room-leave' | 'room-topic'
type WechatyEventName = 'error'
| 'friend'
| 'heartbeat'
| 'login'
| 'logout'
| 'message'
| 'room-join'
| 'room-leave'
| 'room-topic'
| 'scan'
type ScanInfo = {
url: string
......@@ -141,5 +150,5 @@ export {
, PuppetType
, ScanInfo
, WatchdogFood
, WechatyEventType
, WechatyEventName
}
......@@ -10,23 +10,21 @@
* Events Function Wrapper
*
*/
import Config from './config'
import Contact from './contact'
import Message from './message'
import Room from './room'
import Wechaty from './wechaty'
import {
Config
, WechatyEventName
} from './config'
import Contact from './contact'
import Message from './message'
import Room from './room'
import Wechaty from './wechaty'
import log from './brolog-env'
import log from './brolog-env'
type WechatyEventScope = {
say: (content: string, replyTo?: Contact|Contact[]) => void
}
type WechatyEventType = 'error' | 'heartbeat'
| 'login' | 'logout'
| 'message' | 'scan' | 'friend'
| 'room-join' | 'room-leave' | 'room-topic'
const EVENT_CONFIG = {
error: wrapFilehelper
, friend: wrapFilehelper
......@@ -45,7 +43,7 @@ class EventScope {
return Object.keys(EVENT_CONFIG)
}
public static wrap(this: Wechaty|Room, event: WechatyEventType, listener: Function): Function {
public static wrap(this: Wechaty|Room, event: WechatyEventName, listener: Function): Function {
log.verbose('WechatyEvent', 'wrap(%s, %s)', event, typeof listener)
if (typeof listener !== 'function') {
......
......@@ -109,10 +109,10 @@ class IoClient {
const wechaty = this.wechaty
wechaty
.on('login' , user => this.log.info('IoClient', `${user.name()} logined`))
.on('logout' , user => this.log.info('IoClient', `${user.name()} logouted`))
.on('scan', ({url, code}) => this.log.info('IoClient', `[${code}] ${url}`))
.on('message' , message => {
.on('login' , user => this.log.info('IoClient', `${user.name()} logined`))
.on('logout' , user => this.log.info('IoClient', `${user.name()} logouted`))
.on('scan', (url, code) => this.log.info('IoClient', `[${code}] ${url}`))
.on('message' , message => {
message.ready()
.then(this.onMessage.bind(this))
.catch(e => this.log.error('IoClient', 'message.ready() %s' , e))
......
......@@ -13,10 +13,11 @@
import * as WebSocket from 'ws'
// const co = require('co')
import Config, {
WechatyEventType
} from './config'
import Contact from './contact'
import {
Config
// WechatyEventName
} from './config'
import Wechaty from './wechaty'
import log from './brolog-env'
......@@ -27,8 +28,18 @@ type IoSetting = {
protocol?: string
}
type IoEventName = 'botie'
| 'error'
| 'heartbeat'
| 'login'
| 'message'
| 'raw'
| 'reset'
| 'scan'
| 'shutdown'
type IoEvent = {
name: string
name: IoEventName
payload: any
}
......@@ -36,7 +47,7 @@ class Io {
public uuid: string
private protocol: string
private _eventBuffer = []
private eventBuffer = []
private ws: WebSocket
private _currentState: string
......@@ -206,7 +217,7 @@ class Io {
log.verbose('Io', 'on(report): %s', ioEvent.payload)
const user = this.setting.wechaty.user()
if (user) {
const loginEvent = {
const loginEvent: IoEvent = {
name: 'login'
// , payload: user.obj
, payload: user
......@@ -219,7 +230,7 @@ class Io {
&& this.setting.wechaty.puppet
&& this.setting.wechaty.puppet['scan']
if (scan) {
const scanEvent = {
const scanEvent: IoEvent = {
name: 'scan'
, payload: scan
}
......@@ -297,77 +308,82 @@ class Io {
wechaty.on('message', this.ioMessage)
const hookEvents: WechatyEventType[] = [
'scan'
, 'login'
, 'logout'
, 'heartbeat'
, 'error'
]
hookEvents.map(event => {
wechaty.on(event, data => {
const ioEvent = {
name: event
, payload: data
}
switch (event) {
case 'login':
case 'logout':
if (data instanceof Contact) {
// ioEvent.payload = data.obj
ioEvent.payload = data
}
break
case 'error':
ioEvent.payload = data.toString()
break
case 'heartbeat':
ioEvent.payload = {
uuid: this.uuid
, data: data
}
break
wechaty.on('scan', (url, code) => this.send({ name: 'scan', payload: { url, code } }))
wechaty.on('login' , user => this.send({ name: 'login', payload: user }))
wechaty.on('logout' , user => this.send({ name: 'login', payload: user }))
wechaty.on('heartbeat', data => this.send({ name: 'heartbeat', payload: { uuid: this.uuid, data } }))
wechaty.on('error' , error => this.send({ name: 'error', payload: error }))
// const hookEvents: WechatyEventName[] = [
// 'scan'
// , 'login'
// , 'logout'
// , 'heartbeat'
// , 'error'
// ]
// hookEvents.map(event => {
// wechaty.on(event, (data) => {
// const ioEvent: IoEvent = {
// name: event
// , payload: data
// }
// switch (event) {
// case 'login':
// case 'logout':
// if (data instanceof Contact) {
// // ioEvent.payload = data.obj
// ioEvent.payload = data
// }
// break
// case 'error':
// ioEvent.payload = data.toString()
// break
// case 'heartbeat':
// ioEvent.payload = {
// uuid: this.uuid
// , data: data
// }
// break
// default:
// break
// }
// this.send(ioEvent)
// })
// })
default:
break
}
false && wechaty.on('message', m => {
const text = (m.room() ? '['+m.room().topic()+']' : '')
+ '<'+m.from().name()+'>'
+ ':' + m.toStringDigest()
this.send(ioEvent)
})
this.send({ name: 'message', payload: text })
})
// wechaty.on('message', m => {
// const text = (m.room() ? '['+m.room().name()+']' : '')
// + '<'+m.from().name()+'>'
// + ':' + m.toStringDigest()
// const messageEvent = {
// name: 'message'
// , payload: text
// }
// this.send(messageEvent)
// })
return Promise.resolve()
return
}
private send(ioEvent?: IoEvent) {
if (ioEvent) {
log.silly('Io', 'send(%s: %s)', ioEvent.name, ioEvent.payload)
this._eventBuffer.push(ioEvent)
this.eventBuffer.push(ioEvent)
} else { log.silly('Io', 'send()') }
if (!this.connected()) {
log.verbose('Io', 'send() without a connected websocket, eventBuffer.length = %d', this._eventBuffer.length)
log.verbose('Io', 'send() without a connected websocket, eventBuffer.length = %d', this.eventBuffer.length)
return false
}
while (this._eventBuffer.length) {
while (this.eventBuffer.length) {
this.ws.send(
JSON.stringify(
this._eventBuffer.shift()
this.eventBuffer.shift()
)
)
}
......@@ -391,7 +407,7 @@ class Io {
// try to send IoEvents in buffer
this.send()
this._eventBuffer = []
this.eventBuffer = []
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
......
......@@ -6,11 +6,11 @@
* https://github.com/wechaty/wechaty
*
*/
import Contact from './contact'
import Room from './room'
import UtilLib from './util-lib'
import Wechaty from './wechaty'
import log from './brolog-env'
import Config from './config'
import Contact from './contact'
import Room from './room'
import UtilLib from './util-lib'
import log from './brolog-env'
type MessageRawObj = {
MsgId: string
......@@ -274,7 +274,7 @@ class Message {
m.content(mentionList + ' ' + content)
}
return Wechaty.instance()
return Config.puppetInstance()
.send(m)
}
......
......@@ -111,7 +111,7 @@ function checkRoomJoin(content: string): [string[], string] {
throw new Error('checkRoomJoin() not found')
}
const [, inviter, inviteeStr] = found
const [inviter, inviteeStr] = [ found[1], found[2] ]
// "凌" invited "庆次、小桔妹" to the group chat
const inviteeList = inviteeStr.split(/、/)
......@@ -120,7 +120,7 @@ function checkRoomJoin(content: string): [string[], string] {
}
async function fireRoomJoin(m: Message): Promise<void> {
log.verbose('PuppetWebFirer', 'fireRoomJoin()')
log.verbose('PuppetWebFirer', 'fireRoomJoin(%s)', m.content())
const room = m.room()
const content = m.content()
......@@ -128,7 +128,8 @@ async function fireRoomJoin(m: Message): Promise<void> {
let inviteeList: string[], inviter: string
try {
[inviteeList, inviter] = checkRoomJoin(content)
} catch (e) { // not a room join message
} catch (e) {
// not a room join message
return
}
log.silly('PuppetWebFirer', 'fireRoomJoin() inviteeList: %s, inviter: %s'
......@@ -139,7 +140,6 @@ async function fireRoomJoin(m: Message): Promise<void> {
let inviterContact: Contact
let inviteeContactList: Contact[] = []
// co.call(this, function* () {
try {
if (inviter === "You've") {
inviterContact = Contact.load(this.userId)
......@@ -152,57 +152,59 @@ async function fireRoomJoin(m: Message): Promise<void> {
// max = (2*totalTime/backoff) ^ (1/2)
// timeout = 11,250 for {max: 15, backoff: 100}
await retryPromise({ max: max, backoff: backoff }, attempt => {
await retryPromise({ max: max, backoff: backoff }, async attempt => {
log.silly('PuppetWebFirer', 'fireRoomJoin() retryPromise() attempt %d with timeout %d', attempt, timeout)
return room.refresh()
.then(_ => {
let iDone, allDone = true
for (let i in inviteeList) {
iDone = inviteeContactList[i] instanceof Contact
if (!iDone) {
let c = room.member(inviteeList[i])
if (c) {
inviteeContactList[i] = c
} else {
allDone = false
}
}
}
if (!inviterContact) {
inviterContact = room.member(inviter)
}
if (allDone && inviterContact) {
log.silly('PuppetWebFirer', 'fireRoomJoin() resolve() inviteeContactList: %s, inviterContact: %s'
, inviteeContactList.map((c: Contact) => c.name()).join(',')
, inviterContact.name()
)
return Promise.resolve()
} else {
log.silly('PuppetWebFirer', 'fireRoomJoin() reject() inviteeContactList: %s, inviterContact: %s'
, inviteeContactList.map((c: Contact) => c.name()).join(',')
, inviterContact.name()
)
return Promise.reject('not found(yet)')
}
})
.catch(e => {
log.error('PuppetWebFirer', 'fireRoomJoin() retryPromise() room.refresh() rejected: %s', e.stack)
throw e
})
await room.refresh()
// .then(_ => {
let iDone, allDone = true
for (let i in inviteeList) {
iDone = inviteeContactList[i] instanceof Contact
if (!iDone) {
let c = room.member(inviteeList[i])
if (c) {
inviteeContactList[i] = c
} else {
allDone = false
}
}
}
if (!inviterContact) {
inviterContact = room.member(inviter)
}
if (allDone && inviterContact) {
log.silly('PuppetWebFirer', 'fireRoomJoin() resolve() inviteeContactList: %s, inviterContact: %s'
, inviteeContactList.map((c: Contact) => c.name()).join(',')
, inviterContact.name()
)
return
}
throw new Error('not found(yet)')
}).catch(e => {
log.silly('PuppetWebFirer', 'fireRoomJoin() reject() inviteeContactList: %s, inviterContact: %s'
, inviteeContactList.map((c: Contact) => c.name()).join(',')
, inviterContact.name()
)
})
.catch(e => { /* fail safe */ })
if (!inviterContact) {
log.error('PuppetWebFirer', 'firmRoomJoin() inivter not found for %s', inviter)
log.error('PuppetWebFirer', 'firmRoomJoin() inivter not found for %s , `room-join` & `join` event will not fired', inviter)
return
}
if (!inviteeContactList.every(c => c instanceof Contact)) {
log.error('PuppetWebFirer', 'firmRoomJoin() inviteeList not all found for %s', inviteeContactList.join(','))
log.error('PuppetWebFirer', 'firmRoomJoin() inviteeList not all found for %s , only part of them will in the `room-join` or `join` event'
, inviteeContactList.join(',')
)
inviteeContactList = inviteeContactList.filter(c => (c instanceof Contact))
if (inviteeContactList.length < 1) {
log.error('PuppetWebFirer', 'firmRoomJoin() inviteeList empty. `room-join` & `join` event will not fired')
return
}
}
await Promise.all(inviteeContactList.map(c => c.ready()))
......@@ -217,9 +219,8 @@ async function fireRoomJoin(m: Message): Promise<void> {
room.emit('join' , inviteeContactList , inviterContact)
}
// }).catch(e => {
} catch (e) {
log.error('PuppetWebFirer', 'retryPromise() rejected: %s', e.stack)
log.error('PuppetWebFirer', 'exception: %s', e.stack)
}
return
......@@ -232,64 +233,26 @@ function checkRoomLeave(content: string): string {
if (!found) {
return null
}
const [, leaver] = found
return leaver
return found[1] // leaver
}
/**
* You removed "Bruce LEE" from the group chat
*/
async function fireRoomLeave(m) {
async function fireRoomLeave(m: Message) {
log.verbose('PuppetWebFirer', 'fireRoomLeave(%s)', m.content())
const leaver = checkRoomLeave(m.content())
if (!leaver) {
return
}
log.silly('PuppetWebFirer', 'fireRoomLeave() got leaver: %s', leaver)
const room = m.room()
let leaverContact = room.member(leaver)
if (!leaverContact) {
// co.call(this, function* () {
try {
const max = 20
const backoff = 300
const timeout = max * (backoff * max) / 2
// 20 / 300 => 63,000
await retryPromise({ max: max, backoff: backoff }, attempt => {
log.silly('PuppetWebFirer', 'fireRoomLeave() retryPromise() attempt %d with timeout %d', attempt, timeout)
return room.refresh()
.then(_ => {
log.silly('PuppetWebFirer', 'leaver: %s', leaver)
leaverContact = room.member(leaver)
if (leaverContact) {
log.silly('PuppetWebFirer', 'fireRoomLeave() resolve() leaverContact: %s'
, leaverContact
)
return Promise.resolve(leaverContact)
} else {
log.silly('PuppetWebFirer', 'fireRoomLeave() reject() leaver: %s'
, leaver
)
return Promise.reject('not found(yet)')
}
})
.catch(e => {
log.error('PuppetWebFirer', 'fireRoomLeave() retryPromise() room.refresh() rejected: %s', e && e.stack || e)
throw e
})
})
// }).catch(e => {
} catch (e) {
log.error('PuppetWebFirer', 'fireRoomLeave() co exception: %s', e && e.stack || e)
}
}
if (!leaverContact) {
log.error('PuppetWebFirer', 'fireRoomLeave() leaver not found for %s', leaver)
log.error('PuppetWebFirer', 'fireRoomLeave() leaver %s not found, event `room-leave` & `leave` will not be fired')
return
}
......@@ -300,7 +263,7 @@ async function fireRoomLeave(m) {
await room.refresh()
}
function checkRoomTopic(content): [string, string] {
function checkRoomTopic(content: string): [string, string] {
const re = regexConfig.roomTopic
const found = content.match(re)
......@@ -311,7 +274,7 @@ function checkRoomTopic(content): [string, string] {
return [topic, changer]
}
async function fireRoomTopic(m) {
async function fireRoomTopic(m: Message) {
let topic, changer
try {
[topic, changer] = checkRoomTopic(m.content())
......
......@@ -291,13 +291,13 @@ class PuppetWeb extends Puppet {
return this.user
}
public send(message) {
const to = message.get('to')
const room = message.get('room')
public send(message: Message) {
const to = message.to()
const room = message.room()
let content = message.get('content')
let content = message.content()
let destination = to
let destination: Contact|Room = to
if (room) {
destination = room
// TODO use the right @
......@@ -306,12 +306,15 @@ class PuppetWeb extends Puppet {
// }
if (!to) {
message.set('to', room)
message.to(room)
}
}
log.silly('PuppetWeb', `send() destination: ${destination}, content: ${content})`)
return this.bridge.send(destination, content)
log.silly('PuppetWeb', 'send() destination: %s, content: %s)'
, room ? room.topic() : to.name()
, content
)
return this.bridge.send(destination.id, content)
.catch(e => {
log.error('PuppetWeb', 'send() exception: %s', e.message)
throw e
......
......@@ -57,9 +57,9 @@ class Room extends EventEmitter {
// this.id = id
// this.obj = {}
// this.dirtyObj = {}
if (!Config.puppetInstance()) {
throw new Error('Config.puppetInstance() not found')
}
// if (!Config.puppetInstance()) {
// throw new Error('Config.puppetInstance() not found')
// }
}
public toString() { return this.id }
......@@ -141,7 +141,12 @@ class Room extends EventEmitter {
}
public say(content: string, replyTo?: Contact|Contact[]): Promise<any> {
log.verbose('Room', 'say(%s, %s)', content, replyTo)
log.verbose('Room', 'say(%s, %s)'
, content
, Array.isArray(replyTo)
? replyTo.map(c => c.name()).join(', ')
: replyTo.name()
)
const m = new Message()
m.room(this)
......
......@@ -8,22 +8,22 @@
* https://github.com/zixia/wechaty
*
*/
import { EventEmitter } from 'events'
import * as path from 'path'
// const co = require('co')
import * as fs from'fs'
import { EventEmitter } from 'events'
import * as path from 'path'
import * as fs from 'fs'
import Config, {
HeadType
import {
Config
, HeadType
, PuppetType
, WechatyEventType
} from './config'
, WechatyEventName
} from './config'
import Contact from './contact'
import FriendRequest from './friend-request'
import Message from './message'
import Puppet from './puppet'
import PuppetWeb from './puppet-web/index'
import PuppetWeb from './puppet-web/'
import Room from './room'
import UtilLib from './util-lib'
import EventScope from './event-scope'
......@@ -167,7 +167,7 @@ class Wechaty extends EventEmitter {
public on(event: 'room-topic' , listener: (room: Room, topic: string, oldTopic: string, changer: Contact) => void): this
public on(event: 'scan' , listener: (url: string, code: number) => void): this
public on(event: WechatyEventType, listener: Function): this {
public on(event: WechatyEventName, listener: Function): this {
log.verbose('Wechaty', 'on(%s, %s)', event, typeof listener)
typeof FriendRequest
typeof Room
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册