From c0705a221650aea210d5eb43c386bcb4d87ff49f Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:10:26 +0300 Subject: [PATCH] fix+feat: addParticipants + createGroup (#2344) * added required modules * added sendGroupV4Invite implementation * docs updated accordingly * bug fixed * added usage example * ESLint fix * extended resulting object * docs updated accordingly * added required modules * updated comments * bug fixed * added usage example * fixed to catch unexpected errors * removed unnecessary module * updated usage example * minor fixes * changed to return a string if an error is occupied * fixed usage example * removed unneccessary module * added getAddParticipantsRpcResult * updated addParticipants - added AddParticipnatsOptions to provide values for sleep, autoSendInviteV4, comment - combined addParticipants with sendGroupInviteMessage * removed unneccessary sendGroupV4Invite * docs updated * fixed statement fot autoSendInviteV4 * minor fixes - the returned value of addParticipants is no longer wrapped in an array - docs updated - fixed bug with the returned code value when the code is '200' * added logic of retrieving the group profile picture this allows to send an invitation with a profile picture thumbnail of the group * minor fixes * added fix for the inviteV4 to be sent * updated usage example * minor fix * ESLint fix * fix for code 419 * function name getProfilePictureThumb changed to be more clear * refactor: modules moved to window.Store.GroupParticipants * refactor: edited link of usage examples * refactor: LidManipulations changed to LidUtils * refactor: modules regrouping To keep the modules in order were made some changes: - JoinInviteV4 renamed to GroupInviteV4 and grouped with module sendGroupInviteMessage - GroupParticipants grouped with module sendAddParticipantsRPC * fix: injectToFunction fixed to work properly * revert docs changes for Chat * Merge branch 'main' into fix-add-participants * docs fixed * minor fix in injectToFunction * removed data stringifying/parsing * added random sleep time logic * code refactoring * docs updated * addParticipants method refactored * createGroup method extended - added functionality to send private invitations automatically to missing participants - added ability to provide value for message timer - added ability to link a group to the community parent group at the stage of creating a group - extended the returned data after the method call * docs updated + typo fixed * added ability to create group with myself only * example updated * docs clarified * small code refactoring * window.Store.Invite and window.Store.InviteInfo grouped * window.compareWwebVersions fixed * small update to improve the performance of findImpl fix * gid as wid structure * docs + example updated * parenthesis * avoid sleep after the last iteration * fixed compatibility issue for sendGroupInviteMessage * fixed issue with stale groupmetadata in some cases after the participant has left the group, that participant is still stored in groupmetadata * fixed compatibility issue for sendAddParticipantsRPC * minor fix * jsdocs fixed * fixed issue with group creation * fixed createGroup method - added check for unregistered phone number - extended returning data from method call: added title - code refactored - jsdocs fixed * fixed addParticipants method - added check for unregistered number - code refactored * docs fixed * usage example fixed * removed unnecessary method call * jsdocs fixed * check for group admin fixed --- example.js | 72 ++++++++++++++++++ index.d.ts | 107 +++++++++++++++++++------- src/Client.js | 135 ++++++++++++++++++++++++++------- src/structures/GroupChat.js | 147 +++++++++++++++++++++++++++++++----- src/util/Injected.js | 131 +++++++++++++++++++++++++++++--- 5 files changed, 509 insertions(+), 83 deletions(-) diff --git a/example.js b/example.js index 9395144..98e4ee4 100644 --- a/example.js +++ b/example.js @@ -91,6 +91,78 @@ client.on('message', async msg => { } catch (e) { msg.reply('That invite code seems to be invalid.'); } + } else if (msg.body.startsWith('!addmembers')) { + const group = await msg.getChat(); + const result = await group.addParticipants(['number1@c.us', 'number2@c.us', 'number3@c.us']); + /** + * The example of the {@link result} output: + * + * { + * 'number1@c.us': { + * code: 200, + * message: 'The participant was added successfully', + * isInviteV4Sent: false + * }, + * 'number2@c.us': { + * code: 403, + * message: 'The participant can be added by sending private invitation only', + * isInviteV4Sent: true + * }, + * 'number3@c.us': { + * code: 404, + * message: 'The phone number is not registered on WhatsApp', + * isInviteV4Sent: false + * } + * } + * + * For more usage examples: + * @see https://github.com/pedroslopez/whatsapp-web.js/pull/2344#usage-example1 + */ + console.log(result); + } else if (msg.body === '!creategroup') { + const partitipantsToAdd = ['number1@c.us', 'number2@c.us', 'number3@c.us']; + const result = await client.createGroup('Group Title', partitipantsToAdd); + /** + * The example of the {@link result} output: + * { + * title: 'Group Title', + * gid: { + * server: 'g.us', + * user: '1111111111', + * _serialized: '1111111111@g.us' + * }, + * participants: { + * 'botNumber@c.us': { + * statusCode: 200, + * message: 'The participant was added successfully', + * isGroupCreator: true, + * isInviteV4Sent: false + * }, + * 'number1@c.us': { + * statusCode: 200, + * message: 'The participant was added successfully', + * isGroupCreator: false, + * isInviteV4Sent: false + * }, + * 'number2@c.us': { + * statusCode: 403, + * message: 'The participant can be added by sending private invitation only', + * isGroupCreator: false, + * isInviteV4Sent: true + * }, + * 'number3@c.us': { + * statusCode: 404, + * message: 'The phone number is not registered on WhatsApp', + * isGroupCreator: false, + * isInviteV4Sent: false + * } + * } + * } + * + * For more usage examples: + * @see https://github.com/pedroslopez/whatsapp-web.js/pull/2344#usage-example2 + */ + console.log(result); } else if (msg.body === '!groupinfo') { let chat = await msg.getChat(); if (chat.isGroup) { diff --git a/index.d.ts b/index.d.ts index 9020dc1..c916158 100644 --- a/index.d.ts +++ b/index.d.ts @@ -35,12 +35,8 @@ declare namespace WAWebJS { /** Unpins the Chat and returns its new Pin state */ unpinChat(chatId: string): Promise - /** - * Create a new group - * @param name group title - * @param participants an array of Contacts or contact IDs to add to the group - */ - createGroup(name: string, participants: Contact[] | string[]): Promise + /** Creates a new group */ + createGroup(title: string, participants?: string | Contact | Contact[] | string[], options?: CreateGroupOptions): Promise /** Closes the client */ destroy(): Promise @@ -540,14 +536,47 @@ declare namespace WAWebJS { plugged: boolean, } + /** An object that handles options for group creation */ + export interface CreateGroupOptions { + /** + * The number of seconds for the messages to disappear in the group, + * won't take an effect if the group is been creating with myself only + * @default 0 + */ + messageTimer?: number + /** + * The ID of a parent community group to link the newly created group with, + * won't take an effect if the group is been creating with myself only + */ + parentGroupId?: string + /** If true, the inviteV4 will be sent to those participants + * who have restricted others from being automatically added to groups, + * otherwise the inviteV4 won't be sent + * @default true + */ + autoSendInviteV4?: boolean, + /** + * The comment to be added to an inviteV4 (empty string by default) + * @default '' + */ + comment?: string + } + + /** An object that handles the result for createGroup method */ export interface CreateGroupResult { - /** ID for the group that was just created */ - gid: string, - /** participants that were not added to the group. - * Keys represent the ID for participant that was not added and its value is a status code - * that represents the reason why participant could not be added. - * This is usually 403 if the user's privacy settings don't allow you to add them to groups. */ - missingParticipants: Record + /** A group title */ + title: string; + /** An object that handles the newly created group ID */ + gid: ChatId; + /** An object that handles the result value for each added to the group participant */ + participants: { + [participantId: string]: { + statusCode: number, + message: string, + isGroupCreator: boolean, + isInviteV4Sent: boolean + }; + }; } export interface GroupNotification { @@ -1196,9 +1225,9 @@ declare namespace WAWebJS { /** Returns the Contact that corresponds to this Chat. */ getContact: () => Promise, /** Marks this Chat as unread */ - markUnread: () => Promise + markUnread: () => Promise, /** Returns array of all Labels assigned to this Chat */ - getLabels: () => Promise + getLabels: () => Promise, /** Add or remove labels to this Chat */ changeLabels: (labelIds: Array) => Promise } @@ -1258,18 +1287,38 @@ declare namespace WAWebJS { export type ChangeParticipantsPermissions = (participantIds: Array) => Promise<{ status: number }> - /** Adds or removes a list of participants by ID to the group */ - export type ChangeGroupParticipants = - (participantIds: Array) => Promise<{ - status: number; - participants: Array<{ - [key: string]: { - code: number - } - }> - } & { - [key: string]: number; - }> + /** An object that handles the result for addParticipants method */ + export interface AddParticipantsResult { + [participantId: string]: { + code: number; + message: string; + isInviteV4Sent: boolean, + }; + }; + + /** An object that handles options for adding participants */ + export interface AddParticipantsOptions { + /** + * The number of milliseconds to wait before adding the next participant. + * If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added + * (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 + * will be added). If sleep is a number, a sleep time equal to its value will be added + * @default [250,500] + */ + sleep?: Array|number, + /** + * If true, the inviteV4 will be sent to those participants + * who have restricted others from being automatically added to groups, + * otherwise the inviteV4 won't be sent + * @default true + */ + autoSendInviteV4?: boolean, + /** + * The comment to be added to an inviteV4 (empty string by default) + * @default '' + */ + comment?: string + }; /** An object that handles the information about the group membership request */ export interface GroupMembershipRequest { @@ -1313,9 +1362,9 @@ declare namespace WAWebJS { /** Group participants */ participants: Array; /** Adds a list of participants by ID to the group */ - addParticipants: ChangeGroupParticipants; + addParticipants: (participantIds: string|string[], options?: AddParticipantsOptions) => Promise|string>; /** Removes a list of participants by ID to the group */ - removeParticipants: ChangeGroupParticipants; + removeParticipants: (participantIds: string[]) => Promise<{ status: number }>; /** Promotes participants by IDs to admins */ promoteParticipants: ChangeParticipantsPermissions; /** Demotes participants by IDs to regular users */ diff --git a/src/Client.js b/src/Client.js index 977e3ca..d815122 100644 --- a/src/Client.js +++ b/src/Client.js @@ -987,7 +987,7 @@ class Client extends EventEmitter { */ async getInviteInfo(inviteCode) { return await this.pupPage.evaluate(inviteCode => { - return window.Store.InviteInfo.queryGroupInvite(inviteCode); + return window.Store.GroupInvite.queryGroupInvite(inviteCode); }, inviteCode); } @@ -998,7 +998,7 @@ class Client extends EventEmitter { */ async acceptInvite(inviteCode) { const res = await this.pupPage.evaluate(async inviteCode => { - return await window.Store.Invite.joinGroupViaInvite(inviteCode); + return await window.Store.GroupInvite.joinGroupViaInvite(inviteCode); }, inviteCode); return res.gid._serialized; @@ -1015,7 +1015,7 @@ class Client extends EventEmitter { return this.pupPage.evaluate(async inviteInfo => { let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo; let userWid = window.Store.WidFactory.createWid(fromId); - return await window.Store.JoinInviteV4.joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid); + return await window.Store.GroupInviteV4.joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid); }, inviteInfo); } @@ -1291,35 +1291,116 @@ class Client extends EventEmitter { } /** - * Create a new group - * @param {string} name group title - * @param {Array} participants an array of Contacts or contact IDs to add to the group - * @returns {Object} createRes - * @returns {string} createRes.gid - ID for the group that was just created - * @returns {Object.} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups. + * An object that represents the result for a participant added to a group + * @typedef {Object} ParticipantResult + * @property {number} statusCode The status code of the result + * @property {string} message The result message + * @property {boolean} isGroupCreator Indicates if the participant is a group creator + * @property {boolean} isInviteV4Sent Indicates if the inviteV4 was sent to the participant */ - async createGroup(name, participants) { - if (!Array.isArray(participants) || participants.length == 0) { - throw 'You need to add at least one other participant to the group'; - } - if (participants.every(c => c instanceof Contact)) { - participants = participants.map(c => c.id._serialized); - } + /** + * An object that handles the result for {@link createGroup} method + * @typedef {Object} CreateGroupResult + * @property {string} title A group title + * @property {Object} gid An object that handles the newly created group ID + * @property {string} gid.server + * @property {string} gid.user + * @property {string} gid._serialized + * @property {Object.} participants An object that handles the result value for each added to the group participant + */ - const createRes = await this.pupPage.evaluate(async (name, participantIds) => { - const participantWIDs = participantIds.map(p => window.Store.WidFactory.createWid(p)); - return await window.Store.GroupUtils.createGroup(name, participantWIDs, 0); - }, name, participants); + /** + * An object that handles options for group creation + * @typedef {Object} CreateGroupOptions + * @property {number} [messageTimer = 0] The number of seconds for the messages to disappear in the group (0 by default, won't take an effect if the group is been creating with myself only) + * @property {string|undefined} parentGroupId The ID of a parent community group to link the newly created group with (won't take an effect if the group is been creating with myself only) + * @property {boolean} [autoSendInviteV4 = true] If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default) + * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default) + */ - const missingParticipants = createRes.participants.reduce(((missing, c) => { - const id = c.wid._serialized; - const statusCode = c.error ? c.error.toString() : '200'; - if (statusCode != 200) return Object.assign(missing, { [id]: statusCode }); - return missing; - }), {}); + /** + * Creates a new group + * @param {string} title Group title + * @param {string|Contact|Array|undefined} participants A single Contact object or an ID as a string or an array of Contact objects or contact IDs to add to the group + * @param {CreateGroupOptions} options An object that handles options for group creation + * @returns {Promise} Object with resulting data or an error message as a string + */ + async createGroup(title, participants = [], options = {}) { + !Array.isArray(participants) && (participants = [participants]); + participants.map(p => (p instanceof Contact) ? p.id._serialized : p); - return { gid: createRes.wid, missingParticipants }; + return await this.pupPage.evaluate(async (title, participants, options) => { + const { messageTimer = 0, parentGroupId, autoSendInviteV4 = true, comment = '' } = options; + const participantData = {}, participantWids = [], failedParticipants = []; + let createGroupResult, parentGroupWid; + + const addParticipantResultCodes = { + default: 'An unknown error occupied while adding a participant', + 200: 'The participant was added successfully', + 403: 'The participant can be added by sending private invitation only', + 404: 'The phone number is not registered on WhatsApp' + }; + + for (const participant of participants) { + const pWid = window.Store.WidFactory.createWid(participant); + if ((await window.Store.QueryExist(pWid))?.wid) participantWids.push(pWid); + else failedParticipants.push(participant); + } + + parentGroupId && (parentGroupWid = window.Store.WidFactory.createWid(parentGroupId)); + + try { + createGroupResult = await window.Store.GroupUtils.createGroup( + title, + participantWids, + messageTimer, + parentGroupWid + ); + } catch (err) { + return 'CreateGroupError: An unknown error occupied while creating a group'; + } + + for (const participant of createGroupResult.participants) { + let isInviteV4Sent = false; + const participantId = participant.wid._serialized; + const statusCode = participant.error ?? 200; + + if (autoSendInviteV4 && statusCode === 403) { + window.Store.ContactCollection.gadd(participant.wid, { silent: true }); + const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage( + await window.Store.Chat.find(participant.wid), + createGroupResult.wid._serialized, + createGroupResult.subject, + participant.invite_code, + participant.invite_code_exp, + comment, + await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid) + ); + isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + ? addParticipantResult === 'OK' + : addParticipantResult.messageSendResult === 'OK'; + } + + participantData[participantId] = { + statusCode: statusCode, + message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default, + isGroupCreator: participant.type === 'superadmin', + isInviteV4Sent: isInviteV4Sent + }; + } + + for (const f of failedParticipants) { + participantData[f] = { + statusCode: 404, + message: addParticipantResultCodes[404], + isGroupCreator: false, + isInviteV4Sent: false + }; + } + + return { title: title, gid: createGroupResult.wid, participants: participantData }; + }, title, participants, options); } /** diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 2944d2f..d974f9d 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -54,27 +54,140 @@ class GroupChat extends Chat { } /** - * Adds a list of participants by ID to the group - * @param {Array} participantIds - * @returns {Promise} + * An object that handles the result for {@link addParticipants} method + * @typedef {Object} AddParticipantsResult + * @property {number} code The code of the result + * @property {string} message The result message + * @property {boolean} isInviteV4Sent Indicates if the inviteV4 was sent to the partitipant */ - async addParticipants(participantIds) { - return await this.client.pupPage.evaluate(async (chatId, participantIds) => { - const chatWid = window.Store.WidFactory.createWid(chatId); - const chat = await window.Store.Chat.find(chatWid); - const participants = await Promise.all(participantIds.map(async p => { - const wid = window.Store.WidFactory.createWid(p); - return await window.Store.Contact.get(wid); - })); - await window.Store.GroupParticipants.addParticipants(chat, participants); - return { status: 200 }; - }, this.id._serialized, participantIds); + + /** + * An object that handles options for adding participants + * @typedef {Object} AddParticipnatsOptions + * @property {Array|number} [sleep = [250, 500]] The number of milliseconds to wait before adding the next participant. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500] + * @property {boolean} [autoSendInviteV4 = true] If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default) + * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default) + */ + + /** + * Adds a list of participants by ID to the group + * @param {string|Array} participantIds + * @param {AddParticipnatsOptions} options An object thay handles options for adding participants + * @returns {Promise|string>} Returns an object with the resulting data or an error message as a string + */ + async addParticipants(participantIds, options = {}) { + return await this.client.pupPage.evaluate(async (groupId, participantIds, options) => { + const { sleep = [250, 500], autoSendInviteV4 = true, comment = '' } = options; + const participantData = {}; + + !Array.isArray(participantIds) && (participantIds = [participantIds]); + const groupWid = window.Store.WidFactory.createWid(groupId); + const group = await window.Store.Chat.find(groupWid); + const participantWids = participantIds.map((p) => window.Store.WidFactory.createWid(p)); + + const errorCodes = { + default: 'An unknown error occupied while adding a participant', + isGroupEmpty: 'AddParticipantsError: The participant can\'t be added to an empty group', + iAmNotAdmin: 'AddParticipantsError: You have no admin rights to add a participant to a group', + 200: 'The participant was added successfully', + 403: 'The participant can be added by sending private invitation only', + 404: 'The phone number is not registered on WhatsApp', + 408: 'You cannot add this participant because they recently left the group', + 409: 'The participant is already a group member', + 417: 'The participant can\'t be added to the community. You can invite them privately to join this group through its invite link', + 419: 'The participant can\'t be added because the group is full' + }; + + await window.Store.GroupMetadata.queryAndUpdate(groupWid); + const groupMetadata = group.groupMetadata; + const groupParticipants = groupMetadata?.participants; + + if (!groupParticipants) { + return errorCodes.isGroupEmpty; + } + + if (!group.iAmAdmin()) { + return errorCodes.iAmNotAdmin; + } + + const _getSleepTime = (sleep) => { + if (!Array.isArray(sleep) || sleep.length === 2 && sleep[0] === sleep[1]) { + return sleep; + } + if (sleep.length === 1) { + return sleep[0]; + } + (sleep[1] - sleep[0]) < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100); + return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0]; + }; + + for (const pWid of participantWids) { + const pId = pWid._serialized; + + participantData[pId] = { + code: undefined, + message: undefined, + isInviteV4Sent: false + }; + + if (groupParticipants.some(p => p.id._serialized === pId)) { + participantData[pId].code = 409; + participantData[pId].message = errorCodes[409]; + continue; + } + + if (!(await window.Store.QueryExist(pWid))?.wid) { + participantData[pId].code = 404; + participantData[pId].message = errorCodes[404]; + continue; + } + + const rpcResult = + await window.WWebJS.getAddParticipantsRpcResult(groupMetadata, groupWid, pWid); + const { code: rpcResultCode } = rpcResult; + + participantData[pId].code = rpcResultCode; + participantData[pId].message = + errorCodes[rpcResultCode] || errorCodes.default; + + if (autoSendInviteV4 && rpcResultCode === 403) { + let userChat, isInviteV4Sent = false; + window.Store.ContactCollection.gadd(pWid, { silent: true }); + + if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' && + (userChat = await window.Store.Chat.find(pWid))) { + const groupName = group.formattedTitle || group.name; + const res = await window.Store.GroupInviteV4.sendGroupInviteMessage( + userChat, + group.id._serialized, + groupName, + rpcResult.inviteV4Code, + rpcResult.inviteV4CodeExp, + comment, + await window.WWebJS.getProfilePicThumbToBase64(groupWid) + ); + isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6') + ? res === 'OK' + : res.messageSendResult === 'OK'; + } + + participantData[pId].isInviteV4Sent = isInviteV4Sent; + } + + sleep && + participantWids.length > 1 && + participantWids.indexOf(pWid) !== participantWids.length - 1 && + (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep)))); + } + + return participantData; + }, this.id._serialized, participantIds, options); } /** * Removes a list of participants by ID to the group * @param {Array} participantIds - * @returns {Promise} + * @returns {Promise<{ status: number }>} */ async removeParticipants(participantIds) { return await this.client.pupPage.evaluate(async (chatId, participantIds) => { @@ -246,7 +359,7 @@ class GroupChat extends Chat { async getInviteCode() { const codeRes = await this.client.pupPage.evaluate(async chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.Invite.queryGroupInviteCode(chatWid); + return window.Store.GroupInvite.queryGroupInviteCode(chatWid); }, this.id._serialized); return codeRes.code; @@ -259,7 +372,7 @@ class GroupChat extends Chat { async revokeInvite() { const codeRes = await this.client.pupPage.evaluate(chatId => { const chatWid = window.Store.WidFactory.createWid(chatId); - return window.Store.Invite.resetGroupInviteCode(chatWid); + return window.Store.GroupInvite.resetGroupInviteCode(chatWid); }, this.id._serialized); return codeRes.code; diff --git a/src/util/Injected.js b/src/util/Injected.js index 2139b4a..fc99b1d 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -15,9 +15,8 @@ exports.ExposeStore = (moduleRaidStr) => { window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; window.Store.GroupMetadata = window.mR.findModule('GroupMetadata')[0].default.GroupMetadata; window.Store.GroupMetadata.queryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById; - window.Store.Invite = window.mR.findModule('resetGroupInviteCode')[0]; - window.Store.InviteInfo = window.mR.findModule('queryGroupInvite')[0]; window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; + window.Store.ContactCollection = window.mR.findModule('ContactCollection')[0].ContactCollection; window.Store.MediaPrep = window.mR.findModule('prepRawMedia')[0]; window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0]; window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0]; @@ -26,7 +25,6 @@ exports.ExposeStore = (moduleRaidStr) => { window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; window.Store.MessageInfo = window.mR.findModule('sendQueryMsgInfo')[0]; window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; - window.Store.QueryExist = window.mR.findModule('queryExists')[0] ? window.mR.findModule('queryExists')[0].queryExists : window.mR.findModule('queryExist')[0].queryWidExists; window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; window.Store.SendClear = window.mR.findModule('sendClear')[0]; @@ -45,23 +43,28 @@ exports.ExposeStore = (moduleRaidStr) => { window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0]; window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0]; window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0]; - window.Store.GroupParticipants = window.mR.findModule('promoteParticipants')[0]; - window.Store.JoinInviteV4 = window.mR.findModule('queryGroupInviteV4')[0]; window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups; window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0]; window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0]; window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg; window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0]; window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0]; - window.Store.ReplyUtils = window.mR.findModule('canReplyMsg').length > 0 && window.mR.findModule('canReplyMsg')[0]; window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0]; window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0]; window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0]; window.Store.SocketWap = window.mR.findModule('wap')[0]; window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext; window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager; + window.Store.LidUtils = window.mR.findModule('getCurrentLid')[0]; window.Store.WidToJid = window.mR.findModule('widToUserJid')[0]; window.Store.JidToWid = window.mR.findModule('userJidToUserWid')[0]; + + /* eslint-disable no-undef, no-cond-assign */ + window.Store.QueryExist = ((m = window.mR.findModule('queryExists')[0]) ? m.queryExists : window.mR.findModule('queryExist')[0].queryWidExists); + window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 && m[0]; + if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; + /* eslint-enable no-undef, no-cond-assign */ + window.Store.StickerTools = { ...window.mR.findModule('toWebpSticker')[0], ...window.mR.findModule('addWebpMetadata')[0] @@ -72,6 +75,18 @@ exports.ExposeStore = (moduleRaidStr) => { ...window.mR.findModule('sendExitGroup')[0], ...window.mR.findModule('sendSetPicture')[0] }; + window.Store.GroupParticipants = { + ...window.mR.findModule('promoteParticipants')[0], + ...window.mR.findModule('sendAddParticipantsRPC')[0] + }; + window.Store.GroupInvite = { + ...window.mR.findModule('resetGroupInviteCode')[0], + ...window.mR.findModule('queryGroupInvite')[0] + }; + window.Store.GroupInviteV4 = { + ...window.mR.findModule('queryGroupInviteV4')[0], + ...window.mR.findModule('sendGroupInviteMessage')[0] + }; window.Store.MembershipRequestUtils = { ...window.mR.findModule('getMembershipApprovalRequests')[0], ...window.mR.findModule('sendMembershipRequestsActionRPC')[0] @@ -86,9 +101,6 @@ exports.ExposeStore = (moduleRaidStr) => { }; } - // eslint-disable-next-line no-undef - if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; - // TODO remove these once everybody has been updated to WWebJS with legacy sessions removed const _linkPreview = window.mR.findModule('queryLinkPreview'); if (_linkPreview && _linkPreview[0] && _linkPreview[0].default) { @@ -131,7 +143,7 @@ exports.ExposeStore = (moduleRaidStr) => { window.injectToFunction({ moduleId: 'mediaTypeFromProtobuf', index: 0, property: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); - window.injectToFunction({ moduleId: 'typeAttributeFromProtobuf', index: 0, property: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? 'text' : func(...args); }); + window.injectToFunction({ moduleId: 'typeAttributeFromProtobuf', index: 0, property: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); }; exports.LoadUtils = () => { @@ -865,6 +877,105 @@ exports.LoadUtils = () => { throw err; } }; + + window.WWebJS.getProfilePicThumbToBase64 = async (chatWid) => { + const profilePicCollection = await window.Store.ProfilePicThumb.find(chatWid); + + const _readImageAsBase64 = (imageBlob) => { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onloadend = function () { + const base64Image = reader.result; + if (base64Image == null) { + resolve(undefined); + } else { + const base64Data = base64Image.toString().split(',')[1]; + resolve(base64Data); + } + }; + reader.readAsDataURL(imageBlob); + }); + }; + + if (profilePicCollection?.img) { + try { + const response = await fetch(profilePicCollection.img); + if (response.ok) { + const imageBlob = await response.blob(); + if (imageBlob) { + const base64Image = await _readImageAsBase64(imageBlob); + return base64Image; + } + } + } catch (error) { /* empty */ } + } + return undefined; + }; + + window.WWebJS.getAddParticipantsRpcResult = async (groupMetadata, groupWid, participantWid) => { + const participantLidArgs = groupMetadata?.isLidAddressingMode + ? { + phoneNumber: participantWid, + lid: window.Store.LidUtils.getCurrentLid(participantWid) + } + : { phoneNumber: participantWid }; + + const iqTo = window.Store.WidToJid.widToGroupJid(groupWid); + + const participantArgs = + participantLidArgs.lid + ? [{ + participantJid: window.Store.WidToJid.widToUserJid(participantLidArgs.lid), + phoneNumberMixinArgs: { + anyPhoneNumber: window.Store.WidToJid.widToUserJid(participantLidArgs.phoneNumber) + } + }] + : [{ + participantJid: window.Store.WidToJid.widToUserJid(participantLidArgs.phoneNumber) + }]; + + let rpcResult, resultArgs; + const isOldImpl = window.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9'); + const data = { + name: undefined, + code: undefined, + inviteV4Code: undefined, + inviteV4CodeExp: undefined + }; + + try { + rpcResult = await window.Store.GroupParticipants.sendAddParticipantsRPC({ participantArgs, iqTo }); + resultArgs = isOldImpl + ? rpcResult.value.addParticipant[0].addParticipantsParticipantMixins + : rpcResult.value.addParticipant[0] + .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup + .value + .addParticipantsParticipantMixins; + } catch (err) { + data.code = 400; + return data; + } + + if (rpcResult.name === 'AddParticipantsResponseSuccess') { + const code = resultArgs?.value.error ?? '200'; + data.name = resultArgs?.name; + data.code = +code; + data.inviteV4Code = resultArgs?.value.addRequestCode; + data.inviteV4CodeExp = resultArgs?.value.addRequestExpiration?.toString(); + } + + else if (rpcResult.name === 'AddParticipantsResponseClientError') { + const { code: code } = rpcResult.value.errorAddParticipantsClientErrors.value; + data.code = +code; + } + + else if (rpcResult.name === 'AddParticipantsResponseServerError') { + const { code: code } = rpcResult.value.errorServerErrors.value; + data.code = +code; + } + + return data; + }; window.WWebJS.membershipRequestAction = async (groupId, action, requesterIds, sleep) => { const groupWid = window.Store.WidFactory.createWid(groupId);