feat: Channel (#2620)
* window.injectToFunction clarifications * added createChannel method * initial Channel class structure * added getChannelById and getChannels methods * added 4 methods to change channel metadata * added deleteChannel method * added subscribe to/unsubscribe from channel methods * ecmaVersion updated to 2022 * added searchChannels method * refactoring of: getChat, getChannel, getChatModel, getChannelModel * removed Channel.canSend * sendMessage updated to support channels [p1] * createChannel: returns string if an error occupied * added getSubscribers * added mute/unmute methods * searchChannels: minor update * sendMessage updated to support channels [p2] - added support to send image, sticker, gif and video * [REFAC] refactored chat retrieving in getChatOrChannel * [FEAT] added getChannelByInviteCode method * [CHG] added check if channel creation enabled * [FEAT] added sendChannelAdminInvitation method * [WIP] preparations for sending polls [p1] * [BUG] fixed some internal functions * [BUG] fixed poll creation * [COPY] renamed some internal functions - window.WWebJS.getChatOrChannel renamed back to window.WWebJS.getChat -window.WWebJS.getChatOrChannelModel renamed back to window.WWebJS.getChatModel * fix: typo * fix: send docs without caption * fix: fixed some internal function * fix: channel retrieving * fix: fixed internal getChannelMetadata function * fix: fixed channel admin can unsubscribe from channel * feat: channel admin functionality * feat: added fetchMessages method Co-authored-by: tuke307 <58882912+tuke307@users.noreply.github.com> * refactor: revert removing support for Buttons and Lists Note: buttons and lists are deprecated * refactor: revert parameter name from 'chatOrChannel' to 'chat' * fix: support Node v12 * refactor: return 'undefined' in a case of falsy value * refactor: use already created variable * docs: fix 'countryCodes' property type * fix: 'searchChannels' method fixed * fix: add check in 'setReactionSetting' method * sendNewsletterMessageJob requires a proper Msg Object in 2.24xx (#10) Co-authored-by: Sven Neumann <s.neumann@websix.de> * style: fix broken link in readme file * fix: only directPath determines whether hasMedia or not fixes #2952 * fix: get rid of unnecessary 'await' * feat: add option to send polls in channels * refactor: get channel instances with 'getChatById' * chore: fix eslint * fix: fatal error * refactor: delete local models by default when unsubscribing * fix: prevent TypeError if no channel models are returned * fix: update 'searchChannels' function for WWeb > 2.3000.0 * fix: fix for 'getChannelByInviteCode' function * fix: fix for 'sendChannelAdminInvite' function * fix: update 'fetchMessages' for channels * refactor: update 'setReactionSetting' function * fix: update 'sendMessage' for channels * feat: add 'transferChannelOwnership' function * refactor: add 'deleteChannel' function to 'Client' * style: fix project code style * chore: eslint fix * feat: add 'sendSeen' function * fix: fix the function restoration after injection * refactor: remove duplicate module * fix: get isNewsletter properly * fix: fix channels media - add mediaHandle Co-authored-by: devlikepro <155617407+devlikepro@users.noreply.github.com> --------- Co-authored-by: tuke307 <58882912+tuke307@users.noreply.github.com> Co-authored-by: Sven <sven2neumann@gmail.com> Co-authored-by: Sven Neumann <s.neumann@websix.de> Co-authored-by: devlikepro <155617407+devlikepro@users.noreply.github.com>
This commit is contained in:
parent
dd257b2039
commit
d0642b761c
13 changed files with 1241 additions and 234 deletions
|
|
@ -11,7 +11,7 @@
|
|||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
"ecmaVersion": 2022
|
||||
},
|
||||
"plugins": ["mocha"],
|
||||
"ignorePatterns": ["docs"],
|
||||
|
|
|
|||
190
index.d.ts
vendored
190
index.d.ts
vendored
|
|
@ -24,6 +24,15 @@ declare namespace WAWebJS {
|
|||
/**Accepts an invitation to join a group */
|
||||
acceptInvite(inviteCode: string): Promise<string>
|
||||
|
||||
/** Accepts a channel admin invitation and promotes the current user to a channel admin */
|
||||
acceptChannelAdminInvite(channelId: string): Promise<boolean>
|
||||
|
||||
/** Revokes a channel admin invitation sent to a user by a channel owner */
|
||||
revokeChannelAdminInvite(channelId: string, userId: string): Promise<boolean>
|
||||
|
||||
/** Demotes a channel admin to a regular subscriber (can be used also for self-demotion) */
|
||||
demoteChannelAdmin(channelId: string, userId: string): Promise<boolean>
|
||||
|
||||
/** Accepts a private invitation to join a group (v4 invite) */
|
||||
acceptGroupV4Invite: (inviteV4: InviteV4Data) => Promise<{status: number}>
|
||||
|
||||
|
|
@ -42,6 +51,27 @@ declare namespace WAWebJS {
|
|||
/** Creates a new group */
|
||||
createGroup(title: string, participants?: string | Contact | Contact[] | string[], options?: CreateGroupOptions): Promise<CreateGroupResult|string>
|
||||
|
||||
/** Creates a new channel */
|
||||
createChannel(title: string, options?: CreateChannelOptions): Promise<CreateChannelResult | string>
|
||||
|
||||
/** Deletes the channel you created */
|
||||
deleteChannel(channelId: string): Promise<boolean>;
|
||||
|
||||
/** Subscribe to channel */
|
||||
subscribeToChannel(channelId: string): Promise<boolean>
|
||||
|
||||
/** Unsubscribe from channel */
|
||||
unsubscribeFromChannel(channelId: string, options?: UnsubscribeOptions): Promise<boolean>
|
||||
|
||||
/**
|
||||
* Searches for channels based on search criteria, there are some notes:
|
||||
* 1. The method finds only channels you are not subscribed to currently
|
||||
* 2. If you have never been subscribed to a found channel
|
||||
* or you have unsubscribed from it with {@link UnsubscribeOptions.deleteLocalModels} set to 'true',
|
||||
* the lastMessage property of a found channel will be 'null'
|
||||
*/
|
||||
searchChannels(searchOptions: SearchChannelsOptions): Promise<Array<Channel> | []>
|
||||
|
||||
/** Closes the client */
|
||||
destroy(): Promise<void>
|
||||
|
||||
|
|
@ -51,12 +81,18 @@ declare namespace WAWebJS {
|
|||
/** Get all blocked contacts by host account */
|
||||
getBlockedContacts(): Promise<Contact[]>
|
||||
|
||||
/** Get chat instance by ID */
|
||||
/** Gets chat or channel instance by ID */
|
||||
getChatById(chatId: string): Promise<Chat>
|
||||
|
||||
/** Gets a {@link Channel} instance by invite code */
|
||||
getChannelByInviteCode(inviteCode: string): Promise<Channel>
|
||||
|
||||
/** Get all current chat instances */
|
||||
getChats(): Promise<Chat[]>
|
||||
|
||||
/** Gets all cached {@link Channel} instances */
|
||||
getChannels(): Promise<Channel[]>
|
||||
|
||||
/** Get contact instance by ID */
|
||||
getContactById(contactId: string): Promise<Contact>
|
||||
|
||||
|
|
@ -132,6 +168,9 @@ declare namespace WAWebJS {
|
|||
/** Send a message to a specific chatId */
|
||||
sendMessage(chatId: string, content: MessageContent, options?: MessageSendOptions): Promise<Message>
|
||||
|
||||
/** Sends a channel admin invitation to a user, allowing them to become an admin of the channel */
|
||||
sendChannelAdminInvite(chatId: string, channelId: string, options?: { comment?: string }): Promise<boolean>
|
||||
|
||||
/** Searches for messages */
|
||||
searchMessages(query: string, options?: { chatId?: string, page?: number, limit?: number }): Promise<Message[]>
|
||||
|
||||
|
|
@ -217,13 +256,19 @@ declare namespace WAWebJS {
|
|||
deleteProfilePicture(): Promise<boolean>
|
||||
|
||||
/** Gets an array of membership requests */
|
||||
getGroupMembershipRequests: (groupId: string) => Promise<Array<GroupMembershipRequest>>
|
||||
getGroupMembershipRequests(groupId: string): Promise<Array<GroupMembershipRequest>>
|
||||
|
||||
/** Approves membership requests if any */
|
||||
approveGroupMembershipRequests: (groupId: string, options: MembershipRequestActionOptions) => Promise<Array<MembershipRequestActionResult>>;
|
||||
approveGroupMembershipRequests(groupId: string, options: MembershipRequestActionOptions): Promise<Array<MembershipRequestActionResult>>;
|
||||
|
||||
/** Rejects membership requests if any */
|
||||
rejectGroupMembershipRequests: (groupId: string, options: MembershipRequestActionOptions) => Promise<Array<MembershipRequestActionResult>>;
|
||||
rejectGroupMembershipRequests(groupId: string, options: MembershipRequestActionOptions): Promise<Array<MembershipRequestActionResult>>;
|
||||
|
||||
/**
|
||||
* Transfers a channel ownership to another user.
|
||||
* Note: the user you are transferring the channel ownership to must be a channel admin.
|
||||
*/
|
||||
transferChannelOwnership(channelId: string, newOwnerId: string, options?: TransferChannelOwnershipOptions): Promise<boolean>;
|
||||
|
||||
/** Generic event */
|
||||
on(event: string, listener: (...args: any) => void): this
|
||||
|
|
@ -655,6 +700,46 @@ declare namespace WAWebJS {
|
|||
};
|
||||
}
|
||||
|
||||
/** An object that handles options for channel creation */
|
||||
export interface CreateChannelOptions {
|
||||
/** The channel description */
|
||||
description?: string,
|
||||
/** The channel profile picture */
|
||||
picture?: MessageMedia
|
||||
}
|
||||
|
||||
/** An object that handles the result for createGroup method */
|
||||
export interface CreateChannelResult {
|
||||
/** A channel title */
|
||||
title: string,
|
||||
/** An object that handles the newly created channel ID */
|
||||
nid: ChatId,
|
||||
/** The channel invite link, starts with 'https://whatsapp.com/channel/' */
|
||||
inviteLink: string,
|
||||
/** The timestamp the channel was created at */
|
||||
createdAtTs: number
|
||||
}
|
||||
|
||||
/** Options for unsubscribe from a channel */
|
||||
export interface UnsubscribeOptions {
|
||||
/**
|
||||
* If true, after an unsubscription, it will completely remove a channel from the channel collection
|
||||
* making it seem like the current user have never interacted with it.
|
||||
* Otherwise it will only remove a channel from the list of channels the current user is subscribed to
|
||||
* and will set the membership type for that channel to GUEST
|
||||
*/
|
||||
deleteLocalModels?: boolean
|
||||
}
|
||||
|
||||
/** Options for searching for channels */
|
||||
export interface SearchChannelsOptions {
|
||||
searchText?: string;
|
||||
countryCodes?: string[];
|
||||
skipSubscribedNewsletters?: boolean;
|
||||
view?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface GroupNotification {
|
||||
/** ContactId for the user that produced the GroupNotification */
|
||||
author: string,
|
||||
|
|
@ -1471,8 +1556,8 @@ declare namespace WAWebJS {
|
|||
mute: (unmuteDate?: Date) => Promise<{ isMuted: boolean, muteExpiration: number }>,
|
||||
/** Send a message to this chat */
|
||||
sendMessage: (content: MessageContent, options?: MessageSendOptions) => Promise<Message>,
|
||||
/** Set the chat as seen */
|
||||
sendSeen: () => Promise<void>,
|
||||
/** Sets the chat as seen */
|
||||
sendSeen: () => Promise<boolean>,
|
||||
/** Simulate recording audio in chat. This will last for 25 seconds */
|
||||
sendStateRecording: () => Promise<void>,
|
||||
/** Simulate typing in chat. This will last for 25 seconds. */
|
||||
|
|
@ -1493,6 +1578,99 @@ declare namespace WAWebJS {
|
|||
syncHistory: () => Promise<boolean>
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
/** ID that represents the channel */
|
||||
id: {
|
||||
server: string;
|
||||
user: string;
|
||||
_serialized: string;
|
||||
};
|
||||
/** Title of the channel */
|
||||
name: string;
|
||||
/** The channel description */
|
||||
description: string;
|
||||
/** Indicates if it is a Channel */
|
||||
isChannel: boolean;
|
||||
/** Indicates if it is a Group */
|
||||
isGroup: boolean;
|
||||
/** Indicates if the channel is readonly */
|
||||
isReadOnly: boolean;
|
||||
/** Amount of messages unread */
|
||||
unreadCount: number;
|
||||
/** Unix timestamp for when the last activity occurred */
|
||||
timestamp: number;
|
||||
/** Indicates if the channel is muted or not */
|
||||
isMuted: boolean;
|
||||
/** Unix timestamp for when the mute expires */
|
||||
muteExpiration: number;
|
||||
/** Last message in the channel */
|
||||
lastMessage: Message | undefined;
|
||||
|
||||
/** Gets the subscribers of the channel (only those who are in your contact list) */
|
||||
getSubscribers(limit?: number): Promise<{contact: Contact, role: string}[]>;
|
||||
/** Updates the channel subject */
|
||||
setSubject(newSubject: string): Promise<boolean>;
|
||||
/** Updates the channel description */
|
||||
setDescription(newDescription: string): Promise<boolean>;
|
||||
/** Updates the channel profile picture */
|
||||
setProfilePicture(newProfilePicture: MessageMedia): Promise<boolean>;
|
||||
/**
|
||||
* Updates available reactions to use in the channel
|
||||
*
|
||||
* Valid values for passing to the method are:
|
||||
* 0 for NONE reactions to be avaliable
|
||||
* 1 for BASIC reactions to be available: 👍, ❤️, 😂, 😮, 😢, 🙏
|
||||
* 2 for ALL reactions to be available
|
||||
*/
|
||||
setReactionSetting(reactionCode: number): Promise<boolean>;
|
||||
/** Mutes the channel */
|
||||
mute(): Promise<boolean>;
|
||||
/** Unmutes the channel */
|
||||
unmute(): Promise<boolean>;
|
||||
/** Sends a message to this channel */
|
||||
sendMessage(content: string|MessageMedia, options?: MessageSendChannelOptions): Promise<Message>;
|
||||
/** Sets the channel as seen */
|
||||
sendSeen(): Promise<boolean>;
|
||||
/** Sends a channel admin invitation to a user, allowing them to become an admin of the channel */
|
||||
sendChannelAdminInvite(chatId: string, options?: { comment?: string }): Promise<boolean>;
|
||||
/** Accepts a channel admin invitation and promotes the current user to a channel admin */
|
||||
acceptChannelAdminInvite(): Promise<boolean>;
|
||||
/** Revokes a channel admin invitation sent to a user by a channel owner */
|
||||
revokeChannelAdminInvite(userId: string): Promise<boolean>;
|
||||
/** Demotes a channel admin to a regular subscriber (can be used also for self-demotion) */
|
||||
demoteChannelAdmin(userId: string): Promise<boolean>;
|
||||
/** Loads channel messages, sorted from earliest to latest */
|
||||
fetchMessages: (searchOptions: MessageSearchOptions) => Promise<Message[]>;
|
||||
/**
|
||||
* Transfers a channel ownership to another user.
|
||||
* Note: the user you are transferring the channel ownership to must be a channel admin.
|
||||
*/
|
||||
transferChannelOwnership(newOwnerId: string, options?: TransferChannelOwnershipOptions): Promise<boolean>;
|
||||
/** Deletes the channel you created */
|
||||
deleteChannel(): Promise<boolean>;
|
||||
}
|
||||
|
||||
/** Options for transferring a channel ownership to another user */
|
||||
export interface TransferChannelOwnershipOptions {
|
||||
/**
|
||||
* If true, after the channel ownership is being transferred to another user,
|
||||
* the current user will be dismissed as a channel admin and will become to a channel subscriber.
|
||||
*/
|
||||
shouldDismissSelfAsAdmin?: boolean
|
||||
}
|
||||
|
||||
/** Options for sending a message */
|
||||
export interface MessageSendChannelOptions {
|
||||
/** Image or videos caption */
|
||||
caption?: string
|
||||
/** User IDs of user that will be mentioned in the message */
|
||||
mentions?: string[]
|
||||
/** Image or video to be sent */
|
||||
media?: MessageMedia
|
||||
/** Extra options */
|
||||
extra?: any
|
||||
}
|
||||
|
||||
export interface MessageSearchOptions {
|
||||
/**
|
||||
* The amount of messages to return. If no limit is specified, the available messages will be returned.
|
||||
|
|
|
|||
1
index.js
1
index.js
|
|
@ -11,6 +11,7 @@ module.exports = {
|
|||
Chat: require('./src/structures/Chat'),
|
||||
PrivateChat: require('./src/structures/PrivateChat'),
|
||||
GroupChat: require('./src/structures/GroupChat'),
|
||||
Channel: require('./src/structures/Channel'),
|
||||
Message: require('./src/structures/Message'),
|
||||
MessageMedia: require('./src/structures/MessageMedia'),
|
||||
Contact: require('./src/structures/Contact'),
|
||||
|
|
|
|||
411
src/Client.js
411
src/Client.js
|
|
@ -15,7 +15,7 @@ const { LoadUtils } = require('./util/Injected/Utils');
|
|||
const ChatFactory = require('./factories/ChatFactory');
|
||||
const ContactFactory = require('./factories/ContactFactory');
|
||||
const WebCacheFactory = require('./webCache/WebCacheFactory');
|
||||
const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction, Broadcast} = require('./structures');
|
||||
const { Broadcast, Buttons, Call, ClientInfo, Contact, GroupNotification, Label, List, Location, Message, MessageMedia, Poll, PollVote, Reaction } = require('./structures');
|
||||
const NoAuth = require('./authStrategies/NoAuth');
|
||||
const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
|
||||
|
||||
|
|
@ -820,11 +820,9 @@ class Client extends EventEmitter {
|
|||
*
|
||||
*/
|
||||
async sendSeen(chatId) {
|
||||
const result = await this.pupPage.evaluate(async (chatId) => {
|
||||
return await this.pupPage.evaluate(async (chatId) => {
|
||||
return window.WWebJS.sendSeen(chatId);
|
||||
|
||||
}, chatId);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -854,6 +852,7 @@ class Client extends EventEmitter {
|
|||
* @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
|
||||
* @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
|
||||
* @property {MessageMedia} [media] - Media to be sent
|
||||
* @property {any} [extra] - Extra options
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -865,6 +864,19 @@ class Client extends EventEmitter {
|
|||
* @returns {Promise<Message>} Message that was just sent
|
||||
*/
|
||||
async sendMessage(chatId, content, options = {}) {
|
||||
const isChannel = /@\w*newsletter\b/.test(chatId);
|
||||
|
||||
if (isChannel && [
|
||||
options.sendMediaAsDocument, options.quotedMessageId,
|
||||
options.parseVCards, options.isViewOnce,
|
||||
content instanceof Location, content instanceof Contact,
|
||||
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 channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options.mentions) {
|
||||
!Array.isArray(options.mentions) && (options.mentions = [options.mentions]);
|
||||
if (options.mentions.some((possiblyContact) => possiblyContact instanceof Contact)) {
|
||||
|
|
@ -890,14 +902,14 @@ class Client extends EventEmitter {
|
|||
extraOptions: options.extra
|
||||
};
|
||||
|
||||
const sendSeen = typeof options.sendSeen === 'undefined' ? true : options.sendSeen;
|
||||
const sendSeen = options.sendSeen !== false;
|
||||
|
||||
if (content instanceof MessageMedia) {
|
||||
internalOptions.attachment = content;
|
||||
internalOptions.media = content;
|
||||
internalOptions.isViewOnce = options.isViewOnce,
|
||||
content = '';
|
||||
} else if (options.media instanceof MessageMedia) {
|
||||
internalOptions.attachment = options.media;
|
||||
internalOptions.media = options.media;
|
||||
internalOptions.caption = content;
|
||||
internalOptions.isViewOnce = options.isViewOnce,
|
||||
content = '';
|
||||
|
|
@ -914,17 +926,19 @@ class Client extends EventEmitter {
|
|||
internalOptions.contactCardList = content.map(contact => contact.id._serialized);
|
||||
content = '';
|
||||
} else if (content instanceof Buttons) {
|
||||
console.warn('Buttons are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.');
|
||||
if (content.type !== 'chat') { internalOptions.attachment = content.body; }
|
||||
internalOptions.buttons = content;
|
||||
content = '';
|
||||
} else if (content instanceof List) {
|
||||
console.warn('Lists are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.');
|
||||
internalOptions.list = content;
|
||||
content = '';
|
||||
}
|
||||
|
||||
if (internalOptions.sendMediaAsSticker && internalOptions.attachment) {
|
||||
internalOptions.attachment = await Util.formatToWebpSticker(
|
||||
internalOptions.attachment, {
|
||||
if (internalOptions.sendMediaAsSticker && internalOptions.media) {
|
||||
internalOptions.media = await Util.formatToWebpSticker(
|
||||
internalOptions.media, {
|
||||
name: options.stickerName,
|
||||
author: options.stickerAuthor,
|
||||
categories: options.stickerCategories
|
||||
|
|
@ -932,20 +946,60 @@ class Client extends EventEmitter {
|
|||
);
|
||||
}
|
||||
|
||||
const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => {
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
const chat = await window.Store.Chat.find(chatWid);
|
||||
const sentMsg = await this.pupPage.evaluate(async (chatId, content, options, sendSeen) => {
|
||||
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
|
||||
if (!chat) return null;
|
||||
|
||||
if (sendSeen) {
|
||||
await window.WWebJS.sendSeen(chatId);
|
||||
}
|
||||
|
||||
const msg = await window.WWebJS.sendMessage(chat, message, options, sendSeen);
|
||||
return window.WWebJS.getMessageModel(msg);
|
||||
const msg = await window.WWebJS.sendMessage(chat, content, options);
|
||||
return msg
|
||||
? window.WWebJS.getMessageModel(msg)
|
||||
: undefined;
|
||||
}, chatId, content, internalOptions, sendSeen);
|
||||
|
||||
return new Message(this, newMessage);
|
||||
return sentMsg
|
||||
? new Message(this, sentMsg)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SendChannelAdminInviteOptions
|
||||
* @property {?string} comment The comment to be added to an invitation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a channel admin invitation to a user, allowing them to become an admin of the channel
|
||||
* @param {string} chatId The ID of a user to send the channel admin invitation to
|
||||
* @param {string} channelId The ID of a channel for which the invitation is being sent
|
||||
* @param {SendChannelAdminInviteOptions} options
|
||||
* @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise
|
||||
*/
|
||||
async sendChannelAdminInvite(chatId, channelId, options = {}) {
|
||||
const response = await this.pupPage.evaluate(async (chatId, channelId, options) => {
|
||||
const channelWid = window.Store.WidFactory.createWid(channelId);
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
const chat = window.Store.Chat.get(chatWid) || (await window.Store.Chat.find(chatWid));
|
||||
|
||||
if (!chatWid.isUser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await window.Store.SendChannelMessage.sendNewsletterAdminInviteMessage(
|
||||
chat,
|
||||
{
|
||||
newsletterWid: channelWid,
|
||||
invitee: chatWid,
|
||||
inviteMessage: options.comment,
|
||||
base64Thumb: await window.WWebJS.getProfilePicThumbToBase64(channelWid)
|
||||
}
|
||||
);
|
||||
}, chatId, channelId, options);
|
||||
|
||||
return response.messageSendResult === 'OK';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -971,7 +1025,7 @@ class Client extends EventEmitter {
|
|||
* @returns {Promise<Array<Chat>>}
|
||||
*/
|
||||
async getChats() {
|
||||
let chats = await this.pupPage.evaluate(async () => {
|
||||
const chats = await this.pupPage.evaluate(async () => {
|
||||
return await window.WWebJS.getChats();
|
||||
});
|
||||
|
||||
|
|
@ -979,16 +1033,51 @@ class Client extends EventEmitter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get chat instance by ID
|
||||
* Gets all cached {@link Channel} instance
|
||||
* @returns {Promise<Array<Channel>>}
|
||||
*/
|
||||
async getChannels() {
|
||||
const channels = await this.pupPage.evaluate(async () => {
|
||||
return await window.WWebJS.getChannels();
|
||||
});
|
||||
|
||||
return channels.map((channel) => ChatFactory.create(this, channel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets chat or channel instance by ID
|
||||
* @param {string} chatId
|
||||
* @returns {Promise<Chat>}
|
||||
* @returns {Promise<Chat|Channel>}
|
||||
*/
|
||||
async getChatById(chatId) {
|
||||
let chat = await this.pupPage.evaluate(async chatId => {
|
||||
const chat = await this.pupPage.evaluate(async chatId => {
|
||||
return await window.WWebJS.getChat(chatId);
|
||||
}, chatId);
|
||||
return chat
|
||||
? ChatFactory.create(this, chat)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
return ChatFactory.create(this, chat);
|
||||
/**
|
||||
* Gets a {@link Channel} instance by invite code
|
||||
* @param {string} inviteCode The code that comes after the 'https://whatsapp.com/channel/'
|
||||
* @returns {Promise<Channel>}
|
||||
*/
|
||||
async getChannelByInviteCode(inviteCode) {
|
||||
const channel = await this.pupPage.evaluate(async (inviteCode) => {
|
||||
let channelMetadata;
|
||||
try {
|
||||
channelMetadata = await window.WWebJS.getChannelMetadata(inviteCode);
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return null;
|
||||
throw err;
|
||||
}
|
||||
return await window.WWebJS.getChat(channelMetadata.id);
|
||||
}, inviteCode);
|
||||
|
||||
return channel
|
||||
? ChatFactory.create(this, channel)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1058,6 +1147,61 @@ class Client extends EventEmitter {
|
|||
return res.gid._serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a channel admin invitation and promotes the current user to a channel admin
|
||||
* @param {string} channelId The channel ID to accept the admin invitation from
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async acceptChannelAdminInvite(channelId) {
|
||||
return await this.pupPage.evaluate(async (channelId) => {
|
||||
try {
|
||||
await window.Store.ChannelUtils.acceptNewsletterAdminInvite(channelId);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
}, channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes a channel admin invitation sent to a user by a channel owner
|
||||
* @param {string} channelId The channel ID an invitation belongs to
|
||||
* @param {string} userId The user ID the invitation was sent to
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async revokeChannelAdminInvite(channelId, userId) {
|
||||
return await this.pupPage.evaluate(async (channelId, userId) => {
|
||||
try {
|
||||
const userWid = window.Store.WidFactory.createWid(userId);
|
||||
await window.Store.ChannelUtils.revokeNewsletterAdminInvite(channelId, userWid);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
}, channelId, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demotes a channel admin to a regular subscriber (can be used also for self-demotion)
|
||||
* @param {string} channelId The channel ID to demote an admin in
|
||||
* @param {string} userId The user ID to demote
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async demoteChannelAdmin(channelId, userId) {
|
||||
return await this.pupPage.evaluate(async (channelId, userId) => {
|
||||
try {
|
||||
const userWid = window.Store.WidFactory.createWid(userId);
|
||||
await window.Store.ChannelUtils.demoteNewsletterAdmin(channelId, userWid);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
}, channelId, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a private invitation to join a group
|
||||
* @param {object} inviteInfo Invite V4 Info
|
||||
|
|
@ -1134,7 +1278,7 @@ class Client extends EventEmitter {
|
|||
*/
|
||||
async archiveChat(chatId) {
|
||||
return await this.pupPage.evaluate(async chatId => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
await window.Store.Cmd.archiveChat(chat, true);
|
||||
return true;
|
||||
}, chatId);
|
||||
|
|
@ -1146,7 +1290,7 @@ class Client extends EventEmitter {
|
|||
*/
|
||||
async unarchiveChat(chatId) {
|
||||
return await this.pupPage.evaluate(async chatId => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
await window.Store.Cmd.archiveChat(chat, false);
|
||||
return false;
|
||||
}, chatId);
|
||||
|
|
@ -1158,7 +1302,7 @@ class Client extends EventEmitter {
|
|||
*/
|
||||
async pinChat(chatId) {
|
||||
return this.pupPage.evaluate(async chatId => {
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
if (chat.pin) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1181,7 +1325,7 @@ class Client extends EventEmitter {
|
|||
*/
|
||||
async unpinChat(chatId) {
|
||||
return this.pupPage.evaluate(async chatId => {
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
if (!chat.pin) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1233,7 +1377,7 @@ class Client extends EventEmitter {
|
|||
*/
|
||||
async markChatUnread(chatId) {
|
||||
await this.pupPage.evaluate(async chatId => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
await window.Store.Cmd.markChatUnread(chat, true);
|
||||
}, chatId);
|
||||
}
|
||||
|
|
@ -1435,7 +1579,7 @@ class Client extends EventEmitter {
|
|||
for (const participant of createGroupResult.participants) {
|
||||
let isInviteV4Sent = false;
|
||||
const participantId = participant.wid._serialized;
|
||||
const statusCode = participant.error ?? 200;
|
||||
const statusCode = participant.error || 200;
|
||||
|
||||
if (autoSendInviteV4 && statusCode === 403) {
|
||||
window.Store.Contact.gadd(participant.wid, { silent: true });
|
||||
|
|
@ -1474,6 +1618,219 @@ class Client extends EventEmitter {
|
|||
}, title, participants, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that handles the result for {@link createChannel} method
|
||||
* @typedef {Object} CreateChannelResult
|
||||
* @property {string} title A channel title
|
||||
* @property {ChatId} nid An object that handels the newly created channel ID
|
||||
* @property {string} nid.server 'newsletter'
|
||||
* @property {string} nid.user 'XXXXXXXXXX'
|
||||
* @property {string} nid._serialized 'XXXXXXXXXX@newsletter'
|
||||
* @property {string} inviteLink The channel invite link, starts with 'https://whatsapp.com/channel/'
|
||||
* @property {number} createdAtTs The timestamp the channel was created at
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for the channel creation
|
||||
* @typedef {Object} CreateChannelOptions
|
||||
* @property {?string} description The channel description
|
||||
* @property {?MessageMedia} picture The channel profile picture
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new channel
|
||||
* @param {string} title The channel name
|
||||
* @param {CreateChannelOptions} options
|
||||
* @returns {Promise<CreateChannelResult|string>} Returns an object that handles the result for the channel creation or an error message as a string
|
||||
*/
|
||||
async createChannel(title, options = {}) {
|
||||
return await this.pupPage.evaluate(async (title, options) => {
|
||||
let response, { description = null, picture = null } = options;
|
||||
|
||||
if (!window.Store.ChannelUtils.isNewsletterCreationEnabled()) {
|
||||
return 'CreateChannelError: A channel creation is not enabled';
|
||||
}
|
||||
|
||||
if (picture) {
|
||||
picture = await window.WWebJS.cropAndResizeImage(picture, {
|
||||
asDataUrl: true,
|
||||
mimetype: 'image/jpeg',
|
||||
size: 640,
|
||||
quality: 1
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
response = await window.Store.ChannelUtils.createNewsletterQuery({
|
||||
name: title,
|
||||
description: description,
|
||||
picture: picture,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') {
|
||||
return 'CreateChannelError: An error occupied while creating a channel';
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return {
|
||||
title: title,
|
||||
nid: window.Store.JidToWid.newsletterJidToWid(response.idJid),
|
||||
inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`,
|
||||
createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue
|
||||
};
|
||||
}, title, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to channel
|
||||
* @param {string} channelId The channel ID
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async subscribeToChannel(channelId) {
|
||||
return await this.pupPage.evaluate(async (channelId) => {
|
||||
return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Subscribe');
|
||||
}, channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for unsubscribe from a channel
|
||||
* @typedef {Object} UnsubscribeOptions
|
||||
* @property {boolean} [deleteLocalModels = false] If true, after an unsubscription, it will completely remove a channel from the channel collection making it seem like the current user have never interacted with it. Otherwise it will only remove a channel from the list of channels the current user is subscribed to and will set the membership type for that channel to GUEST
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unsubscribe from channel
|
||||
* @param {string} channelId The channel ID
|
||||
* @param {UnsubscribeOptions} options
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async unsubscribeFromChannel(channelId, options) {
|
||||
return await this.pupPage.evaluate(async (channelId, options) => {
|
||||
return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Unsubscribe', options);
|
||||
}, channelId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for transferring a channel ownership to another user
|
||||
* @typedef {Object} TransferChannelOwnershipOptions
|
||||
* @property {boolean} [shouldDismissSelfAsAdmin = false] If true, after the channel ownership is being transferred to another user, the current user will be dismissed as a channel admin and will become to a channel subscriber.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transfers a channel ownership to another user.
|
||||
* Note: the user you are transferring the channel ownership to must be a channel admin.
|
||||
* @param {string} channelId
|
||||
* @param {string} newOwnerId
|
||||
* @param {TransferChannelOwnershipOptions} options
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async transferChannelOwnership(channelId, newOwnerId, options = {}) {
|
||||
return await this.pupPage.evaluate(async (channelId, newOwnerId, options) => {
|
||||
const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
|
||||
const newOwner = window.Store.Contact.get(newOwnerId) || (await window.Store.Contact.find(newOwnerId));
|
||||
if (!channel.newsletterMetadata) {
|
||||
await window.Store.NewsletterMetadataCollection.update(channel.id);
|
||||
}
|
||||
|
||||
try {
|
||||
await window.Store.ChannelUtils.changeNewsletterOwnerAction(channel, newOwner);
|
||||
|
||||
if (options.shouldDismissSelfAsAdmin) {
|
||||
const meContact = window.Store.ContactCollection.getMeContact();
|
||||
meContact && (await window.Store.ChannelUtils.demoteNewsletterAdminAction(channel, meContact));
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, channelId, newOwnerId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for channels based on search criteria, there are some notes:
|
||||
* 1. The method finds only channels you are not subscribed to currently
|
||||
* 2. If you have never been subscribed to a found channel
|
||||
* or you have unsubscribed from it with {@link UnsubscribeOptions.deleteLocalModels} set to 'true',
|
||||
* the lastMessage property of a found channel will be 'null'
|
||||
*
|
||||
* @param {Object} searchOptions Search options
|
||||
* @param {string} [searchOptions.searchText = ''] Text to search
|
||||
* @param {Array<string>} [searchOptions.countryCodes = [your local region]] Array of country codes in 'ISO 3166-1 alpha-2' standart (@see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) to search for channels created in these countries
|
||||
* @param {boolean} [searchOptions.skipSubscribedNewsletters = false] If true, channels that user is subscribed to won't appear in found channels
|
||||
* @param {number} [searchOptions.view = 0] View type, makes sense only when the searchText is empty. Valid values to provide are:
|
||||
* 0 for RECOMMENDED channels
|
||||
* 1 for TRENDING channels
|
||||
* 2 for POPULAR channels
|
||||
* 3 for NEW channels
|
||||
* @param {number} [searchOptions.limit = 50] The limit of found channels to be appear in the returnig result
|
||||
* @returns {Promise<Array<Channel>|[]>} Returns an array of Channel objects or an empty array if no channels were found
|
||||
*/
|
||||
async searchChannels(searchOptions = {}) {
|
||||
return await this.pupPage.evaluate(async ({
|
||||
searchText = '',
|
||||
countryCodes = [window.Store.ChannelUtils.currentRegion],
|
||||
skipSubscribedNewsletters = false,
|
||||
view = 0,
|
||||
limit = 50
|
||||
}) => {
|
||||
searchText = searchText.trim();
|
||||
const currentRegion = window.Store.ChannelUtils.currentRegion;
|
||||
if (![0, 1, 2, 3].includes(view)) view = 0;
|
||||
|
||||
countryCodes = countryCodes.length === 1 && countryCodes[0] === currentRegion
|
||||
? countryCodes
|
||||
: countryCodes.filter((code) => Object.keys(window.Store.ChannelUtils.countryCodesIso).includes(code));
|
||||
|
||||
const viewTypeMapping = {
|
||||
0: 'RECOMMENDED',
|
||||
1: 'TRENDING',
|
||||
2: 'POPULAR',
|
||||
3: 'NEW'
|
||||
};
|
||||
|
||||
searchOptions = {
|
||||
searchText: searchText,
|
||||
countryCodes: countryCodes,
|
||||
skipSubscribedNewsletters: skipSubscribedNewsletters,
|
||||
view: viewTypeMapping[view],
|
||||
categories: [],
|
||||
cursorToken: ''
|
||||
};
|
||||
|
||||
const originalFunction = window.Store.ChannelUtils.getNewsletterDirectoryPageSize;
|
||||
limit !== 50 && (window.Store.ChannelUtils.getNewsletterDirectoryPageSize = () => limit);
|
||||
|
||||
const channels = (await window.Store.ChannelUtils.fetchNewsletterDirectories(searchOptions)).newsletters;
|
||||
|
||||
limit !== 50 && (window.Store.ChannelUtils.getNewsletterDirectoryPageSize = originalFunction);
|
||||
|
||||
return channels
|
||||
? await Promise.all(channels.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true })))
|
||||
: [];
|
||||
}, searchOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the channel you created
|
||||
* @param {string} channelId The ID of a channel to delete
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async deleteChannel(channelId) {
|
||||
return await this.client.pupPage.evaluate(async (channelId) => {
|
||||
const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
|
||||
if (!channel) return false;
|
||||
try {
|
||||
await window.Store.ChannelUtils.deleteNewsletterAction(channel);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
}, channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all current Labels
|
||||
* @returns {Promise<Array<Label>>}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
const PrivateChat = require('../structures/PrivateChat');
|
||||
const GroupChat = require('../structures/GroupChat');
|
||||
const Channel = require('../structures/Channel');
|
||||
|
||||
class ChatFactory {
|
||||
static create(client, data) {
|
||||
if(data.isGroup) {
|
||||
if (data.isGroup) {
|
||||
return new GroupChat(client, data);
|
||||
}
|
||||
|
||||
if (data.isChannel) {
|
||||
return new Channel(client, data);
|
||||
}
|
||||
|
||||
return new PrivateChat(client, data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
382
src/structures/Channel.js
Normal file
382
src/structures/Channel.js
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
'use strict';
|
||||
|
||||
const Base = require('./Base');
|
||||
const Message = require('./Message');
|
||||
|
||||
/**
|
||||
* Channel ID structure
|
||||
* @typedef {Object} ChannelId
|
||||
* @property {string} server
|
||||
* @property {string} user
|
||||
* @property {string} _serialized
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a Channel on WhatsApp
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Channel extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
if (data) this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
this.channelMetadata = data.channelMetadata;
|
||||
|
||||
/**
|
||||
* ID that represents the channel
|
||||
* @type {ChannelId}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
/**
|
||||
* Title of the channel
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
|
||||
/**
|
||||
* The channel description
|
||||
* @type {string}
|
||||
*/
|
||||
this.description = data.channelMetadata.description;
|
||||
|
||||
/**
|
||||
* Indicates if it is a Channel
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isChannel = data.isChannel;
|
||||
|
||||
/**
|
||||
* Indicates if it is a Group
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isGroup = data.isGroup;
|
||||
|
||||
/**
|
||||
* Indicates if the channel is readonly
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isReadOnly = data.isReadOnly;
|
||||
|
||||
/**
|
||||
* Amount of messages unread
|
||||
* @type {number}
|
||||
*/
|
||||
this.unreadCount = data.unreadCount;
|
||||
|
||||
/**
|
||||
* Unix timestamp for when the last activity occurred
|
||||
* @type {number}
|
||||
*/
|
||||
this.timestamp = data.t;
|
||||
|
||||
/**
|
||||
* Indicates if the channel is muted or not
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isMuted = data.isMuted;
|
||||
|
||||
/**
|
||||
* Unix timestamp for when the mute expires
|
||||
* @type {number}
|
||||
*/
|
||||
this.muteExpiration = data.muteExpiration;
|
||||
|
||||
/**
|
||||
* Last message in the channel
|
||||
* @type {Message}
|
||||
*/
|
||||
this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined;
|
||||
|
||||
return super._patch(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subscribers of the channel (only those who are in your contact list)
|
||||
* @param {?number} limit Optional parameter to specify the limit of subscribers to retrieve
|
||||
* @returns {Promise<{contact: Contact, role: string}[]>} Returns an array of objects that handle the subscribed contacts and their roles in the channel
|
||||
*/
|
||||
async getSubscribers(limit) {
|
||||
return await this.client.pupPage.evaluate(async (channelId, limit) => {
|
||||
const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
|
||||
if (!channel) return [];
|
||||
!limit && (limit = window.Store.ChannelUtils.getMaxSubscriberNumber());
|
||||
const response = await window.Store.ChannelSubscribers.mexFetchNewsletterSubscribers(channelId, limit);
|
||||
const contacts = window.Store.ChannelSubscribers.getSubscribersInContacts(response.subscribers);
|
||||
return Promise.all(contacts.map((obj) => ({
|
||||
...obj,
|
||||
contact: window.WWebJS.getContactModel(obj.contact)
|
||||
})));
|
||||
}, this.id._serialized, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel subject
|
||||
* @param {string} newSubject
|
||||
* @returns {Promise<boolean>} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.
|
||||
*/
|
||||
async setSubject(newSubject) {
|
||||
const success = await this._setChannelMetadata({ name: newSubject }, { editName: true });
|
||||
success && (this.name = newSubject);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel description
|
||||
* @param {string} newDescription
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async setDescription(newDescription) {
|
||||
const success = await this._setChannelMetadata({ description: newDescription }, { editDescription: true });
|
||||
success && (this.description = newDescription);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel profile picture
|
||||
* @param {MessageMedia} newProfilePicture
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async setProfilePicture(newProfilePicture) {
|
||||
return await this._setChannelMetadata({ picture: newProfilePicture }, { editPicture: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates available reactions to use in the channel
|
||||
*
|
||||
* Valid values for passing to the method are:
|
||||
* 0 for NONE reactions to be avaliable
|
||||
* 1 for BASIC reactions to be available: 👍, ❤️, 😂, 😮, 😢, 🙏
|
||||
* 2 for ALL reactions to be available
|
||||
* @param {number} reactionCode
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async setReactionSetting(reactionCode) {
|
||||
if (![0, 1, 2].includes(reactionCode)) return false;
|
||||
const reactionMapper = {
|
||||
0: 3,
|
||||
1: 1,
|
||||
2: 0
|
||||
};
|
||||
const success = await this._setChannelMetadata(
|
||||
{ reactionCodesSetting: reactionMapper[reactionCode] },
|
||||
{ editReactionCodesSetting: true }
|
||||
);
|
||||
success && (this.channelMetadata.reactionCodesSetting = reactionCode);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the channel
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async mute() {
|
||||
const success = await this._muteUnmuteChannel('MUTE');
|
||||
if (success) {
|
||||
this.isMuted = true;
|
||||
this.muteExpiration = -1;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the channel
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async unmute() {
|
||||
const success = await this._muteUnmuteChannel('UNMUTE');
|
||||
if (success) {
|
||||
this.isMuted = false;
|
||||
this.muteExpiration = 0;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message options
|
||||
* @typedef {Object} MessageSendOptions
|
||||
* @property {?string} caption Image or video caption
|
||||
* @property {?string[]} mentions User IDs of user that will be mentioned in the message
|
||||
* @property {?MessageMedia} media Image or video to be sent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a message to this channel
|
||||
* @param {string|MessageMedia} content
|
||||
* @param {?MessageSendOptions} options
|
||||
* @returns {Promise<Message>} Message that was just sent
|
||||
*/
|
||||
async sendMessage(content, options) {
|
||||
return this.client.sendMessage(this.id._serialized, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel as seen
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async sendSeen() {
|
||||
return this.client.sendSeen(this.id._serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SendChannelAdminInviteOptions
|
||||
* @property {?string} comment The comment to be added to an invitation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a channel admin invitation to a user, allowing them to become an admin of the channel
|
||||
* @param {string} chatId The ID of a user to send the channel admin invitation to
|
||||
* @param {SendChannelAdminInviteOptions} options
|
||||
* @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise
|
||||
*/
|
||||
async sendChannelAdminInvite(chatId, options = {}) {
|
||||
return this.client.sendChannelAdminInvite(chatId, this.id._serialized, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a channel admin invitation and promotes the current user to a channel admin
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async acceptChannelAdminInvite() {
|
||||
return this.client.acceptChannelAdminInvite(this.id._serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes a channel admin invitation sent to a user by a channel owner
|
||||
* @param {string} userId The user ID the invitation was sent to
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async revokeChannelAdminInvite(userId) {
|
||||
return this.client.revokeChannelAdminInvite(this.id._serialized, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demotes a channel admin to a regular subscriber (can be used also for self-demotion)
|
||||
* @param {string} userId The user ID to demote
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async demoteChannelAdmin(userId) {
|
||||
return this.client.demoteChannelAdmin(this.id._serialized, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for transferring a channel ownership to another user
|
||||
* @typedef {Object} TransferChannelOwnershipOptions
|
||||
* @property {boolean} [shouldDismissSelfAsAdmin = false] If true, after the channel ownership is being transferred to another user, the current user will be dismissed as a channel admin and will become to a channel subscriber.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transfers a channel ownership to another user.
|
||||
* Note: the user you are transferring the channel ownership to must be a channel admin.
|
||||
* @param {string} newOwnerId
|
||||
* @param {TransferChannelOwnershipOptions} options
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async transferChannelOwnership(newOwnerId, options = {}) {
|
||||
return this.client.transferChannelOwnership(this.id._serialized, newOwnerId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads channel messages, sorted from earliest to latest
|
||||
* @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported
|
||||
* @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages
|
||||
* @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined
|
||||
* @returns {Promise<Array<Message>>}
|
||||
*/
|
||||
async fetchMessages(searchOptions) {
|
||||
let messages = await this.client.pupPage.evaluate(async (channelId, searchOptions) => {
|
||||
const msgFilter = (m) => {
|
||||
if (m.isNotification || m.type === 'newsletter_notification') {
|
||||
return false; // dont include notification messages
|
||||
}
|
||||
if (searchOptions && searchOptions.fromMe !== undefined && m.id.fromMe !== searchOptions.fromMe) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
|
||||
let msgs = channel.msgs.getModelsArray().filter(msgFilter);
|
||||
|
||||
if (searchOptions && searchOptions.limit > 0) {
|
||||
while (msgs.length < searchOptions.limit) {
|
||||
const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(channel);
|
||||
if (!loadedMessages || !loadedMessages.length) break;
|
||||
msgs = [...loadedMessages.filter(msgFilter), ...msgs];
|
||||
}
|
||||
|
||||
if (msgs.length > searchOptions.limit) {
|
||||
msgs.sort((a, b) => (a.t > b.t) ? 1 : -1);
|
||||
msgs = msgs.splice(msgs.length - searchOptions.limit);
|
||||
}
|
||||
}
|
||||
|
||||
return msgs.map(m => window.WWebJS.getMessageModel(m));
|
||||
|
||||
}, this.id._serialized, searchOptions);
|
||||
|
||||
return messages.map((msg) => new Message(this.client, msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the channel you created
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async deleteChannel() {
|
||||
return this.client.deleteChannel(this.id._serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to change the channel metadata
|
||||
* @param {string|number|MessageMedia} value The new value to set
|
||||
* @param {string} property The property of a channel metadata to change
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async _setChannelMetadata(value, property) {
|
||||
return await this.client.pupPage.evaluate(async (channelId, value, property) => {
|
||||
const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
|
||||
if (!channel) return false;
|
||||
if (property.editPicture) {
|
||||
value.picture = value.picture
|
||||
? await window.WWebJS.cropAndResizeImage(value.picture, {
|
||||
asDataUrl: true,
|
||||
mimetype: 'image/jpeg',
|
||||
size: 640,
|
||||
quality: 1
|
||||
})
|
||||
: null;
|
||||
}
|
||||
try {
|
||||
await window.Store.ChannelUtils.editNewsletterMetadataAction(channel, property, value);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
}, this.id._serialized, value, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to mute or unmute the channel
|
||||
* @param {string} action The action: 'MUTE' or 'UNMUTE'
|
||||
* @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
|
||||
*/
|
||||
async _muteUnmuteChannel(action) {
|
||||
return await this.client.pupPage.evaluate(async (channelId, action) => {
|
||||
try {
|
||||
action === 'MUTE'
|
||||
? await window.Store.ChannelUtils.muteNewsletter([channelId])
|
||||
: await window.Store.ChannelUtils.unmuteNewsletter([channelId]);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
}, this.id._serialized, action);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Channel;
|
||||
|
|
@ -95,7 +95,7 @@ class Chat extends Base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the chat as seen
|
||||
* Sets the chat as seen
|
||||
* @returns {Promise<Boolean>} result
|
||||
*/
|
||||
async sendSeen() {
|
||||
|
|
@ -104,7 +104,7 @@ class Chat extends Base {
|
|||
|
||||
/**
|
||||
* Clears all messages from the chat
|
||||
* @returns {Promise<Boolean>} result
|
||||
* @returns {Promise<boolean>} result
|
||||
*/
|
||||
async clearMessages() {
|
||||
return this.client.pupPage.evaluate(chatId => {
|
||||
|
|
@ -201,7 +201,7 @@ class Chat extends Base {
|
|||
return true;
|
||||
};
|
||||
|
||||
const chat = window.Store.Chat.get(chatId);
|
||||
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
let msgs = chat.msgs.getModelsArray().filter(msgFilter);
|
||||
|
||||
if (searchOptions && searchOptions.limit > 0) {
|
||||
|
|
|
|||
|
|
@ -191,8 +191,7 @@ class GroupChat extends Chat {
|
|||
*/
|
||||
async removeParticipants(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 chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
const participants = participantIds.map(p => {
|
||||
return chat.groupMetadata.participants.get(p);
|
||||
}).filter(p => Boolean(p));
|
||||
|
|
@ -208,8 +207,7 @@ class GroupChat extends Chat {
|
|||
*/
|
||||
async promoteParticipants(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 chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
const participants = participantIds.map(p => {
|
||||
return chat.groupMetadata.participants.get(p);
|
||||
}).filter(p => Boolean(p));
|
||||
|
|
@ -225,8 +223,7 @@ class GroupChat extends Chat {
|
|||
*/
|
||||
async demoteParticipants(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 chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
const participants = participantIds.map(p => {
|
||||
return chat.groupMetadata.participants.get(p);
|
||||
}).filter(p => Boolean(p));
|
||||
|
|
@ -466,8 +463,7 @@ class GroupChat extends Chat {
|
|||
*/
|
||||
async leave() {
|
||||
await this.client.pupPage.evaluate(async chatId => {
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
const chat = await window.Store.Chat.find(chatWid);
|
||||
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
return window.Store.GroupUtils.sendExitGroup(chat);
|
||||
}, this.id._serialized);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class Message extends Base {
|
|||
* Indicates if the message has media available for download
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.hasMedia = Boolean(data.mediaKey && data.directPath);
|
||||
this.hasMedia = Boolean(data.directPath);
|
||||
|
||||
/**
|
||||
* Message content
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module.exports = {
|
|||
ClientInfo: require('./ClientInfo'),
|
||||
Contact: require('./Contact'),
|
||||
GroupChat: require('./GroupChat'),
|
||||
Channel: require('./Channel'),
|
||||
Location: require('./Location'),
|
||||
Message: require('./Message'),
|
||||
MessageMedia: require('./MessageMedia'),
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ exports.ExposeStore = () => {
|
|||
window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction');
|
||||
window.Store.User = window.require('WAWebUserPrefsMeUser');
|
||||
window.Store.ContactMethods = window.require('WAWebContactGetters');
|
||||
window.Store.UploadUtils = window.require('WAWebUploadManager');
|
||||
window.Store.UserConstructor = window.require('WAWebWid');
|
||||
window.Store.Validators = window.require('WALinkify');
|
||||
window.Store.VCard = window.require('WAWebFrontendVcardUtils');
|
||||
|
|
@ -95,22 +94,21 @@ exports.ExposeStore = () => {
|
|||
window.Store.ReplyUtils = window.require('WAWebMsgReply');
|
||||
window.Store.BotSecret = window.require('WAWebBotMessageSecret');
|
||||
window.Store.BotProfiles = window.require('WAWebBotProfileCollection');
|
||||
window.Store.ContactCollection = window.require('WAWebContactCollection').ContactCollection;
|
||||
window.Store.DeviceList = window.require('WAWebApiDeviceList');
|
||||
window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest');
|
||||
if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620'))
|
||||
window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
|
||||
window.Store.ChatGetters = window.require('WAWebChatGetters');
|
||||
|
||||
window.Store.Settings = {
|
||||
...window.require('WAWebUserPrefsGeneral'),
|
||||
...window.require('WAWebUserPrefsNotifications'),
|
||||
setPushname: window.require('WAWebSetPushnameConnAction').setPushname
|
||||
};
|
||||
|
||||
window.Store.NumberInfo = {
|
||||
...window.require('WAPhoneUtils'),
|
||||
...window.require('WAPhoneFindCC')
|
||||
};
|
||||
|
||||
window.Store.ForwardUtils = {
|
||||
...window.require('WAWebForwardMessagesToChat')
|
||||
};
|
||||
|
|
@ -141,6 +139,37 @@ exports.ExposeStore = () => {
|
|||
...window.require('WAWebApiMembershipApprovalRequestStore'),
|
||||
...window.require('WASmaxGroupsMembershipRequestsActionRPC')
|
||||
};
|
||||
window.Store.ChannelUtils = {
|
||||
...window.require('WAWebLoadNewsletterPreviewChatAction'),
|
||||
...window.require('WAWebNewsletterMetadataQueryJob'),
|
||||
...window.require('WAWebNewsletterCreateQueryJob'),
|
||||
...window.require('WAWebEditNewsletterMetadataAction'),
|
||||
...window.require('WAWebNewsletterDeleteAction'),
|
||||
...window.require('WAWebNewsletterSubscribeAction'),
|
||||
...window.require('WAWebNewsletterUnsubscribeAction'),
|
||||
...window.require('WAWebNewsletterDirectorySearchAction'),
|
||||
...window.require('WAWebNewsletterToggleMuteStateJob'),
|
||||
...window.require('WAWebNewsletterGatingUtils'),
|
||||
...window.require('WAWebNewsletterModelUtils'),
|
||||
...window.require('WAWebMexAcceptNewsletterAdminInviteJob'),
|
||||
...window.require('WAWebMexRevokeNewsletterAdminInviteJob'),
|
||||
...window.require('WAWebChangeNewsletterOwnerAction'),
|
||||
...window.require('WAWebDemoteNewsletterAdminAction'),
|
||||
...window.require('WAWebNewsletterDemoteAdminJob'),
|
||||
countryCodesIso: window.require('WAWebCountriesNativeCountryNames'),
|
||||
currentRegion: window.require('WAWebL10N').getRegion(),
|
||||
};
|
||||
window.Store.SendChannelMessage = {
|
||||
...window.require('WAWebNewsletterUpdateMsgsRecordsJob'),
|
||||
...window.require('WAWebMsgDataFromModel'),
|
||||
...window.require('WAWebNewsletterSendMessageJob'),
|
||||
...window.require('WAWebNewsletterSendMsgAction'),
|
||||
...window.require('WAMediaCalculateFilehash')
|
||||
};
|
||||
window.Store.ChannelSubscribers = {
|
||||
...window.require('WAWebMexFetchNewsletterSubscribersJob'),
|
||||
...window.require('WAWebNewsletterSubscriberListAction')
|
||||
};
|
||||
window.Store.AddressbookContactUtils = {
|
||||
...window.require('WAWebSaveContactAction'),
|
||||
...window.require('WAWebDeleteContactAction')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ exports.LoadUtils = () => {
|
|||
|
||||
window.WWebJS.forwardMessage = async (chatId, msgId) => {
|
||||
const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
|
||||
if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) {
|
||||
return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], true);
|
||||
|
|
@ -15,50 +15,51 @@ exports.LoadUtils = () => {
|
|||
};
|
||||
|
||||
window.WWebJS.sendSeen = async (chatId) => {
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
if (chat !== undefined) {
|
||||
await window.Store.SendSeen.sendSeen(chat, false);
|
||||
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
if (chat) {
|
||||
await window.Store.SendSeen.sendSeen(chat);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
window.WWebJS.sendMessage = async (chat, content, options = {}) => {
|
||||
let attOptions = {};
|
||||
if (options.attachment) {
|
||||
attOptions = options.sendMediaAsSticker
|
||||
? await window.WWebJS.processStickerData(options.attachment)
|
||||
: await window.WWebJS.processMediaData(options.attachment, {
|
||||
const isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
|
||||
|
||||
let mediaOptions = {};
|
||||
if (options.media) {
|
||||
mediaOptions = await window.WWebJS.processMediaData(
|
||||
options.media, {
|
||||
forceSticker: options.sendMediaAsSticker,
|
||||
forceGif: options.sendVideoAsGif,
|
||||
forceVoice: options.sendAudioAsVoice,
|
||||
forceDocument: options.sendMediaAsDocument,
|
||||
forceGif: options.sendVideoAsGif
|
||||
sendToChannel: isChannel
|
||||
});
|
||||
|
||||
attOptions.caption = options.caption;
|
||||
content = options.sendMediaAsSticker ? undefined : attOptions.preview;
|
||||
attOptions.isViewOnce = options.isViewOnce;
|
||||
|
||||
delete options.attachment;
|
||||
mediaOptions.caption = options.caption;
|
||||
content = options.sendMediaAsSticker ? undefined : mediaOptions.preview;
|
||||
mediaOptions.isViewOnce = options.isViewOnce;
|
||||
delete options.media;
|
||||
delete options.sendMediaAsSticker;
|
||||
}
|
||||
|
||||
let quotedMsgOptions = {};
|
||||
if (options.quotedMessageId) {
|
||||
let quotedMessage = await window.Store.Msg.getMessagesById([options.quotedMessageId]);
|
||||
let quotedMessage = window.Store.Msg.get(options.quotedMessageId);
|
||||
!quotedMessage && (quotedMessage = (await window.Store.Msg.getMessagesById([options.quotedMessageId]))?.messages?.[0]);
|
||||
|
||||
if (quotedMessage['messages'].length == 1) {
|
||||
quotedMessage = quotedMessage['messages'][0];
|
||||
|
||||
// TODO remove .canReply() once all clients are updated to >= v2.2241.6
|
||||
const canReply = window.Store.ReplyUtils ?
|
||||
window.Store.ReplyUtils.canReplyMsg(quotedMessage.unsafe()) :
|
||||
quotedMessage.canReply();
|
||||
const canReply = window.Store.ReplyUtils
|
||||
? window.Store.ReplyUtils.canReplyMsg(quotedMessage.unsafe())
|
||||
: quotedMessage.canReply();
|
||||
|
||||
if (canReply) {
|
||||
quotedMsgOptions = quotedMessage.msgContextInfo(chat);
|
||||
}
|
||||
}else{
|
||||
if(!options.ignoreQuoteErrors) {
|
||||
} else {
|
||||
if (!options.ignoreQuoteErrors) {
|
||||
throw new Error('Could not get the quoted message.');
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +107,7 @@ exports.LoadUtils = () => {
|
|||
const { pollName, pollOptions } = options.poll;
|
||||
const { allowMultipleAnswers, messageSecret } = options.poll.options;
|
||||
_pollOptions = {
|
||||
kind: 'pollCreation',
|
||||
type: 'poll_creation',
|
||||
pollName: pollName,
|
||||
pollOptions: pollOptions,
|
||||
|
|
@ -166,7 +168,7 @@ exports.LoadUtils = () => {
|
|||
}
|
||||
|
||||
let buttonOptions = {};
|
||||
if(options.buttons){
|
||||
if (options.buttons) {
|
||||
let caption;
|
||||
if (options.buttons.type === 'chat') {
|
||||
content = options.buttons.body;
|
||||
|
|
@ -214,18 +216,22 @@ exports.LoadUtils = () => {
|
|||
delete options.invokedBotWid;
|
||||
}
|
||||
|
||||
let meUser = window.Store.User.getMaybeMeUser();
|
||||
const lidUser = window.Store.User.getMaybeMeLidUser();
|
||||
const meUser = window.Store.User.getMaybeMeUser();
|
||||
const newId = await window.Store.MsgKey.newId();
|
||||
let from = chat.id.isLid() ? lidUser : meUser;
|
||||
let participant;
|
||||
|
||||
if (chat.id.isGroup() && chat.groupMetadata.isLidAddressingMode) {
|
||||
meUser = window.Store.User.getMaybeMeLidUser();
|
||||
if (chat.isGroup) {
|
||||
from = chat.groupMetadata && chat.groupMetadata.isLidAddressingMode ? lidUser : meUser;
|
||||
participant = window.Store.WidFactory.toUserWid(from);
|
||||
}
|
||||
|
||||
const newMsgId = new window.Store.MsgKey({
|
||||
from: meUser,
|
||||
const newMsgKey = new window.Store.MsgKey({
|
||||
from: from,
|
||||
to: chat.id,
|
||||
id: newId,
|
||||
participant: chat.id.isGroup() ? meUser : undefined,
|
||||
participant: participant,
|
||||
selfDir: 'out',
|
||||
});
|
||||
|
||||
|
|
@ -236,7 +242,7 @@ exports.LoadUtils = () => {
|
|||
|
||||
const message = {
|
||||
...options,
|
||||
id: newMsgId,
|
||||
id: newMsgKey,
|
||||
ack: 0,
|
||||
body: content,
|
||||
from: meUser,
|
||||
|
|
@ -247,11 +253,11 @@ exports.LoadUtils = () => {
|
|||
isNewMsg: true,
|
||||
type: 'chat',
|
||||
...ephemeralFields,
|
||||
...mediaOptions,
|
||||
...(mediaOptions.toJSON ? mediaOptions.toJSON() : {}),
|
||||
...quotedMsgOptions,
|
||||
...locationOptions,
|
||||
..._pollOptions,
|
||||
...attOptions,
|
||||
...(attOptions.toJSON ? attOptions.toJSON() : {}),
|
||||
...quotedMsgOptions,
|
||||
...vcardOptions,
|
||||
...buttonOptions,
|
||||
...listOptions,
|
||||
|
|
@ -264,12 +270,41 @@ exports.LoadUtils = () => {
|
|||
delete message.canonicalUrl;
|
||||
}
|
||||
|
||||
if (isChannel) {
|
||||
const msg = new window.Store.Msg.modelClass(message);
|
||||
const msgDataFromMsgModel = window.Store.SendChannelMessage.msgDataFromMsgModel(msg);
|
||||
const isMedia = Object.keys(mediaOptions).length > 0;
|
||||
await window.Store.SendChannelMessage.addNewsletterMsgsRecords([msgDataFromMsgModel]);
|
||||
chat.msgs.add(msg);
|
||||
chat.t = msg.t;
|
||||
|
||||
const sendChannelMsgResponse = await window.Store.SendChannelMessage.sendNewsletterMessageJob({
|
||||
msg: msg,
|
||||
type: message.type === 'chat' ? 'text' : isMedia ? 'media' : 'pollCreation',
|
||||
newsletterJid: chat.id.toJid(),
|
||||
...(isMedia
|
||||
? {
|
||||
mediaMetadata: msg.avParams(),
|
||||
mediaHandle: isMedia ? mediaOptions.mediaHandle : null,
|
||||
}
|
||||
: {}
|
||||
)
|
||||
});
|
||||
|
||||
if (sendChannelMsgResponse.success) {
|
||||
msg.t = sendChannelMsgResponse.ack.t;
|
||||
msg.serverId = sendChannelMsgResponse.serverId;
|
||||
}
|
||||
msg.updateAck(1, true);
|
||||
await window.Store.SendChannelMessage.updateNewsletterMsgRecord(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
await window.Store.SendMessage.addAndSendMsgToChat(chat, message);
|
||||
return window.Store.Msg.get(newMsgId._serialized);
|
||||
return window.Store.Msg.get(newMsgKey._serialized);
|
||||
};
|
||||
|
||||
window.WWebJS.editMessage = async (msg, content, options = {}) => {
|
||||
|
||||
const extraOptions = options.extraOptions || {};
|
||||
delete options.extraOptions;
|
||||
|
||||
|
|
@ -327,74 +362,51 @@ exports.LoadUtils = () => {
|
|||
};
|
||||
};
|
||||
|
||||
window.WWebJS.processStickerData = async (mediaInfo) => {
|
||||
if (mediaInfo.mimetype !== 'image/webp') throw new Error('Invalid media type');
|
||||
|
||||
window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, sendToChannel }) => {
|
||||
const file = window.WWebJS.mediaInfoToFile(mediaInfo);
|
||||
let filehash = await window.WWebJS.getFileHash(file);
|
||||
let mediaKey = await window.WWebJS.generateHash(32);
|
||||
|
||||
const controller = new AbortController();
|
||||
const uploadedInfo = await window.Store.UploadUtils.encryptAndUpload({
|
||||
blob: file,
|
||||
type: 'sticker',
|
||||
signal: controller.signal,
|
||||
mediaKey
|
||||
const opaqueData = await window.Store.OpaqueData.createFromData(file, file.type);
|
||||
const mediaPrep = window.Store.MediaPrep.prepRawMedia(
|
||||
opaqueData, {
|
||||
asSticker: forceSticker,
|
||||
asGif: forceGif,
|
||||
isPtt: forceVoice,
|
||||
asDocument: forceDocument
|
||||
});
|
||||
|
||||
const stickerInfo = {
|
||||
...uploadedInfo,
|
||||
clientUrl: uploadedInfo.url,
|
||||
deprecatedMms3Url: uploadedInfo.url,
|
||||
uploadhash: uploadedInfo.encFilehash,
|
||||
size: file.size,
|
||||
type: 'sticker',
|
||||
filehash
|
||||
};
|
||||
|
||||
return stickerInfo;
|
||||
};
|
||||
|
||||
window.WWebJS.processMediaData = async (mediaInfo, { forceVoice, forceDocument, forceGif }) => {
|
||||
const file = window.WWebJS.mediaInfoToFile(mediaInfo);
|
||||
const mData = await window.Store.OpaqueData.createFromData(file, file.type);
|
||||
const mediaPrep = window.Store.MediaPrep.prepRawMedia(mData, { asDocument: forceDocument });
|
||||
const mediaData = await mediaPrep.waitForPrep();
|
||||
const mediaObject = window.Store.MediaObject.getOrCreateMediaObject(mediaData.filehash);
|
||||
|
||||
const mediaType = window.Store.MediaTypes.msgToMediaType({
|
||||
type: mediaData.type,
|
||||
isGif: mediaData.isGif
|
||||
isGif: mediaData.isGif,
|
||||
isNewsletter: sendToChannel,
|
||||
});
|
||||
|
||||
if (forceVoice && mediaData.type === 'audio') {
|
||||
mediaData.type = 'ptt';
|
||||
if (forceVoice && mediaData.type === 'ptt') {
|
||||
const waveform = mediaObject.contentInfo.waveform;
|
||||
mediaData.waveform =
|
||||
waveform ?? await window.WWebJS.generateWaveform(file);
|
||||
}
|
||||
|
||||
if (forceGif && mediaData.type === 'video') {
|
||||
mediaData.isGif = true;
|
||||
}
|
||||
|
||||
if (forceDocument) {
|
||||
mediaData.type = 'document';
|
||||
waveform || await window.WWebJS.generateWaveform(file);
|
||||
}
|
||||
|
||||
if (!(mediaData.mediaBlob instanceof window.Store.OpaqueData)) {
|
||||
mediaData.mediaBlob = await window.Store.OpaqueData.createFromData(mediaData.mediaBlob, mediaData.mediaBlob.type);
|
||||
mediaData.mediaBlob = await window.Store.OpaqueData.createFromData(
|
||||
mediaData.mediaBlob,
|
||||
mediaData.mediaBlob.type
|
||||
);
|
||||
}
|
||||
|
||||
mediaData.renderableUrl = mediaData.mediaBlob.url();
|
||||
mediaObject.consolidate(mediaData.toJSON());
|
||||
mediaData.mediaBlob.autorelease();
|
||||
|
||||
const uploadedMedia = await window.Store.MediaUpload.uploadMedia({
|
||||
const dataToUpload = {
|
||||
mimetype: mediaData.mimetype,
|
||||
mediaObject,
|
||||
mediaType
|
||||
});
|
||||
mediaType,
|
||||
...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash } : {})
|
||||
};
|
||||
|
||||
const uploadedMedia = !sendToChannel
|
||||
? await window.Store.MediaUpload.uploadMedia(dataToUpload)
|
||||
: await window.Store.MediaUpload.uploadUnencryptedMedia(dataToUpload);
|
||||
|
||||
const mediaEntry = uploadedMedia.mediaEntry;
|
||||
if (!mediaEntry) {
|
||||
|
|
@ -412,13 +424,14 @@ exports.LoadUtils = () => {
|
|||
uploadhash: mediaEntry.uploadHash,
|
||||
size: mediaObject.size,
|
||||
streamingSidecar: mediaEntry.sidecar,
|
||||
firstFrameSidecar: mediaEntry.firstFrameSidecar
|
||||
firstFrameSidecar: mediaEntry.firstFrameSidecar,
|
||||
mediaHandle: sendToChannel ? mediaEntry.handle : null,
|
||||
});
|
||||
|
||||
return mediaData;
|
||||
};
|
||||
|
||||
window.WWebJS.getMessageModel = message => {
|
||||
window.WWebJS.getMessageModel = (message) => {
|
||||
const msg = message.serialize();
|
||||
|
||||
msg.isEphemeral = message.isEphemeral;
|
||||
|
|
@ -456,54 +469,115 @@ exports.LoadUtils = () => {
|
|||
return _vote;
|
||||
};
|
||||
|
||||
window.WWebJS.getChatModel = async chat => {
|
||||
window.WWebJS.getChat = async (chatId, { getAsModel = true } = {}) => {
|
||||
const isChannel = /@\w*newsletter\b/.test(chatId);
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
let chat;
|
||||
|
||||
let res = chat.serialize();
|
||||
res.isGroup = false;
|
||||
res.formattedTitle = chat.formattedTitle;
|
||||
res.isMuted = chat.mute?.expiration !== 0;
|
||||
|
||||
if (chat.groupMetadata) {
|
||||
res.isGroup = true;
|
||||
const chatWid = window.Store.WidFactory.createWid((chat.id._serialized));
|
||||
await window.Store.GroupMetadata.update(chatWid);
|
||||
chat.groupMetadata.participants._models
|
||||
.filter(x => x.id._serialized.endsWith('@lid'))
|
||||
.forEach(x => { x.id = x.contact.phoneNumber; });
|
||||
res.groupMetadata = chat.groupMetadata.serialize();
|
||||
res.isReadOnly = chat.groupMetadata.announce;
|
||||
if (isChannel) {
|
||||
try {
|
||||
chat = window.Store.NewsletterCollection.get(chatId);
|
||||
if (!chat) {
|
||||
await window.Store.ChannelUtils.loadNewsletterPreviewChat(chatId);
|
||||
chat = await window.Store.NewsletterCollection.find(chatWid);
|
||||
}
|
||||
} catch (err) {
|
||||
chat = null;
|
||||
}
|
||||
} else {
|
||||
chat = window.Store.Chat.get(chatWid) || (await window.Store.Chat.find(chatWid));
|
||||
}
|
||||
|
||||
res.lastMessage = null;
|
||||
if (res.msgs && res.msgs.length) {
|
||||
const lastMessage = chat.lastReceivedKey
|
||||
? window.Store.Msg.get(chat.lastReceivedKey._serialized) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0]
|
||||
: null;
|
||||
if (lastMessage) {
|
||||
res.lastMessage = window.WWebJS.getMessageModel(lastMessage);
|
||||
}
|
||||
}
|
||||
|
||||
delete res.msgs;
|
||||
delete res.msgUnsyncedButtonReplyMsgs;
|
||||
delete res.unsyncedButtonReplies;
|
||||
|
||||
return res;
|
||||
return getAsModel && chat
|
||||
? await window.WWebJS.getChatModel(chat, { isChannel: isChannel })
|
||||
: chat;
|
||||
};
|
||||
|
||||
window.WWebJS.getChat = async chatId => {
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
const chat = await window.Store.Chat.find(chatWid);
|
||||
return await window.WWebJS.getChatModel(chat);
|
||||
window.WWebJS.getChannelMetadata = async (inviteCode) => {
|
||||
const response =
|
||||
await window.Store.ChannelUtils.queryNewsletterMetadataByInviteCode(
|
||||
inviteCode,
|
||||
window.Store.ChannelUtils.getRoleByIdentifier(inviteCode)
|
||||
);
|
||||
|
||||
const picUrl = response.newsletterPictureMetadataMixin?.picture[0]?.queryPictureDirectPathOrEmptyResponseMixinGroup.value.directPath;
|
||||
|
||||
return {
|
||||
id: response.idJid,
|
||||
createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue,
|
||||
titleMetadata: {
|
||||
title: response.newsletterNameMetadataMixin.nameElementValue,
|
||||
updatedAtTs: response.newsletterNameMetadataMixin.nameUpdateTime
|
||||
},
|
||||
descriptionMetadata: {
|
||||
description: response.newsletterDescriptionMetadataMixin.descriptionQueryDescriptionResponseMixin.elementValue,
|
||||
updatedAtTs: response.newsletterDescriptionMetadataMixin.descriptionQueryDescriptionResponseMixin.updateTime
|
||||
},
|
||||
inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`,
|
||||
membershipType: window.Store.ChannelUtils.getRoleByIdentifier(inviteCode),
|
||||
stateType: response.newsletterStateMetadataMixin.stateType,
|
||||
pictureUrl: picUrl ? `https://pps.whatsapp.net${picUrl}` : null,
|
||||
subscribersCount: response.newsletterSubscribersMetadataMixin.subscribersCount,
|
||||
isVerified: response.newsletterVerificationMetadataMixin.verificationState === 'verified'
|
||||
};
|
||||
};
|
||||
|
||||
window.WWebJS.getChats = async () => {
|
||||
const chats = window.Store.Chat.getModelsArray();
|
||||
|
||||
const chatPromises = chats.map(chat => window.WWebJS.getChatModel(chat));
|
||||
return await Promise.all(chatPromises);
|
||||
};
|
||||
|
||||
window.WWebJS.getChannels = async () => {
|
||||
const channels = window.Store.NewsletterCollection.getModelsArray();
|
||||
const channelPromises = channels?.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true }));
|
||||
return await Promise.all(channelPromises);
|
||||
};
|
||||
|
||||
window.WWebJS.getChatModel = async (chat, { isChannel = false } = {}) => {
|
||||
if (!chat) return null;
|
||||
|
||||
const model = chat.serialize();
|
||||
model.isGroup = false;
|
||||
model.isMuted = chat.mute?.expiration !== 0;
|
||||
if (isChannel) {
|
||||
model.isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
|
||||
} else {
|
||||
model.formattedTitle = chat.formattedTitle;
|
||||
}
|
||||
|
||||
if (chat.groupMetadata) {
|
||||
model.isGroup = true;
|
||||
const chatWid = window.Store.WidFactory.createWid(chat.id._serialized);
|
||||
await window.Store.GroupMetadata.update(chatWid);
|
||||
model.groupMetadata.participants._models
|
||||
.filter(x => x.id._serialized.endsWith('@lid'))
|
||||
.forEach(x => { x.id = x.contact.phoneNumber; });
|
||||
model.groupMetadata = chat.groupMetadata.serialize();
|
||||
model.isReadOnly = chat.groupMetadata.announce;
|
||||
}
|
||||
|
||||
if (chat.newsletterMetadata) {
|
||||
await window.Store.NewsletterMetadataCollection.update(chat.id);
|
||||
model.channelMetadata = chat.newsletterMetadata.serialize();
|
||||
model.channelMetadata.createdAtTs = chat.newsletterMetadata.creationTime;
|
||||
}
|
||||
|
||||
model.lastMessage = null;
|
||||
if (model.msgs && model.msgs.length) {
|
||||
const lastMessage = chat.lastReceivedKey
|
||||
? window.Store.Msg.get(chat.lastReceivedKey._serialized) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0]
|
||||
: null;
|
||||
lastMessage && (model.lastMessage = window.WWebJS.getMessageModel(lastMessage));
|
||||
}
|
||||
|
||||
delete model.msgs;
|
||||
delete model.msgUnsyncedButtonReplyMsgs;
|
||||
delete model.unsyncedButtonReplies;
|
||||
|
||||
return model;
|
||||
};
|
||||
|
||||
window.WWebJS.getContactModel = contact => {
|
||||
let res = contact.serialize();
|
||||
res.isBusiness = contact.isBusiness === undefined ? false : contact.isBusiness;
|
||||
|
|
@ -512,50 +586,20 @@ exports.LoadUtils = () => {
|
|||
res.businessProfile = contact.businessProfile.serialize();
|
||||
}
|
||||
|
||||
// TODO: remove useOldImplementation and its checks once all clients are updated to >= v2.2327.4
|
||||
const useOldImplementation
|
||||
= window.compareWwebVersions(window.Debug.VERSION, '<', '2.2327.4');
|
||||
|
||||
res.isMe = useOldImplementation
|
||||
? contact.isMe
|
||||
: window.Store.ContactMethods.getIsMe(contact);
|
||||
res.isUser = useOldImplementation
|
||||
? contact.isUser
|
||||
: window.Store.ContactMethods.getIsUser(contact);
|
||||
res.isGroup = useOldImplementation
|
||||
? contact.isGroup
|
||||
: window.Store.ContactMethods.getIsGroup(contact);
|
||||
res.isWAContact = useOldImplementation
|
||||
? contact.isWAContact
|
||||
: window.Store.ContactMethods.getIsWAContact(contact);
|
||||
res.isMyContact = useOldImplementation
|
||||
? contact.isMyContact
|
||||
: window.Store.ContactMethods.getIsMyContact(contact);
|
||||
res.isMe = window.Store.ContactMethods.getIsMe(contact);
|
||||
res.isUser = window.Store.ContactMethods.getIsUser(contact);
|
||||
res.isGroup = window.Store.ContactMethods.getIsGroup(contact);
|
||||
res.isWAContact = window.Store.ContactMethods.getIsWAContact(contact);
|
||||
res.isMyContact = window.Store.ContactMethods.getIsMyContact(contact);
|
||||
res.isBlocked = contact.isContactBlocked;
|
||||
res.userid = useOldImplementation
|
||||
? contact.userid
|
||||
: window.Store.ContactMethods.getUserid(contact);
|
||||
res.isEnterprise = useOldImplementation
|
||||
? contact.isEnterprise
|
||||
: window.Store.ContactMethods.getIsEnterprise(contact);
|
||||
res.verifiedName = useOldImplementation
|
||||
? contact.verifiedName
|
||||
: window.Store.ContactMethods.getVerifiedName(contact);
|
||||
res.verifiedLevel = useOldImplementation
|
||||
? contact.verifiedLevel
|
||||
: window.Store.ContactMethods.getVerifiedLevel(contact);
|
||||
res.statusMute = useOldImplementation
|
||||
? contact.statusMute
|
||||
: window.Store.ContactMethods.getStatusMute(contact);
|
||||
res.name = useOldImplementation
|
||||
? contact.name
|
||||
: window.Store.ContactMethods.getName(contact);
|
||||
res.shortName = useOldImplementation
|
||||
? contact.shortName
|
||||
: window.Store.ContactMethods.getShortName(contact);
|
||||
res.pushname = useOldImplementation
|
||||
? contact.pushname
|
||||
: window.Store.ContactMethods.getPushname(contact);
|
||||
res.userid = window.Store.ContactMethods.getUserid(contact);
|
||||
res.isEnterprise = window.Store.ContactMethods.getIsEnterprise(contact);
|
||||
res.verifiedName = window.Store.ContactMethods.getVerifiedName(contact);
|
||||
res.verifiedLevel = window.Store.ContactMethods.getVerifiedLevel(contact);
|
||||
res.statusMute = window.Store.ContactMethods.getStatusMute(contact);
|
||||
res.name = window.Store.ContactMethods.getName(contact);
|
||||
res.shortName = window.Store.ContactMethods.getShortName(contact);
|
||||
res.pushname = window.Store.ContactMethods.getPushname(contact);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
|
@ -632,10 +676,6 @@ exports.LoadUtils = () => {
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Referenced from and modified:
|
||||
* @see https://github.com/wppconnect-team/wa-js/commit/290ebfefe6021b3d17f7fdfdda5545bb0473b26f
|
||||
*/
|
||||
window.WWebJS.generateWaveform = async (audioFile) => {
|
||||
try {
|
||||
const audioData = await audioFile.arrayBuffer();
|
||||
|
|
@ -669,7 +709,7 @@ exports.LoadUtils = () => {
|
|||
};
|
||||
|
||||
window.WWebJS.sendClearChat = async (chatId) => {
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
if (chat !== undefined) {
|
||||
await window.Store.SendClear.sendClear(chat, false);
|
||||
return true;
|
||||
|
|
@ -678,7 +718,7 @@ exports.LoadUtils = () => {
|
|||
};
|
||||
|
||||
window.WWebJS.sendDeleteChat = async (chatId) => {
|
||||
let chat = window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
if (chat !== undefined) {
|
||||
await window.Store.SendDelete.sendDelete(chat);
|
||||
return true;
|
||||
|
|
@ -908,7 +948,7 @@ exports.LoadUtils = () => {
|
|||
}
|
||||
|
||||
if (rpcResult.name === 'AddParticipantsResponseSuccess') {
|
||||
const code = resultArgs?.value.error ?? '200';
|
||||
const code = resultArgs?.value.error || '200';
|
||||
data.name = resultArgs?.name;
|
||||
data.code = +code;
|
||||
data.inviteV4Code = resultArgs?.value.addRequestCode;
|
||||
|
|
@ -1023,6 +1063,25 @@ exports.LoadUtils = () => {
|
|||
}
|
||||
};
|
||||
|
||||
window.WWebJS.subscribeToUnsubscribeFromChannel = async (channelId, action, options = {}) => {
|
||||
const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
|
||||
|
||||
if (!channel || channel.newsletterMetadata.membershipType === 'owner') return false;
|
||||
options = { eventSurface: 3, deleteLocalModels: options.deleteLocalModels ?? true };
|
||||
|
||||
try {
|
||||
if (action === 'Subscribe') {
|
||||
await window.Store.ChannelUtils.subscribeToNewsletterAction(channel, options);
|
||||
} else if (action === 'Unsubscribe') {
|
||||
await window.Store.ChannelUtils.unsubscribeFromNewsletterAction(channel, options);
|
||||
} else return false;
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.name === 'ServerStatusCodeError') return false;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => {
|
||||
const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
|
||||
if (!message) return false;
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ class InterfaceController {
|
|||
* @param {string} chatId ID of the chat window that will be opened
|
||||
*/
|
||||
async openChatWindow(chatId) {
|
||||
await this.pupPage.evaluate(async chatId => {
|
||||
const chatWid = window.Store.WidFactory.createWid(chatId);
|
||||
const chat = window.Store.Chat.get(chatWid) || await window.Store.Chat.find(chatWid);
|
||||
await this.pupPage.evaluate(async (chatId) => {
|
||||
const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
await window.Store.Cmd.openChatBottom(chat);
|
||||
}, chatId);
|
||||
}
|
||||
|
|
@ -27,7 +26,7 @@ class InterfaceController {
|
|||
*/
|
||||
async openChatDrawer(chatId) {
|
||||
await this.pupPage.evaluate(async chatId => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
await window.Store.Cmd.openDrawerMid(chat);
|
||||
}, chatId);
|
||||
}
|
||||
|
|
@ -38,7 +37,7 @@ class InterfaceController {
|
|||
*/
|
||||
async openChatSearch(chatId) {
|
||||
await this.pupPage.evaluate(async chatId => {
|
||||
let chat = await window.Store.Chat.get(chatId);
|
||||
let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
|
||||
await window.Store.Cmd.chatSearch(chat);
|
||||
}, chatId);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue