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

shadowx-fca

v9.0.0

Published

Unofficial Facebook Chat API for Node.js — E2EE with auto-reconnect + native binary fallback. Dev by Mueid Mursalin Rifat

Readme


# ⚡ SHADOWX

<p align="center">
  <strong> Facebook Chat API and next-generation Facebook Messenger bot library for Node.js with Auto-Update System</strong><br>
  Modified by Mueid Mursalin Rifat🖤
</p>

🔐 Signal Protocol E2EE Added

<br>

</div>

---

## 📑 Table of Contents

- [✨ Installation](#-installation)
- [🚀 Quick Start](#-quick-start)
- [⚙️ Login Options](#️-login-options)
- [📋 shadowxConfig.json](#-shadowxconfigjson)
- [📡 Event Types](#-event-types)
- [📚 API Reference](#-api-reference)
  - [🔑 Login](#-login)
  - [👂 Listening](#-listening)
  - [🔐 E2EE — Encrypted Conversations](#-e2ee--encrypted-conversations)
  - [💬 Sending Messages](#-sending-messages)
  - [✏️ Message Actions](#️-message-actions)
  - [📎 Attachments & Media](#-attachments--media)
  - [🧵 Thread Management](#-thread-management)
  - [🎨 Thread Customization](#-thread-customization)
  - [👥 Thread Members](#-thread-members)
  - [📊 Polls](#-polls)
  - [📌 Pins](#-pins)
  - [📬 Read / Delivery Receipts](#-read--delivery-receipts)
  - [👤 User Info & Friends](#-user-info--friends)
  - [🌐 Social / Posting](#-social--posting)
  - [🏷️ Stickers](#️-stickers)
  - [🔧 HTTP Utilities](#-http-utilities)
  - [⚡ Misc / Config](#-misc--config)
  - [📢 Broadcast](#-broadcast)
  - [🛡️ Session Guard](#-session-guard)
- [🏗️ E2EE Architecture](#-e2ee-architecture)
- [🔄 Auto Update System](#-auto-update-system)

---

## ✨ Installation

```bash
npm install shadowx-fca

🔥 Node.js ≥ 18 required. No TypeScript, no build step — just pure JavaScript power!

🔄 Automatic Updates

SHADOWX comes with a built-in auto-update system that keeps your bot running on the latest version:

# Auto-update is enabled by default
node index.js

# Disable auto-update
DISABLE_UPDATE=true node index.js
# or
node index.js --no-update

# Force update check
node -e "require('shadowx-fca').checkUpdate()"

🚀 Quick Start

const login = require("shadowx-fca");
const fs    = require("fs");

const appState = JSON.parse(fs.readFileSync("./appstate.json", "utf8"));

login({ appState }, { listenEvents: true }, async (err, api) => {
    if (err) throw err;

    console.log("✅ Logged in as:", api.getCurrentUserID());
    // shadowxConfig.json is auto-created here ↑

    // Optional: Enable session protection
    api.sessionGuard("./appstate.json");

    api.listen((err, event) => {
        if (err) throw err;
        if (event.type === "message" && event.body === "!ping") {
            api.sendMessage("🏓 Pong! I'm alive and kicking!", event.threadID);
        }
    });
});

⚙️ Login Options

🎛️ Option 🔤 Type 🎯 Default 📝 Description selfListen boolean false Receive your own sent messages listenEvents boolean true Receive thread/group events listenTyping boolean false Receive typing indicator events updatePresence boolean false Receive online/offline presence events autoMarkDelivery boolean false Auto-mark incoming messages as delivered autoMarkRead boolean false Auto-mark threads as read after delivery autoReconnect boolean true Auto-reconnect MQTT on disconnect online boolean false Appear as online to others emitReady boolean false Emit a ready event when MQTT is connected autoUpdate boolean true Automatically check for updates on login pageID string — Act as a Facebook Page userAgent string Safari UA Override the HTTP User-Agent proxy string — HTTP proxy URL (http://127.0.0.1:8080)


📋 shadowxConfig.json

🎯 SHADOWX automatically creates shadowxConfig.json in your project directory every time login() succeeds. It's like magic — no setup needed, it just appears!

{
  "botUID":    "61589208980818",
  "botName":  "My Awesome Bot",
  "region":   "EAG",
  "version":  "2.0.0",
  "lastLogin": "2026-05-30T12:00:00.000Z",
  "logs":      false
}

🔍 Set "logs": true to enable colorful per-message console logs showing the sender UID, message body, DM/group indicator, and timestamp. Toggle it live without restarting — the bot re-reads the file on every message. Pretty cool, right?

📖 Read it from your bot code at any time:

const config = require("./shadowxConfig.json");

console.log(`🤖 ${config.botName} running in ${config.region}`);
api.sendMessage("Running as: " + config.botName, threadID);

💡 Add your own fields — SHADOWX preserves custom keys on every login update:

const fs = require("fs");
const config = JSON.parse(fs.readFileSync("./shadowxConfig.json", "utf8"));

// Customize it your way!
config.prefix  = "!";
config.adminID = "100000000000001";
config.customSettings = {
    autoReply: true,
    timezone: "Asia/Dhaka"
};

fs.writeFileSync("./shadowxConfig.json", JSON.stringify(config, null, 2));

📡 Event Types

All events arrive in the listen / listenE2EE callback as callback(err, event). Think of it as your bot's nervous system! 🧠

🎪 event.type ⚡ When it fires message New text or media message message_reply A reply to an existing message — includes event.messageReply message_reaction Reaction added or removed message_unsend A message was unsent — includes original body, attachments, and attachmentType event Thread event (rename, add/remove member, etc.) typ Typing indicator (listenTyping: true) presence User online/offline status (updatePresence: true) change_thread_image Group image changed ready MQTT ready (emitReady: true)

💌 Message Object

{
    type:         "message",
    senderID:     "100000000000001",
    threadID:     "100000000000002",
    messageID:    "mid.$abc...",
    body:         "Hello world 👋",
    args:         ["Hello", "world"],
    attachments:  [],
    mentions:     {},              // { "uid": "@Name" }
    timestamp:    1716000000000,
    isGroup:      false,
    isE2EE:       false            // true when from listenE2EE
}

↩️ Reply Object (message_reply)

When event.type === "message_reply", the event carries a messageReply field describing the original message being replied to. It's like message archaeology! 🏺

{
    type:         "message_reply",
    senderID:     "100000000000001",   // who sent the reply
    threadID:     "100000000000002",
    messageID:    "mid.$reply...",
    body:         "This is the reply text",
    args:         ["This", "is", ...],
    attachments:  [],
    mentions:     {},
    timestamp:    1716000000001,
    isGroup:      true,
    isE2EE:       false,

    // ── The original message being replied to ──
    messageReply: {
        messageID: "mid.$original...",
        senderID:  "100000000000003",  // who sent the original
        threadID:  "100000000000002",
        body:      "Original message text",
        args:      ["Original", "message", "text"],
        attachments: [],
        mentions:  {},
        isGroup:   true,
        timestamp: 1716000000000
    }
}

🎯 Reply handler pattern:

api.listen(async (err, event) => {
    if (event.type === "message_reply" && event.messageReply) {
        const repliedMsgID = event.messageReply.messageID;

        if (replyHandlers.has(repliedMsgID)) {
            const handler = replyHandlers.get(repliedMsgID);
            replyHandlers.delete(repliedMsgID);
            await handler(event);
        }
    }
});

🔄 Works identically in both api.listen (MQTT) and api.listenE2EE (encrypted). E2EE reply events carry isE2EE: true on both the event and the messageReply object.

🗑️ Unsend Event (message_unsend)

Fired when someone unsends a message. SHADOWX automatically caches every incoming message so the original content is available at unsend time — including the full attachments array with URLs. It's like having a photographic memory! 📸

{
    type:              "message_unsend",
    threadID:          "100000000000002",
    messageID:         "mid.$abc123",
    senderID:          "100000000000001",
    deletionTimestamp: 1716000000001,
    timestamp:         1716000000000,

    // ── Original message content (from SHADOWX's message cache) ──
    body:           "Hello world 🌍",     // original text, "" if attachment-only
    attachments:    [ ... ],               // same shape as a normal message's attachments
    attachmentType: "text"                 // convenience field — see values below
}

attachmentType values:

🏷️ Value 📝 What was unsent "text" Plain text message "photo" Image 🖼️ "video" Video 🎬 "audio" Voice / audio clip 🎵 "sticker" Sticker "animated_image" GIF "file" File / document 📄 "unknown" Not seen before unsend (bot was offline when it arrived) 😴

🛡️ Anti-unsend example:

api.listen((err, event) => {
    if (event.type !== "message_unsend") return;

    const { attachmentType, body, attachments, threadID, senderID } = event;

    if (attachmentType === "text") {
        api.sendMessage(`⚠️ ${senderID} unsent: "${body}"`, threadID);
    } else if (attachmentType === "photo" && attachments[0]) {
        api.sendMessage({ body: `⚠️ ${senderID} unsent a photo:`, attachment: require("request")(attachments[0].url) }, threadID);
    } else if (attachmentType === "video") {
        api.sendMessage(`⚠️ ${senderID} unsent a video: ${attachments[0] && attachments[0].url}`, threadID);
    } else if (attachmentType === "audio") {
        api.sendMessage(`⚠️ ${senderID} unsent an audio clip: ${attachments[0] && attachments[0].url}`, threadID);
    } else if (attachmentType === "sticker") {
        api.sendMessage(`⚠️ ${senderID} unsent a sticker: ${attachments[0] && attachments[0].url}`, threadID);
    } else if (attachmentType === "animated_image") {
        api.sendMessage(`⚠️ ${senderID} unsent a GIF: ${attachments[0] && attachments[0].url}`, threadID);
    } else {
        api.sendMessage(`⚠️ ${senderID} unsent a ${attachmentType} message.`, threadID);
    }
});

💡 Pro tip: Content is only available if the bot was online when the message arrived. Messages unsent before the bot saw them will have body: "", attachments: [], and attachmentType: "unknown". The cache holds the last 500 messages to keep things snappy!


🎭 Thread Events (event type)

When event.type === "event", the logMessageType field identifies what went down:

🎪 event.logMessageType 📝 Description log:subscribe Members added to a group — logMessageData.addedParticipants log:unsubscribe Member left or was removed — logMessageData.leftParticipantFbId log:thread-name Group name changed — logMessageData.name log:thread-color Chat theme/color changed 🎨 log:thread-icon Thread emoji changed log:user-nickname Nickname changed log:thread-admins Admin status changed 👑 log:thread-poll Poll created or updated 📊 log:thread-pinned Message pinned/unpinned 📌 log:thread-call Call log entry 📞 log:thread-approval-mode Join approval mode changed log:link-status Joinable link reset 🔗

👋 Join/leave handler example:

api.listen((err, event) => {
    if (event.type !== "event") return;

    if (event.logMessageType === "log:subscribe") {
        const added = (event.logMessageData.addedParticipants || [])
            .map(p => p.userFbId || p);
        api.sendMessage(`🎉 Welcome to the group!`, event.threadID);
    }

    if (event.logMessageType === "log:unsubscribe") {
        const who = event.logMessageData.leftParticipantFbId;
        api.sendMessage(`👋 Goodbye ${who}! We'll miss you!`, event.threadID);
    }
});

📎 Attachment Shapes

{ type: "photo",   ID, url, width, height, filename }  // 🖼️
{ type: "video",   ID, url, duration, width, height }  // 🎬
{ type: "audio",   ID, url, duration, filename }       // 🎵
{ type: "file",    ID, url, size, mimeType, filename }  // 📄
{ type: "sticker", stickerID, url, width, height }     // 🏷️

📚 API Reference


🔑 Login

login(loginData, [options], [callback]) → Promise

// appState (recommended) 🔐
const api = await login({ appState: require("./appstate.json") });

// Email + password 📧
login({ email: "[email protected]", password: "secret" }, callback);

// With options ⚙️
login({ appState }, { listenEvents: true, autoReconnect: true, autoUpdate: true }, callback);

👂 Listening

api.listen(callback) → MessageEmitter

Start listening for all events via MQTT. Your bot's ears are now open! 👂

const emitter = api.listen((err, event) => {
    if (err) return console.error(err);

    if (event.type === "message")
        console.log(`💬 ${event.senderID} → ${event.body}`);

    if (event.type === "message_reaction")
        console.log(`👍 ${event.senderID} reacted ${event.reaction}`);

    if (event.type === "typ")
        console.log(event.from, event.isTyping ? "is typing..." : "stopped typing");
});

// Stop listening
await emitter.stopListeningAsync();

api.listenE2EE(callback) → MessageEmitter

🔐 Combined listener: receives both regular MQTT messages and decrypted E2EE messages in the same callback. Call api.connectE2EE() first.

await api.connectE2EE(); // auto-creates .shadowx/e2ee_device.json

api.listenE2EE((err, event) => {
    if (err) return console.error(err);

    if (event.type === "message") {
        if (event.isE2EE) {
            // Must reply via E2EE for encrypted threads 🔒
            api.e2ee.sendMessage(event.threadID, "Got your encrypted message! 🔐");
        } else {
            api.sendMessage("Got it! 👍", event.threadID);
        }
    }
});

⚠️ Important: For E2EE DM replies, always use api.e2ee.sendMessage() — the regular MQTT path does not reach E2EE threads.


🔐 E2EE — Encrypted Conversations

SHADOWX connects to Facebook's real E2EE infrastructure using the Signal Protocol — the same battle-tested encryption used by the official Messenger mobile app. Military-grade security for your bot! 🛡️

api.connectE2EE([deviceStorePath]) → Promise

Initialize E2EE. Automatically creates the device store directory if it doesn't exist.

// Default path: .shadowx/e2ee_device.json in process.cwd()
// Directory is auto-created — no manual setup needed! ✨
await api.connectE2EE();

// Custom path
await api.connectE2EE("./my_data/e2ee.json");

console.log("🔐 E2EE connected:", api.e2ee.isConnected()); // true

🔑 Keep the device store file safe. Deleting it forces device re-registration with Facebook.

api.e2ee.sendMessage(threadId, msg, [replyToMessageId]) → Promise

msg can be a plain string or a message object with body and/or attachment (readable stream or array of streams). Attachments on E2EE threads are automatically encrypted and sent via the Noise WebSocket. Attachments on non-E2EE threads fall back to SHADOWX's own sendMessage.

// Text only 📝
await api.e2ee.sendMessage("100000000000001", "Hello, encrypted! 🔒");

// Reply ↩️
await api.e2ee.sendMessage("100000000000001", "Reply!", "mid.$replyID");

// Image 🖼️
const fs = require("fs");
await api.e2ee.sendMessage("100000000000001", {
    body: "Check this out! 📸",
    attachment: fs.createReadStream("photo.jpg")
});

// Audio / Video / File — same pattern, type detected automatically 🎯
await api.e2ee.sendMessage("100000000000001", { attachment: fs.createReadStream("voice.ogg") });
await api.e2ee.sendMessage("100000000000001", { attachment: fs.createReadStream("clip.mp4") });

// Multiple attachments 📎
await api.e2ee.sendMessage("100000000000001", {
    attachment: [fs.createReadStream("a.jpg"), fs.createReadStream("b.jpg")]
});

api.e2ee.sendReaction(threadId, messageId, reaction) → Promise

await api.e2ee.sendReaction("100000000000001", "mid.$abc123", "❤️");

api.e2ee.sendTyping(threadId, isTyping) → Promise

await api.e2ee.sendTyping("100000000000001", true);
await api.e2ee.sendTyping("100000000000001", false);

api.e2ee.unsendMessage(messageId, threadId) → Promise

await api.e2ee.unsendMessage("mid.$abc123", "100000000000001");

api.e2ee.editMessage(threadId, messageId, text) → Promise

await api.e2ee.editMessage("100000000000001", "mid.$abc123", "Updated! ✨");

api.e2ee.onMessage(callback)

api.e2ee.onMessage((err, event) => {
    // event.isE2EE is always true 🔒
    console.log("[🔐 E2EE]", event.senderID, ":", event.body);
});

api.e2ee.isConnected() → boolean

if (api.e2ee.isConnected()) await api.e2ee.sendMessage(threadID, "hi 👋");

api.e2ee.disconnect() → Promise

await api.e2ee.disconnect();

💬 Sending Messages

api.sendMessage(msg, threadID, [callback], [replyToMessageID]) → Promise

MQTT-first, HTTP fallback. Lightning fast! ⚡

// Text 📝
await api.sendMessage("Hello! 👋", threadID);

// Reply ↩️
api.sendMessage("Noted! 👍", threadID, callback, replyToMessageID);

// Mention — tag must include the @ prefix; fromIndex is the position of @ in body
api.sendMessage({
    body: "Hey @John!",
    mentions: [{ id: "100000000000001", tag: "@John", fromIndex: 4 }]
}, threadID);

// Multiple mentions 👥
api.sendMessage({
    body: "@Alice and @Bob check this out",
    mentions: [
        { id: "111111111111", tag: "@Alice", fromIndex: 0 },
        { id: "222222222222", tag: "@Bob",   fromIndex: 11 }
    ]
}, threadID);

// Attachment from file stream 📎
api.sendMessage({ body: "File!", attachment: fs.createReadStream("./photo.jpg") }, threadID);

// Attachment from a remote URL (stream via request) 🌐
const request = require("request");
api.sendMessage({ attachment: request("https://example.com/img.jpg") }, threadID);

// Sticker / Emoji / Location 🎯
api.sendMessage({ sticker: "369239263222822" }, threadID);
api.sendMessage({ emoji: "❤️", emojiSize: "large" }, threadID);
api.sendMessage({ location: { latitude: 14.5995, longitude: 120.9842, current: true } }, threadID);

api.sendSticker(stickerID, threadID, [replyTo], [callback])

api.sendSticker("369239263222822", threadID);

api.sendEmoji(emoji, [emojiSize], threadID, [callback])

api.sendEmoji("🔥", "large", threadID);
// emojiSize: "small" | "medium" | "large"

api.sendGif(gifSrc, threadID, [callback])

api.sendGif("https://media.giphy.com/media/xyz/giphy.gif", threadID);

api.sendLocation(lat, lng, threadID, [isCurrent], [callback])

api.sendLocation(14.5995, 120.9842, threadID);

api.sendImage(path, threadID, [caption], [callback])

api.sendImage("./photo.jpg", threadID, "Look at this! 📸");

api.sendVideo(path, threadID, [caption], [callback])

api.sendVideo("./video.mp4", threadID, "Watch 👀");

api.sendAudio(path, threadID, [callback])

api.sendAudio("./voice.ogg", threadID);

api.sendFile(path, threadID, [caption], [callback])

api.sendFile("./doc.pdf", threadID, "Here's the doc 📄");

api.shareLink(url, threadID, [message], [callback])

api.shareLink("https://github.com", threadID, "Check this out! 🔗");

api.shareContact(text, userID, threadID, [callback])

api.shareContact("Meet my friend! 👤", "100000000000001", threadID);

api.forwardAttachment(attachmentID, userOrUsers, [callback])

api.forwardAttachment("attach_fbid", ["uid1", "uid2"]);

Other send methods

🚀 Method 📝 Description api.sendMessageMqtt(msg, threadID) MQTT-only, no HTTP fallback api.OldMessage(msg, threadID, cb, replyTo, isSingleUser) HTTP-only (legacy) api.sendMessageDM(msg, threadID) HTTP DM shorthand


✏️ Message Actions

api.editMessage(text, messageID, [callback])

Uses MQTT (label 742) for instant delivery; falls back to HTTP if MQTT is not yet connected.

// Promise ✨
await api.editMessage("Updated text ✏️", "mid.$abc123");

// Callback
api.editMessage("Updated text ✏️", "mid.$abc123", (err) => {
    if (err) return console.error(err);
    console.log("✅ Message edited!");
});

api.unsendMessage(messageID, [callback])

api.unsendMessage("mid.$abc123", callback);

api.deleteMessage(messageIDs, [callback])

api.deleteMessage(["mid.$abc123", "mid.$def456"], callback);

api.setMessageReaction(reaction, messageID, threadID, [callback])

api.setMessageReaction("😍", messageID, threadID);
api.setMessageReaction("", messageID, threadID); // remove

api.getMessage(threadID, messageID, [callback])

const msg = await api.getMessage(threadID, "mid.$abc123");
console.log(`💬 ${msg.senderID}: ${msg.body}`);

📎 Attachments & Media

api.uploadAttachment(attachments, [callback]) → Promise

const [meta] = await api.uploadAttachment([fs.createReadStream("./photo.jpg")]);
console.log("📁 File ID:", meta.attach_fbid);

💡 Pro tip — stream a URL as attachment:

const request = require("request");
api.sendMessage({ attachment: request("https://example.com/img.jpg") }, threadID);

api.uploadImageToImgbb(image, [expiration], [callback]) → Promise

// URL, Buffer, or base64
const result = await api.uploadImageToImgbb("https://example.com/img.jpg");
console.log("🖼️ URL:", result.data.url);

api.resolvePhotoUrl(photoID, [callback])

api.resolvePhotoUrl("photo_id", (err, url) => console.log(url));

🧵 Thread Management

api.getThreadInfo(threadID, [callback]) → Promise

const info = await api.getThreadInfo(threadID);
// info.threadName, .participantIDs, .isGroup, .unreadCount, .adminIDs, .emoji, .color

api.getThreadList(limit, [timestamp], [tags], [callback]) → Promise

const threads = await api.getThreadList(10, null, ["INBOX"]);
// tags: "INBOX" | "PENDING" | "ARCHIVED"

api.getThreadHistory(threadID, amount, [timestamp], [callback]) → Promise

const messages = await api.getThreadHistory(threadID, 20, null);

api.createGroup(message, participantIDs, [callback])

const { threadID } = await api.createGroup("Hey! 👋", ["uid1", "uid2"]);

api.deleteThread(threadID, [callback])

api.deleteThread(threadID);

api.muteThread(threadID, muteSeconds, [callback])

api.muteThread(threadID, 3600); // mute 1 hour 🔇
api.muteThread(threadID, 0);    // unmute 🔊

api.changeArchivedStatus(threadID, archive, [callback])

api.changeArchivedStatus(threadID, true);  // archive 📦
api.changeArchivedStatus(threadID, false); // unarchive 📂

🎨 Thread Customization

api.setTitle("New Name ✨", threadID);
api.changeThreadColor("#0084FF", threadID);
api.changeThreadEmoji("🔥", threadID);
api.changeNickname("The Boss 👑", threadID, "100000000000001");
api.changeGroupImage(fs.createReadStream("./group.jpg"), threadID);
api.changeThreadTheme("197931430960496", threadID);

👥 Thread Members

api.addUserToGroup("100000000000001", threadID);
api.removeUserFromGroup("100000000000001", threadID);
api.changeAdminStatus(threadID, "100000000000001", true);  // promote 👑
api.changeAdminStatus(threadID, "100000000000001", false); // demote

📊 Polls

api.createPoll(title, threadID, [options], [callback])

api.createPoll("Favorite language? 🤔", threadID, { JS: false, Python: false });

api.setPollVote(pollID, optionIDs, [newOptions], [callback])

api.setPollVote(pollID, ["option_id"], [], callback);

📌 Pins

api.pinMessage("mid.$abc123", threadID);   // MQTT label 430 — pin_msg_v2_
api.unpinMessage("mid.$abc123", threadID); // MQTT label 431 — unpin_msg_v2_

📬 Read / Delivery Receipts

api.markAsRead(threadID);
api.markAsDelivered(threadID, messageID);
api.markAsSeen();
api.markAsReadAll();

// Typing indicator ✍️
api.sendTypingIndicator(threadID, true);
setTimeout(() => api.sendTypingIndicator(threadID, false), 3000);

👤 User Info & Friends

api.getUserInfo(id, [callback]) → Promise

const users = await api.getUserInfo(["uid1", "uid2"]);
// users[uid].name, .thumbSrc, .gender

Quick reference

api.getCurrentUserID()                        // → string
api.getUserID("John Doe", callback)           // search by name 🔍
api.getUID("https://facebook.com/zuck", cb)   // → "4"
api.getFriendsList(callback)                  // full friends list 👥
api.searchFriends("Maria", callback)
api.getAvatarUser("uid", callback)            // returns graph.facebook.com picture URL 🖼️
api.getAvatarUser("uid", "square", callback)  // type: square | large | normal | small
api.getProfileInfo("uid", callback)           // GraphQL node
api.getPublicData("uid", callback)            // public scrape (name, vanity, uid)
api.getRepInfo(callback)                      // account rep info

Actions

api.sendFriendRequest("uid");                 // GraphQL FriendingCometFriendRequestSendMutation
api.handleFriendRequest("uid", true);         // accept via /requests/friends/ajax/ ✅
api.handleFriendRequest("uid", false);        // decline ❌
api.handleMessageRequest(threadID, true);
api.changeBlockedStatus("uid", true);         // block 🚫
api.changeBlockedStatus("uid", false);        // unblock
api.followUser("uid");
api.unfollowUser("uid");
api.unfriend("uid");
api.setActiveStatus(true);                    // appear online 🟢
api.logout();

🌐 Social / Posting

// Reactions: "like" | "love" | "haha" | "wow" | "sad" | "angry" | "care" | "none"
api.reactToPost("postID", "love");           // ❤️
api.reactToComment("commentID", "haha");     // 😄
api.postComment("postID", "Great post! 🔥");
api.deleteComment("commentID");
api.sharePost("postID", "Check this!");
api.getPostInfo("postID", callback);
api.getStoryReactions("feedbackID", callback);

🏷️ Stickers

api.getStickers("thumbs up", null, (err, stickers) => {
    if (stickers.length) api.sendSticker(stickers[0].id, threadID);
});

api.getStickerPacks((err, packs) => {
    packs.forEach(p => console.log(`🏷️ ${p.id}: ${p.name}`));
});

🔧 HTTP Utilities

api.httpGet(url, params, callback);
api.httpPost(url, form, callback);
api.httpPostFormData(url, form, callback);

const dtsg = await api.getFreshDtsg();

⚡ Misc / Config

api.setOptions({ listenTyping: true, proxy: "http://127.0.0.1:8080" });
api.getAppState()        // → cookie array (save to reuse session) 🍪
api.getCurrentUserID()   // → string
api.getCtx()             // → internal ctx object (region, fb_dtsg, etc.)
api.checkForUpdate()     // → Promise<boolean> 🔄

// Add a custom method to the api object
api.addExternalModule("myFunc", (defaultFuncs, api, ctx) => {
    return function (text, threadID) {
        return api.sendMessage("[🤖 BOT] " + text, threadID);
    };
});
api.myFunc("Hello!", threadID);

📢 Broadcast

api.sendBroadcast(msg, threadIDs, [options], [callback]) → Promise

Send to multiple threads with rate limiting and per-thread delivery tracking. Mass messaging made easy! 📣

const result = await api.sendBroadcast(
    "Hello everyone! 👋",
    ["THREAD_1", "THREAD_2", "THREAD_3"],
    {
        delay:    2000,  // ms between sends
        parallel: 2,     // max concurrent sends
        onEach: (err, info, id) => {
            console.log(err ? `❌ Failed: ${id}` : `✅ Sent: ${id}`);
        }
    }
);

console.log(`📊 ${result.sent.length}/${result.total} delivered`);

🛡️ Session Guard

Protects your appstate from corruption and silent logouts. Your appstate's bodyguard! 💪

// Call right after login
api.sessionGuard("./appstate.json");

// Custom timing ⏱️
api.sessionGuard("./appstate.json", {
    interval: 3 * 60 * 1000,  // save every 3 min
    debounce: 60 * 1000        // cooldown after sendMessage
});

What it does:

· 💾 Auto-saves appstate every N minutes · 📝 Saves after every successful sendMessage (debounced) · 🛡️ Corruption guard: never overwrites a larger appstate with a smaller one · 📦 Auto-backup: writes appstate.json.bak before every overwrite

api.saveSession()            // → boolean  (force save now) 💾
api.restoreSessionBackup()   // → boolean  (restore .bak if corrupted) 🔄
api.stopSessionGuard()       // stop the timer ⏹️

🔄 Auto Update System

SHADOWX includes an intelligent auto-update system that keeps your bot current with the latest features and fixes:

How It Works

  1. On Startup: Automatically checks for updates when you require('shadowx-fca')
  2. On Login: Checks again after successful Facebook login
  3. Graceful: Failed update checks never break your bot
  4. Smart: Shows changelog before updating

Manual Control

// Check for updates manually
const { checkUpdate } = require('shadowx-fca');
checkUpdate().then(updated => {
    console.log(updated ? "Updated! 🔄" : "Already latest! ✅");
});

// Via API after login
api.checkForUpdate();

// Environment variables
DISABLE_UPDATE=true node index.js     // Skip update check
AUTO_UPDATE=true node index.js        // Auto-accept updates (CI/CD)

🏗️ E2EE Architecture

SHADOWX harnesses Facebook's real E2EE infrastructure — identical to the official Messenger mobile app:

🔐 Signal Protocol stack
  ├── @signalapp/libsignal-client  — Double Ratchet + X3DH
  ├── Noise_XX_25519_AESGCM_SHA256 — WebSocket handshake
  ├── WA-binary + Protobuf          — message frame encoding
  └── ICDC device registration      — registers bot as E2EE device

🚀 Connection flow
  api.connectE2EE()
    ├─ 1. Auto-create device store directory 📁
    ├─ 2. Bootstrap auth (re-use existing cookie session) 🍪
    ├─ 3. Register device keys with Facebook (first run only) 🔑
    └─ 4. Open Noise WebSocket → ready ✅

📨 Message flow
  Incoming  →  Noise WS  →  listenE2EE callback  (event.isE2EE = true)
  Outgoing  →  api.e2ee.sendMessage()  →  Noise WS  →  Facebook

🔒 The E2EE engine is vendored inside the package. Your bot continues to work even if the upstream npm dependency is removed or broken. Maximum reliability! 💪


⭐ If this project helps you, consider giving it a star! 📧 Questions? Open an issue or reach out on GitHub.

MIT License · Copyright © 2026 Mueid Mursalin Rifat