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

WIP: PadchatRpc for a JsonRPC gateway

上级 719f4d5d
......@@ -94,13 +94,15 @@
},
"dependencies": {
"bl": "^2.0.0",
"brolog": "^1.6.2",
"brolog": "^1.6.3",
"clone-class": "^0.6.11",
"cuid": "^2.1.1",
"express": "^4.16.3",
"file-box": "^0.8.19",
"hot-import": "^0.2.1",
"jimp": "^0.2.28",
"json-rpc-peer": "^0.15.3",
"json-rpc-protocol": "^0.11.4",
"jsqr": "^1.0.4",
"lru-cache": "^4.1.3",
"memory-card": "0.2.0",
......
......@@ -19,18 +19,18 @@ import {
PadchatPayloadType,
PadchatContactMsgType,
PadchatContactRawPayload,
PadchatContactPayload,
PadchatMessagePayload,
PadchatRoomMember,
PadchatRoomMemberPayload,
PadchatRoomRawPayload,
PadchatRoomPayload,
} from './padchat-schemas'
import {
AutoDataType,
FunctionType,
PadchatRpcRequest,
InitType,
WXGenerateWxDatType,
WXGetQRCodeType,
......@@ -46,7 +46,7 @@ import {
WXCheckQRCodeStatus,
StandardType,
WXAddChatRoomMemberType,
} from './bridge.type'
} from './padchat-rpc.type'
import {
PadchatPureFunctionHelper as pfHelper,
......@@ -77,8 +77,8 @@ export class Bridge extends EventEmitter {
private loginSucceed = false
private cacheRoomRawPayload : { [id: string]: PadchatRoomRawPayload }
private cacheContactRawPayload : { [id: string]: PadchatContactRawPayload }
private cacheRoomRawPayload : { [id: string]: PadchatRoomPayload }
private cacheContactRawPayload : { [id: string]: PadchatContactPayload }
private readonly state: StateSwitch
......@@ -151,7 +151,7 @@ export class Bridge extends EventEmitter {
private async sendToWebSocket(name: string, args: string[]): Promise<any> {
const msgId = cuid()
const data: FunctionType = {
const data: PadchatRpcRequest = {
userId: this.options.token,
msgId: msgId,
apiName: name,
......@@ -422,9 +422,9 @@ export class Bridge extends EventEmitter {
/**
* Load all Contact and Room
* see issue https://github.com/lijiarui/test-ipad-puppet/issues/39
* @returns {Promise<(PadchatRoomRawPayload | PadchatContactRawPayload)[]>}
* @returns {Promise<(PadchatRoomPayload | PadchatContactPayload)[]>}
*/
private async WXSyncContact(): Promise<(PadchatRoomRawPayload | PadchatContactRawPayload)[]> {
private async WXSyncContact(): Promise<(PadchatRoomPayload | PadchatContactPayload)[]> {
const result = await this.sendToWebSocket('WXSyncContact', [])
if (!result) {
throw Error('WXSyncContact error! canot get result from websocket server')
......@@ -456,8 +456,8 @@ export class Bridge extends EventEmitter {
log.verbose('PuppetPadchatBridge', `syncContactsAndRooms()`)
let cont = true
// const syncContactMap = new Map<string, PadchatContactRawPayload>()
// const syncRoomMap = new Map<string, PadchatRoomRawPayload>()
// const syncContactMap = new Map<string, PadchatContactPayload>()
// const syncRoomMap = new Map<string, PadchatRoomPayload>()
// let contactIdList: string[] = []
// let roomIdList: string[] = []
......@@ -490,11 +490,11 @@ export class Bridge extends EventEmitter {
if (syncContact.msg_type === PadchatContactMsgType.Contact) {
console.log('syncContact:', syncContact.user_name, syncContact.nick_name)
if (pfHelper.isRoomId(syncContact.user_name)) { // /@chatroom$/.test(syncContact.user_name)) {
this.cacheRoomRawPayload[syncContact.user_name] = syncContact as PadchatRoomRawPayload
// syncRoomMap.set(syncContact.user_name, syncContact as PadchatRoomRawPayload)
this.cacheRoomRawPayload[syncContact.user_name] = syncContact as PadchatRoomPayload
// syncRoomMap.set(syncContact.user_name, syncContact as PadchatRoomPayload)
} else if (syncContact.user_name) {
this.cacheContactRawPayload[syncContact.user_name] = syncContact as PadchatContactRawPayload
// syncContactMap.set(syncContact.user_name, syncContact as PadchatContactRawPayload)
this.cacheContactRawPayload[syncContact.user_name] = syncContact as PadchatContactPayload
// syncContactMap.set(syncContact.user_name, syncContact as PadchatContactPayload)
} else {
throw new Error('no user_name')
}
......@@ -636,7 +636,7 @@ export class Bridge extends EventEmitter {
* Get contact by contact id
* @param {any} id user_name
*/
private async WXGetContact(id: string): Promise<PadchatContactRawPayload | PadchatRoomRawPayload> {
private async WXGetContact(id: string): Promise<PadchatContactPayload | PadchatRoomPayload> {
const result = await this.sendToWebSocket('WXGetContact', [id])
if (!result) {
......@@ -658,11 +658,11 @@ export class Bridge extends EventEmitter {
* Get contact by contact id
* @param {any} id user_name
*/
public async WXGetContactPayload(id: string): Promise<PadchatContactRawPayload> {
public async WXGetContactPayload(id: string): Promise<PadchatContactPayload> {
if (!pfHelper.isContactId(id)) { // /@chatroom$/.test(id)) {
throw Error(`should use WXGetRoomPayload because get a room id :${id}`)
}
const result = await this.WXGetContact(id) as PadchatContactRawPayload
const result = await this.WXGetContact(id) as PadchatContactPayload
return result
}
......@@ -670,11 +670,11 @@ export class Bridge extends EventEmitter {
* Get contact by contact id
* @param {any} id user_name
*/
public async WXGetRoomPayload(id: string): Promise<PadchatRoomRawPayload> {
public async WXGetRoomPayload(id: string): Promise<PadchatRoomPayload> {
if (!pfHelper.isRoomId(id)) { // (/@chatroom$/.test(id))) {
throw Error(`should use WXGetContactPayload because get a contact id :${id}`)
}
const result = await this.WXGetContact(id) as PadchatRoomRawPayload
const result = await this.WXGetContact(id) as PadchatRoomPayload
return result
}
......@@ -965,7 +965,7 @@ export class Bridge extends EventEmitter {
}
}
public async contactRawPayload(id: string): Promise<PadchatContactRawPayload> {
public async contactRawPayload(id: string): Promise<PadchatContactPayload> {
log.verbose('PuppetPadchatBridge', 'contactRawPayload(%s)', id)
const rawPayload = await Misc.retry(async (retry, attempt) => {
......@@ -989,7 +989,7 @@ export class Bridge extends EventEmitter {
return rawPayload
}
public async roomRawPayload(id: string): Promise<PadchatRoomRawPayload> {
public async roomRawPayload(id: string): Promise<PadchatRoomPayload> {
log.verbose('PuppetPadchatBridge', 'roomRawPayload(%s)', id)
const rawPayload = await Misc.retry(async (retry, attempt) => {
......
import { EventEmitter } from 'events'
import * as cuid from 'cuid'
import * as WebSocket from 'ws'
// import { MemoryCard } from 'memory-card'
import {
// PadchatContinue,
// PadchatMsgType,
// PadchatStatus,
PadchatPayload,
PadchatPayloadType,
// PadchatContactMsgType,
PadchatContactPayload,
PadchatMessagePayload,
PadchatRoomMember,
PadchatRoomMemberPayload,
PadchatRoomPayload,
} from './padchat-schemas'
import {
// AutoDataType,
PadchatRpcRequest,
InitType,
WXGenerateWxDatType,
WXGetQRCodeType,
WXInitializeType,
WXCheckQRCodePayload,
WXHeartBeatType,
WXGetLoginTokenType,
WXAutoLoginType,
WXLoginRequestType,
WXSendMsgType,
WXLoadWxDatType,
WXQRCodeLoginType,
WXCheckQRCodeStatus,
StandardType,
WXAddChatRoomMemberType,
} from './padchat-rpc.type'
import {
PadchatPureFunctionHelper as pfHelper,
} from './pure-function-helper'
import { log } from '../config'
// const AUTO_DATA_SLOT = 'autoData'
export interface PadChatRpcPromiseDict {
[rpcId: string]: {
resolver : Function,
reject : Function,
}
}
export class PadchatRpc {
// private socket? : WebSocket
// private readonly rpcPromiseWaittingDict: PadChatRpcPromiseDict
private constructor(
// private endpoint : string,
// private token : string,
) {
throw new Error('should not be instanciated, use static methods only.')
// super() // for EventEmitter
// log.verbose('PadchatRpc', 'constructor(%s, %s)', endpoint, token)
// this.rpcPromiseWaittingDict = {}
}
public async start(): Promise<void> {
log.verbose('PadchatRpc', 'start()')
if (this.socket) {
throw new Error('websocket had already been opened!')
}
const ws = new WebSocket(
this.endpoint,
{ perMessageDeflate: true },
)
this.socket = ws
ws.on('message', (data: string) => {
const payload: PadchatPayload = JSON.parse(data)
this.onServerMessage(payload)
})
ws.on('error', err => this.emit('error', err))
// TODO: add reconnect logic here when disconnected, or throw Error
await new Promise((resolve, reject) => {
ws.once('open', resolve)
ws.once('error', reject)
ws.once('close', reject)
})
}
public stop(): void {
log.verbose('PadchatRpc', 'stop()')
if (!this.socket) {
log.warn('PadchatRpc', 'stop() no serverWebSocket, return.')
return
}
this.socket.removeAllListeners()
this.socket.close()
this.socket = undefined
}
private async sendToWebSocket(
apiName : string,
...params : string[]
): Promise<any> {
if (!this.socket) {
throw new Error('no web socket')
}
const msgId = cuid()
const paramEncodedList = params.map(
arg => encodeURIComponent(arg),
)
const request: PadchatRpcRequest = {
userId: this.token,
msgId,
apiName,
param: paramEncodedList,
}
const payload = JSON.stringify(request)
log.silly('PadchatRpc', 'sendToWebSocket: %s', payload)
this.socket.send(payload)
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
delete this.rpcPromiseWaittingDict[msgId]
reject('PadChat Server timeout for msgId: ' + msgId + ', apiName: ' + apiName + ', args: ' + params.join(', '))
// TODO: send json again or detect init()
}, 30000)
this.rpcPromiseWaittingDict[msgId] = {
resolver: (...args: any[]) => {
delete this.rpcPromiseWaittingDict[msgId]
clearTimeout(timer)
resolve(...args)
},
reject: (...args: any[]) => {
delete this.rpcPromiseWaittingDict[msgId]
clearTimeout(timer)
reject(...args)
},
}
})
}
private onServerMessage(payload: PadchatPayload) {
log.verbose('PadchatRpc', 'onServerPayload(%s)',
JSON.stringify(payload).substr(0, 140),
)
if (!payload.msgId && !payload.data) {
log.warn('PadchatRpc', 'onServerPayload() payload neither `msgId` nor `data`, noop and skipped')
return
}
// check logout:
if (payload.type === PadchatPayloadType.Logout) {
// this.emit('logout', this.selfId())
console.log('payload logout: ', payload)
this.emit('logout')
return
}
if (payload.msgId) {
// Data Return From WebSocket Client
const msgId = payload.msgId
const rawData = this.onServerMessagePadchat(payload)
if (msgId in this.rpcPromiseWaittingDict) {
const resolve = this.rpcPromiseWaittingDict[msgId].resolver
delete this.rpcPromiseWaittingDict[msgId]
// resolve({rawData: rawData, msgId: rawWebSocketData.msgId})
return resolve(rawData)
} else {
log.warn('PadchatRpc', 'wsOnMessage() msgId %s not in resolverDict', msgId)
}
} else {
// Data From Tencent
const tencentPayloadList: PadchatMessagePayload[] = JSON.parse(decodeURIComponent(payload.data))
this.onServerMessageTencent(tencentPayloadList)
}
}
private onServerMessageTencent(messagePayloadList: PadchatMessagePayload[]) {
console.log('tencent messagePayloadList:', messagePayloadList)
// if ( payload.continue === PadchatContinue.Done
// && payload.msg_type === PadchatMsgType.N15_32768
// && payload.status === PadchatStatus.One
// ) {
// // Skip empty message. "continue":0,"msg_type":32768,"status":1,"
// return
// }
// rawWebSocketData:
// {
// "apiName": "",
// "data": "XXXX",
// "msgId": "",
// "userId": "test"
// }
// if (!payload.data) {
// console.error('data: no payload.data')
// return
// }
// const messagePayloadList: PadchatMessagePayload[] = JSON.parse(decodeURIComponent(payload.data))
messagePayloadList.forEach(messagePayload => {
if (!messagePayload.msg_id) {
// {"continue":0,"msg_type":32768,"status":1,"uin":1928023446}
log.silly('PadchatRpc', 'WebSocket Server: get empty message msg_id form Tencent server for payoad: %s',
JSON.stringify(messagePayload),
)
return
}
log.silly('PadchatRpc', 'WebSocket Server rawData result: %s', JSON.stringify(messagePayload))
this.emit('message', messagePayload)
})
}
private onServerMessagePadchat(payload: PadchatPayload): any {
console.log('onServerMessagePadChat:', payload)
log.silly('PadchatRpc', 'return apiName: %s, msgId: %s', payload.apiName, payload.msgId)
let rawData: Object
if (!payload.data) {
log.silly('PadchatRpc', 'WebSocket Server get empty message data form API call: %s', payload.apiName)
rawData = {}
} else {
rawData = JSON.parse(decodeURIComponent(payload.data))
}
// rawWebSocketData:
// {
// "apiName": "WXHeartBeat",
// "data": "%7B%22status%22%3A0%2C%22message%22%3A%22ok%22%7D",
// "msgId": "abc231923912983",
// "userId": "test"
// }
return rawData
}
/**
* Init with WebSocket Server
*/
public async init(): Promise<InitType> {
const result: InitType = await this.sendToWebSocket('init')
log.silly('PadchatRpc', 'init result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('cannot connect to WebSocket init')
}
return result
}
/**
* Get WX block memory
*/
public async WXInitialize(): Promise<WXInitializeType> {
const result = await this.sendToWebSocket('WXInitialize')
log.silly('PadchatRpc', 'WXInitialize result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('cannot connect to WebSocket WXInitialize')
}
return result
}
public async WXGetQRCode(): Promise<WXGetQRCodeType> {
let result = await this.sendToWebSocket('WXGetQRCode')
if (!result || !(result.qr_code)) {
result = await this.WXGetQRCodeTwice()
}
return result
}
private async WXGetQRCodeTwice(): Promise<WXGetQRCodeType> {
await this.WXInitialize()
const resultTwice = await this.sendToWebSocket('WXGetQRCode')
if (!resultTwice || !(resultTwice.qr_code)) {
throw Error('WXGetQRCodeTwice error! canot get result from websocket server when calling WXGetQRCode after WXInitialize')
}
return resultTwice
}
public async WXCheckQRCode(): Promise<WXCheckQRCodePayload> {
// this.checkQrcode()
const result = await this.sendToWebSocket('WXCheckQRCode')
log.silly('PadchatRpc', 'WXCheckQRCode result: %s', JSON.stringify(result))
if (!result) {
throw Error('cannot connect to WebSocket WXCheckQRCode')
}
return result
}
public async WXHeartBeat(): Promise<WXHeartBeatType> {
const result = await this.sendToWebSocket('WXHeartBeat')
log.silly('PadchatRpc', 'WXHeartBeat result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('WXHeartBeat error! canot get result from websocket server')
}
return result
}
/**
* Load all Contact and Room
* see issue https://github.com/lijiarui/test-ipad-puppet/issues/39
* @returns {Promise<(PadchatRoomPayload | PadchatContactPayload)[]>}
*/
private async WXSyncContact(): Promise<(PadchatRoomPayload | PadchatContactPayload)[]> {
const result = await this.sendToWebSocket('WXSyncContact')
if (!result) {
throw Error('WXSyncContact error! canot get result from websocket server')
}
return result
}
/**
* Generate 62 data
*/
public async WXGenerateWxDat(): Promise<WXGenerateWxDatType> {
const result = await this.sendToWebSocket('WXGenerateWxDat')
log.silly('PadchatRpc', 'WXGenerateWxDat result: %s', JSON.stringify(result))
if (!result || !(result.data) || result.status !== 0) {
throw Error('WXGenerateWxDat error! canot get result from websocket server')
}
this.autoData.wxData = result.data
return result
}
/**
* Load 62 data
* @param {string} wxData autoData.wxData
*/
public async WXLoadWxDat(wxData: string): Promise<WXLoadWxDatType> {
const result = await this.sendToWebSocket('WXLoadWxDat', wxData)
if (!result || result.status !== 0) {
throw Error('WXLoadWxDat error! canot get result from websocket server')
}
return result
}
public async WXGetLoginToken(): Promise<WXGetLoginTokenType> {
const result = await this.sendToWebSocket('WXGetLoginToken')
log.silly('PadchatRpc', 'WXGetLoginToken result: %s', JSON.stringify(result))
if (!result || !result.token || result.status !== 0) {
throw Error('WXGetLoginToken error! canot get result from websocket server')
}
this.autoData.token = result.token
return result
}
/**
* Login with token automatically
* @param {string} token autoData.token
* @returns {string} user_name | ''
*/
public async WXAutoLogin(token: string): Promise<WXAutoLoginType | ''> {
const result = await this.sendToWebSocket('WXAutoLogin', token)
log.silly('PadchatRpc', 'WXAutoLogin result: %s, type: %s', JSON.stringify(result), typeof result)
// should get qrcode again
if (!result) {
await this.WXGetQRCode()
return ''
}
// should send wxloginRequest
if (result.status !== 0) {
await this.WXLoginRequest(token)
return ''
}
// login succeed!
this.username = result.user_name
log.silly('PadchatRpc', 'WXAutoLogin bridge autoData user_name: %s', this.username)
this.loginSucceed = true
return result
}
/**
* Login with QRcode
* @param {string} token autoData.token
*/
public async WXLoginRequest(token: string): Promise<WXLoginRequestType | ''> {
// TODO: should show result here
const result = await this.sendToWebSocket('WXLoginRequest', token)
log.silly('PadchatRpc', 'WXLoginRequest result: %s, type: %s', JSON.stringify(result), typeof result)
if (!result || result.status !== 0) {
await this.WXGetQRCode()
return ''
} else {
// check qrcode status
log.silly('PadchatRpc', 'WXLoginRequest begin to check whether user has clicked confirm login')
this.checkQrcode()
}
return result
}
/**
* Send Text Message
* @param {string} to user_name
* @param {string} content text
*/
public async WXSendMsg(to: string, content: string, at = ''): Promise<WXSendMsgType> {
if (to) {
const result = await this.sendToWebSocket('WXSendMsg', to, content, at)
if (!result || result.status !== 0) {
throw Error('WXSendMsg error! canot get result from websocket server')
}
return result
}
throw Error('PadchatRpc, WXSendMsg error! no to!')
}
/**
* Send Image Message
* @param {string} to user_name
* @param {string} data image_data
*/
public WXSendImage(to: string, data: string): void {
this.sendToWebSocket('WXSendImage', to, data)
}
/**
* Get contact by contact id
* @param {any} id user_name
*/
private async WXGetContact(id: string): Promise<any> {
const result = await this.sendToWebSocket('WXGetContact', id)
if (!result) {
throw Error('PadchatRpc, WXGetContact, cannot get result from websocket server!')
}
log.silly('PadchatRpc', 'WXGetContact(%s) result: %s', id, JSON.stringify(result))
if (!result.user_name) {
log.warn('PadchatRpc', 'WXGetContact cannot get user_name, id: %s', id)
}
return result
}
/**
* Get contact by contact id
* @param {any} id user_name
*/
public async WXGetContactPayload(id: string): Promise<PadchatContactPayload> {
if (!pfHelper.isContactId(id)) { // /@chatroom$/.test(id)) {
throw Error(`should use WXGetRoomPayload because get a room id :${id}`)
}
const result = await this.WXGetContact(id) as PadchatContactPayload
return result
}
/**
* Get contact by contact id
* @param {any} id user_name
*/
public async WXGetRoomPayload(id: string): Promise<PadchatRoomPayload> {
if (!pfHelper.isRoomId(id)) { // (/@chatroom$/.test(id))) {
throw Error(`should use WXGetContactPayload because get a contact id :${id}`)
}
const result = await this.WXGetContact(id)
if (result.member) {
result.member = JSON.parse(decodeURIComponent(result.member))
}
return result
}
/**
* Get room member by contact id
* @param {any} id chatroom_id
*/
public async WXGetChatRoomMember(id: string): Promise<PadchatRoomMemberPayload> {
const result = await this.sendToWebSocket('WXGetChatRoomMember', id)
if (!result) {
throw Error('PadchatRpc, WXGetChatRoomMember, cannot get result from websocket server!')
}
log.silly('PadchatRpc', 'WXGetChatRoomMember() result: %s', JSON.stringify(result))
if (!result.user_name || !result.member) {
log.warn('PadchatRpc', 'WXGetChatRoomMember cannot get user_name or member! user_name: %s, member: %s', id, result.member)
}
// tslint:disable-next-line:max-line-length
// change '[{"big_head":"http://wx.qlogo.cn/mmhead/ver_1/DpS0ZssJ5s8tEpSr9JuPTRxEUrCK0USrZcR3PjOMfUKDwpnZLxWXlD4Q38bJpcXBtwXWwevsul1lJqwsQzwItQ/0","chatroom_nick_name":"","invited_by":"wxid_7708837087612","nick_name":"李佳芮","small_head":"http://wx.qlogo.cn/mmhead/ver_1/DpS0ZssJ5s8tEpSr9JuPTRxEUrCK0USrZcR3PjOMfUKDwpnZLxWXlD4Q38bJpcXBtwXWwevsul1lJqwsQzwItQ/132","user_name":"qq512436430"},{"big_head":"http://wx.qlogo.cn/mmhead/ver_1/kcBj3gSibfFd2I9vQ8PBFyQ77cpPIfqkFlpTdkFZzBicMT6P567yj9IO6xG68WsibhqdPuG82tjXsveFATSDiaXRjw/0","chatroom_nick_name":"","invited_by":"wxid_7708837087612","nick_name":"梦君君","small_head":"http://wx.qlogo.cn/mmhead/ver_1/kcBj3gSibfFd2I9vQ8PBFyQ77cpPIfqkFlpTdkFZzBicMT6P567yj9IO6xG68WsibhqdPuG82tjXsveFATSDiaXRjw/132","user_name":"mengjunjun001"},{"big_head":"http://wx.qlogo.cn/mmhead/ver_1/3CsKibSktDV05eReoAicV0P8yfmuHSowfXAMvRuU7HEy8wMcQ2eibcaO1ccS95PskZchEWqZibeiap6Gpb9zqJB1WmNc6EdD6nzQiblSx7dC1eGtA/0","chatroom_nick_name":"","invited_by":"wxid_7708837087612","nick_name":"苏轼","small_head":"http://wx.qlogo.cn/mmhead/ver_1/3CsKibSktDV05eReoAicV0P8yfmuHSowfXAMvRuU7HEy8wMcQ2eibcaO1ccS95PskZchEWqZibeiap6Gpb9zqJB1WmNc6EdD6nzQiblSx7dC1eGtA/132","user_name":"wxid_zj2cahpwzgie12"},{"big_head":"http://wx.qlogo.cn/mmhead/ver_1/piaHuicak41b6ibmcEVxoWKnnhgGDG5EbaD0hibwkrRvKeDs3gs7XQrkym3Q5MlUeSKY8vw2FRVVstialggUxf2zic2O8CvaEsicSJcghf41nibA940/0","chatroom_nick_name":"","invited_by":"wxid_zj2cahpwzgie12","nick_name":"王宁","small_head":"http://wx.qlogo.cn/mmhead/ver_1/piaHuicak41b6ibmcEVxoWKnnhgGDG5EbaD0hibwkrRvKeDs3gs7XQrkym3Q5MlUeSKY8vw2FRVVstialggUxf2zic2O8CvaEsicSJcghf41nibA940/132","user_name":"wxid_7708837087612"}]'
// to Array (PadchatRoomRawMember[])
if (!Array.isArray(JSON.parse(decodeURIComponent(result.member)))) {
log.error('PadchatRpc', 'WXGetChatRoomMember member: %s', result.member)
throw Error('faild to parse chatroom member!')
}
result.member = JSON.parse(decodeURIComponent(result.member)) as PadchatRoomMember[]
return result
}
/**
* Login successfully by qrcode
* @param {any} id user_name
* @param {any} password password
*/
public async WXQRCodeLogin(id: string, password: string): Promise<WXQRCodeLoginType> {
const result = await this.sendToWebSocket('WXQRCodeLogin', id, password)
if (result && result.status === 0) {
log.info('PadchatRpc', 'WXQRCodeLogin, login successfully!')
this.username = result.user_name
this.nickname = result.nick_name
this.loginSucceed = true
}
if (result && (result.status === -3)) {
throw Error('PadchatRpc, WXQRCodeLogin, wrong user_name or password')
}
if (result && (result.status === -301)) {
log.warn('PadchatRpc', 'WXQRCodeLogin, redirect 301')
if (!this.username || !this.password) {
throw Error('PadchatRpc, WXQRCodeLogin, redirect 301 and cannot get username or password here, return!')
}
this.WXQRCodeLogin(this.username, this.password)
}
if (!result) {
throw Error(`PadchatRpc, WXQRCodeLogin, unknown error, data: ${JSON.stringify(result)}`)
}
return result
}
public async checkQrcode(): Promise<void> {
log.verbose('PadchatRpc', 'checkQrcode')
const result = await this.WXCheckQRCode()
if (result && result.status === WXCheckQRCodeStatus.WaitScan) {
log.verbose('PadchatRpc', 'checkQrcode: Please scan the Qrcode!')
setTimeout(() => {
this.checkQrcode()
}, 1000)
return
}
if (result && result.status === WXCheckQRCodeStatus.WaitConfirm) {
log.info('PadchatRpc', 'checkQrcode: Had scan the Qrcode, but not Login!')
setTimeout(() => {
this.checkQrcode()
}, 1000)
return
}
if (result && result.status === WXCheckQRCodeStatus.Confirmed) {
log.info('PadchatRpc', 'checkQrcode: Trying to login... please wait')
if (!result.user_name || !result.password) {
throw Error('PadchatRpc, checkQrcode, cannot get username or password here, return!')
}
this.username = result.user_name
// this.nickname = result.nick_name
this.password = result.password
this.WXQRCodeLogin(this.username, this.password)
return
}
if (result && result.status === WXCheckQRCodeStatus.Timeout) {
log.info('PadchatRpc', 'checkQrcode: Timeout')
return
}
if (result && result.status === WXCheckQRCodeStatus.Cancel) {
log.info('PadchatRpc', 'checkQrcode: Cancel by user')
return
}
log.warn('PadchatRpc', 'checkQrcode: not know the reason, return data: %s', JSON.stringify(result))
return
}
public async WXSetUserRemark(id: string, remark: string): Promise<StandardType> {
const result = await this.sendToWebSocket('WXSetUserRemark', id, remark)
log.silly('PadchatRpc', 'WXSetUserRemark result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('WXSetUserRemark error! canot get result from websocket server')
}
return result
}
public async WXDeleteChatRoomMember(roomId: string, contactId: string): Promise<StandardType> {
const result = await this.sendToWebSocket('WXDeleteChatRoomMember', roomId, contactId)
log.silly('PadchatRpc', 'WXDeleteChatRoomMember result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('WXDeleteChatRoomMember error! canot get result from websocket server')
}
return result
}
public async WXAddChatRoomMember(roomId: string, contactId: string): Promise<boolean> {
const result = (await this.sendToWebSocket('WXAddChatRoomMember', roomId, contactId)) as WXAddChatRoomMemberType
log.silly('PadchatRpc', 'WXAddChatRoomMember result: %s', JSON.stringify(result))
if (result && result.status === -2028) {
// result: {"message":"","status":-2028}
// May be the owner has see not allow other people to join in the room (群聊邀请确认)
log.warn('PadchatRpc', 'WXAddChatRoomMember failed! maybe owner open the should confirm first to invited others to join in the room.')
return false
}
if (!result || result.status !== 0) {
throw Error('WXAddChatRoomMember error! canot get result from websocket server')
}
// see more in WXAddChatRoomMemberType
if (/OK/i.test(result.message)) {
return true
}
return false
}
public async WXSetChatroomName(roomId: string, topic: string): Promise<StandardType> {
const result = await this.sendToWebSocket('WXSetChatroomName', roomId, topic)
log.silly('PadchatRpc', 'WXSetChatroomName result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('WXSetChatroomName error! canot get result from websocket server')
}
return result
}
// TODO
// public async WXCreateChatRoom(userList: string[]): Promise<any> {
// const result = await this.sendToWebSocket('WXCreateChatRoom', userList)
// console.log(result)
// return result
// }
public async WXQuitChatRoom(roomId: string): Promise<StandardType> {
const result = await this.sendToWebSocket('WXQuitChatRoom', roomId)
log.silly('PadchatRpc', 'WXQuitChatRoom result: %s', JSON.stringify(result))
if (!result || result.status !== 0) {
throw Error('WXQuitChatRoom error! canot get result from websocket server')
}
return result
}
// friendRequestSend
// type来源值:
// 2 -通过搜索邮箱
// 3 -通过微信号搜索
// 5 -通过朋友验证消息
// 7 -通过朋友验证消息(可回复)
// 12 -通过QQ好友添加
// 14 -通过群来源
// 15 -通过搜索手机号
// 16 -通过朋友验证消息
// 17 -通过名片分享
// 22 -通过摇一摇打招呼方式
// 25 -通过漂流瓶
// 30 -通过二维码方式
public async WXAddUser(strangerV1: string, strangerV2: string, type: string, verify: string): Promise<any> {
// TODO:
type = '14'
verify = 'hello'
const result = await this.sendToWebSocket('WXAddUser', strangerV1, strangerV2, type, verify)
log.silly('PadchatRpc', 'WXAddUser result: %s', JSON.stringify(result))
return result
}
public async WXAcceptUser(stranger: string, ticket: string): Promise<any> {
const result = await this.sendToWebSocket('WXAcceptUser', stranger, ticket)
log.silly('PadchatRpc', 'WXAcceptUser result: %s', JSON.stringify(result))
return result
}
}
export interface FunctionType {
export interface PadchatRpcRequest {
userId: string,
msgId: string,
apiName: string,
......
......@@ -87,7 +87,7 @@ export interface PadchatPayload {
* - WXGetContact
* @interface PadchatContactPayload
*/
export interface PadchatContactRawPayload {
export interface PadchatContactPayload {
/**
* Sometimes, WXSyncContact can only get the following result:
* {
......@@ -336,7 +336,7 @@ export interface PadchatRoomMember {
* - WXGetContact
* @interface PadchatRoomPayload
*/
export interface PadchatRoomRawPayload {
export interface PadchatRoomPayload {
/**
* Sometimes, WXSyncContact can only get the following result:
* {
......
......@@ -68,9 +68,9 @@ import {
import {
// PadchatPayload,
PadchatContactRawPayload,
PadchatContactPayload,
PadchatMessagePayload,
PadchatRoomRawPayload,
PadchatRoomPayload,
// PadchatMessageType,
// PadchatContinue,
......@@ -90,8 +90,7 @@ export class PuppetPadchat extends Puppet {
private readonly cachePadchatMessagePayload : LRU.Cache<string, PadchatMessagePayload>
// private readonly cachePadchatRoomPayload : LRU.Cache<string, PadchatRoomRawPayload>
public bridge: Bridge
// public botWs: WebSocket
public readonly bridge: Bridge
constructor(
public options: PuppetOptions,
......@@ -179,6 +178,12 @@ export class PuppetPadchat extends Puppet {
public async start(): Promise<void> {
log.verbose('PuppetPadchat', `start() with ${this.options.memory.name}`)
if (this.state.on()) {
log.warn('PuppetPadchat', 'start() already on(pending)?')
await this.state.ready('on')
return
}
/**
* state has two main state: ON / OFF
* ON (pending)
......@@ -190,7 +195,7 @@ export class PuppetPadchat extends Puppet {
await this.startWatchdog()
this.state.on(true)
// this.emit('start')
this.emit('start')
}
......@@ -198,9 +203,7 @@ export class PuppetPadchat extends Puppet {
log.verbose('PuppetPadchat', 'startBridge()')
if (this.state.off()) {
const e = new Error('startBridge() state is off')
log.warn('PuppetPadchat', e.message)
throw e
throw new Error('startBridge() state is off')
}
this.bridge.removeAllListeners()
......@@ -221,8 +224,8 @@ export class PuppetPadchat extends Puppet {
)
this.emit('message', messagePayload.msg_id)
})
this.bridge.on('scan', (qrCode: string, statusCode: number, data?: string) => {
this.emit('scan', qrCode, statusCode, data)
this.bridge.on('scan', (qrCode: string, status: number, data?: string) => {
this.emit('scan', qrCode, status, data)
})
await this.bridge.start()
......@@ -232,7 +235,7 @@ export class PuppetPadchat extends Puppet {
log.verbose('PuppetPadchat', 'quit()')
if (this.state.off()) {
log.warn('PuppetPadchat', 'quit() is called on a OFF puppet. await ready(off) and return.')
log.warn('PuppetPadchat', 'stop() is called on a OFF puppet. await ready(off) and return.')
await this.state.ready('off')
return
}
......@@ -247,8 +250,7 @@ export class PuppetPadchat extends Puppet {
// await some tasks...
this.state.off(true)
// this.emit('stop')
this.emit('stop')
}
public async logout(): Promise<void> {
......@@ -285,7 +287,6 @@ export class PuppetPadchat extends Puppet {
return
}
// public async contactFindAll(query: ContactQueryFilter): Promise<string[]> {
public async contactList(): Promise<string[]> {
log.verbose('PuppetPadchat', 'contactList()')
......@@ -307,14 +308,14 @@ export class PuppetPadchat extends Puppet {
return file
}
public async contactRawPayload(id: string): Promise<PadchatContactRawPayload> {
log.verbose('PuppetPadchat', 'contactRawPayload(%s)', id)
public async contactRawPayload(contactId: string): Promise<PadchatContactPayload> {
log.verbose('PuppetPadchat', 'contactRawPayload(%s)', contactId)
const rawPayload = await this.bridge.contactRawPayload(id)
const rawPayload = await this.bridge.contactRawPayload(contactId)
return rawPayload
}
public async contactRawPayloadParser(rawPayload: PadchatContactRawPayload): Promise<ContactPayload> {
public async contactRawPayloadParser(rawPayload: PadchatContactPayload): Promise<ContactPayload> {
log.verbose('PuppetPadchat', 'contactRawPayloadParser(rawPayload.user_name="%s")', rawPayload.user_name)
const payload: ContactPayload = pfHelper.contactRawPayloadParser(rawPayload)
......@@ -433,14 +434,14 @@ export class PuppetPadchat extends Puppet {
* Room
*
*/
public async roomRawPayload(id: string): Promise<PadchatRoomRawPayload> {
public async roomRawPayload(id: string): Promise<PadchatRoomPayload> {
log.verbose('PuppetPadchat', 'roomRawPayload(%s)', id)
const rawPayload = await this.bridge.roomRawPayload(id)
return rawPayload
}
public async roomRawPayloadParser(rawPayload: PadchatRoomRawPayload): Promise<RoomPayload> {
public async roomRawPayloadParser(rawPayload: PadchatRoomPayload): Promise<RoomPayload> {
log.verbose('PuppetPadchat', 'roomRawPayloadParser(rawPayload.user_name="%s")', rawPayload.user_name)
const roomRawMemberList = (await this.bridge.WXGetChatRoomMember(rawPayload.user_name)).member
......
......@@ -22,14 +22,14 @@ import {
} from '../puppet/'
import {
PadchatContactRawPayload,
PadchatContactPayload,
PadchatMessagePayload,
// PadchatContactMsgType,
// PadchatMessageStatus,
PadchatMessageType,
PadchatRoomRawPayload,
PadchatRoomPayload,
PadchatRoomMember,
} from './padchat-schemas'
......@@ -74,7 +74,7 @@ export class PadchatPureFunctionHelper {
}
public static contactRawPayloadParser(
rawPayload: PadchatContactRawPayload,
rawPayload: PadchatContactPayload,
): ContactPayload {
if (!rawPayload.user_name) {
throw Error('cannot get user_name(wxid)!')
......@@ -203,7 +203,7 @@ export class PadchatPureFunctionHelper {
}
public static roomRawPayloadParser(
rawPayload : PadchatRoomRawPayload,
rawPayload : PadchatRoomPayload,
roomRawMemberList : PadchatRoomMember[],
): RoomPayload {
const aliasDict = {} as { [id: string]: string | undefined }
......
......@@ -66,11 +66,11 @@ async function onScan(
): Promise<void> {
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',
payload, this.state.off())
return
}
// if (this.state.off()) {
// log.verbose('PuppetPuppeteerEvent', 'onScan(%s) state.off()=%s, NOOP',
// payload, this.state.off())
// return
// }
this.scanPayload = payload
......@@ -115,11 +115,11 @@ async function onLogin(
return
}
if (this.state.off()) {
log.verbose('PuppetPuppeteerEvent', 'onLogin(%s, %d) state.off()=%s, NOOP',
note, ttl, this.state.off())
return
}
// if (this.state.off()) {
// log.verbose('PuppetPuppeteerEvent', 'onLogin(%s, %d) state.off()=%s, NOOP',
// note, ttl, this.state.off())
// return
// }
if (this.logonoff()) {
throw new Error('onLogin() user had already logined: ' + this.selfId())
......@@ -151,9 +151,9 @@ async function onLogin(
log.silly('PuppetPuppeteerEvent', `onLogin() user ${userId} logined`)
if (this.state.on() === true) {
await this.saveCookie()
}
// if (this.state.on() === true) {
await this.saveCookie()
// }
// fix issue #668
await this.waitStable()
......
......@@ -59,16 +59,16 @@ class PuppetTest extends PuppetPuppeteer {
}
}
test('Puppet smoke testing', async t => {
const puppet = new PuppetTest({ memory: new MemoryCard() })
const wechaty = new WechatyTest({ puppet })
wechaty.initPuppetAccessory(puppet)
t.ok(puppet.state.off(), 'should be OFF state after instanciate')
puppet.state.on('pending')
t.ok(puppet.state.on(), 'should be ON state after set')
t.ok(puppet.state.pending(), 'should be pending state after set')
})
// test('Puppet smoke testing', async t => {
// const puppet = new PuppetTest({ memory: new MemoryCard() })
// const wechaty = new WechatyTest({ puppet })
// wechaty.initPuppetAccessory(puppet)
// t.ok(puppet.state.off(), 'should be OFF state after instanciate')
// puppet.state.on('pending')
// t.ok(puppet.state.on(), 'should be ON state after set')
// t.ok(puppet.state.pending(), 'should be pending state after set')
// })
test('login/logout events', sinonTest(async function (t: test.Test) {
const sandbox = sinon.createSandbox()
......
......@@ -84,18 +84,20 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public readonly cacheMessagePayload : LRU.Cache<string, MessagePayload>
public readonly cacheRoomPayload : LRU.Cache<string, RoomPayload>
public readonly state : StateSwitch
protected readonly state : StateSwitch
protected readonly watchdog : Watchdog
protected readonly counter : number
protected id?: string
/**
* childPkg stores the `package.json` that the NPM module who extends the `Puppet`
*/
private readonly childPkg: undefined | normalize.Package
/**
* Login-ed User ID
*/
protected id?: string
/**
*
*
......@@ -129,30 +131,6 @@ export abstract class Puppet extends EventEmitter implements Sayable {
this.cacheMessagePayload = new LRU<string, MessagePayload>(lruOptions)
this.cacheRoomPayload = new LRU<string, RoomPayload>(lruOptions)
/**
* 1. Init Classes
*/
// if ( !this.options.wechaty.Contact
// || !this.options.wechaty.FriendRequest
// || !this.options.wechaty.Message
// || !this.options.wechaty.Room
// ) {
// throw new Error('wechaty classes are not inited')
// }
// this.Contact = this.options.wechaty.Contact
// this.FriendRequest = this.options.wechaty.FriendRequest
// this.Message = this.options.wechaty.Message
// this.Room = this.options.wechaty.Room
/**
* Make sure that Wechaty had attached to this puppet
*
* When we declare a wechaty without a puppet instance,
* the wechaty need to attach to puppet later.
*/
// this.options.wechaty.attach(this)
/**
* 2. Load the package.json for Puppet Plugin version range matching
*
......@@ -200,7 +178,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public emit(event: 'room-join', roomId: string, inviteeIdList: string[], inviterId: string) : boolean
public emit(event: 'room-leave', roomId: string, leaverIdList: string[], remover?: 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: 'scan', qrCode: string, status: number, data?: string) : boolean
public emit(event: 'start') : boolean
public emit(event: 'stop') : boolean
// Internal Usage: watchdog
......@@ -228,9 +206,9 @@ export abstract class Puppet extends EventEmitter implements Sayable {
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[], removerId?: string) => void) : this
public on(event: 'room-leave', listener: (roomId: string, leaverIdList : string[], removerId?: 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: 'scan', listener: (qrCode: string, status: number, data?: string) => void) : this
public on(event: 'start', listener: () => void) : this
public on(event: 'stop', listener: () => void) : this
// Internal Usage: watchdog
......@@ -264,8 +242,27 @@ export abstract class Puppet extends EventEmitter implements Sayable {
*
*/
/**
* Need to be called internaly when the puppet is logined.
* this method will emit a `login` event
*/
protected async login(userId: string): Promise<void> {
log.verbose('Puppet', 'login(%s)', userId)
if (this.id) {
throw new Error('must logout first before login again!')
}
this.id = userId
// console.log('this.id=', this.id)
this.emit('login', userId)
}
/**
* `this.id = undefined`
* Need to be called internaly/externaly when the puppet need to be logouted
* this method will emit a `logout` event,
*
* Note: must set `this.id = undefined` in this function.
*/
public abstract async logout(): Promise<void>
......@@ -287,18 +284,6 @@ export abstract class Puppet extends EventEmitter implements Sayable {
}
}
protected async login(userId: string): Promise<void> {
log.verbose('Puppet', 'login(%s)', userId)
if (this.id) {
throw new Error('must logout first before login again!')
}
this.id = userId
// console.log('this.id=', this.id)
this.emit('login', userId)
}
/**
*
* Misc
......@@ -370,7 +355,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public abstract async contactAvatar(contactId: string) : Promise<FileBox>
public abstract async contactList() : Promise<string[]>
public abstract async contactRawPayload(id: string) : Promise<any>
public abstract async contactRawPayload(contactId: string) : Promise<any>
public abstract async contactRawPayloadParser(rawPayload: any) : Promise<ContactPayload>
public async contactSearch(
......@@ -392,15 +377,15 @@ export abstract class Puppet extends EventEmitter implements Sayable {
return searchIdList
}
const searchContactPayloadList = await Promise.all(
const searchContactPayloadList: ContactPayload[] = await Promise.all(
searchIdList.map(
id => this.contactPayload(id),
),
)
const filterFuncion = this.contactQueryFilterFactory(query)
const filterFuncion: ContactPayloadFilterFunction = this.contactQueryFilterFactory(query)
const idList = searchContactPayloadList
const idList: string[] = searchContactPayloadList
.filter(filterFuncion)
.map(payload => payload.id)
......@@ -414,12 +399,6 @@ export abstract class Puppet extends EventEmitter implements Sayable {
JSON.stringify(query),
)
// Object.keys(query).forEach((key: keyof ContactQueryFilter) => {
// if (typeof query[key] === 'undefined') {
// delete query[key]
// }
// })
Object.keys(query).forEach(key => {
if (query[key as keyof ContactQueryFilter] === undefined) {
delete query[key as keyof ContactQueryFilter]
......@@ -430,13 +409,13 @@ export abstract class Puppet extends EventEmitter implements Sayable {
throw new Error('query only support one key. multi key support is not availble now.')
}
// TypeScript bug: have to set `undefined | string | RegExp` at here, or the later code type check will get error
const filterKey = Object.keys(query)[0] as keyof ContactQueryFilter
if (!/^name|alias$/.test(filterKey)) {
throw new Error('key not supported: ' + filterKey)
}
// TypeScript bug: have to set `undefined | string | RegExp` at here, or the later code type check will get error
const filterValue: undefined | string | RegExp = query[filterKey]
if (!filterValue) {
throw new Error('filterValue not found for filterKey: ' + filterKey)
......@@ -444,10 +423,10 @@ export abstract class Puppet extends EventEmitter implements Sayable {
let filterFunction
if (filterValue instanceof RegExp) {
filterFunction = (payload: ContactPayload) => !!payload[filterKey] && filterValue.test(payload[filterKey]!)
} else if (typeof filterValue === 'string') {
if (typeof filterValue === 'string') {
filterFunction = (payload: ContactPayload) => filterValue === payload[filterKey]
} else if (filterValue instanceof RegExp) {
filterFunction = (payload: ContactPayload) => !!payload[filterKey] && filterValue.test(payload[filterKey]!)
} else {
throw new Error('unsupport filterValue type: ' + typeof filterValue)
}
......@@ -456,21 +435,21 @@ export abstract class Puppet extends EventEmitter implements Sayable {
}
public async contactPayload(
id: string,
contactId: string,
noCache = false,
): Promise<ContactPayload> {
log.silly('Puppet', 'contactPayload(id=%s, noCache=%s) @ %s', id, noCache, this)
log.silly('Puppet', 'contactPayload(id=%s, noCache=%s) @ %s', contactId, noCache, this)
if (!id) {
if (!contactId) {
throw new Error('no id')
}
if (noCache) {
log.silly('Puppet', 'contactPayload() cache PURGE')
this.cacheContactPayload.del(id)
this.cacheContactPayload.del(contactId)
}
const hitPayload = this.cacheContactPayload.get(id)
const hitPayload = this.cacheContactPayload.get(contactId)
if (hitPayload) {
log.silly('Puppet', 'contactPayload() cache HIT')
......@@ -479,10 +458,10 @@ export abstract class Puppet extends EventEmitter implements Sayable {
log.silly('Puppet', 'contactPayload() cache MISS')
const rawPayload = await this.contactRawPayload(id)
const rawPayload = await this.contactRawPayload(contactId)
const payload = await this.contactRawPayloadParser(rawPayload)
this.cacheContactPayload.set(id, payload)
this.cacheContactPayload.set(contactId, payload)
log.silly('Puppet', 'contactPayload() cache SET')
return payload
......@@ -496,25 +475,25 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public abstract async friendRequestSend(contactId: string, hello?: string) : Promise<void>
public abstract async friendRequestAccept(contactId: string, ticket: string) : Promise<void>
public abstract async friendRequestRawPayload(id: string) : Promise<any>
public abstract async friendRequestRawPayloadParser(rawPayload: any) : Promise<FriendRequestPayload>
public abstract async friendRequestRawPayload(friendRequestId: string) : Promise<any>
public abstract async friendRequestRawPayloadParser(rawPayload: any) : Promise<FriendRequestPayload>
public async friendRequestPayload(
id: string,
friendRequestId: string,
noCache = false,
): Promise<FriendRequestPayload> {
log.verbose('Puppet', 'friendRequestPayload(id=%s, noCache=%s)', id, noCache)
log.verbose('Puppet', 'friendRequestPayload(id=%s, noCache=%s)', friendRequestId, noCache)
if (!id) {
if (!friendRequestId) {
throw new Error('no id')
}
if (noCache) {
log.silly('Puppet', 'friendRequestPayload() cache PURGE')
this.cacheFriendRequestPayload.del(id)
this.cacheFriendRequestPayload.del(friendRequestId)
}
const hitPayload = this.cacheFriendRequestPayload.get(id)
const hitPayload = this.cacheFriendRequestPayload.get(friendRequestId)
if (hitPayload) {
log.silly('Puppet', 'friendRequestPayload() cache HIT')
......@@ -523,10 +502,10 @@ export abstract class Puppet extends EventEmitter implements Sayable {
log.silly('Puppet', 'friendRequestPayload() cache MISS')
const rawPayload = await this.friendRequestRawPayload(id)
const rawPayload = await this.friendRequestRawPayload(friendRequestId)
const payload = await this.friendRequestRawPayloadParser(rawPayload)
this.cacheFriendRequestPayload.set(id, payload)
this.cacheFriendRequestPayload.set(friendRequestId, payload)
return payload
}
......@@ -537,29 +516,29 @@ export abstract class Puppet extends EventEmitter implements Sayable {
*
*/
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>
public abstract async messageForward(receiver: Receiver, messageId: string) : Promise<void>
public abstract async messageSendText(receiver: Receiver, text: string) : Promise<void>
public abstract async messageSendFile(receiver: Receiver, file: FileBox) : Promise<void>
public abstract async messageRawPayload(id: string) : Promise<any>
public abstract async messageRawPayload(messageId: string) : Promise<any>
public abstract async messageRawPayloadParser(rawPayload: any) : Promise<MessagePayload>
public async messagePayload(
id: string,
messageId : string,
noCache = false,
): Promise<MessagePayload> {
log.verbose('Puppet', 'messagePayload(id=%s, noCache=%s)', id, noCache)
log.verbose('Puppet', 'messagePayload(id=%s, noCache=%s)', messageId, noCache)
if (!id) {
if (!messageId) {
throw new Error('no id')
}
if (noCache) {
log.silly('Puppet', 'messagePayload() cache PURGE')
this.cacheMessagePayload.del(id)
this.cacheMessagePayload.del(messageId)
}
const hitPayload = this.cacheMessagePayload.get(id)
const hitPayload = this.cacheMessagePayload.get(messageId)
if (hitPayload) {
log.silly('Puppet', 'messagePayload() cache HIT')
......@@ -569,10 +548,10 @@ export abstract class Puppet extends EventEmitter implements Sayable {
log.silly('Puppet', 'messagePayload() cache MISS')
const rawPayload = await this.messageRawPayload(id)
const rawPayload = await this.messageRawPayload(messageId)
const payload = await this.messageRawPayloadParser(rawPayload)
this.cacheMessagePayload.set(id, payload)
this.cacheMessagePayload.set(messageId, payload)
log.silly('Puppet', 'messagePayload() cache SET')
return payload
......@@ -591,7 +570,7 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public abstract async roomTopic(roomId: string, topic?: string) : Promise<string | void>
public abstract async roomList() : Promise<string[]>
public abstract async roomRawPayload(id: string) : Promise<any>
public abstract async roomRawPayload(roomId: string) : Promise<any>
public abstract async roomRawPayloadParser(rawPayload: any) : Promise<RoomPayload>
public async roomMemberSearch(
......@@ -648,9 +627,9 @@ export abstract class Puppet extends EventEmitter implements Sayable {
public async roomSearch(query?: RoomQueryFilter): Promise<string[]> {
log.verbose('Puppet', 'roomSearch(%s)', JSON.stringify(query))
const allRoomIdList = await this.roomList()
const allRoomIdList: string[] = await this.roomList()
log.silly('Puppet', 'roomSearch() allRoomIdList.length=%d', allRoomIdList.length)
console.log('allRoomIdList length=' + allRoomIdList.length)
if (!query || Object.keys(query).length <= 0) {
return allRoomIdList
}
......@@ -676,11 +655,9 @@ export abstract class Puppet extends EventEmitter implements Sayable {
protected roomQueryFilterFactory(
query: RoomQueryFilter,
): RoomPayloadFilterFunction {
log.verbose('Puppet', 'roomQueryFilterFactory({ %s })',
Object.keys(query)
.map(k => `${k}: ${query[k as keyof RoomQueryFilter]}`)
.join(', '),
)
log.verbose('Puppet', 'roomQueryFilterFactory(%s)',
JSON.stringify(query),
)
if (Object.keys(query).length !== 1) {
throw new Error('query only support one key. multi key support is not availble now.')
......@@ -745,8 +722,4 @@ export abstract class Puppet extends EventEmitter implements Sayable {
}
// export class WechatError extends Error {
// public code: WechatErrorCode
// }
export default Puppet
{
"compilerOptions": {
"target": "es6"
, "module": "commonjs"
, "outDir": "dist"
, "strict": true
, "traceResolution": false
, "noLib": false
, "module" : "commonjs"
, "outDir" : "dist"
, "traceResolution" : false
, "noLib" : false
, "declaration" : true
, "sourceMap" : true
, "skipLibCheck" : true
, "experimentalDecorators": true
, "emitDecoratorMetadata": true
, "declaration": true
, "sourceMap": true
, "noEmitOnError": true
, "emitDecoratorMetadata" : true
, "experimentalDecorators" : true
, "noUnusedLocals": true
, "noImplicitReturns": true
, "noFallthroughCasesInSwitch": true
, "strictNullChecks": true
, "noImplicitAny": true
, "noUnusedParameters": true
, "noImplicitThis": true
, "skipLibCheck": true
, "lib": [
"esnext"
]
, "strict" : true
, "noEmitOnError" : true
, "noUnusedLocals" : true
, "noImplicitReturns" : true
, "noFallthroughCasesInSwitch" : true
, "strictNullChecks" : true
, "noImplicitAny" : true
, "noUnusedParameters" : true
, "noImplicitThis" : true
}
, "exclude": [
"node_modules/"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册