# 🚘 fca-prince-malhotra

![Image](wiegine.png)

💁 **fca-prince-malhotra** is a fully refactored Facebook Chat API (FCA) client built for **reliable**, **real-time**, and **modular** interaction with Facebook Messenger. Designed with modern bot development in mind, it offers full control over Messenger automation through a clean, stable interface.

---

## 📚 Documentation & Feedback

Full documentation and advanced examples:
[https://exocore-dev-docs-exocore.hf.space](https://exocore-dev-docs-exocore.hf.space)

If you encounter issues or want to give feedback, feel free to message us via Facebook:

* [@Prince Malhotra](https://www.facebook.com/prince.malhotra)

---

## ✨ Features

* 🔐 **Precise Login Mechanism**
  Dynamically scrapes Facebook's login form and submits tokens for secure authentication.

* 💬 **Real-time Messaging**
  Send and receive messages (text, attachments, stickers, replies).

* 📝 **Message Editing**
  Edit your bot’s messages in-place.

* ✍️ **Typing Indicators**
  Detect and send typing status.

* ✅ **Message Status Handling**
  Mark messages as delivered, read, or seen.

* 📂 **Thread Management**

  * Retrieve thread details
  * Load thread message history
  * Get lists with filtering
  * Pin/unpin messages
  * Change and lock group avatars/photos

* 👤 **User Info Retrieval**
  Access name, ID, profile picture, and mutual context.

* 🖼️ **Sticker API**
  Search stickers, list packs, fetch store data, AI-stickers.

* 💬 **Post Interaction**
  Comment and reply to public Facebook posts.

* ➕ **Follow/Unfollow Users**
  Automate social interactions.

* 🌐 **Proxy Support**
  Full support for custom proxies.

* 🧱 **Modular Architecture**
  Organized into pluggable models for maintainability.

* 🛡️ **Robust Error Handling**
  Retry logic, consistent logging, and graceful failovers.

* 🔄 **Advanced Session Management**
  - Long-term cookie persistence (10 years expiry)
  - Auto-save cookies to prevent session loss
  - Automatic session refresh to keep connection alive
  - Smart error handling for session expiry

---

## ⚙️ Installation

> Requires **Node.js v20+**

```bash
npm i fca-prince-malhotra@latest
```

---

## 🚀 Getting Started

### 1. Generate `appstate.json`

This file contains your Facebook session cookies.
Use a browser extension (e.g. "C3C FbState", "CookieEditor") to export cookies after logging in, and save them in this format:

```json
[
  {
    "key": "c_user",
    "value": "your-id"
  }
]
```

If you don't know how to get cookie, you can follow this tutorial **[here](https://appstate-tutorial-ws3.pages.dev)**.

Place this file in the root directory as `appstate.json`.

---

### 2. Basic Usage Example

```js
const fs = require("fs");
const path = require("path");
const { login } = require("fca-prince-malhotra");

let credentials;
try {
  credentials = { appState: JSON.parse(fs.readFileSync("appstate.json", "utf8")) };
} catch (err) {
  console.error("❌ appstate.json is missing or malformed.", err);
  process.exit(1);
}

console.log("Logging in...");

login(credentials, {
  online: true,
  updatePresence: true,
  selfListen: false,
  randomUserAgent: false
}, async (err, api) => {
  if (err) return console.error("LOGIN ERROR:", err);

  console.log(`✅ Logged in as: ${api.getCurrentUserID()}`);

  const commandsDir = path.join(__dirname, "modules", "commands");
  const commands = new Map();

  if (!fs.existsSync(commandsDir)) fs.mkdirSync(commandsDir, { recursive: true });

  for (const file of fs.readdirSync(commandsDir).filter(f => f.endsWith(".js"))) {
    const command = require(path.join(commandsDir, file));
    if (command.name && typeof command.execute === "function") {
      commands.set(command.name, command);
      console.log(`🔧 Loaded command: ${command.name}`);
    }
  }

  api.listenMqtt(async (err, event) => {
    if (err || !event.body || event.type !== "message") return;

    const prefix = "/";
    if (!event.body.startsWith(prefix)) return;

    const args = event.body.slice(prefix.length).trim().split(/ +/);
    const commandName = args.shift().toLowerCase();

    const command = commands.get(commandName);
    if (!command) return;

    try {
      await command.execute({ api, event, args });
    } catch (error) {
      console.error(`Error executing ${commandName}:`, error);
      api.sendMessageMqtt("❌ An error occurred while executing the command.", event.threadID, event.messageID);
    }
  });
});
```

---

### 3. Advanced Session Management (Optional - Recommended)

To prevent account logout and maintain stable sessions, **enable** these optional features:

```js
login(credentials, {
  online: true,
  updatePresence: true,
  selfListen: false,
  
  // Auto-save cookies every 30 minutes (default)
  autoSaveState: true,
  autoSaveInterval: 1000 * 60 * 30, // 30 minutes
  appStatePath: 'appstate.json', // Where to save cookies
  
  // Keep session alive by refreshing every 15 minutes (default)
  keepSessionAlive: true, // Set to false to disable
  sessionRefreshInterval: 1000 * 60 * 15, // 15 minutes
  
  // Other options
  autoReconnect: true, // Auto-reconnect on disconnection
  randomUserAgent: false
}, (err, api) => {
  if (err) return console.error("LOGIN ERROR:", err);
  
  console.log(`✅ Logged in successfully!`);
  console.log(`📝 Auto-save: Cookies will be saved every 30 minutes`);
  console.log(`🔄 Session refresh: Active session will be maintained`);
  
  // Your bot code here
});
```

**Benefits:**
- **Long cookie expiry**: Cookies now last 10 years instead of 1 year (automatic)
- **Auto-save**: Automatically backs up your cookies to prevent loss (opt-in with `autoSaveState: true`)
- **Session refresh**: Keeps your session active to prevent auto-logout (enabled by default, set `keepSessionAlive: false` to disable)
- **Better error handling**: Detects session expiry and provides clear error messages (automatic)

**Note:** 
- `autoSaveState` is **disabled by default**. Set it to `true` to enable automatic cookie backup.
- `keepSessionAlive` is **enabled by default**. Your session will be automatically refreshed every 15 minutes. Set it to `false` if you want to disable this behavior.

---

### 4. Group Avatar Lock (Bonus Feature)

Lock your group's avatar/photo so that it automatically reverts if someone changes it. Similar to how you can lock group names and nicknames.

**Example:**

```js
const fs = require("fs");
const { login } = require("fca-prince-malhotra");

const LOCKED_GROUP_ID = "YOUR_GROUP_THREAD_ID";
const LOCKED_AVATAR_PATH = "./locked_avatar.jpg";

// Load credentials from appstate.json
const credentials = {
  appState: JSON.parse(fs.readFileSync("appstate.json", "utf8"))
};

login(credentials, { listenEvents: true }, (err, api) => {
  if (err) return console.error(err);

  console.log("🔒 Group Avatar Lock enabled!");

  // Set the locked avatar (only if file exists)
  if (fs.existsSync(LOCKED_AVATAR_PATH)) {
    api.changeGroupImage(
      fs.createReadStream(LOCKED_AVATAR_PATH),
      LOCKED_GROUP_ID
    ).then(() => console.log("✅ Locked avatar set!"))
      .catch(err => console.error("Failed to set avatar:", err));
  } else {
    console.log("⚠️ Locked avatar file not found. Skipping initial setup.");
  }

  // Listen for avatar change events and revert automatically
  api.listenMqtt((err, event) => {
    if (err) return console.error(err);

    // Detect group avatar change
    if (
      event.type === "event" &&
      event.logMessageType === "log:thread-icon" &&
      event.threadID === LOCKED_GROUP_ID &&
      event.author !== api.getCurrentUserID()
    ) {
      console.log("⚠️ Avatar changed! Reverting...");
      
      // Revert to locked avatar
      if (fs.existsSync(LOCKED_AVATAR_PATH)) {
        api.changeGroupImage(
          fs.createReadStream(LOCKED_AVATAR_PATH),
          LOCKED_GROUP_ID
        ).then(() => {
          api.sendMessageMqtt(
            "🔒 Group avatar is locked! Reverting to original.",
            LOCKED_GROUP_ID
          );
        }).catch(err => console.error("Revert failed:", err));
      }
    }
  });
});
```

**Full example:** See `examples/group-avatar-lock.js` for a complete implementation with group name and nickname locking.

**Available APIs:**
- `api.changeGroupImage(imageStream, threadID)` - Change group photo
- Event: `log:thread-icon` - Detects when group photo is changed
- `api.gcname(newName, threadID)` - Lock/change group name
- `api.setNickname(nickname, threadID, participantID)` - Lock/change nicknames

---

## 🙌 Credits

* 🔧 **Prince Malhotra** – Main developer, equal maintainer, feature and patch contributions.
* 🔮 **Prince Malhotra** – Foundational core design and architecture.

> Copyright (c) 2025
> Prince Malhotra

---

## 📊 License

**MIT** – Free to use, modify, and distribute. Attribution appreciated.
