feat: whatsapp stories implementation (#3994)
* support sending message from status broadcast * add to index.d.ts * add method to contact * new modules * Update Utils.js * add for the examples * add color assertion function * finishing.. * added waveforms for audio messages and minor fixes * fix eslint * refactor reorganize the store to include additional modules. * adapt * the last one. --------- Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
This commit is contained in:
parent
dec1bee842
commit
6aa1bd85ab
6 changed files with 152 additions and 12 deletions
|
|
@ -523,6 +523,13 @@ client.on('message', async msg => {
|
||||||
// NOTE: this action will take effect after you restart the client.
|
// NOTE: this action will take effect after you restart the client.
|
||||||
const backgroundSync = await client.setBackgroundSync(true);
|
const backgroundSync = await client.setBackgroundSync(true);
|
||||||
console.log(backgroundSync);
|
console.log(backgroundSync);
|
||||||
|
} else if (msg.body === '!postStatus') {
|
||||||
|
await client.sendMessage('status@broadcast', 'Hello there!');
|
||||||
|
// send with a different style
|
||||||
|
await client.sendMessage('status@broadcast', 'Hello again! Looks different?', {
|
||||||
|
fontStyle: 1,
|
||||||
|
backgroundColor: '#0b3296'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
8
index.d.ts
vendored
8
index.d.ts
vendored
|
|
@ -117,6 +117,12 @@ declare namespace WAWebJS {
|
||||||
/** Get all current Broadcasts */
|
/** Get all current Broadcasts */
|
||||||
getBroadcasts(): Promise<Broadcast[]>
|
getBroadcasts(): Promise<Broadcast[]>
|
||||||
|
|
||||||
|
/** Get broadcast instance by current user ID */
|
||||||
|
getBroadcastById(contactId: string): Promise<Broadcast>
|
||||||
|
|
||||||
|
/** Revoke current own status messages */
|
||||||
|
revokeStatusMessage(messageId: string): Promise<void>
|
||||||
|
|
||||||
/** Change labels in chats */
|
/** Change labels in chats */
|
||||||
addOrRemoveLabels(labelIds: Array<number|string>, chatIds: Array<string>): Promise<void>
|
addOrRemoveLabels(labelIds: Array<number|string>, chatIds: Array<string>): Promise<void>
|
||||||
|
|
||||||
|
|
@ -1569,6 +1575,8 @@ declare namespace WAWebJS {
|
||||||
/** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
|
/** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
|
||||||
getCommonGroups: () => Promise<ChatId[]>
|
getCommonGroups: () => Promise<ChatId[]>
|
||||||
|
|
||||||
|
/** Gets the Contact's current status broadcast. */
|
||||||
|
getBroadcast: () => Promise<Broadcast>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContactId {
|
export interface ContactId {
|
||||||
|
|
|
||||||
|
|
@ -948,6 +948,7 @@ class Client extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
async sendMessage(chatId, content, options = {}) {
|
async sendMessage(chatId, content, options = {}) {
|
||||||
const isChannel = /@\w*newsletter\b/.test(chatId);
|
const isChannel = /@\w*newsletter\b/.test(chatId);
|
||||||
|
const isStatus = /@\w*broadcast\b/.test(chatId);
|
||||||
|
|
||||||
if (isChannel && [
|
if (isChannel && [
|
||||||
options.sendMediaAsDocument, options.quotedMessageId,
|
options.sendMediaAsDocument, options.quotedMessageId,
|
||||||
|
|
@ -958,6 +959,16 @@ class Client extends EventEmitter {
|
||||||
].includes(true)) {
|
].includes(true)) {
|
||||||
console.warn('The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.');
|
console.warn('The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.');
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
} else if (isStatus && [
|
||||||
|
options.sendMediaAsDocument, options.quotedMessageId,
|
||||||
|
options.parseVCards, options.isViewOnce, options.sendMediaAsSticker,
|
||||||
|
content instanceof Location, content instanceof Contact,
|
||||||
|
content instanceof Poll, content instanceof Buttons, content instanceof List,
|
||||||
|
Array.isArray(content) && content.length > 0 && content[0] instanceof Contact
|
||||||
|
].includes(true)) {
|
||||||
|
console.warn('The message type is currently not supported for sending in status broadcast,\nthe supported message types are: text, image, gif, audio and video.');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.mentions) {
|
if (options.mentions) {
|
||||||
|
|
@ -1194,6 +1205,11 @@ class Client extends EventEmitter {
|
||||||
return ContactFactory.create(this, contact);
|
return ContactFactory.create(this, contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message by ID
|
||||||
|
* @param {string} messageId
|
||||||
|
* @returns {Promise<Message>}
|
||||||
|
*/
|
||||||
async getMessageById(messageId) {
|
async getMessageById(messageId) {
|
||||||
const msg = await this.pupPage.evaluate(async messageId => {
|
const msg = await this.pupPage.evaluate(async messageId => {
|
||||||
let msg = window.Store.Msg.get(messageId);
|
let msg = window.Store.Msg.get(messageId);
|
||||||
|
|
@ -1982,6 +1998,49 @@ class Client extends EventEmitter {
|
||||||
return broadcasts.map(data => new Broadcast(this, data));
|
return broadcasts.map(data => new Broadcast(this, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get broadcast instance by current user ID
|
||||||
|
* @param {string} contactId
|
||||||
|
* @returns {Promise<Broadcast>}
|
||||||
|
*/
|
||||||
|
async getBroadcastById(contactId) {
|
||||||
|
const broadcast = await this.pupPage.evaluate(async (userId) => {
|
||||||
|
let status;
|
||||||
|
try {
|
||||||
|
status = window.Store.Status.get(userId);
|
||||||
|
if (!status) {
|
||||||
|
status = await window.Store.Status.find(userId);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
status = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status) return window.WWebJS.getStatusModel(status);
|
||||||
|
}, contactId);
|
||||||
|
return new Broadcast(this, broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke current own status messages
|
||||||
|
* @param {string} messageId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async revokeStatusMessage(messageId) {
|
||||||
|
return await this.pupPage.evaluate(async (msgId) => {
|
||||||
|
const status = window.Store.Status.getMyStatus();
|
||||||
|
if (!status) return;
|
||||||
|
|
||||||
|
const msg =
|
||||||
|
window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
|
||||||
|
if (!msg) return;
|
||||||
|
|
||||||
|
if (!msg.id.fromMe || !msg.id.remote.isStatus())
|
||||||
|
throw 'Invalid usage! Can only revoke the message its from own status broadcast';
|
||||||
|
|
||||||
|
return await window.Store.StatusUtils.sendStatusRevokeMsgAction(status, msg);
|
||||||
|
}, messageId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Label instance by ID
|
* Get Label instance by ID
|
||||||
* @param {string} labelId
|
* @param {string} labelId
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,13 @@ class Contact extends Base {
|
||||||
return await this.client.getCommonGroups(this.id._serialized);
|
return await this.client.getCommonGroups(this.id._serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Contact's current status broadcast.
|
||||||
|
* @returns {Promise<Broadcast>}
|
||||||
|
*/
|
||||||
|
async getBroadcast() {
|
||||||
|
return await this.client.getBroadcastById(this.id._serialized);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Contact;
|
module.exports = Contact;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ exports.ExposeStore = () => {
|
||||||
window.Store.MediaObject = window.require('WAWebMediaStorage');
|
window.Store.MediaObject = window.require('WAWebMediaStorage');
|
||||||
window.Store.MediaTypes = window.require('WAWebMmsMediaTypes');
|
window.Store.MediaTypes = window.require('WAWebMmsMediaTypes');
|
||||||
window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload');
|
window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload');
|
||||||
|
window.Store.MediaUpdate = window.require('WAWebMediaUpdateMsg');
|
||||||
window.Store.MsgKey = window.require('WAWebMsgKey');
|
window.Store.MsgKey = window.require('WAWebMsgKey');
|
||||||
window.Store.OpaqueData = window.require('WAWebMediaOpaqueData');
|
window.Store.OpaqueData = window.require('WAWebMediaOpaqueData');
|
||||||
window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge');
|
window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge');
|
||||||
|
|
@ -77,7 +78,6 @@ exports.ExposeStore = () => {
|
||||||
window.Store.PresenceUtils = window.require('WAWebPresenceChatAction');
|
window.Store.PresenceUtils = window.require('WAWebPresenceChatAction');
|
||||||
window.Store.ChatState = window.require('WAWebChatStateBridge');
|
window.Store.ChatState = window.require('WAWebChatStateBridge');
|
||||||
window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups;
|
window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups;
|
||||||
window.Store.StatusUtils = window.require('WAWebContactStatusBridge');
|
|
||||||
window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages');
|
window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages');
|
||||||
window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg;
|
window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg;
|
||||||
window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions');
|
window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions');
|
||||||
|
|
@ -200,6 +200,12 @@ exports.ExposeStore = () => {
|
||||||
...window.require('WAWebSaveContactAction'),
|
...window.require('WAWebSaveContactAction'),
|
||||||
...window.require('WAWebDeleteContactAction')
|
...window.require('WAWebDeleteContactAction')
|
||||||
};
|
};
|
||||||
|
window.Store.StatusUtils = {
|
||||||
|
...window.require('WAWebContactStatusBridge'),
|
||||||
|
...window.require('WAWebSendStatusMsgAction'),
|
||||||
|
...window.require('WAWebRevokeStatusAction'),
|
||||||
|
...window.require('WAWebStatusGatingUtils')
|
||||||
|
};
|
||||||
|
|
||||||
if (!window.Store.Chat._find || !window.Store.Chat.findImpl) {
|
if (!window.Store.Chat._find || !window.Store.Chat.findImpl) {
|
||||||
window.Store.Chat._find = e => {
|
window.Store.Chat._find = e => {
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,11 @@ exports.LoadUtils = () => {
|
||||||
|
|
||||||
window.WWebJS.sendMessage = async (chat, content, options = {}) => {
|
window.WWebJS.sendMessage = async (chat, content, options = {}) => {
|
||||||
const isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
|
const isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
|
||||||
|
const isStatus = window.Store.ChatGetters.getIsBroadcast(chat);
|
||||||
|
|
||||||
let mediaOptions = {};
|
let mediaOptions = {};
|
||||||
if (options.media) {
|
if (options.media) {
|
||||||
mediaOptions = options.sendMediaAsSticker && !isChannel
|
mediaOptions = options.sendMediaAsSticker && !isChannel && !isStatus
|
||||||
? await window.WWebJS.processStickerData(options.media)
|
? await window.WWebJS.processStickerData(options.media)
|
||||||
: await window.WWebJS.processMediaData(options.media, {
|
: await window.WWebJS.processMediaData(options.media, {
|
||||||
forceSticker: options.sendMediaAsSticker,
|
forceSticker: options.sendMediaAsSticker,
|
||||||
|
|
@ -33,7 +34,8 @@ exports.LoadUtils = () => {
|
||||||
forceVoice: options.sendAudioAsVoice,
|
forceVoice: options.sendAudioAsVoice,
|
||||||
forceDocument: options.sendMediaAsDocument,
|
forceDocument: options.sendMediaAsDocument,
|
||||||
forceMediaHd: options.sendMediaAsHd,
|
forceMediaHd: options.sendMediaAsHd,
|
||||||
sendToChannel: isChannel
|
sendToChannel: isChannel,
|
||||||
|
sendToStatus: isStatus
|
||||||
});
|
});
|
||||||
mediaOptions.caption = options.caption;
|
mediaOptions.caption = options.caption;
|
||||||
content = options.sendMediaAsSticker ? undefined : mediaOptions.preview;
|
content = options.sendMediaAsSticker ? undefined : mediaOptions.preview;
|
||||||
|
|
@ -246,6 +248,10 @@ exports.LoadUtils = () => {
|
||||||
participant = window.Store.WidFactory.asUserWidOrThrow(from);
|
participant = window.Store.WidFactory.asUserWidOrThrow(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof chat.id?.isStatus === 'function' && chat.id.isStatus()) {
|
||||||
|
participant = window.Store.WidFactory.asUserWidOrThrow(from);
|
||||||
|
}
|
||||||
|
|
||||||
const newMsgKey = new window.Store.MsgKey({
|
const newMsgKey = new window.Store.MsgKey({
|
||||||
from: from,
|
from: from,
|
||||||
to: chat.id,
|
to: chat.id,
|
||||||
|
|
@ -320,6 +326,37 @@ exports.LoadUtils = () => {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isStatus) {
|
||||||
|
const { backgroundColor, fontStyle } = extraOptions;
|
||||||
|
const isMedia = Object.keys(mediaOptions).length > 0;
|
||||||
|
const mediaUpdate = data => window.Store.MediaUpdate(data, mediaOptions);
|
||||||
|
const msg = new window.Store.Msg.modelClass({
|
||||||
|
...message,
|
||||||
|
author: participant ? participant : null,
|
||||||
|
messageSecret: window.crypto.getRandomValues(new Uint8Array(32)),
|
||||||
|
cannotBeRanked: window.Store.StatusUtils.canCheckStatusRankingPosterGating()
|
||||||
|
});
|
||||||
|
|
||||||
|
// for text only
|
||||||
|
const statusOptions = {
|
||||||
|
color: backgroundColor && window.WWebJS.assertColor(backgroundColor) || 0xff7acca5,
|
||||||
|
font: fontStyle >= 0 && fontStyle <= 7 && fontStyle || 0,
|
||||||
|
text: msg.body
|
||||||
|
};
|
||||||
|
|
||||||
|
await window.Store.StatusUtils[
|
||||||
|
isMedia ?
|
||||||
|
'sendStatusMediaMsgAction' : 'sendStatusTextMsgAction'
|
||||||
|
](
|
||||||
|
...(
|
||||||
|
isMedia ?
|
||||||
|
[msg, mediaUpdate] : [statusOptions]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
const [msgPromise, sendMsgResultPromise] = window.Store.SendMessage.addAndSendMsgToChat(chat, message);
|
const [msgPromise, sendMsgResultPromise] = window.Store.SendMessage.addAndSendMsgToChat(chat, message);
|
||||||
await msgPromise;
|
await msgPromise;
|
||||||
|
|
||||||
|
|
@ -407,7 +444,7 @@ exports.LoadUtils = () => {
|
||||||
return stickerInfo;
|
return stickerInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel }) => {
|
window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel, sendToStatus }) => {
|
||||||
const file = window.WWebJS.mediaInfoToFile(mediaInfo);
|
const file = window.WWebJS.mediaInfoToFile(mediaInfo);
|
||||||
const opaqueData = await window.Store.OpaqueData.createFromData(file, file.type);
|
const opaqueData = await window.Store.OpaqueData.createFromData(file, file.type);
|
||||||
const mediaParams = {
|
const mediaParams = {
|
||||||
|
|
@ -434,7 +471,7 @@ exports.LoadUtils = () => {
|
||||||
throw new Error('media-fault: sendToChat filehash undefined');
|
throw new Error('media-fault: sendToChat filehash undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceVoice && mediaData.type === 'ptt') {
|
if ((forceVoice && mediaData.type === 'ptt') || (sendToStatus && mediaData.type === 'audio')) {
|
||||||
const waveform = mediaObject.contentInfo.waveform;
|
const waveform = mediaObject.contentInfo.waveform;
|
||||||
mediaData.waveform =
|
mediaData.waveform =
|
||||||
waveform || await window.WWebJS.generateWaveform(file);
|
waveform || await window.WWebJS.generateWaveform(file);
|
||||||
|
|
@ -463,7 +500,7 @@ exports.LoadUtils = () => {
|
||||||
mimetype: mediaData.mimetype,
|
mimetype: mediaData.mimetype,
|
||||||
mediaObject,
|
mediaObject,
|
||||||
mediaType,
|
mediaType,
|
||||||
...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash } : {})
|
...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash() } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadedMedia = !sendToChannel
|
const uploadedMedia = !sendToChannel
|
||||||
|
|
@ -1165,4 +1202,20 @@ exports.LoadUtils = () => {
|
||||||
|
|
||||||
return { lid, phone };
|
return { lid, phone };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.WWebJS.assertColor = (hex) => {
|
||||||
|
let color;
|
||||||
|
if (typeof hex === 'number') {
|
||||||
|
color = hex > 0 ? hex : 0xffffffff + parseInt(hex) + 1;
|
||||||
|
} else if (typeof hex === 'string') {
|
||||||
|
let number = hex.trim().replace('#', '');
|
||||||
|
if (number.length <= 6) {
|
||||||
|
number = 'FF' + number.padStart(6, '0');
|
||||||
|
}
|
||||||
|
color = parseInt(number, 16);
|
||||||
|
} else {
|
||||||
|
throw 'Invalid hex color';
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue