Skip to content

Commit

Permalink
feat: Send contacts (#395)
Browse files Browse the repository at this point in the history
Introduces the ability to send contact cards. You can send Contacts directly or send a vCard string and it will be automatically parsed. If you'd like to disable this autoparse functionality, you can set `parseVCards: false` as an option while sending a message.

close #293
  • Loading branch information
pedroslopez committed Oct 26, 2020
1 parent 668106b commit 9b096db
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Take a look at [example.js](https://github.com/pedroslopez/whatsapp-web.js/blob/
| Send media (video) |[(requires google chrome)](https://github.com/pedroslopez/whatsapp-web.js/issues/78#issuecomment-592723583) |
| Send stickers | _pending_ |
| Receive media (images/audio/video/documents) ||
| Send contact cards | _pending_ |
| Send contact cards | |
| Send location ||
| Receive location ||
| Message replies ||
Expand Down
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ declare namespace WAWebJS {
linkPreview?: boolean
/** Send audio as voice message */
sendAudioAsVoice?: boolean
/** Automatically parse vCards and send them as contacts */
parseVCards?: boolean
/** Image or videos caption */
caption?: string
/** Id of the message that is being quoted (or replied to) */
Expand Down Expand Up @@ -549,7 +551,7 @@ declare namespace WAWebJS {
static fromFilePath: (filePath: string) => MessageMedia
}

export type MessageContent = string | MessageMedia | Location
export type MessageContent = string | MessageMedia | Location | Contact | Contact[]

/**
* Represents a Contact on WhatsApp
Expand Down
10 changes: 9 additions & 1 deletion src/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ class Client extends EventEmitter {
* @typedef {Object} MessageSendOptions
* @property {boolean} [linkPreview=true] - Show links preview
* @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message
* @property {boolean} [parseVCards=true] - Automatically parse vCards and send them as contacts
* @property {string} [caption] - Image or video caption
* @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to)
* @property {Contact[]} [mentions] - Contacts that are being mentioned in the message
Expand All @@ -430,7 +431,7 @@ class Client extends EventEmitter {
/**
* Send a message to a specific chatId
* @param {string} chatId
* @param {string|MessageMedia|Location} content
* @param {string|MessageMedia|Location|Contact|Array<Contact>} content
* @param {MessageSendOptions} [options] - Options used when sending the message
*
* @returns {Promise<Message>} Message that was just sent
Expand All @@ -441,6 +442,7 @@ class Client extends EventEmitter {
sendAudioAsVoice: options.sendAudioAsVoice,
caption: options.caption,
quotedMessageId: options.quotedMessageId,
parseVCards: options.parseVCards === false ? false : true,
mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact => contact.id._serialized) : []
};

Expand All @@ -456,6 +458,12 @@ class Client extends EventEmitter {
} else if (content instanceof Location) {
internalOptions.location = content;
content = '';
} else if(content instanceof Contact) {
internalOptions.contactCard = content.id._serialized;
content = '';
} else if(Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) {
internalOptions.contactCardList = content.map(contact => contact.id._serialized);
content = '';
}

const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => {
Expand Down
37 changes: 36 additions & 1 deletion src/util/Injected.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ exports.ExposeStore = (moduleRaidStr) => {
window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0];
window.Store.Cmd = window.mR.findModule('Cmd')[0].default;
window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0];
window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0];
window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default;
window.Store.Validators = window.mR.findModule('findLinks')[0];
window.Store.WidFactory = window.mR.findModule('createWid')[0];
Expand Down Expand Up @@ -78,6 +79,39 @@ exports.LoadUtils = () => {
delete options.location;
}

let vcardOptions = {};
if (options.contactCard) {
let contact = window.Store.Contact.get(options.contactCard);
vcardOptions = {
body: window.Store.VCard.vcardFromContactModel(contact).vcard,
type: 'vcard',
vcardFormattedName: contact.formattedName
};
delete options.contactCard;
} else if(options.contactCardList) {
let contacts = options.contactCardList.map(c => window.Store.Contact.get(c));
let vcards = contacts.map(c => window.Store.VCard.vcardFromContactModel(c));
vcardOptions = {
type: 'multi_vcard',
vcardList: vcards,
body: undefined
};
delete options.contactCardList;
} else if(options.parseVCards && typeof(content) === 'string' && content.startsWith('BEGIN:VCARD')) {
delete options.parseVCards;
try {
const parsed = window.Store.VCard.parseVcard(content);
if(parsed) {
vcardOptions = {
type: 'vcard',
vcardFormattedName: window.Store.VCard.vcardGetNameFromParsed(parsed)
};
}
} catch(_) {
// not a vcard
}
}

if (options.linkPreview) {
delete options.linkPreview;
const link = window.Store.Validators.findLink(content);
Expand Down Expand Up @@ -109,7 +143,8 @@ exports.LoadUtils = () => {
type: 'chat',
...locationOptions,
...attOptions,
...quotedMsgOptions
...quotedMsgOptions,
...vcardOptions
};

await window.Store.SendMessage.addAndSendMsgToChat(chat, message);
Expand Down

0 comments on commit 9b096db

Please sign in to comment.