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
Maintainers
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); // removeapi.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, .colorapi.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, .genderQuick 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 infoActions
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
- On Startup: Automatically checks for updates when you require('shadowx-fca')
- On Login: Checks again after successful Facebook login
- Graceful: Failed update checks never break your bot
- 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
