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.
|
||||
const backgroundSync = await client.setBackgroundSync(true);
|
||||
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 */
|
||||
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 */
|
||||
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. */
|
||||
getCommonGroups: () => Promise<ChatId[]>
|
||||
|
||||
/** Gets the Contact's current status broadcast. */
|
||||
getBroadcast: () => Promise<Broadcast>
|
||||
}
|
||||
|
||||
export interface ContactId {
|
||||
|
|
|
|||
|
|
@ -948,6 +948,7 @@ class Client extends EventEmitter {
|
|||
*/
|
||||
async sendMessage(chatId, content, options = {}) {
|
||||
const isChannel = /@\w*newsletter\b/.test(chatId);
|
||||
const isStatus = /@\w*broadcast\b/.test(chatId);
|
||||
|
||||
if (isChannel && [
|
||||
options.sendMediaAsDocument, options.quotedMessageId,
|
||||
|
|
@ -958,6 +959,16 @@ class Client extends EventEmitter {
|
|||
].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.');
|
||||
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) {
|
||||
|
|
@ -1194,6 +1205,11 @@ class Client extends EventEmitter {
|
|||
return ContactFactory.create(this, contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message by ID
|
||||
* @param {string} messageId
|
||||
* @returns {Promise<Message>}
|
||||
*/
|
||||
async getMessageById(messageId) {
|
||||
const msg = await this.pupPage.evaluate(async messageId => {
|
||||
let msg = window.Store.Msg.get(messageId);
|
||||
|
|
@ -1982,6 +1998,49 @@ class Client extends EventEmitter {
|
|||
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
|
||||
* @param {string} labelId
|
||||
|
|
|
|||
|
|
@ -203,6 +203,13 @@ class Contact extends Base {
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ exports.ExposeStore = () => {
|
|||
window.Store.MediaObject = window.require('WAWebMediaStorage');
|
||||
window.Store.MediaTypes = window.require('WAWebMmsMediaTypes');
|
||||
window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload');
|
||||
window.Store.MediaUpdate = window.require('WAWebMediaUpdateMsg');
|
||||
window.Store.MsgKey = window.require('WAWebMsgKey');
|
||||
window.Store.OpaqueData = window.require('WAWebMediaOpaqueData');
|
||||
window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge');
|
||||
|
|
@ -77,7 +78,6 @@ exports.ExposeStore = () => {
|
|||
window.Store.PresenceUtils = window.require('WAWebPresenceChatAction');
|
||||
window.Store.ChatState = window.require('WAWebChatStateBridge');
|
||||
window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups;
|
||||
window.Store.StatusUtils = window.require('WAWebContactStatusBridge');
|
||||
window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages');
|
||||
window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg;
|
||||
window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions');
|
||||
|
|
@ -200,6 +200,12 @@ exports.ExposeStore = () => {
|
|||
...window.require('WAWebSaveContactAction'),
|
||||
...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) {
|
||||
window.Store.Chat._find = e => {
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ exports.LoadUtils = () => {
|
|||
|
||||
window.WWebJS.sendMessage = async (chat, content, options = {}) => {
|
||||
const isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
|
||||
const isStatus = window.Store.ChatGetters.getIsBroadcast(chat);
|
||||
|
||||
let mediaOptions = {};
|
||||
if (options.media) {
|
||||
mediaOptions = options.sendMediaAsSticker && !isChannel
|
||||
mediaOptions = options.sendMediaAsSticker && !isChannel && !isStatus
|
||||
? await window.WWebJS.processStickerData(options.media)
|
||||
: await window.WWebJS.processMediaData(options.media, {
|
||||
forceSticker: options.sendMediaAsSticker,
|
||||
|
|
@ -33,7 +34,8 @@ exports.LoadUtils = () => {
|
|||
forceVoice: options.sendAudioAsVoice,
|
||||
forceDocument: options.sendMediaAsDocument,
|
||||
forceMediaHd: options.sendMediaAsHd,
|
||||
sendToChannel: isChannel
|
||||
sendToChannel: isChannel,
|
||||
sendToStatus: isStatus
|
||||
});
|
||||
mediaOptions.caption = options.caption;
|
||||
content = options.sendMediaAsSticker ? undefined : mediaOptions.preview;
|
||||
|
|
@ -246,6 +248,10 @@ exports.LoadUtils = () => {
|
|||
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({
|
||||
from: from,
|
||||
to: chat.id,
|
||||
|
|
@ -320,6 +326,37 @@ exports.LoadUtils = () => {
|
|||
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);
|
||||
await msgPromise;
|
||||
|
||||
|
|
@ -407,7 +444,7 @@ exports.LoadUtils = () => {
|
|||
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 opaqueData = await window.Store.OpaqueData.createFromData(file, file.type);
|
||||
const mediaParams = {
|
||||
|
|
@ -434,7 +471,7 @@ exports.LoadUtils = () => {
|
|||
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;
|
||||
mediaData.waveform =
|
||||
waveform || await window.WWebJS.generateWaveform(file);
|
||||
|
|
@ -463,7 +500,7 @@ exports.LoadUtils = () => {
|
|||
mimetype: mediaData.mimetype,
|
||||
mediaObject,
|
||||
mediaType,
|
||||
...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash } : {})
|
||||
...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash() } : {})
|
||||
};
|
||||
|
||||
const uploadedMedia = !sendToChannel
|
||||
|
|
@ -1165,4 +1202,20 @@ exports.LoadUtils = () => {
|
|||
|
||||
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