
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
English | Bahasa Indonesia
Simpler way to code baileys.
In the next major version, all response functions (including the ones for commands) will have their parameters simplified into 2:
context - This will contain: message, captures, group and commandnext functionMore explanation here
npm install wachan
4 types of inputs:
const bot = require("wachan")
// 1) String Input: Respond to messages that have the exact text.
bot.onReceive("Hello", "Hi")
// 2) Regex Input: Respond to messages that have the pattern in the text.
bot.onReceive(/good (morning|afternoon|evening)/i, "to you as well")
// 3) Function Input: Respond to messages if the function returns true
bot.onReceive((msg)=>msg.sender.id===OWNER_ID, "hello boss")
// 4) Enum Input:
bot.onReceive(bot.messageType.audio, "Received audio message")
3 Types of responses:
// 1) String response: Text message
bot.onReceive("Marco", "Polo")
// 2) Object response: More sending options
bot.onReceive("send image", {image:"buffer, url, or path", caption:"This is the caption"})
bot.onReceive("send video", {video:"...", caption:"..."})
bot.onReceive("send gif", {gif:"...", caption:"..."}) // must send a video file for it to animate as whatsapp does not support gif files
bot.onReceive("send audio", {audio:"..."})
bot.onReceive("send sticker", {sticker:"..."}) // webp file
// 3) Function response: Custom actions
bot.onReceive("test", async (context, next) => {
// v1 arguments: message, captures, group, next
const { message, captures, group } = context
const options = {...}
// 3 ways of sending message:
// 1) Using bot.sendMessage()
await bot.sendMessage(TARGET_ID, "string for text message")
await bot.sendMessage(TARGET_ID, options) // more sending options
// 2) Using message.reply()
await message.reply("string for text message")
await message.reply(options) // more sending options
// 3) Returning a value (equivalent to message.reply)
return "string for text message"
return options // more sending options
})
Other events:
// When wachan succesfully connected (processed before offline messages)
bot.onConnected(async () => {
await bot.sendText(targetId, "Wachan is connected!")
})
// When wachan is ready (processed after offline messages)
bot.onReady(async () => {
await bot.sendText(targetId, "Finished reading all unread messages!")
})
Starting the bot:
bot.start()
When first run, a default settings file is created if it's not there.
{
"receiveOfflineMessages": true,
"defaultBotName": "Wachan",
"messageStoreSize": 1000
}
These settings can be altered in the meantime by accessing bot.settings. To save the changes so that it will take effect next run, use bot.settings.save().
receiveOfflineMessages: If set to true, will allow messages received when offline to be processed by bot.onReceive.defaultBotName: Will use this name if bot's own message doesn't have message.sender.namemessageStoreSize: The size limit of the Message Store. This store is used internally to fix some bugs.This is what wachan module exports:
bot: Wachan bot object
bot.onConnected(callback) - Set up the action to be taken when wachan is succesfully connected to whatsapp, before processing offline messages.bot.onReady(callback) - Set up the action to be taken after processing offline messages.bot.onReceive(input, response) - Set up a receiver that responds to incoming messages of specified input.
input: can be a string, regex, function, or enum.
input(message): will filter the message based on the return valuebot.messageType for available enums:
any, nonmedia, media, text, reaction, image, video, gif, audio, sticker, documentresponse: can be a string, an object, or a function.
response(context, next), will execute the function. Explanation hereReceiver object. This Receiver can be removed to stop its behavior by calling receiver.remove()bot.onReceiveReply(message, response) - Set up a receiver that responds to incoming messages as a reply to a specific message, or to any messages if first parameter is set to falsy.bot.onError(response) - Set up a new action to be taken in response of an error.
response - A function to run, response(error, context).
error - The error object.context - An object, containing the arguments from the response function:
messagecapturesgroup (previously groupChat)bot.waitForMessage(input, timeout) - Wait for a message of specified input then return the message
input: The same as input in bot.onReceive() abovetimeout: Time limit for the waiting. If no matching messages have been captured, waitForMessage() returns undefinedbot.sendMessage(targetId, options) - Send a message
targetId - the ID of the chatroom to send tooptions - can be a string / object
bot.getGroupData(jid) - Get data of a group chat by its ID.bot.getUserData(id) - Get data of a user by JID / LID, if the bot has saved it.bot.getBotData() - Get data of the bot (id, lid, name)bot.start(options) - Start the bot. Options:
suppressBaileysLogs: Default true. If true, do not show logs from baileys in the console.phoneNumber: String containing the phone number (with country code, without symbols and spaces). If not specified, wachan will prompt it in the console.configOverrides: An object of config to override bailey's makeWASocket function parametersbot.settings - Settings for the bot. See here
bot.settings.receiveOfflineMessagesbot.settings.defaultBotNamebot.settings.save() - Save the settings. Do this after modifying the settings programmatically.bot.getSocket() - Get the original baileys' socket object.bot.messageType - Contains enums for receiver input. See hereYou can use a function as the response to a message. The first argument is context and the second one is next function (check out Receiver Flow).
Previously, the first argument is message, the second argument is captures (if available), the third is group (if the chatroom is a group chat), and the last is next function. (This is now deprecated. message, captures, and group will later be under context object).
bot.onReceive("test", async function (context, next) {
// const { message, captures, group } = context
})
message: Wachan message object
message.id - ID of the messagemessage.room - ID of the chat roommessage.isPrivate - If the message is a private messagemessage.sender - Sender object
message.sender.id - ID of the sender (in the format of phonenumber@s.whatsapp.net)message.sender.lid - LID of the sender (a hidden id of each Whatsapp user, in the format of randomnumber@lid)message.sender.isMe - true if the sender is the bot itselfmessage.sender.name - Username of the sender.message.sender.isAdmin - true/false if the sender is an admin/not an admin of this group chat. null if this message is a private message. (not in a group)message.timestamp - UNIX timestamp of this message.message.type - Type of this message. Can be one of these: "text", "image", "video", "gif", "audio", "vn", "sticker", "document", "reaction", "buttons", "buttonReply", "contacts", "poll", "vote"message.isMedia - true if this is a media message (type = "image", "video", "gif", "audio", "vn", "sticker", or "document")message.downloadMedia(saveTo) - Get the buffer of the media. If you provide a path in saveTo, it will also save the file there.message.streamMedia() - Get the stream object from the media.message.mimeType - Mimetype for media messages.message.fileName - File name for document messages.message.fileSize - File size for media messages. (in bytes)message.duration - Duration for audio and video messages (in seconds)message.width - Width of videomessage.height - Height of videomessage.text - Text or caption of the message.message.reaction - Information about reaction, if this is a reaction message
message.reaction.emoji - The emoji that is used as reactionmessage.reaction.key - Key object of the reacted messagemessage.buttons - Buttons object. The same as the buttons parameter used to send buttons message. (here)message.title - Title of the buttons messagemessage.footer - Footer text of the buttons messagemessage.buttonReply - Information about tapped button
message.buttonReply.id - The ID that is assigned to the buttonmessage.buttonReply.text - The displayed text on the buttonmessage.buttonReply.pos - The position of the button (first one is 0)message.contacts - List of contacts, if this is a contacts message.
contact.name - Name of the contactcontact.number - Phone number of the contactmessage.poll - Information on poll, if this is a poll message.
message.poll.title - Title of the pollmessage.poll.options - Array containing the options.message.poll.multiple - True if multiple selection is allowedmessage.poll.votes - An object containing options as keys, and array of voter ids as value. Example: {"opt1":["1234@lid"]}message.vote - An update on polling (the sender voting/unvoting)
message.vote.pollId - ID of the poll messagemessage.vote.list - Array of the selected options. Can also be empty as a result of unvoting.message.receivedOnline - true if this message is received when bot is online.message.edited - If this message is an edited message
message.edited.type - The type of the edited messagemessage.edited.text - The text of the message after editedmessage.reply(options) - Reply to the message.
options - Can be a string / object
message.edit(newText) - Edit the text of a message (only for bot's own messages, and only if the message is still editable, which is within 15 minutes after being sent)message.react(emoji) - Send reaction to the message.
emoji - The emoji in string to use as reaction. Use empty string to remove a reaction.message.delete() - Delete this message. Note: the bot needs to be an admin for it to be able to delete messages in a group.message.getQuoted() - Return the message that this message is quoting. null if there isn't.message.toBaileys() - Return the original baileys message object.captures is an object (not an array) of captured text from regex inputs.
The keys depend on the regex. If using regular capturing using brackets, then the result is stored with numeric keys (starting from 0). If using named capture, then the key is a string.
| Input Regex | Received message text | captures |
|---|---|---|
/My name is (\S+)\. I live in (\S+)\./ | "My name is Wachan. I live in NPM." | {"0":"Wachan", "1":"NPM"} |
/My name is (?<name>\S+)\. I live in (?<location>\S+)\./ | "My name is Wachan. I live in NPM." | {"name":"Wachan", "location":"NPM"} |
captures.toArray() returns the array of the captures. Useful for doing array operations on it.
The third argument is group, the object that contains information about the group. This will be null if the message is sent in a private chat. (Now deprecated as function response's third argument, and it will go inside context object).
You can also use bot.getGroupData(id) to get information about other groups.
In the response function, you can return a string/object:
bot.onReceive("test", async () => {
const a = "bro"
return `Hello, ${a}!`
})
bot.onReceive("test", async ({msg}) => `Hello, ${msg.sender.name}!`)
bot.onReceive("test", async () => {
return {text: "Text"}
})
In conclusion, there are 4 methods to send a message:
bot.sendMessage(targetId, options)bot.onReceive(input, response), which is the response.message.reply(options)If the object is a string, then the message will be sent as a text message. However, if it's an object with properties, then it should support these options in the object:
options - The message sending options
options.text - Text/caption to be sentoptions.quoted - Message to be quoted. By default it's the received message (if using method 2, 3, 4). Can be changed or set to null.options.image - Image to send. It can be a buffer, a url, a path or a stream object.options.video - Video to send. It can be a buffer, a url, a path or a stream object.options.gif - Video to send as gif. It can be a buffer, a url, a path or a stream object. (Whatsapp does not actually support GIF files. If you send a GIF file, it won't animate)options.audio - Audio to send. It can be a buffer, a url, a path or a stream object.options.vn - Audio to send as voice note.options.sticker - WebP file to send as sticker (buffer/url/path/stream)options.document - A file to send as document. Supporting properties:
options.mimetype - Mimetype for this document/fileoptions.fileName - Filename for this document/fileoptions.buttons[] - An array of buttons. Each one has these properties below.
button.type - The type of the button: reply, list, url, copy, call.button.text - The displayed text on the button. This is required for reply, url, copy, and call buttons.button.id - ID of the button. Required for reply buttons.button.url - URL to visit when the button is tapped. Required for url buttons.button.code - The code to copy to clipboard when the button is tapped. Required for copy buttons.button.phoneNumber - The number to dial when tapping the button. Required for call buttons.button.title - Title of the list menu. Required for list buttons.button.sections[] - Array of list menu sections. Required for list buttons. Each item is a section:
section.title - Title of the sectionsection.rows[] - List of items in the section. Required in a section. Each item is a row:
row.id - ID of the item on this row. Required.row.title - Title of the item on this row. Required.row.description - Description of the item.row.header - Header text of the item.options.title - Title for messages with buttons.options.footer - Footer for messages with buttons.options.contacts[] - Array of contacts to send. Each item is a contact:
contact.name - The displayed name of the contact.contact.number - The contact number (in string)options.poll - Poll object to send as poll message
options.poll.title - Title of the polloptions.poll.options[] - Options for the poll as array of stringsoptions.poll.multiple - If true, then allow multiple selectionNote: Since bot.sendMessage() and message.reply() return a message object which contains a text property, returning the result of these functions inside a response function can make your bot send message twice. For example:
bot.onReceive("test", async (msg) => {
// this will send 2 messages
// 1. from the effect of the msg.reply()
// 2. from returning the resulting message object created by msg.reply()
return await msg.reply("ok")
})
To mention a user, you can put @<user-lid> in your text (without @lid part). For example: msg.reply("Hello @1234567812345")
To get data of a group chat, use bot.getGroupData(id). Returns, if available:
group
group.id - ID of the groupgroup.subject - The subject / title of the group chatgroup.description - The description of the groupgroup.getParticipants() - Get the list of all participants in the group. It's an array of object with the structure:
participant
participant.id - ID of the participant. Could be a JID or LID.participant.lid - LID of the participantgroup.getAdmins() - Get the list of participants who are admins of the group.group.getMembers() - Get the list of participants who are not admins.The receivers are checked one by one in order you register them. If two or more receivers can be triggered by the same message, then the one that was registered first will be executed.
// Both of these receivers can be triggered by a message that says "tes123" but only the first one will be triggered
bot.onReceive("tes123", "This will be sent.")
bot.onReceive(/^tes/, "This will not be sent.")
In a response function, you can continue the flow to the next receiver using the next() function from the 4th argument (now deprecated as 4th argument, it's the 2nd now):
bot.onReceive(/.*/, (ctx, next) => {
if (userAuthorized(ctx.message.sender.id)) next()
return "You are not authorized!"
})
bot.onReceive("test", "Hello authorized user!")
The message object that is passed to the response functions is the same object. Therefore, you can modify it in a response function, and the modification remains in the subsequent response functions.
bot.onReceive(bot.messageType.any, ({message}, next) => {
message.watermark = "MyBot"
next()
})
bot.onReceive("test", ({message}) => {
return `Brought to you by ${message.watermark}`
})
bot.messageType has the following enum properties:
any: This is equivalent of regex /.*/ in receiver input.nonmedia: This includes text and reaction messages.media: This includes image, video, gif, audio, sticker and document.text, reaction, image, video, gif, audio, sticker, document.You can import tools that can be useful for certain scenarios.
require("wachan/commands")Useful for quickly setting up prefix-command-params style inputs in popular bot format. Ex: /search article
Exports: commands
commands - The Commands Tool. When imported, will add a new item in the settings, bot.settings.commandPrefixes, which is an array of prefixes that can be used to invoke commands.
commands.add(name, response, options) - Add a new command.
name - The name of the commandresponse - String/Object/Function
response(context, next). See here. There is an additional field in context which is command.
context
context.message - Message objectcontext.command - Command information
context.command.prefix - Prefix that is used when invoking this commandcontext.command.name - Original name of the command.context.command.usedName - The alias name that is used to invoke the commandcontext.command.parameters - Command parameter (in Array). Ex: /test a b c -> params = ["a","b","c"]context.command.description - Description of the commandcontext.command.aliases - Alias(es) of this commandcontext.command.hidden - Whether this is a command that is hidden from menu generator.options when you add this command.next - Function to proceed to the next receiver. (See Receiver Flow)response(message, params, command, prefix, group, bot)
message - The command messageparams - Parameters of the commands. Example: /test a b c -> params = ["a","b","c"]command - The command name that is usedprefix - The prefix that is usedgroup - If the chatroom is a group this will be an object that contains information about the group. Else null.bot - The bot object which is the same as wachan's main export.options - Additional options for the command
options.aliases - Array of aliases for the commandoptions.separator - Character to use as parameter string splitter. Default a space (" ")options.description - Command descriptionoptions.sectionName - Section name of this command. This is used when generating a menu (see below at commands.generateMenu())options.hidden - This command will not be shown in the string of the commands.generateMenu() function's result.commands.fromFile(commandName, filePath) - Add a new command from a file. The file must be a .js file exporting a object cmdFile with the structure as follows:
cmdFile.response - Similar to commands.add()'s response parameter. See above.cmdFile.options - Optional. Similar to commands.add()'s options parameter. See above.commands.fromFolder(folderPath) - Scan a folder for command files then add them as commands. The names are taken from the names of the files. See above for the details of a command file.
commands.addPrefix(prefix) - Add a new command prefix. Like aliases, but for prefix.
commands.removePrefix(prefix) - Remove one of the existing command prefixes.
commands.getCommandInfo(commandName) - Get the info about a registered command by its name.
commands.getCommands() - Get a list of the info of all registered commands.
commands.beforeEach(callback) - Add a callback that will be executed before executing each command. This is useful for example for authorization (like admin/owner checking)
callback(context, next) - The callback that will be added
context - Just like context when you add a new command through commands.add()next - Function to skip to the next callback, or the command that is about to be executed.commands.generateMenu(options) - Generate a string of menu that automatically lists all the registered commands and also groups them by their sections. Generation options:
options?.prefix - The prefix to display. By default the first prefix in the registered prefixes list.options?.header - The header or title of the menu. Note: You need to add newlines (\n) manually at the end of it if you want to separate the header and the body in their own lines. By default: "> COMMAND LIST:\n\n"options?.sectionTitleFormat - Use this to format the title of each section. Use <<section>> to mark the position of the section title. By default: "# <<section>>\n" (Again, manually insert the newlines)options?.sectionFooter - The footer of each section. Again, manually insert the newlines but insert them at the beginning. (Ex: "\n------"). By default: "" (empty string)options?.commandFormat - The formatting of each command item. Use <<prefix>>, <<name>>, and <<description>> to mark the position of the prefix, command name, and the description, respectively. By default: "- `<<prefix>><<name>>`: <<description>>"options?.formatter - Formatter function to use, it takes 1 argument that contains information about the command. If this returns falsy, then options.commandFormat will be used.options?.commandSeparator - The separator between command items. By default: "\n" (a newline)options?.sectionSeparator - The separator between sections. By default: "\n\n"options?.unsectionedFirst - If true will show the unsectioned commands before the sectioned ones and vice versa.options?.noDescriptionPlaceholder - The string to show if a command has no description.This is to show you what the generated menu looks like using default formatting:
> COMMAND LIST:
# Section A
- `/cmd1`: Description of the command.
- `/hello`: Say hello.
- `/wachan`: Awesome module.
# Section B
- `/this`: Is an example
- `/you`: Can imagine what it looks like in Whatsapp, I suppose.
- `/nodesc`: No description
Example Usage of this tool:
const cmd = require("wachan/commands")
cmd.add("multiply", function (msg, params) {
const [a, b] = params
const result = Number(a) * Number(b)
return `The result of ${a}*${b} is ${result}`
})
// Will respond when someone types:
// /multiply 4 5
// Bot will multiply 4 and 5 then send the result in chat
How to use beforeEach():
const cmd = require("wachan/commands")
cmd.beforeEach((context, next) => {
const { adminOnly } = context.command
const { isAdmin } = context.message.sender
if (adminOnly && !isAdmin) return `Only admin can use this command!`
next()
})
cmd.add("special", async (context, next) {
return "Special command has been executed!"
}, { adminOnly: true })
// When a user types /special, then we will check if they are an admin or not, if not then the command will not be executed
require("wachan/sticker")You can use this to create WebP stickers that are ready to use in WhatsApp.
Exports: sticker
sticker - The sticker tool
sticker.create(input, options) - Create a new WebP sticker buffer from input.
input - Can be a string of URL or path, or buffer of image/videooptions - Additional options
options.pack - The pack name of the sticker. You can see this at the bottom of the sticker preview window in WhatsApp.options.author - The author name of the sticker. You can see this at the bottom of the sticker preview window in WhatsApp.options.size - The length of one side of the sticker. Default 128 for videos, 512 otherwise. This affects the size of the sticker. And the acceptable sticker file size in Whatsapp is below 1MB.options.mode - The image fitting mode:
"crop" - Crop the sticker into a square area in the center."fit" - Stretch or squeeze the image into a square area."all" - No change. Fit all part of the image in the sticker by zooming out.Example:
const st = require("wachan/sticker")
const input = "url of your image, or path" // or buffer
const sticker = await st.create(input, {
pack: "My stickers",
author: "Me",
mode: "crop"
})
await bot.sendMessage(targetRoom, { sticker })
Exposed are these items for programming custom functionalities.
bot.getSocket()message.toBaileys()bot.start({ suppressBaileysLogs: false }) to show the logs from baileysbot.start({ configOverrides: {...} }) to override baileys makeWASocket function parameters.bot.getBotData()message.isPrivatemessage.streamMedia()groupmessage.fileNamemessage.mimeTypemessage.fileSizemessage.durationmessage.heightmessage.widthmessage.streamMedia()options.formatter from commands.generateMenu(options)ffmpeg-static dependencycontext.command.usedNamebot.messageType.vnaudio and vn types of messagesmessage.edit()bot.messageType.editmessage.edited.typemessage.edited.textbot.onReceiveReply()message.sender.id and message.sender.lid having incorrect valuesbot.getUserData()cmd.beforeEach()options.configOverrides in start() functioncontext and next. This will also be the case for response functions of commands.bot.getGroupData()bot.messageTypebot.onError()phoneNumber option in bot.start(options)buttonsnext() function from the 4th argumentmessage.idmessage.delete()message.getQuoted() is now available in messages with no quoted message, but it returns nullcommands.getCommands()sticker.create()'s new option field sizemessage.react()require("wachan/sticker")groupgroup and botoptions.hiddenmessage.downloadMedia(saveTo) no longer needs an existing file to save filerequire("wachan/commands")commands.fromFile() and commands.fromFolder()commands.getCommandInfo() and commands.generateMenu()bot.settings.messageStoreSize (default: 1000)bot.waitForMessage()message.timestampmessage.sender.lidmessage.getQuoted()require("wachan/commands"))Baileys version to 6.7.19message.receivedOnline can now be falsebot.onReceive() now returns a Receiver object.Receiver objects created from bot.onReceive() can be removed using .remove() method.FAQs
Simpler way to code baileys.
We found that wachan demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.