npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jagproject

v28.8.2

Published

JagProject Baileys Modification — WhatsApp Web API untuk Node.js dengan dukungan multi-device, pesan, grup, newsletter, dan bisnis.

Readme

JagProject bukan produk resmi WhatsApp dan tidak berafiliasi dengan WhatsApp LLC. Gunakan hanya pada akun yang Anda miliki atau kelola, patuhi ketentuan layanan, dan hindari spam serta penyalahgunaan.

🧭 Navigasi

✨ Fitur utama

  • Koneksi multi-device melalui QR atau pairing code.
  • Pengambilan versi WhatsApp Web stable terbaru dengan fallback aman.
  • Pengiriman teks, gambar, video, audio, dokumen, sticker, lokasi, kontak, polling, reaction, edit, hapus, dan pesan sekali lihat.
  • Event pesan, koneksi, presence, grup, panggilan, blocklist, label, serta newsletter.
  • Manajemen grup, profil, privasi, status, katalog bisnis, dan produk.
  • Session multi-file dan in-memory store.
  • Penolakan panggilan, auto-read status, serta auto-read pesan secara opsional.
  • Auto-follow newsletter JagProject setelah koneksi terbuka.
  • CommonJS dan deklarasi TypeScript.

📋 Persyaratan

  • Node.js 20 atau lebih baru.
  • npm, pnpm, atau Yarn.
  • qrcode-terminal jika QR ingin ditampilkan di terminal.
  • sharp direkomendasikan untuk pengolahan gambar dan sticker.

📦 Instalasi

npm install jagproject

Paket tambahan yang direkomendasikan:

npm install pino qrcode-terminal sharp

🚀 Mulai cepat

Contoh berikut membuat session, menampilkan QR, menyimpan kredensial, menangani reconnect, dan membalas pesan masuk.

const {
  makeWASocketLatest,
  useMultiFileAuthState,
  DisconnectReason,
  Browsers
} = require('jagproject')
const { Boom } = require('@hapi/boom')
const pino = require('pino')

async function startBot() {
  const { state, saveCreds } = await useMultiFileAuthState('./session')

  const sock = await makeWASocketLatest({
    auth: state,
    logger: pino({ level: 'silent' }),
    browser: Browsers('Chrome'),
    printQRInTerminal: true,
    syncFullHistory: false,
    markOnlineOnConnect: true
  })

  sock.ev.on('creds.update', saveCreds)

  sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
    if (connection === 'open') {
      console.log('✅ JagProject terhubung')
    }

    if (connection === 'close') {
      const statusCode = new Boom(lastDisconnect?.error)?.output?.statusCode
      const keluar = statusCode === DisconnectReason.loggedOut

      if (!keluar) {
        startBot().catch(console.error)
      } else {
        console.log('❌ Session keluar. Hapus folder session lalu hubungkan ulang.')
      }
    }
  })

  sock.ev.on('messages.upsert', async ({ messages, type }) => {
    if (type !== 'notify') return

    const msg = messages[0]
    if (!msg?.message || msg.key.fromMe) return

    await sock.sendMessage(msg.key.remoteJid, {
      text: 'JagProject aktif 🚀'
    }, {
      quoted: msg
    })
  })
}

startBot().catch(console.error)

🔐 QR dan pairing code

📷 Login dengan QR

Aktifkan printQRInTerminal:

const sock = await makeWASocketLatest({
  auth: state,
  logger,
  printQRInTerminal: true
})

Jika QR tidak tampil, pasang modul terminal QR:

npm install qrcode-terminal

🔢 Login dengan pairing code

Gunakan nomor internasional tanpa tanda +, spasi, atau tanda hubung.

const {
  makeWASocketLatest,
  useMultiFileAuthState
} = require('jagproject')
const pino = require('pino')

async function pairing() {
  const { state, saveCreds } = await useMultiFileAuthState('./session')

  const sock = await makeWASocketLatest({
    auth: state,
    logger: pino({ level: 'silent' }),
    printQRInTerminal: false
  })

  sock.ev.on('creds.update', saveCreds)

  if (!state.creds.registered) {
    const kode = await sock.requestPairingCode('6281234567890')
    console.log('🔐 Pairing code:', kode)
  }
}

pairing().catch(console.error)

Pairing code kustom:

const kode = await sock.requestPairingCode(
  '6281234567890',
  'JAGOAN28'
)

Jangan meminta pairing code berulang-ulang dalam waktu singkat.

🔄 Koneksi dan reconnect

Simpan setiap perubahan kredensial:

sock.ev.on('creds.update', saveCreds)

Tangani status koneksi:

sock.ev.on('connection.update', update => {
  const {
    connection,
    lastDisconnect,
    qr,
    isNewLogin
  } = update

  console.log({ connection, qr, isNewLogin, lastDisconnect })
})

Contoh reconnect yang aman:

const { Boom } = require('@hapi/boom')
const { DisconnectReason } = require('jagproject')

sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
  if (connection !== 'close') return

  const code = new Boom(lastDisconnect?.error)?.output?.statusCode

  if (code !== DisconnectReason.loggedOut) {
    startBot().catch(console.error)
  }
})

Jangan reconnect ketika session sudah loggedOut. Hapus session lama dan lakukan pairing ulang.

🌐 Versi WhatsApp Web otomatis

✅ Socket dengan versi terbaru

const { makeWASocketLatest } = require('jagproject')

const sock = await makeWASocketLatest({
  auth: state,
  logger
})

makeWASocketLatest() akan:

  1. Memeriksa versi WhatsApp Web stable terbaru.
  2. Menggunakan versi terbaru saat membuat socket.
  3. Menolak downgrade apabila sumber jaringan mengirim versi yang lebih lama.
  4. Mencoba sumber cadangan apabila sumber utama gagal.
  5. Menggunakan versi bawaan paket apabila seluruh permintaan jaringan gagal.
  6. Tetap memakai version manual apabila pengguna mengisinya.

🔎 Memeriksa versi secara manual

const {
  fetchLatestWaWebVersion,
  makeWASocket
} = require('jagproject')

const hasil = await fetchLatestWaWebVersion()

console.log({
  version: hasil.version,
  label: hasil.versionLabel,
  channel: hasil.channel,
  source: hasil.source,
  isLatest: hasil.isLatest,
  error: hasil.error
})

const sock = makeWASocket({
  auth: state,
  logger,
  version: hasil.version
})

fetchLatestBaileysVersion() tersedia sebagai alias kompatibilitas untuk fetchLatestWaWebVersion().

📌 Versi manual

const sock = makeWASocket({
  auth: state,
  logger,
  version: [2, 3000, 1044610476]
})

📦 Versi bawaan

const { default: makeWASocket } = require('jagproject')

const sock = makeWASocket({
  auth: state,
  logger
})

makeWASocket() bersifat sinkron. Gunakan makeWASocketLatest() untuk pemeriksaan versi saat bot mulai.

⚙️ Konfigurasi socket

const sock = await makeWASocketLatest({
  auth: state,
  logger,
  browser: Browsers('Chrome'),
  printQRInTerminal: true,
  syncFullHistory: false,
  markOnlineOnConnect: true,
  emitOwnEvents: true,
  fireInitQueries: true,
  generateHighQualityLinkPreview: false,

  rejectCalls: true,
  callRejectMessage: {
    text: 'Maaf, akun ini tidak menerima panggilan.'
  },
  autoReadStatus: false,
  autoReadMessages: false,

  getMessage: async key => {
    return store?.loadMessage(key.remoteJid, key.id)?.message
  },

  cachedGroupMetadata: async jid => {
    return groupCache.get(jid)
  }
})

| Opsi | Fungsi | |---|---| | auth | Kredensial dan Signal key store. | | logger | Logger Pino. | | browser | Identitas browser pada koneksi. | | printQRInTerminal | Menampilkan QR di terminal. | | markOnlineOnConnect | Menandai akun tersedia setelah tersambung. | | syncFullHistory | Meminta sinkronisasi riwayat yang lebih lengkap. | | emitOwnEvents | Memancarkan event untuk aksi dari akun sendiri. | | rejectCalls | Menolak panggilan masuk otomatis. | | callRejectMessage | Pesan opsional setelah panggilan ditolak. | | autoReadStatus | Membaca status masuk otomatis. | | autoReadMessages | Mengirim receipt baca untuk pesan masuk. | | getMessage | Mengambil pesan lama untuk retry dan polling. | | cachedGroupMetadata | Mengurangi permintaan metadata grup berulang. |

📡 Event

Semua event utama tersedia melalui sock.ev.

sock.ev.on('messages.upsert', ({ messages, type }) => {})
sock.ev.on('messages.update', updates => {})
sock.ev.on('messages.delete', update => {})
sock.ev.on('messages.reaction', reactions => {})
sock.ev.on('message-receipt.update', receipts => {})
sock.ev.on('connection.update', update => {})
sock.ev.on('creds.update', saveCreds)
sock.ev.on('presence.update', update => {})
sock.ev.on('contacts.upsert', contacts => {})
sock.ev.on('contacts.update', contacts => {})
sock.ev.on('groups.upsert', groups => {})
sock.ev.on('groups.update', groups => {})
sock.ev.on('group-participants.update', update => {})
sock.ev.on('call', calls => {})
sock.ev.on('blocklist.update', update => {})
sock.ev.on('newsletter.reaction', update => {})
sock.ev.on('newsletter.view', update => {})

📨 Membaca pesan masuk

sock.ev.on('messages.upsert', async ({ messages, type }) => {
  if (type !== 'notify') return

  for (const msg of messages) {
    const jid = msg.key.remoteJid
    const teks =
      msg.message?.conversation ||
      msg.message?.extendedTextMessage?.text ||
      msg.message?.imageMessage?.caption ||
      msg.message?.videoMessage?.caption

    console.log({ jid, teks })
  }
})

💬 Mengirim pesan

✏️ Teks

await sock.sendMessage('[email protected]', {
  text: 'Halo dari JagProject 👋'
})

💭 Balas atau quote

await sock.sendMessage(jid, {
  text: 'Balasan pesan.'
}, {
  quoted: msg
})

👤 Mention

await sock.sendMessage(jid, {
  text: 'Halo @6281234567890',
  mentions: ['[email protected]']
})

🖼️ Gambar

await sock.sendMessage(jid, {
  image: { url: './gambar.jpg' },
  caption: 'Gambar baru 📷'
})

Buffer juga dapat digunakan:

await sock.sendMessage(jid, {
  image: bufferGambar,
  caption: 'Gambar dari buffer.'
})

🎬 Video

await sock.sendMessage(jid, {
  video: { url: './video.mp4' },
  caption: 'Video baru 🎬'
})

🎞️ GIF

await sock.sendMessage(jid, {
  video: { url: './animasi.mp4' },
  gifPlayback: true,
  caption: 'Animasi.'
})

🎧 Audio dan voice note

await sock.sendMessage(jid, {
  audio: { url: './audio.mp3' },
  mimetype: 'audio/mpeg'
})
await sock.sendMessage(jid, {
  audio: { url: './voice-note.ogg' },
  mimetype: 'audio/ogg; codecs=opus',
  ptt: true
})

📄 Dokumen

await sock.sendMessage(jid, {
  document: { url: './laporan.pdf' },
  fileName: 'laporan.pdf',
  mimetype: 'application/pdf',
  caption: 'Dokumen laporan.'
})

🏷️ Sticker

await sock.sendMessage(jid, {
  sticker: { url: './sticker.webp' }
})

📍 Lokasi

await sock.sendMessage(jid, {
  location: {
    degreesLatitude: -5.1477,
    degreesLongitude: 119.4327,
    name: 'Makassar'
  }
})

☎️ Kontak

await sock.sendMessage(jid, {
  contacts: {
    displayName: 'Jagoan Project',
    contacts: [{
      vcard: [
        'BEGIN:VCARD',
        'VERSION:3.0',
        'FN:Jagoan Project',
        'TEL;type=CELL;type=VOICE;waid=6281234567890:+62 812-3456-7890',
        'END:VCARD'
      ].join('\n')
    }]
  }
})

🔥 Reaction

await sock.sendMessage(jid, {
  react: {
    text: '🔥',
    key: msg.key
  }
})

Hapus reaction dengan teks kosong:

await sock.sendMessage(jid, {
  react: {
    text: '',
    key: msg.key
  }
})

📊 Polling

await sock.sendMessage(jid, {
  poll: {
    name: 'Pilih menu:',
    values: ['Nasi goreng', 'Mie ayam', 'Soto'],
    selectableCount: 1
  }
})

👁️ Pesan sekali lihat

await sock.sendMessage(jid, {
  image: { url: './rahasia.jpg' },
  caption: 'Sekali lihat',
  viewOnce: true
})

📝 Edit pesan

const terkirim = await sock.sendMessage(jid, {
  text: 'Teks awal.'
})

await sock.sendMessage(jid, {
  text: 'Teks yang sudah diperbarui.',
  edit: terkirim.key
})

🗑️ Hapus pesan

await sock.sendMessage(jid, {
  delete: msg.key
})

↪️ Teruskan pesan

await sock.sendMessage(jidTujuan, {
  forward: msg
})

🧩 Pesan interaktif

Format tambahan yang dapat dikirim melalui sendMessage():

  • requestPaymentMessage
  • productMessage
  • interactiveMessage
  • albumMessage
  • eventMessage
  • pollResultMessage
  • groupStatusMessage
  • flowMessage

Contoh native flow:

await sock.sendMessage(jid, {
  interactiveMessage: {
    title: 'Menu JagProject',
    body: 'Silakan pilih menu.',
    footer: 'JagProject',
    buttons: [{
      name: 'single_select',
      buttonParamsJson: JSON.stringify({
        title: 'Buka menu',
        sections: [{
          title: 'Pilihan',
          rows: [
            {
              id: 'menu_1',
              title: 'Menu 1',
              description: 'Pilihan pertama'
            },
            {
              id: 'menu_2',
              title: 'Menu 2',
              description: 'Pilihan kedua'
            }
          ]
        }]
      })
    }]
  }
})

Dukungan pesan interaktif dapat berubah mengikuti akun dan protokol WhatsApp. Gunakan try/catch saat mengirim.

📢 Status WhatsApp

📝 Status teks

await sock.sendMessage('status@broadcast', {
  text: 'Status dari JagProject 🚀'
})

🖼️ Status media

await sock.sendMessage('status@broadcast', {
  image: { url: './status.jpg' },
  caption: 'Status gambar.'
})

📣 Status mention

await sock.sendStatusMention({
  text: 'Status dengan mention.'
}, [
  '[email protected]'
])

⌨️ Membaca pesan dan presence

await sock.readMessages([msg.key])

await sock.sendPresenceUpdate('composing', jid)
await sock.sendPresenceUpdate('recording', jid)
await sock.sendPresenceUpdate('paused', jid)
await sock.sendPresenceUpdate('available')
await sock.sendPresenceUpdate('unavailable')

Subscribe presence sebelum menunggu status online atau mengetik:

await sock.presenceSubscribe(jid)

📥 Download media

const { downloadMediaMessage } = require('jagproject')

const buffer = await downloadMediaMessage(
  msg,
  'buffer',
  {},
  {
    logger,
    reuploadRequest: sock.updateMediaMessage
  }
)

Simpan buffer ke file:

const fs = require('fs')
fs.writeFileSync('./hasil-media.bin', buffer)

👤 Profil dan pengguna

const hasil = await sock.onWhatsApp('6281234567890')
const foto = await sock.profilePictureUrl(jid, 'image')
const status = await sock.fetchStatus(jid)
const bisnis = await sock.getBusinessProfile(jid)

console.log({ hasil, foto, status, bisnis })

Perbarui profil:

await sock.updateProfileName('Nama Bot')
await sock.updateProfileStatus('Aktif dengan JagProject')
await sock.updateProfilePicture(sock.user.id, {
  url: './avatar.jpg'
})
await sock.removeProfilePicture(sock.user.id)

Blokir atau buka blokir:

await sock.updateBlockStatus(jid, 'block')
await sock.updateBlockStatus(jid, 'unblock')

const blocklist = await sock.fetchBlocklist()

🔒 Privasi

const privasi = await sock.fetchPrivacySettings(true)
console.log(privasi)

await sock.updateLastSeenPrivacy('contacts')
await sock.updateOnlinePrivacy('match_last_seen')
await sock.updateProfilePicturePrivacy('contacts')
await sock.updateStatusPrivacy('contacts')
await sock.updateReadReceiptsPrivacy('all')
await sock.updateGroupsAddPrivacy('contacts')
await sock.updateDefaultDisappearingMode(86400)

Nilai yang didukung dapat berbeda pada setiap jenis pengaturan dan dapat berubah mengikuti server WhatsApp.

👥 Grup

➕ Membuat grup

const grup = await sock.groupCreate('Grup JagProject', [
  '[email protected]',
  '[email protected]'
])

console.log(grup)

ℹ️ Metadata grup

const metadata = await sock.groupMetadata(
  '[email protected]'
)

console.log(metadata.subject)
console.log(metadata.participants)

👤 Mengelola peserta

await sock.groupParticipantsUpdate(groupJid, [userJid], 'add')
await sock.groupParticipantsUpdate(groupJid, [userJid], 'remove')
await sock.groupParticipantsUpdate(groupJid, [userJid], 'promote')
await sock.groupParticipantsUpdate(groupJid, [userJid], 'demote')

⚙️ Pengaturan grup

await sock.groupUpdateSubject(groupJid, 'Nama baru')
await sock.groupUpdateDescription(groupJid, 'Deskripsi baru')

await sock.groupSettingUpdate(groupJid, 'announcement')
await sock.groupSettingUpdate(groupJid, 'not_announcement')
await sock.groupSettingUpdate(groupJid, 'locked')
await sock.groupSettingUpdate(groupJid, 'unlocked')

await sock.groupMemberAddMode(groupJid, 'admin_add')
await sock.groupJoinApprovalMode(groupJid, 'on')

🔗 Undangan grup

const kode = await sock.groupInviteCode(groupJid)
await sock.groupRevokeInvite(groupJid)
await sock.groupAcceptInvite(kode)

const info = await sock.groupGetInviteInfo(kode)
console.log(info)

✅ Permintaan bergabung

const daftar = await sock.groupRequestParticipantsList(groupJid)
console.log(daftar)

await sock.groupRequestParticipantsUpdate(
  groupJid,
  ['[email protected]'],
  'approve'
)

Gunakan 'reject' untuk menolak permintaan.

⏳ Pesan sementara dan keluar grup

await sock.groupToggleEphemeral(groupJid, 86400)
await sock.groupLeave(groupJid)

📰 Newsletter

Auto-follow newsletter JagProject berjalan setelah connection.update berubah menjadi open. Status follow diperiksa terlebih dahulu agar permintaan tidak dikirim berulang apabila akun sudah mengikuti.

Method yang tersedia:

  • newsletterCreate()
  • newsletterMetadata()
  • newsletterFollow()
  • newsletterUnfollow()
  • newsletterMute()
  • newsletterUnmute()
  • newsletterUpdateName()
  • newsletterUpdateDescription()
  • newsletterUpdatePicture()
  • newsletterRemovePicture()
  • newsletterReactMessage()
  • newsletterFetchMessages()
  • newsletterFetchUpdates()
  • newsletterFetchAllSubscribe()
  • newsletterAdminCount()
  • newsletterChangeOwner()
  • newsletterDemote()
  • newsletterDelete()
  • subscribeNewsletterUpdates()

🔎 Metadata newsletter

const info = await sock.newsletterMetadata(
  'jid',
  '120363315304652958@newsletter'
)

console.log(info)

➕ Follow dan unfollow

await sock.newsletterFollow(newsletterJid)
await sock.newsletterUnfollow(newsletterJid)

🔕 Mute dan unmute

await sock.newsletterMute(newsletterJid)
await sock.newsletterUnmute(newsletterJid)

📨 Mengambil pesan newsletter

const messages = await sock.newsletterFetchMessages(
  newsletterJid,
  20,
  0,
  0
)

console.log(messages)

🔥 Reaction newsletter

await sock.newsletterReactMessage(
  newsletterJid,
  serverId,
  '🔥'
)

🗂️ Chat dan store

📌 Mengelola chat

await sock.chatModify({ archive: true }, jid)
await sock.chatModify({ archive: false }, jid)
await sock.chatModify({ markRead: false }, jid)
await sock.chatModify({ pin: true }, jid)
await sock.chatModify({ pin: false }, jid)

Star atau unstar pesan:

await sock.star(jid, [{
  id: msg.key.id,
  fromMe: msg.key.fromMe
}], true)

Ganti parameter terakhir menjadi false untuk menghapus star.

🤖 AI Rich Response

JagProject menyediakan builder sock.AIRich untuk menyusun respons AI kaya konten secara bertahap. Builder dapat menggabungkan teks, kode, tabel, sumber, reels, gambar, video, produk, post, metadata, dan suggestion dalam satu respons.

AI Rich memakai struktur pesan WhatsApp yang dapat berubah di sisi server/klien. Uji terlebih dahulu pada akun pengembangan sebelum dipakai di produksi.

Mengirim langsung ke room/chat yang sedang aktif

Semua contoh di bagian ini memakai m.chat, yaitu JID room/chat dari pesan yang sedang diproses. Pola ini umum dipakai pada handler bot yang sudah melakukan serialize message.

// Contoh di dalam command/handler yang sudah menyediakan object `m`
const ai = new sock.AIRich(sock)

ai
  .addText('Halo! Respons ini dikirim ke chat yang sedang aktif.')
  .addSuggestion([
    'Jelaskan fitur AI Rich',
    'Berikan contoh kode'
  ])

await ai.send(m.chat)

Jika handler Anda memakai event messages.upsert mentah dan belum mempunyai properti m.chat, gunakan m.key.remoteJid:

sock.ev.on('messages.upsert', async ({ messages, type }) => {
  if (type !== 'notify') return

  const m = messages[0]
  if (!m?.message || m.key.fromMe) return

  const chat = m.chat || m.key?.remoteJid
  if (!chat) return

  const ai = new sock.AIRich(sock)

  ai
    .setTitle('JagProject AI')
    .addText('AI Rich aktif di room ini.')
    .addSuggestion([
      'Coba tabel',
      'Coba kode',
      'Coba gambar'
    ])

  await ai.send(chat)
})

Jadi, bila framework/handler Anda sudah menyediakan m.chat, cukup gunakan m.chat. Bila tidak, gunakan m.key.remoteJid dari pesan WhatsApp mentah.

Contoh paling sederhana di command handler

// Misalnya di case/command `ai`
const ai = new sock.AIRich(sock)

ai
  .setTitle('JagProject AI')
  .addText('Berikut contoh kode JavaScript:')
  .addCode('javascript', 'const hello = "world"\nconsole.log(hello)')
  .addSuggestion([
    'Jelaskan kode ini',
    'Buat versi TypeScript'
  ])

// Balas ke private chat / grup tempat command dikirim
await ai.send(m.chat)

Semua method builder mengembalikan instance yang sama, sehingga dapat dirangkai dengan chaining.

addText() — teks AI

const ai = new sock.AIRich(sock)

ai.addText('Halo! Ini adalah jawaban dari AI.')

await ai.send(m.chat)

Link dapat ditulis langsung di teks:

ai.addText(
  'Baca [dokumentasi JagProject](https://example.com/docs) untuk informasi lengkap.'
)

Opsi ekstraksi inline entity dapat diatur bila diperlukan:

ai.addText('Teks respons', {
  hyperlink: true,
  citation: true,
  latex: true
})

addCode() — blok kode

Format:

ai.addCode(language, code)

Contoh JavaScript:

const ai = new sock.AIRich(sock)

ai
  .addText('Contoh penggunaan async/await:')
  .addCode(
    'javascript',
    `async function main() {
  const result = await Promise.resolve('JagProject')
  console.log(result)
}

main()`
  )

await ai.send(m.chat)

Contoh Python:

ai.addCode(
  'python',
  `name = "JagProject"
print(name)`
)

addTable() — tabel

addTable() menerima array dua dimensi berisi string. Baris pertama dipakai sebagai header.

const ai = new sock.AIRich(sock)

ai
  .addText('Ringkasan paket:')
  .addTable([
    ['Fitur', 'Status', 'Keterangan'],
    ['AI Rich', 'Aktif', 'Builder respons AI'],
    ['Newsletter', 'Aktif', 'Create, follow, react'],
    ['Communities', 'Aktif', 'Metadata dan member']
  ])

await ai.send(m.chat)

addSource() — sumber/referensi

Satu sumber:

const ai = new sock.AIRich(sock)

ai
  .addText('Sumber yang digunakan:')
  .addSource([
    'https://example.com/favicon.png',
    'https://example.com/artikel',
    'Dokumentasi resmi'
  ])

await ai.send(m.chat)

Beberapa sumber:

ai.addSource([
  [
    'https://example.com/favicon-1.png',
    'https://example.com/docs',
    'Dokumentasi'
  ],
  [
    'https://example.org/favicon-2.png',
    'https://example.org/reference',
    'Referensi tambahan'
  ]
])

Urutan setiap source adalah:

[profileOrFaviconUrl, sourceUrl, displayName]

addReels() — kartu reels/video pendek

const ai = new sock.AIRich(sock)

ai.addReels({
  username: '@jagproject',
  profileIconUrl: 'https://example.com/avatar.jpg',
  thumbnailUrl: 'https://example.com/reel-thumb.jpg',
  videoUrl: 'https://example.com/reel.mp4',
  reels_title: 'Demo JagProject',
  likes_count: 1200,
  shares_count: 75,
  view_count: 15000,
  reel_source: 'IG',
  is_verified: true
})

await ai.send(m.chat)

Beberapa reels sekaligus:

ai.addReels([
  {
    username: '@creator1',
    thumbnailUrl: 'https://example.com/1.jpg',
    videoUrl: 'https://example.com/1.mp4'
  },
  {
    username: '@creator2',
    thumbnailUrl: 'https://example.com/2.jpg',
    videoUrl: 'https://example.com/2.mp4'
  }
])

addImage() — gambar

Satu gambar:

const ai = new sock.AIRich(sock)

ai
  .addText('Berikut gambar hasil pencarian:')
  .addImage('https://example.com/image.jpg')

await ai.send(m.chat)

Beberapa gambar:

ai.addImage([
  'https://example.com/image-1.jpg',
  'https://example.com/image-2.jpg',
  'https://example.com/image-3.jpg'
])

addVideo() — video

Satu video:

const ai = new sock.AIRich(sock)

ai.addVideo('https://example.com/video.mp4')

await ai.send(m.chat)

Durasi video dapat ditambahkan setelah URL dengan pemisah |:

ai.addVideo('https://example.com/video.mp4|15')

Contoh beberapa video:

ai.addVideo([
  'https://example.com/video-1.mp4|10',
  'https://example.com/video-2.mp4|24'
])

Angka setelah | adalah durasi yang akan dimasukkan ke metadata video.

addProduct() — kartu produk

Satu produk:

const ai = new sock.AIRich(sock)

ai.addProduct({
  title: 'JagProject Premium',
  brand: 'Jagoan Project',
  price: 'Rp100.000',
  sale_price: 'Rp75.000',
  product_url: 'https://example.com/product',
  image_url: 'https://example.com/product.jpg',
  icon_url: 'https://example.com/product-icon.jpg'
})

await ai.send(m.chat)

Beberapa produk akan dibuat sebagai horizontal scroll:

ai.addProduct([
  {
    title: 'Produk A',
    brand: 'Brand A',
    price: 'Rp50.000',
    product_url: 'https://example.com/a',
    image_url: 'https://example.com/a.jpg'
  },
  {
    title: 'Produk B',
    brand: 'Brand B',
    price: 'Rp80.000',
    product_url: 'https://example.com/b',
    image_url: 'https://example.com/b.jpg'
  }
])

addPost() — kartu post sosial

const ai = new sock.AIRich(sock)

ai.addPost({
  title: 'JagProject Update',
  subtitle: 'Update terbaru',
  username: '@jagproject',
  profile_picture_url: 'https://example.com/avatar.jpg',
  thumbnail_url: 'https://example.com/post.jpg',
  post_caption: 'Contoh post yang ditampilkan melalui AI Rich.',
  likes_count: 2500,
  comments_count: 130,
  shares_count: 90,
  post_url: 'https://example.com/post/1',
  source_app: 'INSTAGRAM',
  footer_label: 'Lihat post',
  is_verified: true,
  orientation: 'LANDSCAPE',
  post_type: 'VIDEO'
})

await ai.send(m.chat)

Untuk carousel post, kirim array object:

ai.addPost([
  {
    username: '@jagproject',
    thumbnail_url: 'https://example.com/post-1.jpg',
    post_url: 'https://example.com/post/1',
    post_caption: 'Post pertama'
  },
  {
    username: '@jagproject',
    thumbnail_url: 'https://example.com/post-2.jpg',
    post_url: 'https://example.com/post/2',
    post_caption: 'Post kedua'
  }
])

addMetadata() — catatan/metadata teks

const ai = new sock.AIRich(sock)

ai
  .addText('Jawaban utama dari AI.')
  .addMetadata('AI dapat membuat kesalahan. Periksa informasi penting.')

await ai.send(m.chat)

addMetadata() adalah nama yang direkomendasikan. Alias lama addTip() tetap tersedia untuk kompatibilitas.

addSuggestion() — tombol saran lanjutan

const ai = new sock.AIRich(sock)

ai
  .addText('Apa yang ingin kamu lakukan selanjutnya?')
  .addSuggestion([
    'Ringkas jawaban',
    'Berikan contoh kode',
    'Jelaskan lebih detail'
  ])

await ai.send(m.chat)

Satu suggestion juga didukung:

ai.addSuggestion('Lanjutkan')

addSuggestion() adalah nama yang direkomendasikan. Alias lama addSuggest() tetap tersedia untuk kompatibilitas.

setTitle() dan setFooter()

const ai = new sock.AIRich(sock)

ai
  .setTitle('JagProject AI')
  .addText('Ini isi jawaban.')
  .setFooter('Generated with JagProject')

await ai.send(m.chat)

setTitle() mengisi judul/disclaimer pada metadata pesan AI, sedangkan setFooter() menambahkan metadata teks di bagian akhir response.

Contoh langsung untuk command bot

Contoh berikut dapat ditempatkan di handler switch/case. Saat user mengirim command di private chat atau grup, hasil AI Rich dikirim kembali ke room yang sama melalui m.chat.

case 'airich': {
  const ai = new sock.AIRich(sock)

  ai
    .setTitle('JagProject AI')
    .addText(`Halo ${m.pushName || 'user'}! Ini contoh AI Rich di chat ini.`)
    .addTable([
      ['Fitur', 'Status'],
      ['Text', 'Aktif'],
      ['Code', 'Aktif'],
      ['Suggestion', 'Aktif']
    ])
    .addCode(
      'javascript',
      `const room = m.chat
console.log('Reply to:', room)`
    )
    .addMetadata('Pesan ini dibuat langsung dari command handler.')
    .addSuggestion([
      'Contoh gambar',
      'Contoh produk',
      'Contoh reels'
    ])
    .setFooter('JagProject AI Rich')

  await ai.send(m.chat)
  break
}

Contoh AI Rich lengkap

const ai = new sock.AIRich(sock)

ai
  .setTitle('JagProject AI')
  .addText('Berikut hasil analisis yang saya temukan.')
  .addTable([
    ['Item', 'Nilai'],
    ['Status', 'Berhasil'],
    ['Confidence', '95%']
  ])
  .addCode(
    'javascript',
    `const status = 'Berhasil'
console.log({ status })`
  )
  .addSource([
    'https://example.com/favicon.png',
    'https://example.com/docs',
    'Dokumentasi'
  ])
  .addImage('https://example.com/result.jpg')
  .addMetadata('Periksa kembali data penting sebelum digunakan.')
  .addSuggestion([
    'Jelaskan tabel',
    'Buat contoh lain',
    'Tampilkan sumber'
  ])
  .setFooter('JagProject AI Rich')

await ai.send(m.chat)

Advanced: addSubmessage() dan addSection()

Untuk primitive yang belum memiliki helper khusus, builder menyediakan akses raw:

const ai = new sock.AIRich(sock)

ai.addSubmessage({
  messageType: 2,
  messageText: 'Raw submessage'
})

ai.addSection({
  view_model: {
    primitive: {
      text: 'Raw section',
      __typename: 'GenAIMetadataTextPrimitive'
    },
    __typename: 'GenAISingleLayoutViewModel'
  }
})

await ai.send(m.chat)

Gunakan API raw hanya bila memahami struktur unified response yang dipakai WhatsApp.

Advanced: build() tanpa langsung mengirim

const ai = new sock.AIRich(sock)

const content = ai
  .addText('Preview payload AI Rich')
  .addSuggestion('Lanjutkan')
  .build({
    forwarded: false,
    includesUnifiedResponse: true,
    includesSubmessages: true
  })

console.dir(content, { depth: null })

Untuk penggunaan normal, lebih sederhana memakai:

await ai.send(m.chat, {
  forwarded: false,
  includesUnifiedResponse: true,
  includesSubmessages: true
})

Format ringkas melalui sendMessage()

Selain builder, format ringkas berikut tetap dapat digunakan:

await sock.sendMessage(m.chat, {
  richResponse: [
    { text: 'Contoh jawaban AI' },
    {
      code: 'console.log("JagProject")',
      language: 'javascript'
    }
  ]
})

Untuk satu blok kode:

await sock.sendMessage(m.chat, {
  code: 'print("JagProject")',
  language: 'python'
})

Ringkasan method AI Rich

| Method | Fungsi | | --- | --- | | addText(text, options?) | Menambahkan teks/markdown AI | | addCode(language, code) | Menambahkan blok kode | | addTable(rows) | Menambahkan tabel | | addSource(sources) | Menambahkan sumber/referensi | | addReels(items) | Menambahkan reels/video pendek | | addImage(url) | Menambahkan satu atau beberapa gambar | | addVideo(url) | Menambahkan satu atau beberapa video | | addProduct(data) | Menambahkan kartu produk | | addPost(data) | Menambahkan kartu post sosial | | addMetadata(text) | Menambahkan metadata/catatan teks | | addSuggestion(text) | Menambahkan saran prompt lanjutan | | setTitle(text) | Mengatur title/disclaimer AI | | setFooter(text) | Menambahkan footer response | | setContextInfo(data) | Menambahkan contextInfo custom | | addPayload(data) | Menambahkan payload tambahan | | addSubmessage(data) | Menambahkan submessage raw | | addSection(data) | Menambahkan unified section raw | | build(options?) | Membuat payload tanpa mengirim | | send(jid, options?) | Membuat dan mengirim AI Rich response |

🧰 Utility tambahan v28.8.1

  • MessageRetryManager: runtime utility retry sekarang benar-benar tersedia sesuai deklarasi TypeScript.
  • getSenderJid(msg): mengambil JID pengirim dengan aman dari objek pesan.
  • captureEventStream(ev, file) / readAndEmitEventStream(file): merekam dan replay event untuk debugging.
  • updateBussinesProfile(), updateCoverPhoto(), removeCoverPhoto(): fungsi profil bisnis yang sebelumnya belum ada pada runtime JagProject.
  • Communities: runtime API communityMetadata(), invite/member/settings, dan communityFetchAllParticipating() sekarang tersedia.
  • newsletterQuery() / newsletterWMexQuery() serta newsletterFetchAllParticipating() kini diekspos.
  • useSingleFileAuthState() dan useMongoFileAuthState() tersedia untuk kompatibilitas/migrasi; multi-file tetap direkomendasikan untuk penggunaan baru.

💾 In-memory store

const {
  makeInMemoryStore,
  useMultiFileAuthState,
  makeWASocketLatest
} = require('jagproject')
const pino = require('pino')

const logger = pino({ level: 'silent' })
const store = makeInMemoryStore({ logger })

async function start() {
  const { state, saveCreds } = await useMultiFileAuthState('./session')
  const sock = await makeWASocketLatest({ auth: state, logger })

  store.bind(sock.ev)
  sock.ev.on('creds.update', saveCreds)

  const pesan = await store.loadMessage(jid, messageId)
  console.log(pesan)
}

start().catch(console.error)

Session multi-file cocok untuk pengembangan dan bot kecil. Sistem produksi berskala besar sebaiknya memakai key store serta database yang mendukung transaksi, backup, dan pemulihan.

🛍️ Katalog bisnis

🛒 Katalog dan koleksi

const katalog = await sock.getCatalog({
  jid: sock.user.id,
  limit: 10
})

const collections = await sock.getCollections(sock.user.id)

console.log({ katalog, collections })

📦 Detail pesanan

const order = await sock.getOrderDetails(
  orderId,
  tokenBase64
)

console.log(order)

🏷️ Produk

const produkBaru = await sock.productCreate(product)
const produkUpdate = await sock.productUpdate(productId, update)
await sock.productDelete([productId])

Ketersediaan fitur bisnis bergantung pada tipe akun dan dukungan server.

🪵 Logger dan debug

const pino = require('pino')

const logger = pino({ level: 'debug' })
const sock = await makeWASocketLatest({
  auth: state,
  logger
})

Banner JagProject tidak ditampilkan secara default.

Windows CMD:

set BAILEYS_SHOW_BANNER=1
node index.js

Linux atau macOS:

BAILEYS_SHOW_BANNER=1 node index.js

🧰 Pemecahan masalah

❌ QR tidak tampil

Pastikan modul QR terpasang dan opsi terminal aktif:

npm install qrcode-terminal
printQRInTerminal: true

❌ Pairing code gagal

  • Gunakan nomor tanpa +, spasi, atau tanda hubung.
  • Pastikan waktu sistem dan koneksi internet benar.
  • Hapus session rusak lalu lakukan pairing ulang.
  • Jangan meminta pairing code terlalu sering.

🔌 Koneksi terus terputus

  • Periksa lastDisconnect.error.
  • Jangan reconnect ketika statusnya DisconnectReason.loggedOut.
  • Pastikan setiap event creds.update disimpan.
  • Pastikan hanya satu proses menggunakan folder session yang sama.

🔁 Pesan gagal retry

Implementasikan getMessage agar pesan lama dapat diambil kembali:

getMessage: async key => {
  return store?.loadMessage(key.remoteJid, key.id)?.message
}

🌐 Versi terbaru gagal diambil

makeWASocketLatest() otomatis menggunakan versi bawaan apabila pemeriksaan jaringan gagal. Versi manual juga dapat digunakan:

version: [2, 3000, 1044610476]

🖼️ Sticker atau gambar gagal diproses

Pasang sharp:

npm install sharp

Pastikan file dapat dibaca dan format media didukung.

📁 Session tidak dapat ditulis

  • Pastikan folder aplikasi memiliki izin tulis.
  • Jangan menyimpan session pada filesystem sementara.
  • Jangan menjalankan beberapa instance dengan session yang sama tanpa sistem locking.

🛡️ Keamanan

  • Jangan membagikan folder session, credential, pairing code, atau token.
  • Jangan memasukkan session ke Git.
  • Gunakan environment variable untuk secret.
  • Batasi izin filesystem pada server.
  • Backup session secara terenkripsi.
  • Terapkan rate limit agar bot tidak mengirim pesan berlebihan.
  • Validasi pengirim sebelum menjalankan perintah penting.

Contoh .gitignore:

session/
.env
*.tgz
node_modules/

📄 Lisensi

JagProject didistribusikan sesuai berkas LICENSE. Setiap dependensi tetap mengikuti lisensinya masing-masing.