diff --git a/src/WAClient/Base.ts b/src/WAClient/Base.ts index bc63fdf..9758b13 100644 --- a/src/WAClient/Base.ts +++ b/src/WAClient/Base.ts @@ -1,5 +1,5 @@ import WAConnection from '../WAConnection/WAConnection' -import { MessageStatus, MessageStatusUpdate, PresenceUpdate, Presence, ChatModification, WABroadcastListInfo, WAUrlInfo } from './Constants' +import { MessageStatusUpdate, PresenceUpdate, Presence, WABroadcastListInfo } from './Constants' import { WAMessage, WANode, @@ -7,7 +7,6 @@ import { WAFlag, MessageLogLevel, } from '../WAConnection/Constants' -import { proto } from '../../WAMessage/WAMessage' export default class WhatsAppWebBase extends WAConnection { /** Set the callback for message status updates (when a message is delivered, read etc.) */ @@ -186,13 +185,4 @@ export default class WhatsAppWebBase extends WAConnection { const json = ['action', {epoch: this.msgCount.toString(), type: 'set'}, nodes] return this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) as Promise<{status: number}> } - /** Query a string to check if it has a url, if it does, return required info */ - async urlQuery (text: string) { - const query = ['query', {type: 'url', url: text, epoch: this.msgCount.toString()}, null] - const response = await this.queryExpecting200 (query, [26, WAFlag.ignore]) - if (response[1]) { - response[1].jpegThumbnail = response[2] - } - return response[1] as WAUrlInfo - } } diff --git a/src/WAClient/Constants.ts b/src/WAClient/Constants.ts index 0c02819..aa77734 100644 --- a/src/WAClient/Constants.ts +++ b/src/WAClient/Constants.ts @@ -136,3 +136,4 @@ export interface WALocationMessage { } export type WAContactMessage = proto.ContactMessage export type WAMessageKey = proto.IMessageKey +export type WATextMessage = proto.ExtendedTextMessage diff --git a/src/WAClient/Messages.ts b/src/WAClient/Messages.ts index c2dd857..5306ff3 100644 --- a/src/WAClient/Messages.ts +++ b/src/WAClient/Messages.ts @@ -12,9 +12,11 @@ import { WAMessageKey, ChatModification, MessageInfo, + WATextMessage, + WAUrlInfo, } from './Constants' import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes } from '../WAConnection/Utils' -import { WAMessageContent, WAMetric, WAFlag, WANode, WAMessage } from '../WAConnection/Constants' +import { WAMessageContent, WAMetric, WAFlag, WANode, WAMessage, WAMessageProto } from '../WAConnection/Constants' import { validateJIDForSending, generateThumbnail, getMediaKeys } from './Utils' import { proto } from '../../WAMessage/WAMessage' @@ -100,6 +102,23 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { const actual = await this.loadConversation (jid, 1, index) return actual[0] } + /** Query a string to check if it has a url, if it does, return required extended text message */ + async generateLinkPreview (text: string) { + const query = ['query', {type: 'url', url: text, epoch: this.msgCount.toString()}, null] + const response = await this.queryExpecting200 (query, [26, WAFlag.ignore]) + + if (response[1]) response[1].jpegThumbnail = response[2] + const data = response[1] as WAUrlInfo + + const content = {text} as WATextMessage + content.canonicalUrl = data['canonical-url'] + content.matchedText = data['matched-text'] + content.jpegThumbnail = data.jpegThumbnail + content.description = data.description + content.title = data.title + content.previewType = 0 + return content + } /** * Search WhatsApp messages with a given text string * @param txt the search string @@ -170,21 +189,24 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { } async sendMessage( id: string, - message: string | WALocationMessage | WAContactMessage | Buffer, + message: string | WATextMessage | WALocationMessage | WAContactMessage | Buffer, type: MessageType, options: MessageOptions = {}, ) { if (options.validateID === true || !('validateID' in options)) { validateJIDForSending (id) } - let m: any = {} + let m: WAMessageContent = {} switch (type) { case MessageType.text: case MessageType.extendedText: - if (typeof message !== 'string') { - throw new Error('expected message to be a string') + if (typeof message === 'string') { + m.extendedTextMessage = {text: message} + } else if ('text' in message) { + m.extendedTextMessage = message as WATextMessage + } else { + throw new Error ('message needs to be a string or object with property \'text\'') } - m.extendedTextMessage = { text: message } break case MessageType.location: case MessageType.liveLocation: @@ -197,7 +219,7 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { m = await this.prepareMediaMessage(message as Buffer, type, options) break } - return this.sendGenericMessage(id, m as WAMessageContent, options) + return this.sendGenericMessage(id, m, options) } /** Prepare a media message for sending */ protected async prepareMediaMessage(buffer: Buffer, mediaType: MessageType, options: MessageOptions = {}) { @@ -283,7 +305,7 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { } } message[key].caption = options?.caption - message[key].jpegThumbnail = options?.thumbnail + if (!message[key].jpegThumbnail) message[key].jpegThumbnail = options?.thumbnail const messageJSON = { key: { @@ -293,7 +315,8 @@ export default class WhatsAppWebMessages extends WhatsAppWebGroups { }, message: message, messageTimestamp: timestamp, - participant: id.includes('@g.us') ? this.userMetaData.id : null + participant: id.includes('@g.us') ? this.userMetaData.id : null, + status: WAMessageProto.proto.WebMessageInfo.WEB_MESSAGE_INFO_STATUS.PENDING } const json = ['action', {epoch: this.msgCount.toString(), type: 'relay'}, [['message', null, messageJSON]]] const response = await this.queryExpecting200(json, [WAMetric.message, WAFlag.ignore], null, messageJSON.key.id) diff --git a/src/WAClient/Tests.ts b/src/WAClient/Tests.ts index 907ff61..190abc4 100644 --- a/src/WAClient/Tests.ts +++ b/src/WAClient/Tests.ts @@ -33,6 +33,14 @@ WAClientTest('Messages', (client) => { const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text) assert.strictEqual(message.message.conversation, 'hello fren') }) + it('should send a link preview', async () => { + const content = await client.generateLinkPreview ('hello this is from https://www.github.com/adiwajshing/Baileys') + const message = await sendAndRetreiveMessage(client, content, MessageType.text) + const received = message.message.extendedTextMessage + assert.strictEqual(received.text, content.text) + + fs.writeFileSync ('Media/received-thumb.jpeg', content.jpegThumbnail) + }) it('should quote a message', async () => { const messages = await client.loadConversation(testJid, 2) const message = await sendAndRetreiveMessage(client, 'hello fren 2', MessageType.extendedText, { @@ -109,12 +117,6 @@ WAClientTest('Misc', (client) => { it('should return the stories', async () => { await client.getStories() }) - it('should return a preview', async () => { - const info = await client.urlQuery ('fren have you seen https://www.github.com/adiwajshing/Baileys') - assert.equal (info["matched-text"], 'https://www.github.com/adiwajshing/Baileys') - - await assert.rejects (() => client.urlQuery('oh hello there')) - }) it('should return the profile picture', async () => { const response = await client.getProfilePicture(testJid) assert.ok(response) diff --git a/src/WAClient/Utils.ts b/src/WAClient/Utils.ts index 57fa40c..a5e4ad6 100644 --- a/src/WAClient/Utils.ts +++ b/src/WAClient/Utils.ts @@ -60,8 +60,9 @@ const extractVideoThumb = async ( }) }) as Promise -/** generates a thumbnail for a given media, if required */ +export const compressImage = async (buffer: Buffer) => sharp(buffer).resize(48, 48).jpeg().toBuffer() +/** generates a thumbnail for a given media, if required */ export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, info: MessageOptions) { if (info.thumbnail === null || info.thumbnail) { // don't do anything if the thumbnail is already provided, or is null @@ -69,7 +70,7 @@ export async function generateThumbnail(buffer: Buffer, mediaType: MessageType, throw new Error('audio messages cannot have thumbnails') } } else if (mediaType === MessageType.image || mediaType === MessageType.sticker) { - const buff = await sharp(buffer).resize(48, 48).jpeg().toBuffer() + const buff = await compressImage (buffer) info.thumbnail = buff.toString('base64') } else if (mediaType === MessageType.video) { const filename = './' + randomBytes(5).toString('hex') + '.mp4'