From 382cedffa18a9e22db41e37d802f3142de44a173 Mon Sep 17 00:00:00 2001 From: Yuan Gao Date: Sat, 30 Mar 2019 01:02:00 -0700 Subject: [PATCH] revise room.say() mention function (#1729) * revise room.say() mention function * add test for new mention func * more change according to review * make sub tests independent from each other * bump package version * add tagged template code example for room.say() --- package.json | 2 +- src/user/room.spec.ts | 93 ++++++++++++++++++++++++++++++++- src/user/room.ts | 118 +++++++++++++++++++++++++++++++----------- 3 files changed, 179 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 6c309d2a..a37c400e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wechaty", - "version": "0.23.40", + "version": "0.25.1", "description": "Wechaty is a Bot SDK for Wechat Personal Account", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", diff --git a/src/user/room.spec.ts b/src/user/room.spec.ts index 687bbabe..ebf81c00 100755 --- a/src/user/room.spec.ts +++ b/src/user/room.spec.ts @@ -23,10 +23,14 @@ import test from 'blue-tape' import sinon from 'sinon' -import { RoomPayload } from 'wechaty-puppet' +import { + ContactPayload, + RoomMemberPayload, + RoomPayload +} from 'wechaty-puppet' import { PuppetMock } from 'wechaty-puppet-mock' -import { Wechaty } from '../wechaty' +import { Wechaty } from '../wechaty' test('findAll()', async t => { const EXPECTED_ROOM_ID = 'test-id' @@ -54,3 +58,88 @@ test('findAll()', async t => { await wechaty.stop() }) + +test('say()', async _ => { + + const sandbox = sinon.createSandbox() + const callback = sinon.spy() + + const puppet = new PuppetMock() + const wechaty = new Wechaty({ puppet }) + + await wechaty.start() + + const EXPECTED_ROOM_ID = 'roomId' + const EXPECTED_ROOM_TOPIC = 'test-topic' + const EXPECTED_CONTACT_1_ID = 'contact1' + const EXPECTED_CONTACT_1_ALIAS = 'little1' + const EXPECTED_CONTACT_2_ID = 'contact2' + const EXPECTED_CONTACT_2_ALIAS = 'big2' + const CONTACT_MAP: { [contactId: string]: string } = {} + CONTACT_MAP[EXPECTED_CONTACT_1_ID] = EXPECTED_CONTACT_1_ALIAS + CONTACT_MAP[EXPECTED_CONTACT_2_ID] = EXPECTED_CONTACT_2_ALIAS + + sandbox.stub(puppet, 'roomMemberPayload').callsFake(async (_, contactId) => { + await new Promise(r => setImmediate(r)) + return { + id: contactId, + roomAlias: CONTACT_MAP[contactId], + } as RoomMemberPayload + }) + sandbox.stub(puppet, 'roomPayload').callsFake(async () => { + await new Promise(r => setImmediate(r)) + return { + topic: EXPECTED_ROOM_TOPIC, + } as RoomPayload + }) + sandbox.stub(puppet, 'contactPayload').callsFake(async (contactId) => { + await new Promise(r => setImmediate(r)) + return { + id: contactId, + } as ContactPayload + }) + // sandbox.spy(puppet, 'messageSendText') + sandbox.stub(puppet, 'messageSendText').callsFake(callback) + + const room = wechaty.Room.load(EXPECTED_ROOM_ID) + const contact1 = wechaty.Contact.load(EXPECTED_CONTACT_1_ID) + const contact2 = wechaty.Contact.load(EXPECTED_CONTACT_2_ID) + await contact1.sync() + await contact2.sync() + await room.sync() + + test('say with Tagged Template', async t => { + callback.resetHistory() + await room.say`To be ${contact1} or not to be ${contact2}` + + t.deepEqual(callback.getCall(0).args, [ + { contactId: EXPECTED_CONTACT_1_ID, roomId: EXPECTED_ROOM_ID }, + 'To be @little1 or not to be @big2', + [EXPECTED_CONTACT_1_ID, EXPECTED_CONTACT_2_ID], + ], 'Tagged Template say should be matched') + }) + + test('say with regular mention contact', async t => { + callback.resetHistory() + await room.say('Yo', contact1) + + t.deepEqual(callback.getCall(0).args, [ + { contactId: EXPECTED_CONTACT_1_ID, roomId: EXPECTED_ROOM_ID }, + '@little1 Yo', + [EXPECTED_CONTACT_1_ID], + ], 'Single mention should work with old ways') + }) + + test('say with multiple mention contact', async t => { + callback.resetHistory() + await room.say('hey buddies, let\'s party', contact1, contact2) + + t.deepEqual(callback.getCall(0).args, [ + { contactId: EXPECTED_CONTACT_1_ID, roomId: EXPECTED_ROOM_ID }, + '@little1 @big2 hey buddies, let\'s party', + [EXPECTED_CONTACT_1_ID, EXPECTED_CONTACT_2_ID], + ], 'Multiple mention should work with new way') + }) + + await wechaty.stop() +}) diff --git a/src/user/room.ts b/src/user/room.ts index 4226d351..95a4a014 100644 --- a/src/user/room.ts +++ b/src/user/room.ts @@ -382,10 +382,10 @@ export class Room extends Accessory implements Sayable { } public say (text: string) : Promise - public say (text: string, mention: Contact) : Promise - public say (text: string, mention: Contact[]) : Promise + public say (text: string, ...mentionList: Contact[]) : Promise public say (file: FileBox) : Promise public say (url: UrlLink) : Promise + public say (textList: TemplateStringsArray, ...mentionList: Contact[]): Promise public say (...args: never[]): never @@ -423,65 +423,121 @@ export class Room extends Accessory implements Sayable { * // 4. Send text inside room and mention @mention contact * const contact = await bot.Contact.find({name: 'lijiarui'}) // change 'lijiarui' to any of the room member * await room.say('Hello world!', contact) + * + * // 5. Send text inside room and mention someone with Tagged Template + * const contact2 = await bot.Contact.find({name: 'zixia'}) // change 'zixia' to any of the room member + * await room.say`Hello ${contact}, here is the world ${contact2}` */ public async say ( - textOrContactOrFileOrUrl : string | Contact | FileBox | UrlLink, - mention? : Contact | Contact[], + textOrListOrContactOrFileOrUrl : string | Contact | FileBox | UrlLink | TemplateStringsArray, + ...mentionList : Contact[] ): Promise { - let replyToList: Contact[] = [] - replyToList = replyToList.concat(mention || []) - - const mentionAliasList = await Promise.all( - replyToList.map( - async c => await this.alias(c) || c.name() - ) - ) - log.verbose('Room', 'say(%s, %s)', - textOrContactOrFileOrUrl, - mentionAliasList.join(', '), + textOrListOrContactOrFileOrUrl, + mentionList.join(', '), ) let text: string - if (typeof textOrContactOrFileOrUrl === 'string') { + if (typeof textOrListOrContactOrFileOrUrl === 'string') { - if (mentionAliasList.length > 0) { - // const AT_SEPRATOR = String.fromCharCode(8197) - const AT_SEPRATOR = FOUR_PER_EM_SPACE - const mentionList = mentionAliasList.map(roomAlias => '@' + roomAlias).join(AT_SEPRATOR) + if (mentionList.length > 0) { + const AT_SEPARATOR = FOUR_PER_EM_SPACE + const mentionAlias = await Promise.all(mentionList.map(async contact => + '@' + (await this.alias(contact) || contact.name()) + )) + const mentionText = mentionAlias.join(AT_SEPARATOR) - text = mentionList + ' ' + textOrContactOrFileOrUrl + text = mentionText + ' ' + textOrListOrContactOrFileOrUrl } else { - text = textOrContactOrFileOrUrl + text = textOrListOrContactOrFileOrUrl } const receiver = { - contactId : replyToList.length && replyToList[0].id || undefined, + contactId : mentionList.length && mentionList[0].id || undefined, roomId : this.id, } await this.puppet.messageSendText( receiver, text, - replyToList.map(c => c.id), + mentionList.map(c => c.id), ) - } else if (textOrContactOrFileOrUrl instanceof FileBox) { + } else if (textOrListOrContactOrFileOrUrl instanceof FileBox) { + /** + * 2. File Message + */ await this.puppet.messageSendFile({ roomId: this.id, - }, textOrContactOrFileOrUrl) - } else if (textOrContactOrFileOrUrl instanceof Contact) { + }, textOrListOrContactOrFileOrUrl) + } else if (textOrListOrContactOrFileOrUrl instanceof Contact) { + /** + * 3. Contact Card + */ await this.puppet.messageSendContact({ roomId: this.id, - }, textOrContactOrFileOrUrl.id) - } else if (textOrContactOrFileOrUrl instanceof UrlLink) { + }, textOrListOrContactOrFileOrUrl.id) + } else if (textOrListOrContactOrFileOrUrl instanceof UrlLink) { /** * 4. Link Message */ await this.puppet.messageSendUrl({ contactId : this.id - }, textOrContactOrFileOrUrl.payload) + }, textOrListOrContactOrFileOrUrl.payload) + } else if (textOrListOrContactOrFileOrUrl instanceof Array) { + await this.sayTemplateStringsArray( + textOrListOrContactOrFileOrUrl, + ...mentionList, + ) } else { - throw new Error('arg unsupported: ' + textOrContactOrFileOrUrl) + throw new Error('arg unsupported: ' + textOrListOrContactOrFileOrUrl) + } + } + + private async sayTemplateStringsArray ( + textList: TemplateStringsArray, + ...mentionList: Contact[] + ) { + const receiver = { + contactId : mentionList.length && mentionList[0].id || undefined, + roomId : this.id, + } + if (mentionList.length === 0) { + /** + * No mention in the string + */ + await this.puppet.messageSendText( + receiver, + textList[0], + ) + } else if (textList.length === 1) { + /** + * Constructed mention string, skip inserting @ signs + */ + await this.puppet.messageSendText( + receiver, + textList[0], + mentionList.map(c => c.id), + ) + } else { + /** + * Mention in the string + */ + const strLength = textList.length + const mentionLength = mentionList.length + if (strLength - mentionLength !== 1) { + throw new Error(`Can not say message, invalid Tagged Template.`) + } + let constructedString = '' + let i = 0 + for (; i < mentionLength; i++) { + constructedString += textList[i] + '@' + (await this.alias(mentionList[i]) || mentionList[i].name()) + } + constructedString += textList[i] + await this.puppet.messageSendText( + receiver, + constructedString, + mentionList.map(c => c.id), + ) } } -- GitLab