
Security News
GitHub to Block pull_request_target Workflows by Default Starting December 2
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.
neokex-fca
Advanced tools
Facebook Messenger chat API client for Node.js. Drop-in compatible with the classic Messenger bot API, with a rich feature set: music stickers, message effects, avatar effects, bump, polls, themes, AI themes and realtime events. Built for GoatBot projects
A complete Facebook Messenger chat API client for Node.js.
neokex-fca gives your bot the full Messenger surface — messaging, threads,
reactions, effects, music stickers, polls, themes and realtime events — behind
the familiar classic Messenger bot interface. Existing bot code keeps
working: change one require, keep every call.
npm install neokex-fca
Requires Node.js 18 or newer.
| Developer | @neoaz |
| GitHub | https://github.com/nkxVoid |
| License | MIT |
const login = require("neokex-fca");
login({ appState: require("./appstate.json") }, (err, api) => {
if (err) return console.error(err);
api.listenMqtt((err, event) => {
if (err) return console.error(err);
if (event.type === "message" && event.body === "/ping") {
api.sendMessage("pong", event.threadID);
}
});
});
appState is the exported cookie array in the format the classic client expects.
No further configuration is required.
npm install neokex-fca
const login = require("neokex-fca");
GoatBot loads its engine from a local folder. To switch it to neokex-fca:
Install the package:
npm install neokex-fca
In bot/login/login.js, change the require line to:
const login = require("neokex-fca");
That is the whole change. The client mirrors the surface GoatBot uses —
getCurrentUserID, getAppState, setOptions, listenMqtt,
stopListening and login.utils.checkLiveCookie all behave the same way.
Every method accepts a Node-style callback anywhere in the tail of its argument
list, or returns a promise when the callback is omitted. login follows the
same rule.
// callback
api.sendMessage("hi", threadID, (err, info) => { /* ... */ });
// promise
const info = await api.sendMessage("hi", threadID);
Every method lives in its own module under lib/methods/, so the parameter list
and return shape of any call are discoverable without reading the internals.
| Method | What it does |
|---|---|
sendMessage(message, threadID, [callback], [replyToMessage], [isGroup]) | Send text, an attachment, or a rich message object. |
sendTypingIndicator(threadID, [isTyping], [isGroup], [callback]) | Show or stop the typing indicator. Returns an end() handle. |
unsendMessage(messageID, [callback]) | Recall a message you sent. |
setMessageReaction(reaction, messageID, [threadID], [callback], [forceCustomReaction]) | React with an emoji; "" removes it. |
editMessage(text, messageID, [callback], [threadID]) | Edit an outgoing message. |
deleteMessage(messageOrMessages, [callback]) | Delete one or many messages. |
forwardAttachment(attachmentID, threadID, [callback]) | Forward an existing attachment. |
uploadAttachment(attachments, [callback]) | Upload once, reuse the handle. |
getMessage(threadIDOrMessageID, [messageID], [callback]) | Fetch a message. |
bumpMessage(messageID, [threadID], [callback]) | Bump a message. |
createPoll(threadID, question, options, [callback]) | Create a poll. |
await api.sendMessage({ body: "hello", mentions: [{ tag: "@Neo", id: "1000123" }] }, threadID);
await api.setMessageReaction("❤️", messageID, threadID);
await api.bumpMessage(messageID);
getThreadList, getThreadInfo, getThreadHistory, getThreadPictures,
searchForThread, markAsRead, markAsDelivered, markAsSeen,
markAsReadAll, muteThread, changeArchivedStatus, setTitle,
changeNickname, changeThreadColor, changeThreadEmoji, changeGroupImage,
createNewGroup, deleteThread, changeAdminStatus, addUserToGroup,
removeUserFromGroup, handleMessageRequest, changeBlockedStatus.
const info = await api.getThreadInfo(threadID);
await api.setTitle("Project Team", threadID);
await api.addUserToGroup(["1000123", "1000456"], threadID);
Search for a track and send it as a music sticker.
const [track] = await api.searchMusic("shape of you");
await api.sendMusicSticker(track, threadID);
searchMusic(query, [options]) returns tracks; sendMusicSticker(track, threadID)
accepts a track, a bare songId, or a { songId, title, artist, startTime }
object.
Discover the Messenger effects and avatar power-ups this session supports, then send one. Both lists are answered locally (no request).
// Discover what is available.
const effects = api.listEffects();
// [ { name: "love", id: 1, type: "text" }, { name: "fire", id: 4, type: "text" }, ... ]
const avatarEffects = api.listAvatarEffects();
// [ { name: "avatar_love", id: 1000, type: "avatar" }, ... ]
api.isAvatarSupported("avatar_love"); // true
// Text effect: rides along with the message.
await api.sendMessage({ body: "hello", effect: "fire" }, threadID);
// Avatar effect: the engine attaches the matching animation.
await api.sendMessage({ body: " ", effect: "avatar_love" }, threadID);
An unknown effect name is rejected with Unknown effect: <name>.
await api.changeThreadColor("Messenger Blue", threadID);
await api.changeThreadEmoji("🌊", threadID);
getUserInfo(ids), getUserID(username), getFriendsList(),
changeAvatar(image, [caption]), changeBio(bio, [publish]),
unfriend(userID), handleFriendRequest(userID, accept),
setPostReaction(postID, [type]), resolvePhotoUrl(photoID).
const users = await api.getUserInfo(["1000123", "1000456"]);
await api.changeNickname("Neo", threadID, "1000123");
Pass a Buffer, a readable stream or a local file path as attachment. The
client uploads it and references it in the send call.
const fs = require("fs");
await api.sendMessage({
body: "here is a photo",
attachment: fs.createReadStream("./photo.jpg")
}, threadID);
Multiple attachments:
await api.sendMessage({
attachment: [
fs.createReadStream("./one.jpg"),
fs.createReadStream("./two.mp4")
]
}, threadID);
api.listenMqtt(callback) opens the event stream and reconnects with backoff.
The name is kept for compatibility with existing bots; events arrive over the
server's realtime socket, not MQTT. Pass an options object instead of a callback
to receive an EventEmitter.
const emitter = api.listenMqtt({ autoReconnect: true });
emitter.on("message", event => { /* ... */ });
emitter.on("error", err => { /* ... */ });
// Later
emitter.stopListening();
Event objects use the legacy classic bot field names (senderID, threadID,
messageID, logMessageType, logMessageData, messageReply, and so on).
| Option | Default | Meaning |
|---|---|---|
selfListen | false | Receive your own messages. |
selfListenEvent | false | Receive your own non-message events. |
listenEvents | false | Receive non-message events. |
listenTyping | false | Receive typing events. |
updatePresence | false | Broadcast presence. |
autoMarkDelivery | true | Auto-send delivery receipts. |
autoMarkRead | false | Auto-mark threads read. |
autoReconnect | true | Reconnect the event stream. |
typingIndicator | false | Send a composing signal before a text send. |
online | false | Appear online. |
emitReady | true | Emit a ready event once listening. |
timeoutMs | 30000 | Per-request timeout. |
maxRetries | 2 | Retries for transient network failures. |
api.setOptions({ selfListen: true, listenEvents: true });
getAppState(), setOptions(options), setListenerOptions(options),
getListenerOptions(), refreshFb_dtsg(), logout(), stopListening().
getAppState() is synchronous — api.getAppState().map(...) works without
awaiting.
Answered instantly, with no request:
getCurrentUserID(), getEmojiUrl(emoji, [size], [pixelRatio]),
listEffects(), listAvatarEffects(), isAvatarSupported(name),
threadColors.
getDeviceIdentifiers() returns the device identity the session presents
({ deviceID, familyDeviceID, machineID }); persist it and pass it back on the
next login to keep a stable device fingerprint.
For a call the typed methods do not cover, httpGet, httpPost and
httpPostFormData send the request through the logged-in session and return the
raw response body as a string. The server only forwards these to Messenger
hosts, so they are for Messenger APIs, not arbitrary URLs.
const body = await api.httpPost("https://<messenger-host>/<path>", {
key: "value"
});
const data = JSON.parse(body);
httpGet(url, form?) takes query fields; httpPostFormData(url, form) sends
multipart form data for uploads.
Check whether a cookie set still authenticates, without creating a session or storing anything:
const live = await login.utils.checkLiveCookie(require("./appstate.json"));
Accepts the exported cookie array, a c_user=...; xs=... cookie string, or a
cookies.txt export.
Type definitions ship with the package (index.d.ts) and cover every method.
See the examples/ directory:
basic.js — login, listen and reply.attachments.js — send a local file.options.js — realtime options and the cookie liveness check.npm test
The suite exercises login, argument shaping, both calling styles, local helpers, attachments, realtime events, raw requests and the liveness probe.
MIT © @neoaz
FAQs
Facebook Messenger chat API client for Node.js. Drop-in compatible with the classic Messenger bot API, with a rich feature set: music stickers, message effects, avatar effects, bump, polls, themes, AI themes and realtime events. Built for GoatBot projects
The npm package neokex-fca receives a total of 481 weekly downloads. As such, neokex-fca popularity was classified as not popular.
We found that neokex-fca demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.

Security News
/Application Security
A practical guide to securing GitHub Actions against supply chain attacks, covering malicious pull requests, credential theft, dependency risks, and release pipelines.

Security News
Composer 2.11 adds dependency cooldowns, joining package managers that delay new releases to reduce supply chain risks while allowing urgent security updates.