ckptw
Create powerful WhatsApp bots easily.
- ✨ Effortless
- 🧱 Builder
- 🛒 Built in Collector
- ⏰ Built in Cooldown
- 🔑 Built in Command handler
- 🎉 And more!
Table Of Contents
Installation
npm install @mengkodingan/ckptw
yarn add @mengkodingan/ckptw
pnpm add @mengkodingan/ckptw
Example
import { Client, Events, MessageType } from "@mengkodingan/ckptw";
const bot = new Client({
prefix: "!",
printQRInTerminal: true,
readIncommingMsg: true
});
bot.ev.once(Events.ClientReady, (m) => {
console.log(`ready at ${m.user.id}`);
});
bot.command('ping', async(ctx) => ctx.reply({ text: 'pong!' }));
bot.command('hi', async(ctx) => ctx.reply('hello! you can use string as a first parameter in reply function too!'));
bot.hears('test', async(ctx) => ctx.reply('test 1 2 3 beep boop...'));
bot.hears(MessageType.stickerMessage, async(ctx) => ctx.reply('wow, cool sticker'));
bot.hears(['help', 'menu'], async(ctx) => ctx.reply('hears can be use with array too!'));
bot.hears(/(using\s?)?regex/, async(ctx) => ctx.reply('or using regex!'));
bot.launch();
Or using the Events
import { Client, Events } from "@mengkodingan/ckptw";
const bot = new Client({
prefix: "!",
printQRInTerminal: true,
readIncommingMsg: true
});
bot.ev.once(Events.ClientReady, (m) => {
console.log(`ready at ${m.user.id}`);
});
bot.ev.on(Events.MessagesUpsert, (m, ctx) => {
if(m.key.fromMe) return;
if(m.content === "hello") {
ctx.reply("hi 👋");
}
})
bot.launch();
Client Configuration
export interface ClientOptions {
prefix: Array<string> | string | RegExp;
readIncommingMsg?: boolean;
authDir?: string;
printQRInTerminal?: boolean;
qrTimeout?: number;
markOnlineOnConnect?: boolean;
phoneNumber?: string;
usePairingCode?: boolean;
selfReply?: boolean;
WAVersion?: [number, number, number];
autoMention?: boolean;
}
Command Options
bot.command(opts: CommandOptions | string, code?: (ctx: Ctx) => Promise<any>)
bot.command('ping', async(ctx) => ctx.reply('pong!'))
export interface CommandOptions {
name: string;
aliases?: Array<string>;
code: (ctx: Ctx) => Promise<any>;
}
bot.command({
name: 'ping',
code: async(ctx) => ctx.reply('pong!');
})
Command Handler
With command handler you dont need all your command is located in one file.
-
in your main file
import { CommandHandler } from "@mengkodingan/ckptw";
import path from "path";
const cmd = new CommandHandler(bot, path.resolve() + '/CommandsPath');
cmd.load();
-
in your command file
module.exports = {
name: "ping",
code: async (ctx) => {
ctx.reply("pong!");
},
};
You can add a type
property to define the handler type... For now there are only command
and hears
types.
module.exports = {
name: "hears with command handler",
type: "hears",
code: async (ctx) => {
ctx.reply("hello world!");
},
};
Command Cooldown
Cooldown can give a delay on the command. This can be done to prevent users from spamming your bot commands.
import { Cooldown } from "@mengkodingan/ckptw";
bot.command('ping', async(ctx) => {
const cd = new Cooldown(ctx, 8000);
if(cd.onCooldown) return ctx.reply(`slow down... wait ${cd.timeleft}ms`);
ctx.reply('pong!')
})
if you want to trigger some function when the cooldown end, you can use the "end" events in the cooldown:
⚠
Will always be triggered when the cooldown is over (even though the users only runs the command once)
cd.on("end", () => {
ctx.reply({ text: "cd timeout" });
})
Cooldown getter:
cd.onCooldown;
cd.timeleft;
Builder
-
Button
Make a button message with Button Builder.
export type ButtonType = 'cta_url' | 'cta_call' | 'cta_copy' | 'cta_reminder' | 'cta_cancel_reminder' | 'address_message' | 'send_location' | 'quick_reply';
import { ButtonBuilder } from "@mengkodingan/ckptw";
let button = new ButtonBuilder()
.setId('!ping')
.setDisplayText('command Ping')
.setType('quick_reply')
.build();
let button2 = new ButtonBuilder()
.setId('id2')
.setDisplayText('copy code')
.setType('cta_copy')
.setCopyCode('hello world')
.build();
let button3 = new ButtonBuilder()
.setId('id3')
.setDisplayText('@mengkodingan/ckptw')
.setType('cta_url')
.setURL('https://github.com/mengkodingan/ckptw')
.setMerchantURL('https://github.com/mengkodingan')
.build();
ctx.replyInteractiveMessage({
body: 'this is body',
footer: 'this is footer',
nativeFlowMessage: { buttons: [button, button2, button3] }
})
-
Sections
Sections message is like a list.
import { SectionsBuilder } from "@mengkodingan/ckptw";
let section1 = new SectionsBuilder()
.setDisplayText("Click me")
.addSection({
title: 'Title 1',
rows: [
{ header: "Row Header 1", title: "Row Title 1", description: "Row Description 1", id: "Row Id 1" },
{ header: "Row Header 2", title: "Row Title 2", description: "Row Description 2", id: "Row Id 2" }
]
})
.addSection({
title: 'This is title 2',
rows: [
{ title: "Ping", id: "!ping" },
{ title: "Hello world", id: "hello world" },
]
})
.build();
ctx.sendInteractiveMessage(ctx.id!, {
body: 'this is body',
footer: 'this is footer',
nativeFlowMessage: { buttons: [section1] }
})
-
Carousel
A carousel message is a type of message that slides like a carousel.
import { ButtonBuilder, CarouselBuilder } from "@mengkodingan/ckptw";
let button = new ButtonBuilder()
.setId('!ping')
.setDisplayText('command Ping')
.setType('quick_reply')
.build();
let exampleMediaAttachment = await ctx.prepareWAMessageMedia({ image: { url: "https://github.com/mengkodingan.png" } }, { upload: ctx._client.waUploadToServer })
let cards = new CarouselBuilder()
.addCard({
body: "BODY 1",
footer: "FOOTER 1",
header: {
title: "HEADER TITLE 1",
hasMediaAttachment: true,
...exampleMediaAttachment
},
nativeFlowMessage: { buttons: [button] }
})
.addCard({
body: "BODY 2",
footer: "FOOTER 2",
header: {
title: "HEADER TITLE 2",
hasMediaAttachment: true,
...exampleMediaAttachment
},
nativeFlowMessage: { buttons: [button] }
})
.build();
ctx.replyInteractiveMessage({
body: "this is body",
footer: "this is footer",
carouselMessage: {
cards,
},
});
-
Contact
Send a contact.
import { VCardBuilder } from "@mengkodingan/ckptw";
const vcard = new VCardBuilder()
.setFullName("John Doe")
.setOrg("PT Mencari Cinta Sejati")
.setNumber("621234567890")
.build();
ctx.reply({ contacts: { displayName: "John D", contacts: [{ vcard }] }});
-
Template Buttons (⚠ DEPRCATED! Use button builder instead.)
Send a button with "attachment".
import { TemplateButtonsBuilder } from "@mengkodingan/ckptw";
const templateButtons = new TemplateButtonsBuilder()
.addURL({ displayText: 'ckptw at Github', url: 'https://github.com/mengkodingan/ckptw' })
.addCall({ displayText: 'call me', phoneNumber: '+1234567890' })
.addQuickReply({ displayText: 'just a normal button', id: 'btn1' })
.build();
ctx.sendMessage(ctx.id, { text: "template buttons", templateButtons });
Collector
There are several options that can be used in the collector:
export interface CollectorArgs {
time?: number;
max?: number;
endReason?: string[];
maxProcessed?: number;
filter?: () => boolean;
}
-
Message Collector
let col = ctx.MessageCollector({ time: 10000 });
ctx.reply({ text: "say something... Timeout: 10s" });
col.on("collect", (m) => {
console.log("COLLECTED", m);
ctx.sendMessage(ctx.id, {
text: `Collected: ${m.content}\nFrom: ${m.sender}`,
});
});
col.on("end", (collector, r) => {
console.log("ended", r);
ctx.sendMessage(ctx.id, { text: `Collector ended` });
});
-
Awaited Messages
ctx.awaitMessages({ time: 10000 }).then((m) => ctx.reply(`got ${m.length} array length`)).catch(() => ctx.reply('end'))
Downloading Media
the code below will save the received image to ./saved.jpeg
import { MessageType } from "@mengkodingan/ckptw";
import fs from "node:fs";
bot.ev.on(Events.MessagesUpsert, async(m, ctx) => {
if(ctx.getMessageType() === MessageType.imageMessage) {
const buffer = await ctx.msg.media.toBuffer();
fs.writeFileSync('./saved.jpeg', buffer);
}
});
ctx.msg.media.toBuffer()
ctx.msg.media.toStream()
ctx.quoted.media.toBuffer()
ctx.quoted.media.toStream()
Events
Firstly you must import the Events Constant like this:
import { Events } from "@mengkodingan/ckptw";
-
Available Events
- ClientReady - Emitted when the bot client is ready.
- MessagesUpsert - Received an messages.
- QR - The bot QR is ready to scan. Return the QR Codes.
- GroupsJoin - Emitted when bot joining groups.
- UserJoin - Emitted when someone joins a group where bots are also in that group.
- UserLeave - Same with UserJoin but this is when the user leaves the group.
- Poll - Emitted when someone create a poll message.
- PollVote - Emitted when someone votes for one/more options in a poll.
- Reactions - Emitted when someone reacts to a message.
- Call - Emitted when someone calling, call was accepted or rejected.
Sending Message
ctx.sendMessage(ctx.id, { text: "hello" });
ctx.reply("hello");
ctx.reply({ text: "hello" });
ctx.sendMessage(ctx.id, { image: { url: 'https://example.com/image.jpeg' }, caption: "image caption" });
ctx.reply({ image: { url: 'https://example.com/image.jpeg' }, caption: "image caption" });
ctx.reply({ audio: { url: './audio.mp3' }, mimetype: 'audio/mp4', ptt: false });
ctx.reply({ sticker: { url: './tmp/generatedsticker.webp' }});
import fs from "node:fs";
ctx.reply({ video: fs.readFileSync("./video.mp4"), caption: "video caption", gifPlayback: false });
Formatter
WhatsApp allows you to format text inside your messages. Like bolding your message, etc. This function formats strings into several markdown styles supported by WhatsApp.
⚠ Some new text formatting is only available on Web and Mac desktop.
You can see the Whatsapp FAQ about formatting messages here.
import { bold, inlineCode, italic, monospace, quote, strikethrough } from "@mengkodingan/ckptw";
const str = "Hello World";
const boldString = bold(str);
const italicString = italic(str);
const strikethroughString = strikethrough(str);
const quoteString = quote(str);
const inlineCodeString = inlineCode(str);
const monospaceString = monospace(str);
Editing Message
let res = await ctx.reply("old text");
ctx.editMessage(res.key, "new text");
Deleting Message
let res = await ctx.reply("testing");
ctx.deleteMessage(res.key);
Poll Message
singleSelect
means you can only select one of the multiple options in the poll. Default to be false
ctx.sendPoll(ctx.id, { name: "ini polling", values: ["abc", "def"], singleSelect: true })
Get Mentions
You can use the function from ctx
to get the jid array mentioned in the message. For example, a message containing hello @jstn @person
where @jstn
& @person
is a mention, then you can get an array containing the jid of the two mentioned users.
ctx.getMentioned()
Auto Mention
You can mention someone without having to enter each Whatsapp Jid into the mentions
array.
If you are still confused about what this is, perhaps you can check out the code comparison for mentioning someone below:
ctx.reply("Hello @62812345678");
ctx.reply({ text: "Hello @62812345678", mentions: ['62812345678@s.whatsapp.net'] });
Group Stuff
ctx.groups.create(subject: string, members: string[]);
ctx.groups.inviteCodeInfo(code: string);
ctx.groups.acceptInvite(code: string);
ctx.groups.acceptInviteV4(key: string | proto.IMessageKeinviteMessage: proto.Message.IGroupInviteMessage);
ctx.group(jid?: string);
ctx.group().members();
ctx.group().inviteCode();
ctx.group().revokeInviteCode();
ctx.group().joinApproval(mode: "on" | "off");
ctx.group().leave();
ctx.group().membersCanAddMemberMode(mode: "on" | "off");
ctx.group().metadata();
ctx.group().toggleEphemeral(expiration: number);
ctx.group().updateDescription(description: number);
ctx.group().updateSubject(subject: number);
ctx.group().membersUpdate(members: string[], action: ParticipantAction);
ctx.group().kick(members: string[]);
ctx.group().add(members: string[]);
ctx.group().promote(members: string[]);
ctx.group().demote(members: string[]);
ctx.group().pendingMembers();
ctx.group().pendingMembersUpdate(members: string[], action: 'reject' | 'approve');
ctx.group().approvePendingMembers(members: string[]);
ctx.group().rejectPendingMembers(members: string[]);
ctx.group().updateSetting(setting: 'announcement' | 'not_announcement' | 'locked' | 'unlocked')
ctx.group().open()
ctx.group().close()
ctx.group().lock()
ctx.group().unlock()
Misc
ctx.reply({ text: "test" });
ctx.reply("you can use string as a first parameter too!");
ctx.sendInteractiveMessage(jid: string, content: IInteractiveMessageContent, options: MessageGenerationOptionsFromContent | {} = {});
ctx.replyInteractiveMessage(content: IInteractiveMessageContent, options: MessageGenerationOptionsFromContent | {} = {});
bot.hears('test', async(ctx) => ctx.reply('test 1 2 3 beep boop...'));
import { MessageType } from "@mengkodingan/ckptw";
bot.hears(MessageType.stickerMessage, async(ctx) => ctx.reply('wow, cool sticker'));
ctx.react(jid: string, emoji: string, key?: WAProto.IMessageKey);
ctx.react(ctx.id, "👀");
bot.readyAt;
ctx.id
ctx.decodedId
ctx.args
ctx.sender
ctx.quoted
ctx.getMessageType()
ctx.getContentType(content: WAProto.IMessage | undefined)
ctx.downloadContentFromMessage(downloadable: DownloadableMessage, type: MediaType, opts?: MediaDownloadOptions)
ctx.read()
ctx.simulateTyping()
ctx.simulateRecording()
bot.bio("Hi there!");
await bot.fetchBio("1234@s.whatsapp.net");
await bot.block("1234@s.whatsapp.net");
await bot.unblock("1234@s.whatsapp.net");
ctx.getDevice(id)
ctx.getDevice()
ctx.isGroup()
bot.core
ctx._client