whatsapp-web.js
whatsapp-web.js copied to clipboard
feat: `Channel`
Table of Contents
- Description
- Related Issues
- Usage Example
- I Want to Test this PR
- I Got an Error While Testing This PR ❌
- How Has the PR Been Tested (latest test on 29.09.2024)
- Types of Changes
Description
The PR introduces new functionality for managing channels.
Added new methods:
-
Client.createChannel
that takes 2 parameters:-
title
(required) - an object
options
(optional) with properties:-
description
for a channel description -
picture
for a channel profile picture which will be set upon a channel creation
-
-
-
Client.subscribeToChannel
to subscribe to channel that takes a channel ID -
Client.unsubscribeFromChannel
to unsubscribe from channel. The method takes 2 parameters:- channelId (required)
- an optional object
options
with propertydeleteLocalModels
(default value istrue
). If true, after an unsubscription, it will completely remove a channel from the client 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
-
Client.searchChannels
to search channels, the method takes 5 optional parameters:-
searchText
, the text by which you want to find the channel (empty string by default) -
countryCodes
, an array of country codes in ISO 3166-1 alpha-2 standart to search for channels created in these countries, your local region is used as a value by default -
skipSubscribedNewsletters
, a boolean value (default value isfalse
). If true, channels that user is subscribed to won't appear in found channels -
view
for specifing the category of channels to get, valid values are:-
0
for RECOMMENDED channels (default value) -
1
for TRENDING channels -
2
for POPULAR channels -
3
for NEW channels
-
-
limit
for specifing the limit of found channels to be appear in the returnig results (default value is 50)
-
-
Updated
Client.getChatById
to retrieve aChannel
instance by its ID -
Client.getChannelByInviteCode
to retrieve aChannel
instance by its invite code (that comes afterhttps://whatsapp.com/channel/
), e.g.:0029Va4K0PZ5a245NkngBA2M
(an invite code of a WhatsApp channel) -
Client.getChannels
that returns all your cachedChannel
objects as an array -
Client.sendChannelAdminInvite
andChannel.sendChannelAdminInvite
to send a channel admin invitation to a user, allowing them to become an admin of the channel, you can also provide a text comment that will be sent along within invitation by providing a string in acomment
property of an optionaloptions
object -
Client.acceptChannelAdminInvite
andChannel.acceptChannelAdminInvite
to accept a channel admin invitation and promote the current user to a channel admin -
Client.revokeChannelAdminInvite
andChannel.revokeChannelAdminInvite
to revoke a channel admin invitation previously sent to a user by a channel owner -
Client.demoteChannelAdmin
andChannel.demoteChannelAdmin
to demote a channel admin to a regular subscriber (can be used also for self-demotion) -
Channel.sendMessage
and updatedClient.sendMessage
to send a message to a channel (currently supported message types to send are: text, image, sticker, gif, video, voice and poll) -
Channel.fetchMessages
that works similarly toChat.fetchMessages
-
Channel.getSubscribers
to get subscribers of the channel (only those who are in your contact list), you can pass an optionallimit
parameter to specify the limit of subscribers to retrieve (if not specified, thelimit
value will be set to the maximum provided by WhatsApp) -
Channel.setSubject
that updates the channel subject (name) -
Channel.setDescription
that updates the channel description -
Channel.setProfilePicture
that updates the channel propfile picture -
Channel.setReactionSetting
that updates available reactions to use in the channel, valid values to pass:-
0
for NONE reactions to be avaliable (turnes off using reactions in a channel) -
1
for BASIC reactions to be available: 👍, ❤️, 😂, 😮, 😢, 🙏 -
2
for ALL reactions to be available
-
-
Channel.mute
andChannel.unmute
to mute and unmute channel respectively (once you subcribed to a channel it will be muted) -
Client.deleteChannel
andChannel.deleteChannel
that deletes the channel you created -
Client.transferChannelOwnership
andChannel.transferChannelOwnership
that transfers a channel ownership to another user. Note: The user you are transferring the channel ownership to must be a channel admin. You can also pass an optional objectoptions
with a propertyshouldDismissSelfAsAdmin
(default value isfalse
). 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.
Related Issues
The PR closes #2094, closes #2529, closes #2538, closes #2551, closes #2952
Usage Example
1. To create a channel:
const { MessageMedia, ... } = require('whatsapp-web.js');
// client initialization...
client.on('ready', async () => {
let createdChannel = await client.createChannel('ChannelName');
/**
* The example output of the {@link createdChannel}:
* {
* title: 'ChannelName',
* nid: {
* server: 'newsletter',
* user: 'XXXXXXXXXX',
* _serialized: 'XXXXXXXXXX@newsletter'
* },
* inviteLink: 'https://whatsapp.com/channel/INVITE_CODE',
* createdAtTs: 1700002175
* }
*/
console.log(createdChannel);
// You can also provide optional parametes:
const pic = await MessageMedia.fromUrl(
'https://i.chzbgr.com/full/9817556992/hAA1BE0BC/bag-12',
{ unsafeMime: true }
);
createdChannel = await client.createChannel('ChannelName', {
description: 'Description',
picture: pic
});
});
2. To subscribe to a channel:
// client initialization...
client.on('ready', async () => {
let channelId = 'XXXXXXXXXX@newsletter';
// True if the operation completed successfully, false otherwise
console.log(await client.subscribeToChannel(channelId));
});
3. To unsubscribe from a channel:
// client initialization...
client.on('ready', async () => {
let channelId = 'XXXXXXXXXX@newsletter';
// True if the operation completed successfully, false otherwise
console.log(await client.unsubscribeFromChannel(channelId/* , { deleteLocalModels: true } */));
});
4. To search for channels:
// client initialization...
client.on('ready', async () => {
const foundChannels = await client.searchChannels({
searchText: 'StandWithUS',
countryCodes: ['IL'],
skipSubscribedNewsletters: true,
view: 0,
limit: 5
});
/**
* {
* id: {
* server: 'newsletter',
* user: '120363189916697314',
* _serialized: '120363189916697314@newsletter'
* },
* t: 1697713253,
* unreadCount: 0,
* unreadDividerOffset: 0,
* isReadOnly: true,
* muteExpiration: -1,
* isAutoMuted: false,
* name: 'StandWithUs',
* hasUnreadMention: false,
* archiveAtMentionViewedInDrawer: false,
* hasChatBeenOpened: false,
* isDeprecated: false,
* pendingInitialLoading: false,
* celebrationAnimationLastPlayed: 0,
* hasRequestedWelcomeMsg: false,
* isGroup: false,
* isChannel: true,
* channelMetadata: {
* id: {
* server: 'newsletter',
* user: '120363189916697314',
* _serialized: '120363189916697314@newsletter'
* },
* creationTime: 1697713253,
* name: 'StandWithUs',
* nameUpdateTime: 1697713253815244,
* description: 'Supporting Israel And Fighting Antisemitism\n',
* descriptionUpdateTime: 1699462060238701,
* inviteCode: '0029Va8fZhM05MUj1A47hA0F',
* size: 39690,
* verified: true,
* membershipType: 'guest',
* suspended: false,
* geosuspended: false,
* terminated: false,
* messageDeliveryUpdates: [],
* geosuspendedCountries: [],
* pendingAdmins: [],
* subscribers: [],
* createdAtTs: 1697713253
* },
* lastMessage: null
* }
*/
console.log(foundChannels[0]); // Outputs the first found channel in an array
});
5. To get a channel by its ID:
// client initialization...
client.on('ready', async () => {
let channelId = '120363189916697314@newsletter';
// The output will be a `Channel` instance, if exists:
console.log(await client.getChatById(channelId));
});
6. To get a channel by its invite code:
// client initialization...
client.on('ready', async () => {
let channelInviteCode = '0029Va8fZhM05MUj1A47hA0F';
// The output will be a `Channel` instance, if exists:
console.log(await client.getChannelByInviteCode(channelInviteCode));
});
7. To get all cached channels:
// client initialization...
client.on('ready', async () => {
// The output will be an array of your cached `Channel` objects:
console.log(await client.getChannels());
});
8. To send a channel admin invitation to a user as a channel admin:
// client initialization...
client.on('ready', async () => {
const userId = '[email protected]';
const channelId = 'YYYYYYYYYY@newsletter';
const greeting = 'Hello, please become a channel admin';
// True if the operation completed successfully, false otherwise
console.log(await client.sendChannelAdminInvite(userId, channelId, { comment: greeting }));
// OR
const channel = await getChatById(channelId);
await channel.sendChannelAdminInvite(userId, { comment: greeting });
});
9. To accept a channel admin invitation as a regular user:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
// True if the operation completed successfully, false otherwise
console.log(await client.acceptChannelAdminInvite(channelId));
// OR
const channel = await getChatById(channelId);
await channel.acceptChannelAdminInvite();
});
10. To revoke a channel admin invitation as a channel admin:
// client initialization...
client.on('ready', async () => {
const userId = '[email protected]';
const channelId = 'YYYYYYYYYY@newsletter';
// True if the operation completed successfully, false otherwise
console.log(await client.revokeChannelAdminInvite(channelId, userId));
// OR
const channel = await getChatById(channelId);
await channel.revokeChannelAdminInvite(userId);
});
11. To demote a channel admin:
// client initialization...
client.on('ready', async () => {
const userId = '[email protected]';
const channelId = 'YYYYYYYYYY@newsletter';
// True if the operation completed successfully, false otherwise
console.log(await client.demoteChannelAdmin(channelId, userId));
// OR
const channel = await getChatById(channelId);
await channel.demoteChannelAdmin(userId);
});
12. To get channel subscribers:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const channel = await getChatById(channelId);
const limit = 5;
/**
* The example of an output (in my case there is only one subscriber):
*
* [
* {
* contact: {
* id: {
* server: 'c.us',
* user: 'XXXXXXXXX',
* _serialized: '[email protected]'
* },
* name: 'Test',
* shortName: 'Test',
* pushname: 'Test',
* type: 'in',
* isBusiness: false,
* isEnterprise: false,
* isSmb: false,
* isContactSyncCompleted: 1,
* textStatusLastUpdateTime: -1,
* isMe: false,
* isUser: true,
* isGroup: false,
* isWAContact: true,
* isMyContact: true,
* isBlocked: false,
* userid: 'XXXXXXXXX'
* },
* role: 'subscriber'
* }
* ]
*/
console.log(await channel.getSubscribers(limit));
});
13. To set channel subject (name):
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const channel = await getChatById(channelId);
const newSubject = 'NewSubject';
// True if the operation completed successfully, false otherwise
console.log(await channel.setSubject(newSubject));
});
14. To set channel description:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const channel = await getChatById(channelId);
const newDescription = 'NewSubject';
// True if the operation completed successfully, false otherwise
console.log(await channel.setDescription(newDescription));
});
15. To set channel profile picture:
const { MessageMedia, ... } = require('whatsapp-web.js');
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const channel = await getChatById(channelId);
const newPicture = await MessageMedia.fromUrl(
'https://i.redd.it/l9vklw5gh4841.jpg',
{ unsafeMime: true }
);
// True if the operation completed successfully, false otherwise
console.log(await channel.setProfilePicture(newPicture));
});
16. To set a channel available reactions:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const channel = await getChatById(channelId);
// True if the operation completed successfully, false otherwise
console.log(await channel.setReactionSetting(0)); // Allows all reactions
console.log(await channel.setReactionSetting(1)); // Allows default reactions (👍, ❤️, 😂, 😮, 😢, 🙏)
console.log(await channel.setReactionSetting(2)); // Turns off reactions
});
17. To mute/unmute a channel:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const channel = await getChatById(channelId);
// True if the operation completed successfully, false otherwise
console.log(await channel.unmute());
console.log(await channel.mute());
});
18. To delete a channel:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
// True if the operation completed successfully, false otherwise
console.log(await client.deleteChannel(channelId));
// OR
const channel = await getChatById(channelId);
await channel.deleteChannel();
});
19. To transfer a channel ownership:
// client initialization...
client.on('ready', async () => {
const channelId = 'YYYYYYYYYY@newsletter';
const newChannelOwnerId = '[email protected]';
// True if the operation completed successfully, false otherwise
console.log(await client.transferChannelOwnership(channelId, newChannelOwnerId));
/**
* The same but 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.
*/
console.log(await client.transferChannelOwnership(channelId, newChannelOwnerId, { shouldDismissSelfAsAdmin: true }));
// OR through the 'Channel' object:
const channel = await getChatById(channelId);
await channel.transferChannelOwnership(newChannelOwnerId);
});
To test this PR by yourself you can run one of the following commands:
- NPM
npm install github:alechkos/whatsapp-web.js#channels
- YARN
yarn add github:alechkos/whatsapp-web.js#channels
If you encounter any errors while testing this PR, please provide in a comment:
- The code you've used without any sensitive information (use syntax highlighting for more readability)
- The error you got
- The library version
- The WWeb version:
console.log(await client.getWWebVersion());
- The browser (Chrome/Chromium)
[!IMPORTANT] You have to reapply the PR each time it is changed (new commits were pushed since your last application)
How Has The PR Been Tested (latest test on 29.09.2024)
Tested with a code provided in usage example.
Tested On:
Environment:
- Android 10:
- Types of accounts:
- Standart: latest
- WhatsApp Business: latest
- Types of accounts:
- Windows 11:
- WWebJS: v1.26.1-alpha.1
- WWeb: v2.3000.1016898497
- Puppeteer: v18.2.1
- Node.js: 20.11.0
- Google Chrome: latest
Types of Changes
- [ ] Dependency change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix/feature that would cause existing functionality to change)
Checklist
- [X] My code follows the code style of this project
- [ ] I have updated the usage example accordingly (example.js)
- [X] I have updated the documentation accordingly (index.d.ts)
sending photo in channels has some problems
@bruninoit
sending photo in channels has some problems
wanna tell me what they are?
wanna tell me what they are?
After sending an image in a channel... If you access the channel from the channel owner and try to press the button to send the image again, WhatsApp crashes If you access the channel from a channel subscriber and try to download the image, it won't download. I used the exact same code to send a photo in private chat and it works correctly, while sending in a channel there are these problems.
Tested and worked great here. Here is how I did it.
Step 1: Install @alechkos wweb.js version:
npm install github:alechkos/whatsapp-web.js#channels --save
Step 2: Create a channel and get it's ID:
const channelId = (await client.createChannel('MyChannel))?.nid._serialized
console.log('My channel id is:', channelId)
Step 3: Start publishing:
await client.sendMessage(channelId, 'Hello World')
One question, tho. How to publish media?
@devsakae
One question, tho. How to publish media?
sending media is still doesn't work
I used the exact same code to send a photo in private chat and it works correctly, while sending in a channel there are these problems.
Same here. I even though it was my connection, but every other media downloaded from channels work great.
It's a bug
@devsakae @bruninoit
Thank you for testing, sendMessage
has been fixed and now supports these message types to send: text, image, sticker, gif, or video
Care to share how I install the latest changes?
I did npm install
(I usually go with npm ci), but it didn't downloaded the changes made https://github.com/pedroslopez/whatsapp-web.js/pull/2620/commits/ab40eddf6d5fff7517a143f6c03db13f3254ba7b
@devsakae
how I install the latest changes?
npm install github:alechkos/whatsapp-web.js#channels
Of course.. 🫥
Ty
I get an error when trying to run client.getChats()
:
throw new Error('Evaluation failed: ' + helper_js_1.helper.getExceptionMessage(exceptionDetails));
^
Error: Evaluation failed: TypeError: Cannot read properties of undefined (reading 'isChannel')
at Object.window.WWebJS.getChatOrChannelModel (__puppeteer_evaluation_script__:404:67)
at __puppeteer_evaluation_script__:394:62
at Array.map (<anonymous>)
at Object.window.WWebJS.getChats (__puppeteer_evaluation_script__:394:36)
at __puppeteer_evaluation_script__:2:40
at ExecutionContext._evaluateInternal (/Users/tobimori/Developer/vierbeiner-in-not/node_modules/.pnpm/[email protected]/node_modules/puppeteer/src/common/ExecutionContext.ts:273:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at ExecutionContext.evaluate (/Users/tobimori/Developer/vierbeiner-in-not/node_modules/.pnpm/[email protected]/node_modules/puppeteer/src/common/ExecutionContext.ts:140:12)
at async Client.getChats (/Users/tobimori/Developer/vierbeiner-in-not/node_modules/.pnpm/github.com+alechkos+whatsapp-web.js@6b18bd1260164167d0354ba21501a049065320d2/node_modules/whatsapp-web.js/src/Client.js:974:21)
at get (/Users/tobimori/Developer/vierbeiner-in-not/whatsapp/routes/chats.ts:10:17)
@tobimori
I get an error when trying to run
client.getChats()
Thank you, fixed
Could you add inviteLink on response of Channel
@fukumori
Could you add inviteLink on response of Channel
What do you mean?
How can one delete a message in a channel?
Seems deleting the message by id doesn't work. While I do not get an error, the message is still available.
@themazim
How can one delete a message in a channel?
Seems deleting the message by id doesn't work. While I do not get an error, the message is still available.
I will check it
@themazim
How can one delete a message in a channel?
You can do it just like in regular chat:
// ...
const msg = await client.getMessageById(msgId);
await msg.delete(true);
// ...
@themazim
How can one delete a message in a channel?
You can do it just like in regular chat:
// ... const msg = await client.getMessageById(msgId); await msg.delete(true); // ...
Thanks a lot. This actually helped. I set everyone: false
which simply doesn't seem to work with channels.
Hi, can anyone check if changing message caption works? I've tried but can only get it to work with simple text messages (no media).
This is my code:
//...
const msg = await client.getMessageById(msgId);
await msg.edit(caption);
//...
@roockisgreat
I've tried but can only get it to work with simple text messages (no media)
Check the PR that adds that functionality, also I have not checked the message editing in channels yet
Thank you very much, I confirm that with these changes the editing media caption works. (PR)
Hi, i'm here again sorry. The subscribeToChannel method doesn't work, it returns an error:
(node:15308) UnhandledPromiseRejectionWarning: Error: Evaluation failed: i at ExecutionContext._evaluateInternal
Furthermore, the unsubscribeFromChannel method does not return any errors, but I always remain subscribed to the channel (even using the deleteLocalModels option).
Final question, if I'm not subscribed to a channel, is the getChannelByInviteCode method normal that returns undefined?
@roockisgreat I will check
Can someone check if sending a link to a channel would load the preview (the preview of the link)? (not working for me)
@carlvallory
Can someone check if sending a link to a channel would load the preview (the preview of the link)? (not working for me)
It won't, it needs to be implemented also
@roockisgreat
The subscribeToChannel method doesn't work, it returns an error. Furthermore, the method does not return any errors, but I always remain subscribed to the channel (even using the deleteLocalModels option). If I'm not subscribed to a channel, is the
Client.getChannelByInviteCode
method normal that returns undefined?
Thank you for testing :)
I fixed some functionality, now Client.subscribeToChannel
, Client.unsubscribeFromChannel
and Client.getChannelByInviteCode
methods work properly
So, I confirm that the Client.unsubscribeFromChannel
method works. But the Client.subscribeToChannel
method does not work yet (this window.WWebJS.getChat(channelId, { getAsModel: false })
return undefined). The Client.getChannelByInviteCode
still return undefined if you are not subscribe to channel, is it normal?
@roockisgreat Did you run this before the new test?
npm install github:alechkos/whatsapp-web.js#channels
Also provide your code
Yes, before the new test i run:
npm uninstall whatsapp-web.js
npm install github:alechkos/whatsapp-web.js#channels
My code for getChannelByInviteCode
:
const invite_code = "xxxxxx"; // 'https://whatsapp.com/channel/xxxxxx'
const channel = await client.getChannelByInviteCode(invite_code);
console.log(channel); //return undefined
My code for subscribeToChannel
:
let res = await client.subscribeToChannel("xxxxxx@newsletter");
console.log(res) //return false because window.WWebJS.getChat(channelId, { getAsModel: false }) return undefined
The unsubscribeFromChannel
methode work perfect instead now.
EDIT:
For the getChannelByInviteCode
method the problem is ChatFactory.create(this, result)
(with the options.getMetadata = true
return the data)
EDIT 2:
For the subscribeToChannel
method the problem seems the function window.WWebJS.getChat
. Inside this function the methods window.Store.NewsletterCollection.get(chatId)
and await window.Store.ChannelUtils.queryAndUpdateNewsletterMetadataAction(chatId,{ fields: fields })
return undefined.