@eryxenx/insta-chat

中文文档
2.2.7发布于 2026/11/08Apache-2.0TypeScriptCommonJS近 30 天下载 2617

Unofficial Instagram Chat API for Node.js | Build Instagram DM bots on personal accounts — send messages, media, voice notes, reactions, edit & unsend, listen for incoming chats. No browser at runtime

原始使用文档
文档目录19 节
  1. ⚡ What is this?
  2. 📦 Installation
  3. 🚀 Quick start
  4. Getting the cookie
  5. 🧠 Session params (important)
  6. 📚 API
  7. Login
  8. Two layers
  9. Messaging (FCA surface)
  10. Messaging (native InstaAPI )
  11. Media
  12. Threads & users
  13. Full profiles ( getUserInfoV2 / getUserProfile )
  14. Listening
  15. Realtime (MQTT)
  16. 🧩 Example — a tiny DM bot
  17. 🗂️ Project structure
  18. 📋 Changelog
  19. 📄 License

💬 @eryxenx/insta-chat

Unofficial Instagram DM Chat API for Node.js

Send messages, media, voice notes & reactions · edit, unsend, listen · build Instagram bots on personal accounts · zero browser at runtime

npm license node


⚡ What is this?

@eryxenx/insta-chat is a standalone Instagram Direct Message API — the Instagram sibling of an FCA package. It talks to the same web endpoints www.instagram.com uses (Relay GraphQL + rupload), so you can build a bot on your own personal account:

  • ✅ Send text, photos, videos, voice notes
  • ✅ React, edit and unsend messages
  • ✅ Listen for incoming DMs — realtime MQTT push or polling — and dispatch them to your own commands
  • ✅ Typing indicators, mark-seen, mute/unmute, hide
  • ✅ Users — lookup by id / username, search
  • ✅ Pure HTTP at runtime — no Puppeteer, no browser, no native build

⚠️ Unofficial. Not affiliated with Instagram or Meta. Use only on accounts you own and at your own risk. This package automates a personal account, not the official Instagram Business API.


📦 Installation

npm install @eryxenx/insta-chat

Requires Node.js >= 18.


🚀 Quick start

const login = require("@eryxenx/insta-chat");

// `cookie` is your browser cookie string (needs at least `sessionid`).
login(cookie, (err, api) => {
  if (err) return console.error("Login error:", err.message);
  console.log("Logged in as", api.username || api.userID);

  // send a DM
  api.sendMessage("Hello from my bot 👋", "340282366841710300949128138275732109616");
});

Promise style works too:

const api = await login(cookie);
await api.sendMessage("hi", threadID);

Log in to https://www.instagram.com in a browser, open DevTools → Network → copy the Cookie request header (it contains sessionid, ds_user_id, csrftoken, ig_did). Paste the whole string; only sessionid is mandatory.


🧠 Session params (important)

Instagram's web API rejects requests that do not carry the browser session params (__dyn, __csr, __hs, __hsi, __s, fb_dtsg, jazoest, ...). They are produced by Instagram's own JS bundle but are session-scoped, so one real browser page load is enough — capture once, reuse until it expires.

const { harvestSessionParams, findChromium } = require("@eryxenx/insta-chat");

const params = await harvestSessionParams({ /* cookie jar */ });
// -> { __dyn, __csr, __hs, fb_dtsg, lsd, ... }

harvestSessionParams drives a system Chrome/Chromium through puppeteer-core (optional dependency, no browser download). Pass the result to login:

const api = await login(cookie, { sessionParams: params });

If you skip this, the package still tries to bootstrap the minimum tokens (fb_dtsg, lsd) from the Instagram home page automatically. When the params go stale use api.refreshSession().


📚 API

Login

CallDescription
login(cookie, [options], [cb])Returns/clbacks a ready InstaClient (FCA surface).
login(cookie, { native: true })Returns the bare InstaAPI instead.
createClient(cookie, [options])Alias of login.

options: { native, sessionParams, sessionParamsFile, deviceFile, pollInterval, minPollIntervalMs, realtime }.

To make listen() push instead of poll, opt into realtime MQTT:

const api = await login(cookie, { realtime: { enabled: true } });

realtime is { enabled, fallback, host, keepAliveMs }. With fallback !== false (the default) a broker that will not connect degrades to the inbox poller, so the bot still gets events. keepAliveMs is the PINGREQ interval; it must stay below the CONNECT keep-alive (60 s) and defaults to 25 s. See Listening.

Two layers

LayerWhat it isHow to get it
InstaClientFCA / GoatBot-Pro shaped surface (sendMessage, setMessageReaction, getThreadInfo, …)login(...)
InstaAPINative Instagram client (sendReaction, sendPhotoBuffer, getInbox, …)login(..., { native: true }) or client.api

The FCA methods are one file each under src/api/<group>/<method>.js; adding a method is adding a file. createFcaClient(client) gives a namespaced view (messages.send(), threads.getInfo()); new InstaContext(client, threadID) binds a thread. EventNormalizer maps raw inbox events to the FCA event shape.

Messaging (FCA surface)

MethodDescription
sendMessage(form, threadID, [cb], [replyToMessage])Send text/attachment; quote-reply by passing the target message id.
editMessage(text, messageID)Edit an outgoing message (within 15 min).
unsendMessage(messageID)Unsend a message.
setMessageReaction(emoji, messageID, [cb], [threadID])React to a message.
sendTypingIndicator(threadID)Typing indicator.
getThreadInfo / getThreadList / getThreadHistoryThread reads.
getUserInfo / getUIDUser lookup.
getUserInfoV2(idOrIds)Full profile (all details) for one id or an array of ids.
getUserProfile(idOrHandle)The complete merged profile object.
getProfilePictureV2(idOrHandle, opts)Profile picture at any size (square + HD).
getProfileBio(idOrHandle) / getUserID(idOrHandle) / getUserFriendship(id)Bio, id, friendship state.
getSelfInfo()The logged-in account's own profile.
getAppState / getCookiesCookie jar.

Messaging (native InstaAPI)

MethodDescription
sendMessage(text, threadID, replyToItemId?)Send a text DM (optionally quote-reply).
replyToMessage(threadID, text, replyToItemId?)Send a reply (quote the target item).
sendLink(threadID, link, text)Send a link with optional text.
sendReaction(threadID, itemID, emoji)React to a message.
removeReaction(threadID, itemID)Remove a reaction.
unsendMessage(threadID, itemID)Unsend a message.
editMessage(threadID, itemID, text)Edit an outgoing message (within 15 min).
startTyping(threadID) / stopTyping(threadID)Typing indicator.

Media

MethodDescription
sendPhoto(threadID, filePath, [opts])Photo from disk.
sendPhotoBuffer(threadID, buffer, [opts])Photo from a Buffer.
sendPhotoFromUrl(threadID, url, [opts])Photo from a URL.
sendVideo / sendVideoBuffer / sendVideoFromUrlVideo variants.
sendVoice / sendVoiceBuffer / sendVoiceFromUrlVoice-note variants.

opts: { filename, text }.

Threads & users

MethodDescription
getThreadInfo(threadID)Thread metadata.
getThreadHistory(threadID, limit)Recent messages.
markThreadSeen(threadID, itemID)Mark as read.
muteThread / unmuteThread / hideThreadThread state.
getUserInfo(userID) / getUserInfoByUsername(username)User lookup.
searchUsers(query, limit)Search users.
getUserProfile(idOrUsername, [opts])Complete profile merged from every source (see below).
getProfilePicture(idOrUsername, [opts])Best picture URL; opts.size resizes via the CDN.

Full profiles (getUserInfoV2 / getUserProfile)

V1 getUserInfo returns a small fca-shaped subset. V2 returns every detail a web session can read, and it is logout-safe: profile lookups go through a resolver that tries several sources and never turns a rate-limit (429) or a login redirect (302) into a dead-session error - the bug that used to log the bot out whenever a command looked up a profile.

Sources, merged in order (a field a source cannot supply stays null):

  1. Inbox/thread record - the users the client has already seen in the DM views. Free, needs no request, and the only source that still answers once the profile REST paths 429. getInbox() populates it automatically.
  2. web_profile_info REST - canonical counts (followers/following/media), biography, verified/private/business flags, external url, category.
  3. Profile GraphQL (PolarisProfilePageContentQuery) - richest payload when Instagram answers it.
  4. The account's own page - the home shell Instagram server-renders embeds the logged-in account's full profile, so self always resolves.
const info = await api.getUserInfoV2("32715034217");
// info["32715034217"] => { id, username, name, biography, followerCount,
//   followingCount, mediaCount, isVerified, isPrivate, isBusinessAccount,
//   externalUrl, category, profilePicUrl, profilePicUrlHd, fbid, ... }

const profile = await api.getUserProfile("@someuser");   // the raw merged object
const pic     = await api.getProfilePictureV2("someuser", { size: 640 });
// pic => { url, hd, thumb, sizes: { "150": ..., "320": ..., "640": ..., "1080": ... } }

Listening

const stop = api.listen((err, event) => {
  if (err) return console.error(err.message);
  console.log(event.senderID, "->", event.body);
  // event = { type, threadID, senderID, messageID, body, attachments, timestamp }
});

// later
stop();

listen uses the realtime MQTT stream when you pass { realtime: { enabled: true } } to login — Instagram pushes each new DM over the broker, so replies are near-instant. Without it, listen polls the inbox (default every 15s, override with options.pollInterval). Either way the callback and event shape are identical, and a realtime connection that fails falls back to polling unless you set realtime: { enabled: true, fallback: false }.

Realtime (MQTT)

Instagram's realtime broker (edge-mqtt.facebook.com) is an MQTToT connection authenticated by the same sessionid cookie — no app login. listen() drives it when realtime is enabled; you can also use it directly:

const api = await login(cookie, { native: true });

api.realtimeOn("message", (e) => console.log(e.message));
api.realtimeOn("typing", (e) => console.log(e.threadID, "is typing"));
api.realtimeOn("seen", (e) => console.log(e.threadID, "read"));
api.realtimeOn("presence", (e) => console.log(e.value));

await api.realtimeConnect();
await api.realtimeSubscribe();   // reads seq_id/snapshot_at_ms from the inbox query
// ...later
api.realtimeDisconnect();

realtimeConnect() reuses the client only while it is connected; after a drop it builds a fresh one, so attach your handlers to the client it returns and re-attach whenever it differs from the last one you saw:

let client = await api.realtimeConnect();
if (client !== previous) {
  client.on("direct", onRealtimeDirect);
  client.on("disconnected", () => reconnectLater());
  previous = client;
}

RealtimeClient (the raw client) and RealtimeListener (the listen() wrapper) are exported too, for callers that want to drive the socket themselves.


🧩 Example — a tiny DM bot

const login = require("@eryxenx/insta-chat");

(async () => {
  const api = await login(process.env.IG_COOKIE);

  api.listen(async (err, event) => {
    if (err || !event || !event.body) return;
    if (event.body === "/ping") await api.sendMessage("pong 🏓", event.threadID);
    await api.sendReaction(event.threadID, event.messageID, "✅");
  });
})();

🗂️ Project structure

PathDescription
index.jsEntry point; exports login + helpers.
index.d.tsTypeScript definitions.
module/login.jsClassic login(credentials, [options], [callback]).
src/api.jsInstaAPI facade wiring every native method group.
src/api/FCA methods, one file each: messaging/, threads/, users/, action/, realtime/.
src/app/InstaClient.jsThe FCA-shaped client.
src/app/InstaContext.jsPer-thread view over a client.
src/app/createFcaClient.jsNamespaced view (messages.*, threads.*).
src/app/loadApiMethods.jsAttaches every src/api/*/*.js onto a client.
src/events/normalizeEvent.jsRaw inbox event → FCA event.
src/utils/messageStyle.js*bold* / _italic_ → Unicode.
src/http.jsHTTP client: web headers, session params, GraphQL, rupload.
src/constants.jsEndpoint + Relay doc_id table.
src/cookies.jsCookie parsing / normalization.
src/session.jsBrowser session-param harvesting (puppeteer-core).
src/deviceIdentity.jsPersisted device identity.
src/methods/Native: message, media, reactions, thread, user, inbox, typing.
src/realtime/mqttot.jsDependency-free MQTToT codec (Thrift compact + MQTT packets + TLS transport).
src/realtime/client.jsRealtime MQTT client: connect, dispatch, direct events.
src/realtime/bridge.jsMessage-sync patch → raw inbox event.
src/realtime/listener.jsWires realtime into listen() with poller fallback.

See DOCS.md for the full protocol notes (GraphQL doc_ids, send / upload / reaction flows).


📋 Changelog

See CHANGELOG.md.

📄 License

Apache-2.0 © EryXenX

Credit required: you may use and modify this library, but you must keep the original copyright headers, the NOTICE file and the author credit (EryXenX), and clearly state your changes. Removing or replacing the credit violates the license.

版本与发布记录

@eryxenx/insta-chat 共发布 38 个版本,首次发布于 2026/10/08;过去 12 个月发布了 38 个版本。

版本发布日期距今
2.2.7latest2026/11/085 天前
2.2.62026/11/085 天前
2.2.52026/11/086 天前
2.2.42026/11/086 天前
2.2.32026/11/076 天前
2.2.22026/11/076 天前

其他发布通道:probe → 2.0.1。

关于 @eryxenx/insta-chat 的常见问题

@eryxenx/insta-chat 是什么?

Unofficial Instagram Chat API for Node.js | Build Instagram DM bots on personal accounts — send messages, media, voice notes, reactions, edit & unsend, listen for incoming chats. No browser at runtime

如何安装 @eryxenx/insta-chat?

在项目目录运行 npm install @eryxenx/insta-chat,或使用 pnpm add / yarn add / bun add。

@eryxenx/insta-chat 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@eryxenx/insta-chat 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@eryxenx/insta-chat 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@eryxenx/insta-chat 可以商用吗?

@eryxenx/insta-chat 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@eryxenx/insta-chat 的下载量有多少?

根据 npm 官方下载统计,@eryxenx/insta-chat 在 2026/10/14 – 2026/11/12 这 30 天内被下载 2,617 次,日均约 87 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@eryxenx/insta-chat 还在维护吗?

最新版本 2.2.7 发布于 2026/11/08(5 天前);至今共发布 38 个版本,其中过去 12 个月发布了 38 个;首次发布于 2026/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。