diff --git a/src/puppet-padchat/padchat-rpc.ts b/src/puppet-padchat/padchat-rpc.ts index c7188becef876bd6ab1902ba3387c6063f09bb6d..796fc48088febd0930cece0a21b7e02fe97825da 100644 --- a/src/puppet-padchat/padchat-rpc.ts +++ b/src/puppet-padchat/padchat-rpc.ts @@ -197,7 +197,8 @@ export class PadchatRpc extends EventEmitter { // JSON.stringify(payload).length, // ) - // console.log('server payload:', payload) + // XXX + console.log('server payload:', payload) if (payload.type === PadchatPayloadType.Logout) { // {"type":-1,"msg":"掉线了"} @@ -750,8 +751,8 @@ export class PadchatRpc extends EventEmitter { // 30 -通过二维码方式 public async WXAddUser(strangerV1: string, strangerV2: string, type: string, verify: string): Promise { // TODO: - type = '14' - verify = 'hello' + // type = '14' + // verify = 'hello' const result = await this.rpcCall('WXAddUser', strangerV1, strangerV2, type, verify) log.silly('PadchatRpc', 'WXAddUser result: %s', JSON.stringify(result)) return result diff --git a/src/puppet-padchat/padchat-schemas.ts b/src/puppet-padchat/padchat-schemas.ts index 094bdc37f334494ba2f411a8b3e05d957cf7da2b..d43ffd2a5ed37f46319229444bf3904a6d3744b0 100644 --- a/src/puppet-padchat/padchat-schemas.ts +++ b/src/puppet-padchat/padchat-schemas.ts @@ -113,7 +113,6 @@ export interface PadchatContactPayload { */ msg_type? : PadchatContactMsgType, continue? : PadchatContinue, - ticket? : string, uin? : number, big_head : string, // "http://wx.qlogo.cn/mmhead/ver_1/xfCMmibHH74xGLoyeDFJadrZXX3eOEznPefiaCa3iczxZGMwPtDuSbRQKx3Xdm18un303mf0NFia3USY2nO2VEYILw/0", @@ -133,7 +132,8 @@ export interface PadchatContactPayload { // "http://wx.qlogo.cn/mmhead/ver_1/xfCMmibHH74xGLoyeDFJadrZXX3eOEznPefiaCa3iczxZGMwPtDuSbRQKx3Xdm18un303mf0NFia3USY2nO2VEYILw/132", small_head : string, status : PadchatContactRoomStatus, // 1 when use WXSyncContact, 0 when use WXGetContact - stranger : string, // "v1_0468f2cd3f0efe7ca2589d57c3f9ba952a3789e41b6e78ee00ed53d1e6096b88@stranger", + stranger? : string, // 用户v1码,从未加过好友则为空 "v1_0468f2cd3f0efe7ca2589d57c3f9ba952a3789e41b6e78ee00ed53d1e6096b88@stranger" + ticket? : string, // 用户v2码,如果非空则为单向好友(非对方好友) 'v2_xxx@stranger' user_name : string, // "mengjunjun001" | "qq512436430" Unique name message? : string, @@ -392,3 +392,54 @@ export interface PadchatRoomMemberListPayload { status : PadchatRoomMemberStatus, // number, // 0, user_name : string, // '6350854677@chatroom' } + +// xml2js.parseString(str.replace(/\+/g, ' '), (err, data) => console.log(data)) +// { msg: +// { '$': +// { fromusername: 'lizhuohuan', +// encryptusername: +// 'v1_cf269def9b946093f9d131a5e733ba169351013c95e46a860cddecaf485c4b10@stranger', +// fromnickname: '李卓桓', +// content: 'xixixi', +// fullpy: 'lizhuohuan', +// shortpy: 'LZH', +// imagestatus: '3', +// scene: '6', +// country: 'CN', +// province: 'Beijing', +// city: 'Haidian', +// sign: 'PreAngel投资人。水木清华BBS站长。投资人中最会飞的AI程序员。', +// percard: '1', +// sex: '1', +// alias: '', +// weibo: '', +// weibonickname: '', +// albumflag: '0', +// albumstyle: '0', +// albumbgimgid: '913943270785024_913943270785024', +// snsflag: '177', +// snsbgimgid: +// 'http://shmmsns.qpic.cn/mmsns/NoFChqEQomEyhyNjzExH3v78BHSVmIzHBIdOECg9jgcTpRNwThgXJicCsGicI6Kib4xLETc2PuKwhM/0', +// snsbgobjectid: '12683064081608282338', +// mhash: 'd98b28f4cb1708bb584f3e66078e0a0d', +// mfullhash: 'd98b28f4cb1708bb584f3e66078e0a0d', +// bigheadimgurl: +// 'http://wx.qlogo.cn/mmhead/ver_1/ciaaFRTCqfHIKLY0wBjv3h0LSPkCEEcJ0fo6kQkMxQLBiahJWFk7rS9G4VLU5n9OfAnXWlMaIV01oeTITYS0OHlg/0', +// smallheadimgurl: +// 'http://wx.qlogo.cn/mmhead/ver_1/ciaaFRTCqfHIKLY0wBjv3h0LSPkCEEcJ0fo6kQkMxQLBiahJWFk7rS9G4VLU5n9OfAnXWlMaIV01oeTITYS0OHlg/96', +// ticket: +// 'v2_1a0d2cf325e64b6f74bed09e944529e7cc7a7580cb323475050664566dd0302d89b8e2ed95b596b459cf762d94a0ce606da39babbae0dc26b18a62e079bfc120@stranger', +// opcode: '2', +// googlecontact: '', +// qrticket: '', +// chatroomusername: '', +// sourceusername: '', +// sourcenickname: '' }, +// brandlist: [ [Object] ] } } + +export interface PadchatFriendRequestPayload { + fromusername : string, // 'lizhuohuan' + encryptusername : string // v1_xxx@stranger' + content : string, // 'hello' + ticket : string, // 'v2_1a0d2cf325e64b6f74bed09e944529e7cc7a7580cb323475050664566dd0302d89b8e2ed95b596b459cf762d94a0ce606da39babbae0dc26b18a62e079bfc120@stranger', +} diff --git a/src/puppet-padchat/puppet-padchat.ts b/src/puppet-padchat/puppet-padchat.ts index 6e69b5663f8402d8cf27f1bbf887d273d7a146df..e065f02345ef9f9682c77a9cca18bf2b3d6f8447 100644 --- a/src/puppet-padchat/puppet-padchat.ts +++ b/src/puppet-padchat/puppet-padchat.ts @@ -45,7 +45,8 @@ import { PuppetOptions, Receiver, FriendRequestPayload, -} from '../puppet/' + FriendRequestPayloadReceive, +} from '../puppet/' import { PadchatPureFunctionHelper as pfHelper, @@ -273,7 +274,7 @@ export class PuppetPadchat extends Puppet { this.emit('stop') } - public async logout(): Promise { + public async logout(passive = false): Promise { log.verbose('PuppetPadchat', 'logout()') if (!this.id) { @@ -283,6 +284,10 @@ export class PuppetPadchat extends Puppet { this.emit('logout', this.id) // becore we will throw above by logonoff() when this.user===undefined this.id = undefined + // if (!passive) { + // await this.bridge.WXLogout() + // } + await this.bridge.logout() } @@ -565,6 +570,15 @@ export class PuppetPadchat extends Puppet { await this.bridge.WXDeleteChatRoomMember(roomId, contactId) } + public async roomQrCode(roomId: string): Promise { + log.verbose('PuppetPadchat', 'roomQrCode(%s)', roomId) + + // TODO + + throw new Error('not support') + + } + public async roomAvatar(roomId: string): Promise { log.verbose('PuppetPadchat', 'roomAvatar(%s)', roomId) @@ -583,7 +597,20 @@ export class PuppetPadchat extends Puppet { contactId : string, ): Promise { log.verbose('PuppetPadchat', 'roomAdd(%s, %s)', roomId, contactId) - await this.bridge.WXAddChatRoomMember(roomId, contactId) + + // XXX: did there need to calc the total number of the members in this room? + // if n <= 40 then add() else invite() ? + + try { + log.verbose('PuppetPadchat', 'roomAdd(%s, %s) try to Add', roomId, contactId) + await this.bridge.WXAddChatRoomMember(roomId, contactId) + } catch (e) { + // FIXME + console.error(e) + log.warn('PuppetPadchat', 'roomAdd(%s, %s) Add exception: %s', e) + log.verbose('PuppetPadchat', 'roomAdd(%s, %s) try to Invite', roomId, contactId) + await this.bridge.WXInviteChatRoomMember(roomId, contactId) + } } public async roomTopic(roomId: string) : Promise @@ -611,9 +638,11 @@ export class PuppetPadchat extends Puppet { ): Promise { log.verbose('PuppetPadchat', 'roomCreate(%s, %s)', contactIdList, topic) - // TODO - // await this.bridge.crea - return 'mock_room_id' + // FIXME: + const roomId = this.bridge.WXCreateChatRoom(contactIdList) + console.log('roomCreate returl:', roomId) + + return roomId } public async roomQuit(roomId: string): Promise { @@ -646,50 +675,50 @@ export class PuppetPadchat extends Puppet { const rawPayload = await this.contactRawPayload(contactId) - let strangerV1 - let strangerV2 - if (pfHelper.isStrangerV1(rawPayload.stranger)) { // /^v1_/i.test(rawPayload.stranger)) { - strangerV1 = rawPayload.stranger - } else if (pfHelper.isStrangerV2(rawPayload.stranger)) { // /^v2_/i.test(rawPayload.stranger)) { - strangerV2 = rawPayload.stranger - } else { - throw new Error('stranger neither v1 nor v2!') - } + // XXX + console.log('rawPayload.stranger: ', rawPayload) + + // let strangerV1 + // let strangerV2 + // if (pfHelper.isStrangerV1(rawPayload.stranger)) { + // strangerV1 = rawPayload.stranger + // } else if (pfHelper.isStrangerV2(rawPayload.stranger)) { + // strangerV2 = rawPayload.stranger + // } else { + // throw new Error('stranger neither v1 nor v2!') + // } // Issue #1252 : what's wrong here? await this.bridge.WXAddUser( - strangerV1 || '', - strangerV2 || '', + rawPayload.stranger || '', + rawPayload.ticket || '', '14', hello, ) } public async friendRequestAccept( - contactId : string, - ticket : string, + friendRequestId : string, ): Promise { - log.verbose('PuppetPadchat', 'friendRequestAccept(%s, %s)', contactId, ticket) + log.verbose('PuppetPadchat', 'friendRequestAccept(%s)', friendRequestId) - // TODO - - // const rawPayload = await this.contactRawPayload(contactId) + const payload = await this.friendRequestPayload(friendRequestId) as FriendRequestPayloadReceive - // if (!rawPayload.ticket) { - // throw new Error('no ticket') - // } + if (!payload.ticket) { + throw new Error('no ticket') + } - // await this.bridge.WXAcceptUser( - // rawPayload.stranger, - // rawPayload.ticket, - // ) + await this.bridge.WXAcceptUser( + payload.contactId, + payload.ticket, + ) } public async friendRequestRawPayloadParser(rawPayload: PadchatMessagePayload) : Promise { log.verbose('PuppetPadchat', 'friendRequestRawPayloadParser(%s)', rawPayload) - const payload: FriendRequestPayload = pfHelper.friendRequestRawPayloadParser(rawPayload) + const payload: FriendRequestPayload = await pfHelper.friendRequestRawPayloadParser(rawPayload) return payload } diff --git a/src/puppet-padchat/pure-function-helper.spec.ts b/src/puppet-padchat/pure-function-helper.spec.ts index 2de8cfa42ffd8eda43b03648029ddb43a91cef7c..f13424df11c6004b2148cbb8414c742a672eadc8 100755 --- a/src/puppet-padchat/pure-function-helper.spec.ts +++ b/src/puppet-padchat/pure-function-helper.spec.ts @@ -14,13 +14,16 @@ import { PadchatContinue, PadchatContactPayload, PadchatContactRoomStatus, - // PadchatMessagePayload, + + PadchatMessagePayload, } from './padchat-schemas' import { ContactGender, ContactType, ContactPayload, + FriendRequestPayload, + FriendRequestType, // MessagePayload, } from '../puppet/' @@ -151,6 +154,25 @@ test('contactRawPayloadParser', async t => { t.throws(() => pfHelper.contactRawPayloadParser(undefined as any), 'should throw exception for undifined') }) +test('friendRequestRawPayloadParser()', async t => { + const DATA = '%5B%7B%22content%22%3A%22%3Cmsg+fromusername%3D%5C%22lizhuohuan%5C%22+encryptusername%3D%5C%22v1_cf269def9b946093f9d131a5e733ba169351013c95e46a860cddecaf485c4b10%40stranger%5C%22+fromnickname%3D%5C%22%E6%9D%8E%E5%8D%93%E6%A1%93%5C%22+content%3D%5C%22xixixi%5C%22+fullpy%3D%5C%22lizhuohuan%5C%22+shortpy%3D%5C%22LZH%5C%22+imagestatus%3D%5C%223%5C%22+scene%3D%5C%226%5C%22+country%3D%5C%22CN%5C%22+province%3D%5C%22Beijing%5C%22+city%3D%5C%22Haidian%5C%22+sign%3D%5C%22PreAngel%E6%8A%95%E8%B5%84%E4%BA%BA%E3%80%82%E6%B0%B4%E6%9C%A8%E6%B8%85%E5%8D%8EBBS%E7%AB%99%E9%95%BF%E3%80%82%E6%8A%95%E8%B5%84%E4%BA%BA%E4%B8%AD%E6%9C%80%E4%BC%9A%E9%A3%9E%E7%9A%84AI%E7%A8%8B%E5%BA%8F%E5%91%98%E3%80%82%5C%22+percard%3D%5C%221%5C%22+sex%3D%5C%221%5C%22+alias%3D%5C%22%5C%22+weibo%3D%5C%22%5C%22+weibonickname%3D%5C%22%5C%22+albumflag%3D%5C%220%5C%22+albumstyle%3D%5C%220%5C%22+albumbgimgid%3D%5C%22913943270785024_913943270785024%5C%22+snsflag%3D%5C%22177%5C%22+snsbgimgid%3D%5C%22http%3A%2F%2Fshmmsns.qpic.cn%2Fmmsns%2FNoFChqEQomEyhyNjzExH3v78BHSVmIzHBIdOECg9jgcTpRNwThgXJicCsGicI6Kib4xLETc2PuKwhM%2F0%5C%22+snsbgobjectid%3D%5C%2212683064081608282338%5C%22+mhash%3D%5C%22d98b28f4cb1708bb584f3e66078e0a0d%5C%22+mfullhash%3D%5C%22d98b28f4cb1708bb584f3e66078e0a0d%5C%22+bigheadimgurl%3D%5C%22http%3A%2F%2Fwx.qlogo.cn%2Fmmhead%2Fver_1%2FciaaFRTCqfHIKLY0wBjv3h0LSPkCEEcJ0fo6kQkMxQLBiahJWFk7rS9G4VLU5n9OfAnXWlMaIV01oeTITYS0OHlg%2F0%5C%22+smallheadimgurl%3D%5C%22http%3A%2F%2Fwx.qlogo.cn%2Fmmhead%2Fver_1%2FciaaFRTCqfHIKLY0wBjv3h0LSPkCEEcJ0fo6kQkMxQLBiahJWFk7rS9G4VLU5n9OfAnXWlMaIV01oeTITYS0OHlg%2F96%5C%22+ticket%3D%5C%22v2_1a0d2cf325e64b6f74bed09e944529e7cc7a7580cb323475050664566dd0302d89b8e2ed95b596b459cf762d94a0ce606da39babbae0dc26b18a62e079bfc120%40stranger%5C%22+opcode%3D%5C%222%5C%22+googlecontact%3D%5C%22%5C%22+qrticket%3D%5C%22%5C%22+chatroomusername%3D%5C%22%5C%22+sourceusername%3D%5C%22%5C%22+sourcenickname%3D%5C%22%5C%22%3E%3Cbrandlist+count%3D%5C%220%5C%22+ver%3D%5C%22652101432%5C%22%3E%3C%2Fbrandlist%3E%3C%2Fmsg%3E%22%2C%22continue%22%3A1%2C%22description%22%3A%22%22%2C%22from_user%22%3A%22fmessage%22%2C%22msg_id%22%3A%222957327798149218888%22%2C%22msg_source%22%3A%22%22%2C%22msg_type%22%3A5%2C%22status%22%3A1%2C%22sub_type%22%3A37%2C%22timestamp%22%3A1528557626%2C%22to_user%22%3A%22wxid_5zj4i5htp9ih22%22%2C%22uin%22%3A1928023446%7D%5D%0A' + const TENCENT_PAYLOAD_LIST: PadchatMessagePayload[] = JSON.parse(decodeURIComponent(DATA)) + const PADCHAT_MESSAGE_PAYLOAD = TENCENT_PAYLOAD_LIST[0] + const EXPECTED_FRIEND_REQUEST_PAYLOAD = { + id : '2957327798149218888', + contactId : 'lizhuohuan', + hello : 'xixixi', + ticket : 'v2_1a0d2cf325e64b6f74bed09e944529e7cc7a7580cb323475050664566dd0302d89b8e2ed95b596b459cf762d94a0ce606da39babbae0dc26b18a62e079bfc120@stranger', + type : FriendRequestType.Receive, + } + + // console.log(PADCHAT_MESSAGE_PAYLOAD) + + const friendRequestPayload: FriendRequestPayload = await pfHelper.friendRequestRawPayloadParser(PADCHAT_MESSAGE_PAYLOAD) + // console.log(friendRequestPayload) + + t.deepEqual(friendRequestPayload, EXPECTED_FRIEND_REQUEST_PAYLOAD, 'should parse friendRequestPayload right') +}) // TODO // test('roomRawPayloadParser', async t => { diff --git a/src/puppet-padchat/pure-function-helper.ts b/src/puppet-padchat/pure-function-helper.ts index e14400bb75d406c359c4a1bde951802917cf91c8..6d54de2eb69aef8e01c7c306e7342cf9dc6291a8 100644 --- a/src/puppet-padchat/pure-function-helper.ts +++ b/src/puppet-padchat/pure-function-helper.ts @@ -8,8 +8,9 @@ * [Master the JavaScript Interview: What is a Pure Function?](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976) * */ -import jsQR from 'jsqr' -import Jimp from 'jimp' +import jsQR from 'jsqr' +import Jimp from 'jimp' +import { parseString } from 'xml2js' import { ContactPayload, @@ -21,6 +22,7 @@ import { RoomPayload, FriendRequestPayload, + FriendRequestType, } from '../puppet/' import { @@ -32,6 +34,7 @@ import { PadchatMessageType, PadchatRoomPayload, + PadchatFriendRequestPayload, // PadchatRoomMemberPayload, } from './padchat-schemas' @@ -168,6 +171,7 @@ export class PadchatPureFunctionHelper { case PadchatMessageType.Recalled: case PadchatMessageType.StatusNotify: case PadchatMessageType.Sys: + case PadchatMessageType.SysNotice: type = MessageType.Unknown break @@ -247,10 +251,48 @@ export class PadchatPureFunctionHelper { return payload } - public static friendRequestRawPayloadParser( + public static async friendRequestRawPayloadParser( rawPayload: PadchatMessagePayload, - ) : FriendRequestPayload { - throw new Error('to do ' + rawPayload) + ) : Promise { + + let tryXmlText = rawPayload.content + tryXmlText = tryXmlText.replace(/\+/g, ' ') + + interface XmlSchema { + msg?: { + $?: PadchatFriendRequestPayload, + } + } + + const padchatFriendRequestPayload = await new Promise((resolve, reject) => { + parseString(tryXmlText, { explicitArray: false }, (err, obj: XmlSchema) => { + if (err) { // HTML can not be parsed to JSON + return reject(err) + } + if (!obj) { + // FIXME: when will this happen? + return reject(new Error('parseString() return empty obj')) + } + if (!obj.msg || !obj.msg.$) { + return reject(new Error('parseString() return unknown obj')) + } + return resolve(obj.msg.$) + }) + }) + + console.log(padchatFriendRequestPayload) + + const friendRequestPayload: FriendRequestPayload = { + id : rawPayload.msg_id, + // use v1 stranger id as contactid at here: + contactId : padchatFriendRequestPayload.encryptusername, + hello : padchatFriendRequestPayload.content, + ticket : padchatFriendRequestPayload.ticket, + type : FriendRequestType.Receive, + } + + return friendRequestPayload + // switch (rawPayload.sub_type) { // case PadchatMessageType.VerifyMsg: // if (!rawPayload.RecommendInfo) {