nodejs-insta-private-api-mqt 1.3.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3677 -0
- package/dist/constants/constants.js +342 -0
- package/dist/constants/index.js +58 -0
- package/dist/core/client.js +419 -0
- package/dist/core/nav-chain.js +282 -0
- package/dist/core/repository.js +7 -0
- package/dist/core/request.js +390 -0
- package/dist/core/state.js +1473 -0
- package/dist/core/utils.js +786 -0
- package/dist/downloadMedia.js +381 -0
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.js +38 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/extend.js +167 -0
- package/dist/fbns/fbns.client.d.ts +32 -0
- package/dist/fbns/fbns.client.events.d.ts +41 -0
- package/dist/fbns/fbns.client.events.js +3 -0
- package/dist/fbns/fbns.client.events.js.map +1 -0
- package/dist/fbns/fbns.client.js +252 -0
- package/dist/fbns/fbns.client.js.map +1 -0
- package/dist/fbns/fbns.device-auth.d.ts +17 -0
- package/dist/fbns/fbns.device-auth.js +54 -0
- package/dist/fbns/fbns.device-auth.js.map +1 -0
- package/dist/fbns/fbns.types.d.ts +83 -0
- package/dist/fbns/fbns.types.js +3 -0
- package/dist/fbns/fbns.types.js.map +1 -0
- package/dist/fbns/fbns.utilities.d.ts +2 -0
- package/dist/fbns/fbns.utilities.js +79 -0
- package/dist/fbns/fbns.utilities.js.map +1 -0
- package/dist/fbns/index.d.ts +4 -0
- package/dist/fbns/index.js +21 -0
- package/dist/fbns/index.js.map +1 -0
- package/dist/index.js +139 -0
- package/dist/mqtt-shim.d.ts +96 -0
- package/dist/mqtt-shim.js +15 -0
- package/dist/mqttot/index.d.ts +4 -0
- package/dist/mqttot/index.js +21 -0
- package/dist/mqttot/index.js.map +1 -0
- package/dist/mqttot/mqttot.client.d.ts +39 -0
- package/dist/mqttot/mqttot.client.js +318 -0
- package/dist/mqttot/mqttot.client.js.map +1 -0
- package/dist/mqttot/mqttot.connect.request.packet.d.ts +7 -0
- package/dist/mqttot/mqttot.connect.request.packet.js +9 -0
- package/dist/mqttot/mqttot.connect.request.packet.js.map +1 -0
- package/dist/mqttot/mqttot.connect.response.packet.d.ts +7 -0
- package/dist/mqttot/mqttot.connect.response.packet.js +24 -0
- package/dist/mqttot/mqttot.connect.response.packet.js.map +1 -0
- package/dist/mqttot/mqttot.connection.d.ts +57 -0
- package/dist/mqttot/mqttot.connection.js +79 -0
- package/dist/mqttot/mqttot.connection.js.map +1 -0
- package/dist/package.json +59 -0
- package/dist/realtime/commands/commands.d.ts +15 -0
- package/dist/realtime/commands/commands.js +71 -0
- package/dist/realtime/commands/commands.js.map +1 -0
- package/dist/realtime/commands/direct.commands.d.ts +75 -0
- package/dist/realtime/commands/direct.commands.js +417 -0
- package/dist/realtime/commands/direct.commands.js.map +1 -0
- package/dist/realtime/commands/enhanced.direct.commands.js +1731 -0
- package/dist/realtime/commands/enhanced.direct.commands.js.bak +967 -0
- package/dist/realtime/commands/index.d.ts +2 -0
- package/dist/realtime/commands/index.js +20 -0
- package/dist/realtime/commands/index.js.map +1 -0
- package/dist/realtime/delta-sync.manager.js +293 -0
- package/dist/realtime/features/dm-sender.js +88 -0
- package/dist/realtime/features/error-handler.js +185 -0
- package/dist/realtime/features/gap-handler.js +61 -0
- package/dist/realtime/features/persistent-logger.js +186 -0
- package/dist/realtime/features/presence.manager.js +66 -0
- package/dist/realtime/features/session-health-monitor.js +345 -0
- package/dist/realtime/index.js +30 -0
- package/dist/realtime/messages/app-presence.event.d.ts +9 -0
- package/dist/realtime/messages/app-presence.event.js +3 -0
- package/dist/realtime/messages/app-presence.event.js.map +1 -0
- package/dist/realtime/messages/index.d.ts +3 -0
- package/dist/realtime/messages/index.js +20 -0
- package/dist/realtime/messages/index.js.map +1 -0
- package/dist/realtime/messages/message-sync.message.d.ts +222 -0
- package/dist/realtime/messages/message-sync.message.js +43 -0
- package/dist/realtime/messages/message-sync.message.js.map +1 -0
- package/dist/realtime/messages/realtime-sub.direct.data.d.ts +11 -0
- package/dist/realtime/messages/realtime-sub.direct.data.js +3 -0
- package/dist/realtime/messages/realtime-sub.direct.data.js.map +1 -0
- package/dist/realtime/messages/thread-update.message.d.ts +68 -0
- package/dist/realtime/messages/thread-update.message.js +3 -0
- package/dist/realtime/messages/thread-update.message.js.map +1 -0
- package/dist/realtime/mixins/index.d.ts +3 -0
- package/dist/realtime/mixins/index.js +20 -0
- package/dist/realtime/mixins/index.js.map +1 -0
- package/dist/realtime/mixins/message-sync.mixin.d.ts +8 -0
- package/dist/realtime/mixins/message-sync.mixin.js +596 -0
- package/dist/realtime/mixins/message-sync.mixin.js.map +1 -0
- package/dist/realtime/mixins/mixin.d.ts +19 -0
- package/dist/realtime/mixins/mixin.js +41 -0
- package/dist/realtime/mixins/mixin.js.map +1 -0
- package/dist/realtime/mixins/presence-typing.mixin.js +33 -0
- package/dist/realtime/mixins/realtime-sub.mixin.d.ts +8 -0
- package/dist/realtime/mixins/realtime-sub.mixin.js +181 -0
- package/dist/realtime/mixins/realtime-sub.mixin.js.map +1 -0
- package/dist/realtime/parsers/graphql-parser.js +43 -0
- package/dist/realtime/parsers/graphql.parser.d.ts +15 -0
- package/dist/realtime/parsers/graphql.parser.js +22 -0
- package/dist/realtime/parsers/graphql.parser.js.map +1 -0
- package/dist/realtime/parsers/index.d.ts +6 -0
- package/dist/realtime/parsers/index.js +23 -0
- package/dist/realtime/parsers/index.js.map +1 -0
- package/dist/realtime/parsers/iris-parser.js +43 -0
- package/dist/realtime/parsers/iris.parser.d.ts +17 -0
- package/dist/realtime/parsers/iris.parser.js +10 -0
- package/dist/realtime/parsers/iris.parser.js.map +1 -0
- package/dist/realtime/parsers/json-parser.js +43 -0
- package/dist/realtime/parsers/json.parser.d.ts +6 -0
- package/dist/realtime/parsers/json.parser.js +10 -0
- package/dist/realtime/parsers/json.parser.js.map +1 -0
- package/dist/realtime/parsers/parser.d.ts +9 -0
- package/dist/realtime/parsers/parser.js +3 -0
- package/dist/realtime/parsers/parser.js.map +1 -0
- package/dist/realtime/parsers/region-hint-parser.js +43 -0
- package/dist/realtime/parsers/region-hint.parser.d.ts +12 -0
- package/dist/realtime/parsers/region-hint.parser.js +15 -0
- package/dist/realtime/parsers/region-hint.parser.js.map +1 -0
- package/dist/realtime/parsers/skywalker-parser.js +43 -0
- package/dist/realtime/parsers/skywalker.parser.d.ts +12 -0
- package/dist/realtime/parsers/skywalker.parser.js +15 -0
- package/dist/realtime/parsers/skywalker.parser.js.map +1 -0
- package/dist/realtime/parsers-advanced.js +158 -0
- package/dist/realtime/proto/common.proto +38 -0
- package/dist/realtime/proto/direct.proto +65 -0
- package/dist/realtime/proto/ig-messages.proto +83 -0
- package/dist/realtime/proto/iris.proto +188 -0
- package/dist/realtime/proto-parser.js +195 -0
- package/dist/realtime/protocols/iris.handshake.js +74 -0
- package/dist/realtime/protocols/proto-definitions.js +80 -0
- package/dist/realtime/protocols/skywalker.protocol.js +91 -0
- package/dist/realtime/realtime.client.events.js +3 -0
- package/dist/realtime/realtime.client.js +1915 -0
- package/dist/realtime/realtime.service.js +462 -0
- package/dist/realtime/reconnect.manager.js +88 -0
- package/dist/realtime/session.manager.js +121 -0
- package/dist/realtime/subscriptions/graphql.subscription.d.ts +47 -0
- package/dist/realtime/subscriptions/graphql.subscription.js +99 -0
- package/dist/realtime/subscriptions/graphql.subscription.js.map +1 -0
- package/dist/realtime/subscriptions/index.d.ts +2 -0
- package/dist/realtime/subscriptions/index.js +19 -0
- package/dist/realtime/subscriptions/index.js.map +1 -0
- package/dist/realtime/subscriptions/skywalker.subscription.d.ts +4 -0
- package/dist/realtime/subscriptions/skywalker.subscription.js +13 -0
- package/dist/realtime/subscriptions/skywalker.subscription.js.map +1 -0
- package/dist/realtime/topic-map.js +71 -0
- package/dist/realtime/topic.js +80 -0
- package/dist/repositories/account.repository.js +575 -0
- package/dist/repositories/bloks.repository.js +70 -0
- package/dist/repositories/captcha.repository.js +44 -0
- package/dist/repositories/challenge.repository.js +120 -0
- package/dist/repositories/clip.repository.js +165 -0
- package/dist/repositories/close-friends.repository.js +46 -0
- package/dist/repositories/collection.repository.js +68 -0
- package/dist/repositories/direct-thread.repository.js +446 -0
- package/dist/repositories/direct.repository.js +232 -0
- package/dist/repositories/explore.repository.js +70 -0
- package/dist/repositories/fbsearch.repository.js +140 -0
- package/dist/repositories/feed.repository.js +245 -0
- package/dist/repositories/friendship.repository.js +296 -0
- package/dist/repositories/fundraiser.repository.js +49 -0
- package/dist/repositories/hashtag.repository.js +99 -0
- package/dist/repositories/highlights.repository.js +121 -0
- package/dist/repositories/insights.repository.js +82 -0
- package/dist/repositories/location.repository.js +84 -0
- package/dist/repositories/media.repository.js +395 -0
- package/dist/repositories/multiple-accounts.repository.js +41 -0
- package/dist/repositories/news.repository.js +35 -0
- package/dist/repositories/note.repository.js +57 -0
- package/dist/repositories/notification.repository.js +79 -0
- package/dist/repositories/share.repository.js +35 -0
- package/dist/repositories/signup.repository.js +218 -0
- package/dist/repositories/story.repository.js +290 -0
- package/dist/repositories/timeline.repository.js +60 -0
- package/dist/repositories/totp.repository.js +139 -0
- package/dist/repositories/track.repository.js +53 -0
- package/dist/repositories/upload.repository.js +204 -0
- package/dist/repositories/user.repository.js +360 -0
- package/dist/sendmedia/index.js +27 -0
- package/dist/sendmedia/sendFile.js +72 -0
- package/dist/sendmedia/sendPhoto.js +142 -0
- package/dist/sendmedia/sendRavenPhoto.js +153 -0
- package/dist/sendmedia/sendRavenVideo.js +158 -0
- package/dist/sendmedia/uploadPhoto.js +107 -0
- package/dist/sendmedia/uploadfFile.js +130 -0
- package/dist/services/live.service.js +139 -0
- package/dist/services/search.service.js +115 -0
- package/dist/shared/index.js +96 -0
- package/dist/shared/shared.js +86 -0
- package/dist/thrift/index.d.ts +3 -0
- package/dist/thrift/index.js +20 -0
- package/dist/thrift/index.js.map +1 -0
- package/dist/thrift/thrift.d.ts +59 -0
- package/dist/thrift/thrift.js +101 -0
- package/dist/thrift/thrift.js.map +1 -0
- package/dist/thrift/thrift.reading.d.ts +41 -0
- package/dist/thrift/thrift.reading.js +327 -0
- package/dist/thrift/thrift.reading.js.map +1 -0
- package/dist/thrift/thrift.writing.d.ts +44 -0
- package/dist/thrift/thrift.writing.js +342 -0
- package/dist/thrift/thrift.writing.js.map +1 -0
- package/dist/types/index.js +285 -0
- package/dist/useMultiFileAuthState.js +1768 -0
- package/dist/utils/helper-1.js +1 -0
- package/dist/utils/helper-10.js +1 -0
- package/dist/utils/helper-11.js +1 -0
- package/dist/utils/helper-12.js +1 -0
- package/dist/utils/helper-13.js +1 -0
- package/dist/utils/helper-14.js +1 -0
- package/dist/utils/helper-15.js +1 -0
- package/dist/utils/helper-16.js +1 -0
- package/dist/utils/helper-17.js +1 -0
- package/dist/utils/helper-18.js +1 -0
- package/dist/utils/helper-19.js +1 -0
- package/dist/utils/helper-2.js +1 -0
- package/dist/utils/helper-20.js +1 -0
- package/dist/utils/helper-21.js +1 -0
- package/dist/utils/helper-22.js +1 -0
- package/dist/utils/helper-23.js +1 -0
- package/dist/utils/helper-24.js +1 -0
- package/dist/utils/helper-25.js +1 -0
- package/dist/utils/helper-26.js +1 -0
- package/dist/utils/helper-27.js +1 -0
- package/dist/utils/helper-28.js +1 -0
- package/dist/utils/helper-29.js +1 -0
- package/dist/utils/helper-3.js +1 -0
- package/dist/utils/helper-30.js +1 -0
- package/dist/utils/helper-4.js +1 -0
- package/dist/utils/helper-5.js +1 -0
- package/dist/utils/helper-6.js +1 -0
- package/dist/utils/helper-7.js +1 -0
- package/dist/utils/helper-8.js +1 -0
- package/dist/utils/helper-9.js +1 -0
- package/dist/utils/index.js +280 -0
- package/dist/utils/insta-mqtt-helper.js +128 -0
- package/examples/listen-to-messages.js +86 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,3677 @@
|
|
|
1
|
+
Full featured Java and nodejs Instagram api private with Mqtt full suport and api rest
|
|
2
|
+
|
|
3
|
+
Dear users The repository on github is currently unavailable will be available soon
|
|
4
|
+
|
|
5
|
+
If you Like This Project You Can Help Me with Donation revolut , My revolut revtag is @gvny22
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# nodejs-insta-private-api-mqt
|
|
9
|
+
|
|
10
|
+
This project implements a complete and production-ready MQTT protocol client for Instagram's real-time messaging infrastructure. Instagram uses MQTT natively for direct messages, notifications, and real-time presence updates. This library replicates that exact implementation, allowing developers to build high-performance bots and automation tools that communicate with Instagram's backend using the same protocol the official app uses.
|
|
11
|
+
|
|
12
|
+
By leveraging MQTT instead of Instagram's REST API, this library achieves sub-500ms message latency, bidirectional real-time communication, and native support for notifications, presence tracking, and thread management. The implementation is reverse-engineered from Instagram's mobile app protocol and tested extensively for reliability and compatibility.
|
|
13
|
+
|
|
14
|
+
## Features (v5.66.0 - Complete REST API + MQTT)
|
|
15
|
+
|
|
16
|
+
### REST API (32 Repositories)
|
|
17
|
+
- **Account** - Login, 2FA (TOTP + SMS), challenge resolver, edit profile, change password, privacy settings
|
|
18
|
+
- **User** - Info, search, follow/unfollow, block/unblock, mute, get posts/reels/stories with pagination
|
|
19
|
+
- **Media** - Like, comment (pin/unpin/bulk delete/reply), save, archive, download, PK/shortcode conversion
|
|
20
|
+
- **Reels/Clips** - Upload, configure, discover reels, download, music info
|
|
21
|
+
- **Stories** - Upload photo/video stories, react, mark seen, highlights management
|
|
22
|
+
- **Highlights** - Create, edit, delete highlights, add/remove stories, update cover
|
|
23
|
+
- **Direct Messages** - Send text/photo/video/link/media, inbox, pending inbox, group threads
|
|
24
|
+
- **Friendship** - Follow, block, restrict, close friends, favorites, pending requests
|
|
25
|
+
- **Search** - Users, hashtags, places, music, recent/suggested searches
|
|
26
|
+
- **Explore** - Topical explore, report, mark seen
|
|
27
|
+
- **Feed** - Timeline, hashtag/location feeds, saved/liked, carousel upload
|
|
28
|
+
- **Upload** - Photo/video upload with configure to feed, story, or clips
|
|
29
|
+
- **Insights** - Account, media, reel, story analytics (business/creator accounts)
|
|
30
|
+
- **Notes** - Create, delete, view Instagram Notes
|
|
31
|
+
- **Notifications** - Per-type notification settings (likes, comments, follows, etc.)
|
|
32
|
+
- **TOTP** - 2FA setup with authenticator app, SMS 2FA, backup codes
|
|
33
|
+
- **Challenge** - Auto-resolve security checkpoints, verify methods
|
|
34
|
+
- **Signup** - Account creation, email/phone verification, username availability
|
|
35
|
+
- **Music/Tracks** - Search, get info, download audio tracks
|
|
36
|
+
- **Fundraiser** - Create, donate, get fundraiser info
|
|
37
|
+
- **Multiple Accounts** - Account family, switch accounts
|
|
38
|
+
- **Captcha** - reCAPTCHA / hCaptcha handling
|
|
39
|
+
- **Share** - Decode QR/NFC share codes, parse share URLs
|
|
40
|
+
- **Bloks** - Low-level Instagram Bloks engine actions
|
|
41
|
+
|
|
42
|
+
### MQTT Real-Time
|
|
43
|
+
- **Real-time MQTT messaging** - Receive and send DMs with <500ms latency
|
|
44
|
+
- **FBNS Push Notifications** - Follows, likes, comments, story mentions, live broadcasts
|
|
45
|
+
- **33 Preset Devices** - 21 iOS + 12 Android device emulation
|
|
46
|
+
- **View-Once Media** - Download disappearing photos/videos before they expire
|
|
47
|
+
- **Raven (View-Once) Sending** - Send view-once and replayable photos/videos via REST
|
|
48
|
+
- **sendPhoto() / sendVideo()** - Upload and send media directly via MQTT
|
|
49
|
+
- **Session persistence** - Multi-file auth state like Baileys (WhatsApp library)
|
|
50
|
+
- **Automatic reconnection** - Smart error classification with type-specific backoff
|
|
51
|
+
- **Session health monitoring** - Auto-relogin, uptime tracking
|
|
52
|
+
- **Persistent logging** - File-based logging with rotation
|
|
53
|
+
- **Message ordering** - Per-thread message queuing
|
|
54
|
+
- **Pure JavaScript** - No compilation required, works in Node.js 18+
|
|
55
|
+
|
|
56
|
+
## Scope: DM-Focused Implementation
|
|
57
|
+
|
|
58
|
+
This library is optimized for Direct Messages and implements the core MQTT protocols used by Instagram for:
|
|
59
|
+
- Real-time message delivery and reception
|
|
60
|
+
- Presence status tracking
|
|
61
|
+
- Typing indicators
|
|
62
|
+
- Notifications for follows, mentions, and calls
|
|
63
|
+
- Group thread management
|
|
64
|
+
|
|
65
|
+
**For full MQTT coverage analysis, see [MQTT_COVERAGE_ANALYSIS.md](MQTT_COVERAGE_ANALYSIS.md)**
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install nodejs-insta-private-api-mqt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Requires **Node.js 18 or higher**.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## NEW: Custom Device Emulation (v5.60.7)
|
|
78
|
+
|
|
79
|
+
**Default Device:** Samsung Galaxy S25 Ultra (Android 15) - used automatically if you don't set a custom device.
|
|
80
|
+
|
|
81
|
+
This feature allows you to **choose which phone model Instagram sees** when your bot connects. Instead of using a default device, you can emulate any Android phone like Samsung Galaxy S25 Ultra, Huawei P60 Pro, Google Pixel 9, and more.
|
|
82
|
+
|
|
83
|
+
### Why Use Custom Device Emulation?
|
|
84
|
+
|
|
85
|
+
- **Avoid detection** - Use realistic, modern device fingerprints
|
|
86
|
+
- **Match your target audience** - Emulate devices popular in specific regions
|
|
87
|
+
- **Testing** - Test how Instagram behaves with different devices
|
|
88
|
+
- **Reduce bans** - Modern devices are less likely to trigger security checks
|
|
89
|
+
|
|
90
|
+
### Quick Start: Use a Preset Device
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const { IgApiClient } = require('nodejs-insta-private-api');
|
|
94
|
+
|
|
95
|
+
const ig = new IgApiClient();
|
|
96
|
+
|
|
97
|
+
// Set device BEFORE login
|
|
98
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
99
|
+
|
|
100
|
+
// Now login - Instagram will see a Samsung S25 Ultra
|
|
101
|
+
await ig.login({
|
|
102
|
+
username: 'your_username',
|
|
103
|
+
password: 'your_password'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log('Logged in with device:', ig.state.deviceString);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Available Preset Devices
|
|
110
|
+
|
|
111
|
+
| Device Name | Manufacturer | Android Version |
|
|
112
|
+
|-------------|--------------|-----------------|
|
|
113
|
+
| Samsung Galaxy S25 Ultra | Samsung | Android 15 |
|
|
114
|
+
| Samsung Galaxy S24 Ultra | Samsung | Android 14 |
|
|
115
|
+
| Samsung Galaxy S23 Ultra | Samsung | Android 14 |
|
|
116
|
+
| Samsung Galaxy Z Fold 5 | Samsung | Android 14 |
|
|
117
|
+
| Huawei P60 Pro | Huawei | Android 12 |
|
|
118
|
+
| Huawei Mate 60 Pro | Huawei | Android 12 |
|
|
119
|
+
| Google Pixel 8 Pro | Google | Android 14 |
|
|
120
|
+
| Google Pixel 9 Pro | Google | Android 15 |
|
|
121
|
+
| OnePlus 12 | OnePlus | Android 14 |
|
|
122
|
+
| Xiaomi 14 Ultra | Xiaomi | Android 14 |
|
|
123
|
+
| Xiaomi Redmi Note 13 Pro | Xiaomi | Android 14 |
|
|
124
|
+
| OPPO Find X7 Ultra | OPPO | Android 14 |
|
|
125
|
+
|
|
126
|
+
### Set a Fully Custom Device
|
|
127
|
+
|
|
128
|
+
For complete control, use `setCustomDevice()` with your own configuration:
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
const ig = new IgApiClient();
|
|
132
|
+
|
|
133
|
+
ig.state.setCustomDevice({
|
|
134
|
+
manufacturer: 'samsung',
|
|
135
|
+
model: 'SM-S928B',
|
|
136
|
+
device: 'e3q',
|
|
137
|
+
androidVersion: '15',
|
|
138
|
+
androidApiLevel: 35,
|
|
139
|
+
resolution: '1440x3120',
|
|
140
|
+
dpi: '505dpi',
|
|
141
|
+
chipset: 'qcom',
|
|
142
|
+
build: 'UP1A.231005.007'
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Quick Start: Instant MQTT Boot
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqt');
|
|
152
|
+
|
|
153
|
+
async function startBot() {
|
|
154
|
+
const ig = new IgApiClient();
|
|
155
|
+
const auth = await useMultiFileAuthState('./auth_info_ig');
|
|
156
|
+
|
|
157
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
158
|
+
|
|
159
|
+
const realtime = new RealtimeClient(ig);
|
|
160
|
+
|
|
161
|
+
realtime.on('connected', () => {
|
|
162
|
+
console.log('Bot is online and MQTT is connected!');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
realtime.on('message_live', async (msg) => {
|
|
166
|
+
console.log(`[${msg.username}]: ${msg.text}`);
|
|
167
|
+
|
|
168
|
+
if (msg.text.toLowerCase() === 'ping') {
|
|
169
|
+
await realtime.directCommands.sendText({
|
|
170
|
+
threadId: msg.thread_id,
|
|
171
|
+
text: 'pong!'
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (!auth.hasSession()) {
|
|
177
|
+
await ig.login({
|
|
178
|
+
username: 'your_username',
|
|
179
|
+
password: 'your_password'
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await auth.saveCreds(ig);
|
|
183
|
+
await realtime.startRealTimeListener();
|
|
184
|
+
await auth.saveMqttSession(realtime);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
startBot().catch(console.error);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## EnhancedDirectCommands - Complete MQTT Methods Reference
|
|
194
|
+
|
|
195
|
+
All MQTT direct messaging functionality is available through `realtime.directCommands`. These methods use proper payload formatting that matches the instagram_mqtt library format.
|
|
196
|
+
|
|
197
|
+
### Basic Messaging
|
|
198
|
+
|
|
199
|
+
#### Send Text Message
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
await realtime.directCommands.sendText({
|
|
203
|
+
threadId: '340282366841710300949128114477782749726',
|
|
204
|
+
text: 'Hello from MQTT!'
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### Send Text (Alternative Signature)
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
await realtime.directCommands.sendTextViaRealtime(threadId, 'Hello!');
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Reply to Message (Quote Reply)
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
await realtime.directCommands.replyToMessage(threadId, messageId, 'This is my reply');
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### Edit Message
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
await realtime.directCommands.editMessage(threadId, itemId, 'Updated text here');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Delete Message
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
await realtime.directCommands.deleteMessage(threadId, itemId);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Content Sharing
|
|
235
|
+
|
|
236
|
+
#### Send Hashtag
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
await realtime.directCommands.sendHashtag({
|
|
240
|
+
threadId: threadId,
|
|
241
|
+
hashtag: 'photography',
|
|
242
|
+
text: 'Check this out'
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Send Like (Heart)
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
await realtime.directCommands.sendLike({
|
|
250
|
+
threadId: threadId
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Send Location
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
await realtime.directCommands.sendLocation({
|
|
258
|
+
threadId: threadId,
|
|
259
|
+
locationId: '123456789',
|
|
260
|
+
text: 'Meet me here'
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### Send Media (Share Post)
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
await realtime.directCommands.sendMedia({
|
|
268
|
+
threadId: threadId,
|
|
269
|
+
mediaId: 'media_id_here',
|
|
270
|
+
text: 'Check this post'
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Send Profile
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
await realtime.directCommands.sendProfile({
|
|
278
|
+
threadId: threadId,
|
|
279
|
+
userId: '12345678',
|
|
280
|
+
text: 'Follow this account'
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### Send User Story
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
await realtime.directCommands.sendUserStory({
|
|
288
|
+
threadId: threadId,
|
|
289
|
+
storyId: 'story_id_here',
|
|
290
|
+
text: 'Did you see this?'
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Send Link
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
await realtime.directCommands.sendLink({
|
|
298
|
+
threadId: threadId,
|
|
299
|
+
link: 'https://example.com',
|
|
300
|
+
text: 'Check this link'
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Send Animated Media (GIF/Sticker)
|
|
305
|
+
|
|
306
|
+
```javascript
|
|
307
|
+
await realtime.directCommands.sendAnimatedMedia({
|
|
308
|
+
threadId: threadId,
|
|
309
|
+
id: 'giphy_id_here',
|
|
310
|
+
isSticker: false
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Send Voice Message (after upload)
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
await realtime.directCommands.sendVoice({
|
|
318
|
+
threadId: threadId,
|
|
319
|
+
uploadId: 'your_upload_id',
|
|
320
|
+
waveform: [0.1, 0.5, 0.8, 0.3],
|
|
321
|
+
waveformSamplingFrequencyHz: 10
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### Reactions
|
|
328
|
+
|
|
329
|
+
#### Send Reaction
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
await realtime.directCommands.sendReaction({
|
|
333
|
+
threadId: threadId,
|
|
334
|
+
itemId: messageId,
|
|
335
|
+
reactionType: 'like'
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Send Emoji Reaction
|
|
340
|
+
|
|
341
|
+
```javascript
|
|
342
|
+
await realtime.directCommands.sendReaction({
|
|
343
|
+
threadId: threadId,
|
|
344
|
+
itemId: messageId,
|
|
345
|
+
reactionType: 'emoji',
|
|
346
|
+
emoji: '🔥'
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Remove Reaction
|
|
351
|
+
|
|
352
|
+
```javascript
|
|
353
|
+
await realtime.directCommands.removeReaction({
|
|
354
|
+
threadId: threadId,
|
|
355
|
+
itemId: messageId
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
### Read Receipts & Activity
|
|
362
|
+
|
|
363
|
+
#### Mark Message as Seen
|
|
364
|
+
|
|
365
|
+
Marks a message as seen (read receipt) in a DM thread. This uses Instagram's REST API internally and falls back to MQTT if needed. The method returns the server response — when successful, `status` will be `"ok"`.
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
// Basic usage — mark a specific message as seen
|
|
369
|
+
const result = await realtime.directCommands.markAsSeen({
|
|
370
|
+
threadId: '340282366841710300949128114477782749726',
|
|
371
|
+
itemId: '32661457411201420841385410521202688'
|
|
372
|
+
});
|
|
373
|
+
console.log(result.status); // "ok"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### Auto-Read All Incoming Messages
|
|
377
|
+
|
|
378
|
+
A common pattern is to automatically mark every incoming message as seen, so the sender always sees the blue "Seen" indicator:
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
realtime.on('message', async (data) => {
|
|
382
|
+
const threadId = data.parsed?.threadId || data.message?.thread_id;
|
|
383
|
+
const itemId = data.parsed?.messageId || data.message?.item_id;
|
|
384
|
+
const status = data.parsed?.status;
|
|
385
|
+
|
|
386
|
+
// Only mark messages from other users (skip our own)
|
|
387
|
+
if (status === 'sent' || !threadId || !itemId) return;
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
await realtime.directCommands.markAsSeen({ threadId, itemId });
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.error('Failed to mark as seen:', err.message);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### Mark as Seen with Delay (Human-Like Behavior)
|
|
398
|
+
|
|
399
|
+
If you want the bot to appear more human, you can add a small delay before sending the read receipt:
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
realtime.on('message', async (data) => {
|
|
403
|
+
const threadId = data.parsed?.threadId || data.message?.thread_id;
|
|
404
|
+
const itemId = data.parsed?.messageId || data.message?.item_id;
|
|
405
|
+
|
|
406
|
+
if (data.parsed?.status === 'sent' || !threadId || !itemId) return;
|
|
407
|
+
|
|
408
|
+
// Wait 1-3 seconds before marking as seen
|
|
409
|
+
const delay = 1000 + Math.random() * 2000;
|
|
410
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
411
|
+
|
|
412
|
+
await realtime.directCommands.markAsSeen({ threadId, itemId });
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### Indicate Typing
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
// Start typing
|
|
420
|
+
await realtime.directCommands.indicateActivity({
|
|
421
|
+
threadId: threadId,
|
|
422
|
+
isActive: true
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Stop typing
|
|
426
|
+
await realtime.directCommands.indicateActivity({
|
|
427
|
+
threadId: threadId,
|
|
428
|
+
isActive: false
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### Mark Visual Message as Seen (disappearing media)
|
|
433
|
+
|
|
434
|
+
Mark view-once photos/videos as seen. Works the same way as `markAsSeen` but specifically for disappearing media items:
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
await realtime.directCommands.markVisualMessageSeen({
|
|
438
|
+
threadId: threadId,
|
|
439
|
+
itemId: messageId
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### Thread Management
|
|
446
|
+
|
|
447
|
+
#### Add Member to Thread
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
await realtime.directCommands.addMemberToThread(threadId, userId);
|
|
451
|
+
|
|
452
|
+
// Add multiple members
|
|
453
|
+
await realtime.directCommands.addMemberToThread(threadId, [userId1, userId2]);
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Remove Member from Thread
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
await realtime.directCommands.removeMemberFromThread(threadId, userId);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### Leave Thread
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
await realtime.directCommands.leaveThread(threadId);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### Update Thread Title
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
await realtime.directCommands.updateThreadTitle(threadId, 'New Group Name');
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
#### Mute Thread
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
await realtime.directCommands.muteThread(threadId);
|
|
478
|
+
|
|
479
|
+
// Mute until specific time
|
|
480
|
+
await realtime.directCommands.muteThread(threadId, Date.now() + 3600000);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Unmute Thread
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
await realtime.directCommands.unmuteThread(threadId);
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
### Message Requests
|
|
492
|
+
|
|
493
|
+
#### Approve Pending Thread
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
await realtime.directCommands.approveThread(threadId);
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### Decline Pending Thread
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
await realtime.directCommands.declineThread(threadId);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
### Moderation
|
|
508
|
+
|
|
509
|
+
#### Block User in Thread
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
await realtime.directCommands.blockUserInThread(threadId, userId);
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### Report Thread
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
await realtime.directCommands.reportThread(threadId, 'spam');
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
### Disappearing Media (View-Once)
|
|
524
|
+
|
|
525
|
+
#### Send Disappearing Photo
|
|
526
|
+
|
|
527
|
+
```javascript
|
|
528
|
+
await realtime.directCommands.sendDisappearingPhoto({
|
|
529
|
+
threadId: threadId,
|
|
530
|
+
uploadId: 'your_upload_id',
|
|
531
|
+
viewMode: 'once' // 'once' or 'replayable'
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
#### Send Disappearing Video
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
await realtime.directCommands.sendDisappearingVideo({
|
|
539
|
+
threadId: threadId,
|
|
540
|
+
uploadId: 'your_upload_id',
|
|
541
|
+
viewMode: 'once'
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
### Raven (View-Once / Disappearing) Media
|
|
548
|
+
|
|
549
|
+
Send photos and videos that disappear after viewing — just like the Instagram app camera button in DMs. Instagram calls these "raven" messages internally.
|
|
550
|
+
|
|
551
|
+
**How it works under the hood:**
|
|
552
|
+
1. The photo/video is uploaded to `rupload.facebook.com/messenger_image/` (Instagram's dedicated raven upload endpoint)
|
|
553
|
+
2. Then broadcast via REST to `/direct_v2/threads/broadcast/raven_attachment/`
|
|
554
|
+
|
|
555
|
+
You don't need to worry about any of that — the library handles everything. Just pick a method and go.
|
|
556
|
+
|
|
557
|
+
**Two view modes:**
|
|
558
|
+
| Mode | What happens |
|
|
559
|
+
|------|-------------|
|
|
560
|
+
| `'once'` | Recipient opens it once, then it's gone forever |
|
|
561
|
+
| `'replayable'` | Recipient can replay it, but it still disappears from the chat |
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
#### Quick Start — Send a View-Once Photo (Standalone, No MQTT)
|
|
566
|
+
|
|
567
|
+
The simplest way to send a disappearing photo. No MQTT setup needed — just login and send:
|
|
568
|
+
|
|
569
|
+
```javascript
|
|
570
|
+
const { IgApiClient, useMultiFileAuthState, sendRavenPhotoOnce } = require('nodejs-insta-private-api-mqt');
|
|
571
|
+
const fs = require('fs');
|
|
572
|
+
|
|
573
|
+
async function main() {
|
|
574
|
+
const ig = new IgApiClient();
|
|
575
|
+
const auth = await useMultiFileAuthState('./my_session');
|
|
576
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
577
|
+
|
|
578
|
+
// Login (or restore session)
|
|
579
|
+
await ig.login({ username: 'your_username', password: 'your_password' });
|
|
580
|
+
await auth.saveCreds(ig);
|
|
581
|
+
|
|
582
|
+
// Read any photo from disk
|
|
583
|
+
const photo = fs.readFileSync('./secret_photo.jpg');
|
|
584
|
+
|
|
585
|
+
// Send it as view-once — one line, that's it
|
|
586
|
+
const result = await sendRavenPhotoOnce(ig, photo, {
|
|
587
|
+
threadId: '340282366841710300949128114477782749726' // your DM thread ID
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
console.log('Sent! Status:', result.status); // "ok"
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
main().catch(console.error);
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
#### Send a Replayable Photo
|
|
599
|
+
|
|
600
|
+
Same thing, but the recipient can replay it before it disappears:
|
|
601
|
+
|
|
602
|
+
```javascript
|
|
603
|
+
const { sendRavenPhotoReplayable } = require('nodejs-insta-private-api-mqt');
|
|
604
|
+
|
|
605
|
+
const photo = fs.readFileSync('./photo.jpg');
|
|
606
|
+
|
|
607
|
+
const result = await sendRavenPhotoReplayable(ig, photo, {
|
|
608
|
+
threadId: 'your_thread_id'
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
console.log('Replayable photo sent!', result.status);
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
#### Send a View-Once Video
|
|
617
|
+
|
|
618
|
+
Experience the new and improved video sending! Our implementation is now fully compatible with the latest Instagram protocols.
|
|
619
|
+
|
|
620
|
+
```javascript
|
|
621
|
+
const { IgApiClientExt } = require('nodejs-insta-private-api-mqt');
|
|
622
|
+
const fs = require('fs');
|
|
623
|
+
|
|
624
|
+
async function sendMySecretVideo() {
|
|
625
|
+
const ig = new IgApiClientExt();
|
|
626
|
+
await ig.login({ username: 'your_username', password: 'your_password' });
|
|
627
|
+
|
|
628
|
+
// Just read your video file
|
|
629
|
+
const videoData = fs.readFileSync('./my_awesome_video.mp4');
|
|
630
|
+
|
|
631
|
+
// Send it instantly as a view-once message
|
|
632
|
+
const result = await ig.sendRavenVideoOnce(videoData, {
|
|
633
|
+
threadId: '1234567890', // The DM thread ID
|
|
634
|
+
duration: 10, // Video length in seconds
|
|
635
|
+
width: 720, // Width (default 720)
|
|
636
|
+
height: 1280 // Height (default 1280)
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
if (result.status === 'ok') {
|
|
640
|
+
console.log('Boom! Your secret video is on its way.');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
#### Send a Replayable Video
|
|
648
|
+
|
|
649
|
+
If you want them to be able to see it one more time before it disappears:
|
|
650
|
+
|
|
651
|
+
```javascript
|
|
652
|
+
const result = await ig.sendRavenVideoReplayable(videoData, {
|
|
653
|
+
threadId: '1234567890',
|
|
654
|
+
duration: 15
|
|
655
|
+
});
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
#### Using sendRavenPhoto / sendRavenVideo Directly (Choose Mode)
|
|
661
|
+
|
|
662
|
+
If you want to pick the view mode dynamically, use the base `sendRavenPhoto` or `sendRavenVideo` functions with the `viewMode` option:
|
|
663
|
+
|
|
664
|
+
```javascript
|
|
665
|
+
const { sendRavenPhoto, sendRavenVideo } = require('nodejs-insta-private-api-mqt');
|
|
666
|
+
const fs = require('fs');
|
|
667
|
+
|
|
668
|
+
// Photo — choose 'once' or 'replayable'
|
|
669
|
+
const photoResult = await sendRavenPhoto(ig, fs.readFileSync('./photo.jpg'), {
|
|
670
|
+
threadId: 'your_thread_id',
|
|
671
|
+
viewMode: 'once' // or 'replayable'
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Video — same pattern
|
|
675
|
+
const videoResult = await sendRavenVideo(ig, fs.readFileSync('./video.mp4'), {
|
|
676
|
+
threadId: 'your_thread_id',
|
|
677
|
+
viewMode: 'replayable', // or 'once'
|
|
678
|
+
duration: 8,
|
|
679
|
+
width: 720,
|
|
680
|
+
height: 1280
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
#### With MQTT / RealtimeClient (directCommands)
|
|
687
|
+
|
|
688
|
+
If you already have an MQTT connection running (for receiving messages in real-time), you can use `directCommands` to send raven media without importing anything extra:
|
|
689
|
+
|
|
690
|
+
```javascript
|
|
691
|
+
// After setting up realtime connection...
|
|
692
|
+
|
|
693
|
+
const fs = require('fs');
|
|
694
|
+
const photo = fs.readFileSync('./secret.jpg');
|
|
695
|
+
|
|
696
|
+
// View-once photo
|
|
697
|
+
await realtime.directCommands.sendRavenPhotoOnce({
|
|
698
|
+
threadId: threadId,
|
|
699
|
+
photoBuffer: photo
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Replayable photo
|
|
703
|
+
await realtime.directCommands.sendRavenPhotoReplayable({
|
|
704
|
+
threadId: threadId,
|
|
705
|
+
photoBuffer: photo
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Or specify viewMode manually
|
|
709
|
+
await realtime.directCommands.sendRavenPhoto({
|
|
710
|
+
threadId: threadId,
|
|
711
|
+
photoBuffer: photo,
|
|
712
|
+
viewMode: 'once' // 'once' or 'replayable'
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// View-once video
|
|
716
|
+
const video = fs.readFileSync('./clip.mp4');
|
|
717
|
+
await realtime.directCommands.sendRavenVideoOnce({
|
|
718
|
+
threadId: threadId,
|
|
719
|
+
videoBuffer: video,
|
|
720
|
+
duration: 10,
|
|
721
|
+
width: 720,
|
|
722
|
+
height: 1280
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// Replayable video
|
|
726
|
+
await realtime.directCommands.sendRavenVideoReplayable({
|
|
727
|
+
threadId: threadId,
|
|
728
|
+
videoBuffer: video,
|
|
729
|
+
duration: 10
|
|
730
|
+
});
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
#### Complete Bot Example — Auto-Reply with Disappearing Photo
|
|
736
|
+
|
|
737
|
+
Here's a full working example of a bot that automatically replies to incoming messages with a view-once photo:
|
|
738
|
+
|
|
739
|
+
```javascript
|
|
740
|
+
const {
|
|
741
|
+
IgApiClient,
|
|
742
|
+
withRealtime,
|
|
743
|
+
useMultiFileAuthState,
|
|
744
|
+
GraphQLSubscriptions,
|
|
745
|
+
SkywalkerSubscriptions,
|
|
746
|
+
sendRavenPhotoOnce,
|
|
747
|
+
sendRavenPhotoReplayable,
|
|
748
|
+
} = require('nodejs-insta-private-api-mqt');
|
|
749
|
+
const fs = require('fs');
|
|
750
|
+
|
|
751
|
+
async function startBot() {
|
|
752
|
+
const ig = new IgApiClient();
|
|
753
|
+
const auth = await useMultiFileAuthState('./bot_session');
|
|
754
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
755
|
+
|
|
756
|
+
await ig.login({ username: 'bot_username', password: 'bot_password' });
|
|
757
|
+
await auth.saveCreds(ig);
|
|
758
|
+
|
|
759
|
+
const { realtime } = withRealtime(ig);
|
|
760
|
+
|
|
761
|
+
realtime.on('message', async (data) => {
|
|
762
|
+
// When someone sends you a text message
|
|
763
|
+
if (data.message && data.message.text) {
|
|
764
|
+
const threadId = data.message.thread_id;
|
|
765
|
+
const text = data.message.text.toLowerCase();
|
|
766
|
+
|
|
767
|
+
if (text === '!secret') {
|
|
768
|
+
// Reply with a view-once photo
|
|
769
|
+
const photo = fs.readFileSync('./secret_photo.jpg');
|
|
770
|
+
await sendRavenPhotoOnce(ig, photo, { threadId });
|
|
771
|
+
console.log('Sent view-once photo to', threadId);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (text === '!replay') {
|
|
775
|
+
// Reply with a replayable photo
|
|
776
|
+
const photo = fs.readFileSync('./replay_photo.jpg');
|
|
777
|
+
await sendRavenPhotoReplayable(ig, photo, { threadId });
|
|
778
|
+
console.log('Sent replayable photo to', threadId);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
await realtime.connect({
|
|
784
|
+
graphQlSubs: [GraphQLSubscriptions.getDirectTypingSubscription(ig.state.cookieUserId)],
|
|
785
|
+
skywalkerSubs: [SkywalkerSubscriptions.directSub(ig.state.cookieUserId)],
|
|
786
|
+
irisData: await ig.request.send({ url: '/api/v1/direct_v2/inbox/', method: 'GET' }),
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
console.log('Bot is running! Send "!secret" or "!replay" in DMs.');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
startBot().catch(console.error);
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
---
|
|
796
|
+
|
|
797
|
+
#### All Available Raven Functions
|
|
798
|
+
|
|
799
|
+
| Function | What it does |
|
|
800
|
+
|----------|-------------|
|
|
801
|
+
| `sendRavenPhoto(ig, buffer, { threadId, viewMode })` | Send disappearing photo with chosen mode |
|
|
802
|
+
| `sendRavenPhotoOnce(ig, buffer, { threadId })` | Send view-once photo (opens once, then gone) |
|
|
803
|
+
| `sendRavenPhotoReplayable(ig, buffer, { threadId })` | Send replayable photo (can replay, still disappears) |
|
|
804
|
+
| `sendRavenVideo(ig, buffer, { threadId, viewMode, duration, width, height })` | Send disappearing video with chosen mode |
|
|
805
|
+
| `sendRavenVideoOnce(ig, buffer, { threadId, duration, width, height })` | Send view-once video |
|
|
806
|
+
| `sendRavenVideoReplayable(ig, buffer, { threadId, duration, width, height })` | Send replayable video |
|
|
807
|
+
|
|
808
|
+
All functions return a response object with `status: 'ok'` on success and a `payload` containing the `item_id` and `thread_id`.
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
#### Low-Level: broadcastRaven (DirectThread Repository)
|
|
813
|
+
|
|
814
|
+
If you want full control over the process, you can handle the upload yourself and just call the broadcast. **Important:** media must be uploaded to `rupload.facebook.com/messenger_image/` (not `rupload_igphoto/`) for raven to work.
|
|
815
|
+
|
|
816
|
+
```javascript
|
|
817
|
+
// Step 1: Upload to messenger_image endpoint (see sendRavenPhoto.js for full headers)
|
|
818
|
+
// Step 2: Broadcast
|
|
819
|
+
const result = await ig.directThread.broadcastRaven({
|
|
820
|
+
uploadId: uploadId, // from the messenger_image upload response
|
|
821
|
+
attachmentFbid: mediaId, // media_id from the upload response
|
|
822
|
+
threadId: threadId,
|
|
823
|
+
viewMode: 'once' // 'once' or 'replayable'
|
|
824
|
+
});
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
### Notifications
|
|
830
|
+
|
|
831
|
+
#### Send Screenshot Notification
|
|
832
|
+
|
|
833
|
+
```javascript
|
|
834
|
+
await realtime.directCommands.sendScreenshotNotification({
|
|
835
|
+
threadId: threadId,
|
|
836
|
+
itemId: messageId
|
|
837
|
+
});
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
#### Send Replay Notification
|
|
841
|
+
|
|
842
|
+
```javascript
|
|
843
|
+
await realtime.directCommands.sendReplayNotification({
|
|
844
|
+
threadId: threadId,
|
|
845
|
+
itemId: messageId
|
|
846
|
+
});
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
### Media Upload (HTTP + Broadcast)
|
|
852
|
+
|
|
853
|
+
These methods handle the full flow: upload via HTTP rupload, then broadcast to thread.
|
|
854
|
+
|
|
855
|
+
#### Send Photo
|
|
856
|
+
|
|
857
|
+
```javascript
|
|
858
|
+
const fs = require('fs');
|
|
859
|
+
const photoBuffer = fs.readFileSync('./photo.jpg');
|
|
860
|
+
|
|
861
|
+
await realtime.directCommands.sendPhoto({
|
|
862
|
+
threadId: threadId,
|
|
863
|
+
photoBuffer: photoBuffer,
|
|
864
|
+
caption: 'Check this out',
|
|
865
|
+
mimeType: 'image/jpeg'
|
|
866
|
+
});
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
#### Send Video
|
|
870
|
+
|
|
871
|
+
```javascript
|
|
872
|
+
const fs = require('fs');
|
|
873
|
+
const videoBuffer = fs.readFileSync('./video.mp4');
|
|
874
|
+
|
|
875
|
+
await realtime.directCommands.sendVideo({
|
|
876
|
+
threadId: threadId,
|
|
877
|
+
videoBuffer: videoBuffer,
|
|
878
|
+
caption: 'Watch this',
|
|
879
|
+
duration: 15,
|
|
880
|
+
width: 720,
|
|
881
|
+
height: 1280
|
|
882
|
+
});
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
### Foreground State (Connection Keepalive)
|
|
888
|
+
|
|
889
|
+
```javascript
|
|
890
|
+
await realtime.directCommands.sendForegroundState({
|
|
891
|
+
inForegroundApp: true,
|
|
892
|
+
inForegroundDevice: true,
|
|
893
|
+
keepAliveTimeout: 60
|
|
894
|
+
});
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
---
|
|
898
|
+
|
|
899
|
+
## Complete Method Reference Table
|
|
900
|
+
|
|
901
|
+
| Method | Description |
|
|
902
|
+
|--------|-------------|
|
|
903
|
+
| `sendText({ threadId, text })` | Send text message |
|
|
904
|
+
| `sendTextViaRealtime(threadId, text)` | Send text (alternative) |
|
|
905
|
+
| `sendHashtag({ threadId, hashtag, text })` | Send hashtag |
|
|
906
|
+
| `sendLike({ threadId })` | Send heart/like |
|
|
907
|
+
| `sendLocation({ threadId, locationId, text })` | Send location |
|
|
908
|
+
| `sendMedia({ threadId, mediaId, text })` | Share a post |
|
|
909
|
+
| `sendProfile({ threadId, userId, text })` | Share a profile |
|
|
910
|
+
| `sendUserStory({ threadId, storyId, text })` | Share a story |
|
|
911
|
+
| `sendLink({ threadId, link, text })` | Send a link |
|
|
912
|
+
| `sendAnimatedMedia({ threadId, id, isSticker })` | Send GIF/sticker |
|
|
913
|
+
| `sendVoice({ threadId, uploadId, waveform })` | Send voice message |
|
|
914
|
+
| `sendReaction({ threadId, itemId, emoji })` | React to message |
|
|
915
|
+
| `removeReaction({ threadId, itemId })` | Remove reaction |
|
|
916
|
+
| `replyToMessage(threadId, messageId, text)` | Quote reply |
|
|
917
|
+
| `editMessage(threadId, itemId, newText)` | Edit message |
|
|
918
|
+
| `deleteMessage(threadId, itemId)` | Delete message |
|
|
919
|
+
| `markAsSeen({ threadId, itemId })` | Mark as read |
|
|
920
|
+
| `indicateActivity({ threadId, isActive })` | Typing indicator |
|
|
921
|
+
| `markVisualMessageSeen({ threadId, itemId })` | Mark disappearing media seen |
|
|
922
|
+
| `addMemberToThread(threadId, userId)` | Add group member |
|
|
923
|
+
| `removeMemberFromThread(threadId, userId)` | Remove group member |
|
|
924
|
+
| `leaveThread(threadId)` | Leave group |
|
|
925
|
+
| `updateThreadTitle(threadId, title)` | Change group name |
|
|
926
|
+
| `muteThread(threadId, muteUntil)` | Mute thread |
|
|
927
|
+
| `unmuteThread(threadId)` | Unmute thread |
|
|
928
|
+
| `approveThread(threadId)` | Accept message request |
|
|
929
|
+
| `declineThread(threadId)` | Decline message request |
|
|
930
|
+
| `blockUserInThread(threadId, userId)` | Block user |
|
|
931
|
+
| `reportThread(threadId, reason)` | Report thread |
|
|
932
|
+
| `sendDisappearingPhoto({ threadId, uploadId })` | Send view-once photo (MQTT metadata only) |
|
|
933
|
+
| `sendDisappearingVideo({ threadId, uploadId })` | Send view-once video (MQTT metadata only) |
|
|
934
|
+
| `sendRavenPhoto({ threadId, photoBuffer, viewMode })` | Upload & send disappearing photo (full flow) |
|
|
935
|
+
| `sendRavenPhotoOnce({ threadId, photoBuffer })` | Upload & send view-once photo |
|
|
936
|
+
| `sendRavenPhotoReplayable({ threadId, photoBuffer })` | Upload & send replayable photo |
|
|
937
|
+
| `sendRavenVideo({ threadId, videoBuffer, viewMode })` | Upload & send disappearing video (full flow) |
|
|
938
|
+
| `sendRavenVideoOnce({ threadId, videoBuffer })` | Upload & send view-once video |
|
|
939
|
+
| `sendRavenVideoReplayable({ threadId, videoBuffer })` | Upload & send replayable video |
|
|
940
|
+
| `sendScreenshotNotification({ threadId, itemId })` | Screenshot alert |
|
|
941
|
+
| `sendReplayNotification({ threadId, itemId })` | Replay alert |
|
|
942
|
+
| `sendPhoto({ threadId, photoBuffer, caption })` | Upload & send photo |
|
|
943
|
+
| `sendVideo({ threadId, videoBuffer, caption })` | Upload & send video |
|
|
944
|
+
| `sendForegroundState(state)` | Connection keepalive |
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
## Download Media from Messages
|
|
949
|
+
|
|
950
|
+
This feature provides Baileys-style media download for Instagram DM messages.
|
|
951
|
+
|
|
952
|
+
### Quick Start: Save View-Once Photo
|
|
953
|
+
|
|
954
|
+
```javascript
|
|
955
|
+
const {
|
|
956
|
+
downloadContentFromMessage,
|
|
957
|
+
isViewOnceMedia
|
|
958
|
+
} = require('nodejs-insta-private-api-mqt');
|
|
959
|
+
const fs = require('fs');
|
|
960
|
+
|
|
961
|
+
realtime.on('message', async (data) => {
|
|
962
|
+
const msg = data.message;
|
|
963
|
+
|
|
964
|
+
if (isViewOnceMedia(msg)) {
|
|
965
|
+
const stream = await downloadContentFromMessage(msg);
|
|
966
|
+
|
|
967
|
+
let buffer = Buffer.from([]);
|
|
968
|
+
for await (const chunk of stream) {
|
|
969
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const ext = stream.mediaInfo.type.includes('video') ? 'mp4' : 'jpg';
|
|
973
|
+
fs.writeFileSync(`viewonce_${Date.now()}.${ext}`, buffer);
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### Download Regular Media
|
|
979
|
+
|
|
980
|
+
```javascript
|
|
981
|
+
const { downloadMediaBuffer, hasMedia } = require('nodejs-insta-private-api-mqt');
|
|
982
|
+
|
|
983
|
+
realtime.on('message', async (data) => {
|
|
984
|
+
const msg = data.message;
|
|
985
|
+
|
|
986
|
+
if (hasMedia(msg)) {
|
|
987
|
+
const { buffer, mediaInfo } = await downloadMediaBuffer(msg);
|
|
988
|
+
fs.writeFileSync(`media_${Date.now()}.jpg`, buffer);
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
### Media Functions Reference
|
|
994
|
+
|
|
995
|
+
| Function | Description |
|
|
996
|
+
|----------|-------------|
|
|
997
|
+
| `downloadContentFromMessage(message)` | Download as stream |
|
|
998
|
+
| `downloadMediaBuffer(message)` | Download as Buffer |
|
|
999
|
+
| `extractMediaUrls(message)` | Get CDN URLs |
|
|
1000
|
+
| `hasMedia(message)` | Check if has media |
|
|
1001
|
+
| `getMediaType(message)` | Get media type |
|
|
1002
|
+
| `isViewOnceMedia(message)` | Check if disappearing |
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
## Building Instagram Bots
|
|
1007
|
+
|
|
1008
|
+
### Auto-Reply Bot Example
|
|
1009
|
+
|
|
1010
|
+
```javascript
|
|
1011
|
+
const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqt');
|
|
1012
|
+
const fs = require('fs');
|
|
1013
|
+
|
|
1014
|
+
(async () => {
|
|
1015
|
+
const ig = new IgApiClient();
|
|
1016
|
+
const session = JSON.parse(fs.readFileSync('session.json'));
|
|
1017
|
+
await ig.state.deserialize(session);
|
|
1018
|
+
|
|
1019
|
+
const realtime = new RealtimeClient(ig);
|
|
1020
|
+
const inbox = await ig.direct.getInbox();
|
|
1021
|
+
|
|
1022
|
+
await realtime.connect({
|
|
1023
|
+
graphQlSubs: ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
|
|
1024
|
+
irisData: inbox
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
console.log('Bot Active\n');
|
|
1028
|
+
|
|
1029
|
+
realtime.on('message', async (data) => {
|
|
1030
|
+
const msg = data.message;
|
|
1031
|
+
if (!msg?.text) return;
|
|
1032
|
+
|
|
1033
|
+
console.log(`[${msg.from_user_id}]: ${msg.text}`);
|
|
1034
|
+
|
|
1035
|
+
if (msg.text.toLowerCase().includes('hello')) {
|
|
1036
|
+
await realtime.directCommands.sendText({
|
|
1037
|
+
threadId: msg.thread_id,
|
|
1038
|
+
text: 'Hey! Thanks for reaching out!'
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
await new Promise(() => {});
|
|
1044
|
+
})();
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Smart Bot with Reactions and Typing
|
|
1048
|
+
|
|
1049
|
+
```javascript
|
|
1050
|
+
realtime.on('message', async (data) => {
|
|
1051
|
+
const msg = data.message;
|
|
1052
|
+
if (!msg?.text) return;
|
|
1053
|
+
|
|
1054
|
+
// Mark as seen
|
|
1055
|
+
await realtime.directCommands.markAsSeen({
|
|
1056
|
+
threadId: msg.thread_id,
|
|
1057
|
+
itemId: msg.item_id
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
// Show typing
|
|
1061
|
+
await realtime.directCommands.indicateActivity({
|
|
1062
|
+
threadId: msg.thread_id,
|
|
1063
|
+
isActive: true
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1067
|
+
|
|
1068
|
+
if (msg.text.toLowerCase().includes('hi')) {
|
|
1069
|
+
await realtime.directCommands.sendText({
|
|
1070
|
+
threadId: msg.thread_id,
|
|
1071
|
+
text: 'Hello there!'
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// React with emoji
|
|
1075
|
+
await realtime.directCommands.sendReaction({
|
|
1076
|
+
threadId: msg.thread_id,
|
|
1077
|
+
itemId: msg.item_id,
|
|
1078
|
+
emoji: '👋'
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Stop typing
|
|
1083
|
+
await realtime.directCommands.indicateActivity({
|
|
1084
|
+
threadId: msg.thread_id,
|
|
1085
|
+
isActive: false
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
## Session Management
|
|
1093
|
+
|
|
1094
|
+
### Multi-File Auth State (Baileys Style)
|
|
1095
|
+
|
|
1096
|
+
```javascript
|
|
1097
|
+
const authState = await useMultiFileAuthState('./auth_folder');
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
| Method | Description |
|
|
1101
|
+
|--------|-------------|
|
|
1102
|
+
| `hasSession()` | Check if credentials exist |
|
|
1103
|
+
| `hasMqttSession()` | Check if MQTT session exists |
|
|
1104
|
+
| `loadCreds(ig)` | Load saved credentials |
|
|
1105
|
+
| `saveCreds(ig)` | Save current credentials |
|
|
1106
|
+
| `isSessionValid(ig)` | Validate with Instagram |
|
|
1107
|
+
| `loadMqttSession()` | Get saved MQTT session |
|
|
1108
|
+
| `saveMqttSession(realtime)` | Save MQTT session |
|
|
1109
|
+
| `clearSession()` | Delete all session files |
|
|
1110
|
+
|
|
1111
|
+
---
|
|
1112
|
+
|
|
1113
|
+
## API Reference
|
|
1114
|
+
|
|
1115
|
+
### IgApiClient
|
|
1116
|
+
|
|
1117
|
+
```javascript
|
|
1118
|
+
// Login
|
|
1119
|
+
await ig.login({
|
|
1120
|
+
username: 'your_username',
|
|
1121
|
+
password: 'your_password'
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// Save session
|
|
1125
|
+
const serialized = ig.state.serialize();
|
|
1126
|
+
fs.writeFileSync('session.json', JSON.stringify(serialized));
|
|
1127
|
+
|
|
1128
|
+
// Load session
|
|
1129
|
+
const session = JSON.parse(fs.readFileSync('session.json'));
|
|
1130
|
+
await ig.state.deserialize(session);
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
### Direct Messages (REST)
|
|
1134
|
+
|
|
1135
|
+
```javascript
|
|
1136
|
+
// Get inbox
|
|
1137
|
+
const inbox = await ig.direct.getInbox();
|
|
1138
|
+
|
|
1139
|
+
// Get thread
|
|
1140
|
+
const thread = await ig.direct.getThread(threadId);
|
|
1141
|
+
|
|
1142
|
+
// Send text by username (looks up user, finds/creates thread, sends)
|
|
1143
|
+
await ig.direct.send({ to: 'target_username', message: 'Hello!' });
|
|
1144
|
+
|
|
1145
|
+
// Send text by user ID (no username lookup needed)
|
|
1146
|
+
await ig.direct.sendToUserId('12345678', 'Hello!');
|
|
1147
|
+
|
|
1148
|
+
// Send text to an existing thread (by thread ID)
|
|
1149
|
+
await ig.directThread.sendToGroup({ threadId: '340282366841710300...', message: 'Hello!' });
|
|
1150
|
+
|
|
1151
|
+
// Low-level broadcast (full control over payload)
|
|
1152
|
+
await ig.directThread.broadcast({
|
|
1153
|
+
threadIds: ['340282366841710300...'],
|
|
1154
|
+
item: 'text',
|
|
1155
|
+
form: { text: 'Hello!' },
|
|
1156
|
+
});
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
---
|
|
1160
|
+
|
|
1161
|
+
## REST API — Full Reference (v5.66.0)
|
|
1162
|
+
|
|
1163
|
+
Everything below uses the REST HTTP endpoints, not MQTT. You don't need `RealtimeClient` for any of this — just `IgApiClient` and a valid session.
|
|
1164
|
+
|
|
1165
|
+
```javascript
|
|
1166
|
+
const { IgApiClient } = require('nodejs-insta-private-api-mqt');
|
|
1167
|
+
const ig = new IgApiClient();
|
|
1168
|
+
|
|
1169
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
1170
|
+
await ig.login({ username: 'your_username', password: 'your_password' });
|
|
1171
|
+
// you're ready to use any of the methods below
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
### Authentication & Login
|
|
1177
|
+
|
|
1178
|
+
#### Basic Login
|
|
1179
|
+
|
|
1180
|
+
```javascript
|
|
1181
|
+
await ig.login({ username: 'your_username', password: 'your_password' });
|
|
1182
|
+
|
|
1183
|
+
// or the shorter way
|
|
1184
|
+
await ig.account.login('your_username', 'your_password');
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
#### Two-Factor Authentication (2FA)
|
|
1188
|
+
|
|
1189
|
+
When Instagram asks for a 2FA code, `login()` throws an error that contains the `two_factor_identifier`. Catch it and finish the flow:
|
|
1190
|
+
|
|
1191
|
+
```javascript
|
|
1192
|
+
try {
|
|
1193
|
+
await ig.account.login('username', 'password');
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
if (err.response?.body?.two_factor_required) {
|
|
1196
|
+
const twoFactorId = err.response.body.two_factor_info.two_factor_identifier;
|
|
1197
|
+
|
|
1198
|
+
// user enters the code from their authenticator app or SMS
|
|
1199
|
+
const code = '123456';
|
|
1200
|
+
|
|
1201
|
+
await ig.account.twoFactorLogin('username', code, twoFactorId, '1');
|
|
1202
|
+
// verificationMethod: '1' = SMS, '3' = TOTP app
|
|
1203
|
+
console.log('2FA login successful');
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
#### TOTP Two-Factor Setup
|
|
1209
|
+
|
|
1210
|
+
Set up authenticator-app-based 2FA for your account. This generates a seed you can add to Google Authenticator, Authy, etc.
|
|
1211
|
+
|
|
1212
|
+
```javascript
|
|
1213
|
+
// generate a TOTP seed (this is the secret key for your authenticator app)
|
|
1214
|
+
const seed = await ig.totp.generateSeed();
|
|
1215
|
+
console.log('Add this to your authenticator app:', seed.totp_seed);
|
|
1216
|
+
|
|
1217
|
+
// after adding it, verify with a code from the app to confirm
|
|
1218
|
+
const code = '123456'; // from authenticator app
|
|
1219
|
+
await ig.totp.enable(code);
|
|
1220
|
+
console.log('TOTP 2FA is now enabled');
|
|
1221
|
+
|
|
1222
|
+
// get backup codes in case you lose your authenticator
|
|
1223
|
+
const backupCodes = await ig.totp.getBackupCodes();
|
|
1224
|
+
console.log('Save these somewhere safe:', backupCodes);
|
|
1225
|
+
|
|
1226
|
+
// disable TOTP 2FA
|
|
1227
|
+
await ig.totp.disable();
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
#### SMS-Based 2FA
|
|
1231
|
+
|
|
1232
|
+
```javascript
|
|
1233
|
+
// enable SMS 2FA
|
|
1234
|
+
await ig.totp.smsTwoFactorEnable('+1234567890');
|
|
1235
|
+
|
|
1236
|
+
// confirm with the code you received
|
|
1237
|
+
await ig.totp.smsTwoFactorConfirm('123456');
|
|
1238
|
+
|
|
1239
|
+
// disable it later
|
|
1240
|
+
await ig.totp.disableSmsTwoFactor();
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
#### Challenge Resolver
|
|
1244
|
+
|
|
1245
|
+
When Instagram triggers a security checkpoint (suspicious login, new location, etc.), you need to resolve the challenge:
|
|
1246
|
+
|
|
1247
|
+
```javascript
|
|
1248
|
+
try {
|
|
1249
|
+
await ig.account.login('username', 'password');
|
|
1250
|
+
} catch (err) {
|
|
1251
|
+
if (err.response?.body?.challenge) {
|
|
1252
|
+
const challengeUrl = err.response.body.challenge.api_path;
|
|
1253
|
+
|
|
1254
|
+
// option 1: automatic resolution (tries to handle it for you)
|
|
1255
|
+
const result = await ig.challenge.auto(challengeUrl);
|
|
1256
|
+
console.log('Challenge result:', result);
|
|
1257
|
+
|
|
1258
|
+
// option 2: manual step-by-step
|
|
1259
|
+
// first, see what verification methods are available
|
|
1260
|
+
const page = await ig.challenge.getChallengePage(challengeUrl);
|
|
1261
|
+
|
|
1262
|
+
// choose SMS (0) or email (1)
|
|
1263
|
+
await ig.challenge.selectVerifyMethod(challengeUrl, 1);
|
|
1264
|
+
|
|
1265
|
+
// enter the code you received
|
|
1266
|
+
await ig.challenge.sendSecurityCode(challengeUrl, '123456');
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
---
|
|
1272
|
+
|
|
1273
|
+
### Account Management
|
|
1274
|
+
|
|
1275
|
+
#### Get Current User Info
|
|
1276
|
+
|
|
1277
|
+
```javascript
|
|
1278
|
+
const me = await ig.account.currentUser();
|
|
1279
|
+
console.log('Username:', me.user.username);
|
|
1280
|
+
console.log('Follower count:', me.user.follower_count);
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
#### Edit Profile
|
|
1284
|
+
|
|
1285
|
+
```javascript
|
|
1286
|
+
await ig.account.editProfile({
|
|
1287
|
+
fullName: 'John Doe',
|
|
1288
|
+
biography: 'building cool stuff',
|
|
1289
|
+
externalUrl: 'https://example.com',
|
|
1290
|
+
email: 'john@example.com',
|
|
1291
|
+
phoneNumber: '+1234567890',
|
|
1292
|
+
username: 'johndoe_new'
|
|
1293
|
+
});
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
#### Set Biography
|
|
1297
|
+
|
|
1298
|
+
```javascript
|
|
1299
|
+
await ig.account.setBiography('i like building things that work');
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
#### Set External URL / Remove Bio Links
|
|
1303
|
+
|
|
1304
|
+
```javascript
|
|
1305
|
+
await ig.account.setExternalUrl('https://mywebsite.com');
|
|
1306
|
+
await ig.account.removeBioLinks();
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
#### Change Password
|
|
1310
|
+
|
|
1311
|
+
```javascript
|
|
1312
|
+
await ig.account.changePassword('old_password_here', 'new_password_here');
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
#### Switch to Private / Public
|
|
1316
|
+
|
|
1317
|
+
```javascript
|
|
1318
|
+
await ig.account.setPrivate();
|
|
1319
|
+
await ig.account.setPublic();
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
#### Set Gender
|
|
1323
|
+
|
|
1324
|
+
```javascript
|
|
1325
|
+
// 1 = male, 2 = female, 3 = prefer not to say, 4 = custom
|
|
1326
|
+
await ig.account.setGender(1);
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
#### Profile Picture
|
|
1330
|
+
|
|
1331
|
+
```javascript
|
|
1332
|
+
// you need an upload_id from a previous photo upload
|
|
1333
|
+
await ig.account.profilePictureChange(uploadId);
|
|
1334
|
+
await ig.account.profilePictureRemove();
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
#### Password Encryption Keys
|
|
1338
|
+
|
|
1339
|
+
```javascript
|
|
1340
|
+
// get the public keys for Instagram's password encryption (needed for some flows)
|
|
1341
|
+
const keys = await ig.account.passwordPublicKeys();
|
|
1342
|
+
```
|
|
1343
|
+
|
|
1344
|
+
#### Account Recovery
|
|
1345
|
+
|
|
1346
|
+
```javascript
|
|
1347
|
+
// send password recovery via email
|
|
1348
|
+
await ig.account.sendRecoveryFlowEmail('user@example.com');
|
|
1349
|
+
|
|
1350
|
+
// or via SMS
|
|
1351
|
+
await ig.account.sendRecoveryFlowSms('+1234567890');
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1356
|
+
### User Operations
|
|
1357
|
+
|
|
1358
|
+
#### Fetch User Info
|
|
1359
|
+
|
|
1360
|
+
```javascript
|
|
1361
|
+
// by username
|
|
1362
|
+
const user = await ig.user.infoByUsername('instagram');
|
|
1363
|
+
console.log('User ID:', user.pk);
|
|
1364
|
+
console.log('Followers:', user.follower_count);
|
|
1365
|
+
|
|
1366
|
+
// by user ID
|
|
1367
|
+
const userById = await ig.user.info('25025320');
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
#### Resolve Username ↔ User ID
|
|
1371
|
+
|
|
1372
|
+
```javascript
|
|
1373
|
+
const userId = await ig.user.userIdFromUsername('instagram');
|
|
1374
|
+
// returns '25025320'
|
|
1375
|
+
|
|
1376
|
+
const username = await ig.user.usernameFromUserId('25025320');
|
|
1377
|
+
// returns 'instagram'
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
#### Search Users
|
|
1381
|
+
|
|
1382
|
+
```javascript
|
|
1383
|
+
const results = await ig.user.search('john', 20);
|
|
1384
|
+
results.users.forEach(u => {
|
|
1385
|
+
console.log(u.username, '-', u.full_name);
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
// exact match
|
|
1389
|
+
const exact = await ig.user.searchExact('johndoe');
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
#### Follow / Unfollow
|
|
1393
|
+
|
|
1394
|
+
```javascript
|
|
1395
|
+
await ig.user.follow('25025320');
|
|
1396
|
+
await ig.user.unfollow('25025320');
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
#### Block / Unblock
|
|
1400
|
+
|
|
1401
|
+
```javascript
|
|
1402
|
+
await ig.user.block('25025320');
|
|
1403
|
+
await ig.user.unblock('25025320');
|
|
1404
|
+
|
|
1405
|
+
// see all blocked users
|
|
1406
|
+
const blocked = await ig.user.getBlockedUsers();
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
#### Mute / Unmute
|
|
1410
|
+
|
|
1411
|
+
```javascript
|
|
1412
|
+
// mute posts, stories, or both
|
|
1413
|
+
await ig.user.mute('25025320', { mutePosts: true, muteStories: true });
|
|
1414
|
+
await ig.user.unmute('25025320', { unmutePosts: true, unmuteStories: true });
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
#### Get Followers / Following (with pagination)
|
|
1418
|
+
|
|
1419
|
+
```javascript
|
|
1420
|
+
// get up to 200 followers at a time
|
|
1421
|
+
const followers = await ig.user.getFollowers('25025320', 200);
|
|
1422
|
+
console.log('Got', followers.users.length, 'followers');
|
|
1423
|
+
|
|
1424
|
+
// pagination — pass the maxId from the previous response
|
|
1425
|
+
const moreFollowers = await ig.user.getFollowers('25025320', 200, followers.next_max_id);
|
|
1426
|
+
|
|
1427
|
+
// same for following
|
|
1428
|
+
const following = await ig.user.getFollowing('25025320', 200);
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
#### Get User's Posts (with pagination)
|
|
1432
|
+
|
|
1433
|
+
```javascript
|
|
1434
|
+
// grab the latest 50 posts
|
|
1435
|
+
const posts = await ig.user.getUserMedias('25025320', 50);
|
|
1436
|
+
posts.items.forEach(item => {
|
|
1437
|
+
console.log(item.pk, '-', item.caption?.text?.substring(0, 50));
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
// next page
|
|
1441
|
+
const morePosts = await ig.user.getUserMedias('25025320', 50, posts.next_max_id);
|
|
1442
|
+
```
|
|
1443
|
+
|
|
1444
|
+
#### Get User's Reels / Clips
|
|
1445
|
+
|
|
1446
|
+
```javascript
|
|
1447
|
+
const reels = await ig.user.getUserReels('25025320', 50);
|
|
1448
|
+
const clips = await ig.user.getUserClips('25025320', 50);
|
|
1449
|
+
```
|
|
1450
|
+
|
|
1451
|
+
#### Get User's Stories
|
|
1452
|
+
|
|
1453
|
+
```javascript
|
|
1454
|
+
const stories = await ig.user.getUserStories('25025320');
|
|
1455
|
+
stories.reel?.items.forEach(story => {
|
|
1456
|
+
console.log('Story:', story.pk, 'taken at:', story.taken_at);
|
|
1457
|
+
});
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
#### Get Tagged Posts
|
|
1461
|
+
|
|
1462
|
+
```javascript
|
|
1463
|
+
const tagged = await ig.user.getUserTags('25025320');
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
#### Mutual Followers
|
|
1467
|
+
|
|
1468
|
+
```javascript
|
|
1469
|
+
const mutual = await ig.user.getMutualFollowers('25025320');
|
|
1470
|
+
```
|
|
1471
|
+
|
|
1472
|
+
#### Remove a Follower
|
|
1473
|
+
|
|
1474
|
+
```javascript
|
|
1475
|
+
await ig.user.removeFollower('25025320');
|
|
1476
|
+
```
|
|
1477
|
+
|
|
1478
|
+
#### Report a User
|
|
1479
|
+
|
|
1480
|
+
```javascript
|
|
1481
|
+
// reason: 1 = spam, 2 = inappropriate, etc.
|
|
1482
|
+
await ig.user.report('25025320', 1);
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
#### Get Suggested Users
|
|
1486
|
+
|
|
1487
|
+
```javascript
|
|
1488
|
+
const suggestions = await ig.user.getSuggested();
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
#### Friendship Status (Bulk)
|
|
1492
|
+
|
|
1493
|
+
```javascript
|
|
1494
|
+
// check relationship status with multiple users at once
|
|
1495
|
+
const statuses = await ig.user.getFriendshipStatuses(['12345', '67890', '11111']);
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
### Media Operations
|
|
1501
|
+
|
|
1502
|
+
#### Get Media Info
|
|
1503
|
+
|
|
1504
|
+
```javascript
|
|
1505
|
+
const info = await ig.media.info('3193593212003331660');
|
|
1506
|
+
console.log('Type:', info.items[0].media_type);
|
|
1507
|
+
console.log('Likes:', info.items[0].like_count);
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
#### PK / Shortcode Conversion
|
|
1511
|
+
|
|
1512
|
+
These are super useful when you have a post URL and need the numeric ID, or the other way around.
|
|
1513
|
+
|
|
1514
|
+
```javascript
|
|
1515
|
+
const { MediaRepository } = require('nodejs-insta-private-api-mqt');
|
|
1516
|
+
|
|
1517
|
+
// convert shortcode to numeric PK
|
|
1518
|
+
const pk = MediaRepository.mediaPkFromCode('CxR7Bsejq5M');
|
|
1519
|
+
// '3193593212003331660'
|
|
1520
|
+
|
|
1521
|
+
// convert PK back to shortcode
|
|
1522
|
+
const code = MediaRepository.mediaCodeFromPk('3193593212003331660');
|
|
1523
|
+
// 'CxR7Bsejq5M'
|
|
1524
|
+
|
|
1525
|
+
// extract PK directly from a full URL
|
|
1526
|
+
const pkFromUrl = MediaRepository.mediaPkFromUrl('https://www.instagram.com/p/CxR7Bsejq5M/');
|
|
1527
|
+
// '3193593212003331660'
|
|
1528
|
+
```
|
|
1529
|
+
|
|
1530
|
+
#### Like / Unlike
|
|
1531
|
+
|
|
1532
|
+
```javascript
|
|
1533
|
+
await ig.media.like('3193593212003331660');
|
|
1534
|
+
await ig.media.unlike('3193593212003331660');
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1537
|
+
#### Comment
|
|
1538
|
+
|
|
1539
|
+
```javascript
|
|
1540
|
+
const comment = await ig.media.comment('3193593212003331660', 'great shot!');
|
|
1541
|
+
console.log('Comment ID:', comment.comment.pk);
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
#### Reply to a Comment
|
|
1545
|
+
|
|
1546
|
+
```javascript
|
|
1547
|
+
await ig.media.replyToComment('3193593212003331660', '17858893269000001', '@user thanks!');
|
|
1548
|
+
```
|
|
1549
|
+
|
|
1550
|
+
#### Like / Unlike Comments
|
|
1551
|
+
|
|
1552
|
+
```javascript
|
|
1553
|
+
await ig.media.likeComment('3193593212003331660', '17858893269000001');
|
|
1554
|
+
await ig.media.unlikeComment('3193593212003331660', '17858893269000001');
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
#### Pin / Unpin Comments
|
|
1558
|
+
|
|
1559
|
+
```javascript
|
|
1560
|
+
await ig.media.pinComment('3193593212003331660', '17858893269000001');
|
|
1561
|
+
await ig.media.unpinComment('3193593212003331660', '17858893269000001');
|
|
1562
|
+
```
|
|
1563
|
+
|
|
1564
|
+
#### Delete Comments (Single or Bulk)
|
|
1565
|
+
|
|
1566
|
+
```javascript
|
|
1567
|
+
// single
|
|
1568
|
+
await ig.media.deleteComment('3193593212003331660', '17858893269000001');
|
|
1569
|
+
|
|
1570
|
+
// bulk delete
|
|
1571
|
+
await ig.media.bulkDeleteComments('3193593212003331660', [
|
|
1572
|
+
'17858893269000001',
|
|
1573
|
+
'17858893269000002',
|
|
1574
|
+
'17858893269000003'
|
|
1575
|
+
]);
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
#### Get Comments (Paginated)
|
|
1579
|
+
|
|
1580
|
+
```javascript
|
|
1581
|
+
const comments = await ig.media.comments('3193593212003331660', null, 20);
|
|
1582
|
+
// next page:
|
|
1583
|
+
const moreComments = await ig.media.comments('3193593212003331660', comments.next_min_id, 20);
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
#### Get Comment Thread (Replies to a Comment)
|
|
1587
|
+
|
|
1588
|
+
```javascript
|
|
1589
|
+
const thread = await ig.media.commentThreadComments('3193593212003331660', '17858893269000001');
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
#### Get Likers
|
|
1593
|
+
|
|
1594
|
+
```javascript
|
|
1595
|
+
const likers = await ig.media.likers('3193593212003331660');
|
|
1596
|
+
likers.users.forEach(u => console.log(u.username));
|
|
1597
|
+
```
|
|
1598
|
+
|
|
1599
|
+
#### Save / Unsave
|
|
1600
|
+
|
|
1601
|
+
```javascript
|
|
1602
|
+
await ig.media.save('3193593212003331660');
|
|
1603
|
+
|
|
1604
|
+
// save to a specific collection
|
|
1605
|
+
await ig.media.save('3193593212003331660', 'collection_id_here');
|
|
1606
|
+
|
|
1607
|
+
await ig.media.unsave('3193593212003331660');
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
#### Archive / Unarchive
|
|
1611
|
+
|
|
1612
|
+
```javascript
|
|
1613
|
+
await ig.media.archive('3193593212003331660');
|
|
1614
|
+
await ig.media.unarchive('3193593212003331660');
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
#### Delete Media
|
|
1618
|
+
|
|
1619
|
+
```javascript
|
|
1620
|
+
// mediaType: 'PHOTO', 'VIDEO', 'CAROUSEL'
|
|
1621
|
+
await ig.media.delete('3193593212003331660', 'PHOTO');
|
|
1622
|
+
```
|
|
1623
|
+
|
|
1624
|
+
#### Edit Caption
|
|
1625
|
+
|
|
1626
|
+
```javascript
|
|
1627
|
+
await ig.media.edit('3193593212003331660', 'new caption goes here', {
|
|
1628
|
+
usertags: { in: [{ user_id: '12345', position: [0.5, 0.5] }] }
|
|
1629
|
+
});
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
#### Enable / Disable Comments
|
|
1633
|
+
|
|
1634
|
+
```javascript
|
|
1635
|
+
await ig.media.disableComments('3193593212003331660');
|
|
1636
|
+
await ig.media.enableComments('3193593212003331660');
|
|
1637
|
+
```
|
|
1638
|
+
|
|
1639
|
+
#### Download Media
|
|
1640
|
+
|
|
1641
|
+
```javascript
|
|
1642
|
+
// download by URL
|
|
1643
|
+
const buffer = await ig.media.downloadByUrl('https://instagram.cdnurl.com/...');
|
|
1644
|
+
|
|
1645
|
+
// download by PK (photo or video)
|
|
1646
|
+
const photo = await ig.media.downloadPhoto('3193593212003331660');
|
|
1647
|
+
const video = await ig.media.downloadVideo('3193593212003331660');
|
|
1648
|
+
```
|
|
1649
|
+
|
|
1650
|
+
#### oEmbed
|
|
1651
|
+
|
|
1652
|
+
```javascript
|
|
1653
|
+
const oembed = await ig.media.oembed('https://www.instagram.com/p/CxR7Bsejq5M/');
|
|
1654
|
+
console.log(oembed.title, '-', oembed.author_name);
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
#### Get User Who Posted a Media
|
|
1658
|
+
|
|
1659
|
+
```javascript
|
|
1660
|
+
const user = await ig.media.getUser('3193593212003331660');
|
|
1661
|
+
```
|
|
1662
|
+
|
|
1663
|
+
---
|
|
1664
|
+
|
|
1665
|
+
### Reels / Clips
|
|
1666
|
+
|
|
1667
|
+
Upload and browse Reels through the REST API.
|
|
1668
|
+
|
|
1669
|
+
#### Upload a Reel
|
|
1670
|
+
|
|
1671
|
+
```javascript
|
|
1672
|
+
const result = await ig.clip.upload({
|
|
1673
|
+
videoBuffer: fs.readFileSync('./reel.mp4'),
|
|
1674
|
+
caption: 'check this out',
|
|
1675
|
+
coverImage: fs.readFileSync('./cover.jpg'), // optional
|
|
1676
|
+
duration: 15,
|
|
1677
|
+
width: 1080,
|
|
1678
|
+
height: 1920,
|
|
1679
|
+
audisMuted: false,
|
|
1680
|
+
});
|
|
1681
|
+
console.log('Reel PK:', result.media?.pk);
|
|
1682
|
+
```
|
|
1683
|
+
|
|
1684
|
+
#### Configure a Video as Reel (after uploading separately)
|
|
1685
|
+
|
|
1686
|
+
```javascript
|
|
1687
|
+
const configured = await ig.clip.configure({
|
|
1688
|
+
upload_id: uploadId,
|
|
1689
|
+
caption: 'my reel',
|
|
1690
|
+
duration: 15,
|
|
1691
|
+
width: 1080,
|
|
1692
|
+
height: 1920,
|
|
1693
|
+
});
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
#### Discover Reels / Connected Reels
|
|
1697
|
+
|
|
1698
|
+
```javascript
|
|
1699
|
+
// the explore-style reels feed
|
|
1700
|
+
const discover = await ig.clip.discoverReels(10);
|
|
1701
|
+
discover.items.forEach(reel => {
|
|
1702
|
+
console.log(reel.media.code, '-', reel.media.caption?.text?.substring(0, 40));
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
// paginate
|
|
1706
|
+
const more = await ig.clip.discoverReels(10, discover.paging_info?.max_id);
|
|
1707
|
+
|
|
1708
|
+
// connected reels (similar reels after watching one)
|
|
1709
|
+
const connected = await ig.clip.connectedReels(10);
|
|
1710
|
+
```
|
|
1711
|
+
|
|
1712
|
+
#### Download a Reel
|
|
1713
|
+
|
|
1714
|
+
```javascript
|
|
1715
|
+
const reelBuffer = await ig.clip.download('3193593212003331660');
|
|
1716
|
+
|
|
1717
|
+
// or from URL
|
|
1718
|
+
const reelFromUrl = await ig.clip.downloadByUrl('https://instagram.cdnurl.com/...');
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
#### Get Music Info for Reels
|
|
1722
|
+
|
|
1723
|
+
```javascript
|
|
1724
|
+
const music = await ig.clip.musicInfo({ music_canonical_id: '12345' });
|
|
1725
|
+
```
|
|
1726
|
+
|
|
1727
|
+
---
|
|
1728
|
+
|
|
1729
|
+
### Stories
|
|
1730
|
+
|
|
1731
|
+
#### Get Your Story Feed (Tray)
|
|
1732
|
+
|
|
1733
|
+
```javascript
|
|
1734
|
+
const tray = await ig.story.getFeed();
|
|
1735
|
+
tray.tray.forEach(reel => {
|
|
1736
|
+
console.log(reel.user.username, '- stories:', reel.media_count);
|
|
1737
|
+
});
|
|
1738
|
+
```
|
|
1739
|
+
|
|
1740
|
+
#### Get Someone's Stories
|
|
1741
|
+
|
|
1742
|
+
```javascript
|
|
1743
|
+
const stories = await ig.story.getUserStories('25025320');
|
|
1744
|
+
stories.reel?.items.forEach(item => {
|
|
1745
|
+
console.log('Type:', item.media_type, 'Taken at:', item.taken_at);
|
|
1746
|
+
});
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
#### Upload a Photo Story
|
|
1750
|
+
|
|
1751
|
+
```javascript
|
|
1752
|
+
const result = await ig.story.upload({
|
|
1753
|
+
file: fs.readFileSync('./story.jpg'),
|
|
1754
|
+
caption: 'hello world', // optional
|
|
1755
|
+
});
|
|
1756
|
+
console.log('Story ID:', result.media?.pk);
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
#### Upload a Video Story
|
|
1760
|
+
|
|
1761
|
+
```javascript
|
|
1762
|
+
const result = await ig.story.uploadVideo({
|
|
1763
|
+
file: fs.readFileSync('./story.mp4'),
|
|
1764
|
+
duration: 10,
|
|
1765
|
+
width: 1080,
|
|
1766
|
+
height: 1920,
|
|
1767
|
+
});
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
#### Mark Stories as Seen
|
|
1771
|
+
|
|
1772
|
+
```javascript
|
|
1773
|
+
await ig.story.seen([
|
|
1774
|
+
{ id: 'media_id_1', taken_at: 1700000000, user: { pk: '25025320' } }
|
|
1775
|
+
]);
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
#### React to a Story
|
|
1779
|
+
|
|
1780
|
+
```javascript
|
|
1781
|
+
await ig.story.react({
|
|
1782
|
+
mediaId: '3193593212003331660',
|
|
1783
|
+
reelId: '25025320',
|
|
1784
|
+
emoji: '🔥'
|
|
1785
|
+
});
|
|
1786
|
+
```
|
|
1787
|
+
|
|
1788
|
+
---
|
|
1789
|
+
|
|
1790
|
+
### Highlights
|
|
1791
|
+
|
|
1792
|
+
#### Get User's Highlights
|
|
1793
|
+
|
|
1794
|
+
```javascript
|
|
1795
|
+
const highlights = await ig.highlights.getHighlightsTray('25025320');
|
|
1796
|
+
highlights.tray.forEach(h => {
|
|
1797
|
+
console.log(h.id, '-', h.title);
|
|
1798
|
+
});
|
|
1799
|
+
```
|
|
1800
|
+
|
|
1801
|
+
#### Get a Specific Highlight
|
|
1802
|
+
|
|
1803
|
+
```javascript
|
|
1804
|
+
const highlight = await ig.highlights.getHighlight('highlight:12345678');
|
|
1805
|
+
```
|
|
1806
|
+
|
|
1807
|
+
#### Create a Highlight
|
|
1808
|
+
|
|
1809
|
+
```javascript
|
|
1810
|
+
await ig.highlights.create('My Trip', ['story_id_1', 'story_id_2'], 'cover_media_id');
|
|
1811
|
+
```
|
|
1812
|
+
|
|
1813
|
+
#### Edit a Highlight
|
|
1814
|
+
|
|
1815
|
+
```javascript
|
|
1816
|
+
await ig.highlights.edit('highlight_id', 'Updated Title', ['new_story_id']);
|
|
1817
|
+
```
|
|
1818
|
+
|
|
1819
|
+
#### Add / Remove Stories from Highlight
|
|
1820
|
+
|
|
1821
|
+
```javascript
|
|
1822
|
+
await ig.highlights.addStories('highlight_id', ['story_id_3', 'story_id_4']);
|
|
1823
|
+
await ig.highlights.removeStories('highlight_id', ['story_id_1']);
|
|
1824
|
+
```
|
|
1825
|
+
|
|
1826
|
+
#### Update Highlight Cover
|
|
1827
|
+
|
|
1828
|
+
```javascript
|
|
1829
|
+
await ig.highlights.updateCover('highlight_id', 'cover_media_id');
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
#### Delete a Highlight
|
|
1833
|
+
|
|
1834
|
+
```javascript
|
|
1835
|
+
await ig.highlights.delete('highlight_id');
|
|
1836
|
+
```
|
|
1837
|
+
|
|
1838
|
+
---
|
|
1839
|
+
|
|
1840
|
+
### Upload & Configure Media
|
|
1841
|
+
|
|
1842
|
+
#### Upload a Photo Post
|
|
1843
|
+
|
|
1844
|
+
```javascript
|
|
1845
|
+
const upload = await ig.upload.photo({
|
|
1846
|
+
file: fs.readFileSync('./photo.jpg'),
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
const post = await ig.upload.configurePhoto({
|
|
1850
|
+
upload_id: upload.upload_id,
|
|
1851
|
+
caption: 'sunset vibes',
|
|
1852
|
+
usertags: {
|
|
1853
|
+
in: [{ user_id: '12345', position: [0.5, 0.5] }]
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
console.log('Posted! PK:', post.media?.pk);
|
|
1857
|
+
```
|
|
1858
|
+
|
|
1859
|
+
#### Upload a Video Post
|
|
1860
|
+
|
|
1861
|
+
```javascript
|
|
1862
|
+
const upload = await ig.upload.video({
|
|
1863
|
+
file: fs.readFileSync('./video.mp4'),
|
|
1864
|
+
duration: 30,
|
|
1865
|
+
width: 1080,
|
|
1866
|
+
height: 1920,
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
const post = await ig.upload.configureVideo({
|
|
1870
|
+
upload_id: upload.upload_id,
|
|
1871
|
+
caption: 'check this clip',
|
|
1872
|
+
duration: 30,
|
|
1873
|
+
width: 1080,
|
|
1874
|
+
height: 1920,
|
|
1875
|
+
});
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
#### Configure as Reel (Clips)
|
|
1879
|
+
|
|
1880
|
+
```javascript
|
|
1881
|
+
const reel = await ig.upload.configureToClips({
|
|
1882
|
+
upload_id: upload.upload_id,
|
|
1883
|
+
caption: 'my first reel',
|
|
1884
|
+
duration: 15,
|
|
1885
|
+
width: 1080,
|
|
1886
|
+
height: 1920,
|
|
1887
|
+
});
|
|
1888
|
+
```
|
|
1889
|
+
|
|
1890
|
+
#### Configure as Story
|
|
1891
|
+
|
|
1892
|
+
```javascript
|
|
1893
|
+
const story = await ig.upload.configureToStory({
|
|
1894
|
+
upload_id: upload.upload_id,
|
|
1895
|
+
});
|
|
1896
|
+
```
|
|
1897
|
+
|
|
1898
|
+
#### Upload a Carousel (Multiple Photos/Videos)
|
|
1899
|
+
|
|
1900
|
+
```javascript
|
|
1901
|
+
const carousel = await ig.feed.uploadCarousel({
|
|
1902
|
+
caption: 'summer trip highlights',
|
|
1903
|
+
children: [
|
|
1904
|
+
{ type: 'photo', file: fs.readFileSync('./pic1.jpg') },
|
|
1905
|
+
{ type: 'photo', file: fs.readFileSync('./pic2.jpg') },
|
|
1906
|
+
{ type: 'video', file: fs.readFileSync('./vid1.mp4'), duration: 10, width: 1080, height: 1080 },
|
|
1907
|
+
]
|
|
1908
|
+
});
|
|
1909
|
+
```
|
|
1910
|
+
|
|
1911
|
+
---
|
|
1912
|
+
|
|
1913
|
+
### Feed
|
|
1914
|
+
|
|
1915
|
+
#### Home Timeline
|
|
1916
|
+
|
|
1917
|
+
```javascript
|
|
1918
|
+
const feed = await ig.timeline.getFeed();
|
|
1919
|
+
feed.feed_items?.forEach(item => {
|
|
1920
|
+
const media = item.media_or_ad;
|
|
1921
|
+
if (media) console.log(media.user.username, '-', media.caption?.text?.substring(0, 40));
|
|
1922
|
+
});
|
|
1923
|
+
```
|
|
1924
|
+
|
|
1925
|
+
#### Hashtag Feed
|
|
1926
|
+
|
|
1927
|
+
```javascript
|
|
1928
|
+
const tagFeed = await ig.feed.getTag('photography');
|
|
1929
|
+
```
|
|
1930
|
+
|
|
1931
|
+
#### Location Feed
|
|
1932
|
+
|
|
1933
|
+
```javascript
|
|
1934
|
+
const locFeed = await ig.feed.getLocation('213385402');
|
|
1935
|
+
```
|
|
1936
|
+
|
|
1937
|
+
#### Liked Posts
|
|
1938
|
+
|
|
1939
|
+
```javascript
|
|
1940
|
+
const liked = await ig.feed.getLiked();
|
|
1941
|
+
```
|
|
1942
|
+
|
|
1943
|
+
#### Saved Posts
|
|
1944
|
+
|
|
1945
|
+
```javascript
|
|
1946
|
+
const saved = await ig.feed.getSaved();
|
|
1947
|
+
```
|
|
1948
|
+
|
|
1949
|
+
#### Reels Tray (Stories of People You Follow)
|
|
1950
|
+
|
|
1951
|
+
```javascript
|
|
1952
|
+
const tray = await ig.feed.reelsTray();
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1955
|
+
#### Explore Feed
|
|
1956
|
+
|
|
1957
|
+
```javascript
|
|
1958
|
+
const explore = await ig.feed.getExploreFeed();
|
|
1959
|
+
```
|
|
1960
|
+
|
|
1961
|
+
#### Reels Feed
|
|
1962
|
+
|
|
1963
|
+
```javascript
|
|
1964
|
+
const reels = await ig.feed.getReelsFeed();
|
|
1965
|
+
const userReels = await ig.feed.getUserReelsFeed('25025320');
|
|
1966
|
+
```
|
|
1967
|
+
|
|
1968
|
+
#### Reels Media (Bulk)
|
|
1969
|
+
|
|
1970
|
+
```javascript
|
|
1971
|
+
// get stories for multiple users at once
|
|
1972
|
+
const reelsMedia = await ig.feed.reelsMedia(['25025320', '12345678']);
|
|
1973
|
+
```
|
|
1974
|
+
|
|
1975
|
+
---
|
|
1976
|
+
|
|
1977
|
+
### Timeline (Reels)
|
|
1978
|
+
|
|
1979
|
+
```javascript
|
|
1980
|
+
// get reels from your timeline
|
|
1981
|
+
const reels = await ig.timeline.reels(10);
|
|
1982
|
+
|
|
1983
|
+
// explore reels
|
|
1984
|
+
const exploreReels = await ig.timeline.exploreReels(10);
|
|
1985
|
+
```
|
|
1986
|
+
|
|
1987
|
+
---
|
|
1988
|
+
|
|
1989
|
+
### Direct Messages (REST API)
|
|
1990
|
+
|
|
1991
|
+
The REST-based DM methods. These work without MQTT — they're regular HTTP requests.
|
|
1992
|
+
|
|
1993
|
+
#### Get Inbox
|
|
1994
|
+
|
|
1995
|
+
```javascript
|
|
1996
|
+
const inbox = await ig.direct.getInbox();
|
|
1997
|
+
inbox.inbox.threads.forEach(t => {
|
|
1998
|
+
console.log(t.thread_title || t.users[0]?.username, '- last:', t.last_permanent_item?.text);
|
|
1999
|
+
});
|
|
2000
|
+
|
|
2001
|
+
// paginate
|
|
2002
|
+
const page2 = await ig.direct.getInbox(inbox.inbox.oldest_cursor, 20);
|
|
2003
|
+
```
|
|
2004
|
+
|
|
2005
|
+
#### Pending Inbox (Message Requests)
|
|
2006
|
+
|
|
2007
|
+
```javascript
|
|
2008
|
+
const pending = await ig.direct.getPendingInbox();
|
|
2009
|
+
```
|
|
2010
|
+
|
|
2011
|
+
#### Get a Thread
|
|
2012
|
+
|
|
2013
|
+
```javascript
|
|
2014
|
+
const thread = await ig.direct.getThread(threadId);
|
|
2015
|
+
thread.thread.items.forEach(msg => {
|
|
2016
|
+
console.log(msg.user_id, ':', msg.text || `[${msg.item_type}]`);
|
|
2017
|
+
});
|
|
2018
|
+
```
|
|
2019
|
+
|
|
2020
|
+
#### Send Text via REST
|
|
2021
|
+
|
|
2022
|
+
There are multiple ways to send a text message via the REST API:
|
|
2023
|
+
|
|
2024
|
+
```javascript
|
|
2025
|
+
// Method 1: By username (auto-resolves user ID and thread)
|
|
2026
|
+
await ig.direct.send({ to: 'target_username', message: 'hey there!' });
|
|
2027
|
+
|
|
2028
|
+
// Method 2: By user ID (skips username lookup)
|
|
2029
|
+
await ig.direct.sendToUserId('25025320', 'hey there!');
|
|
2030
|
+
|
|
2031
|
+
// Method 3: By thread ID (for existing conversations / groups)
|
|
2032
|
+
await ig.directThread.sendToGroup({ threadId: threadId, message: 'hey there!' });
|
|
2033
|
+
|
|
2034
|
+
// Method 4: Low-level broadcast (full control)
|
|
2035
|
+
// Use threadIds to send to existing threads:
|
|
2036
|
+
await ig.directThread.broadcast({
|
|
2037
|
+
threadIds: [threadId],
|
|
2038
|
+
item: 'text',
|
|
2039
|
+
form: { text: 'hey there!' },
|
|
2040
|
+
});
|
|
2041
|
+
|
|
2042
|
+
// Or use userIds to send to users directly (creates thread if needed):
|
|
2043
|
+
await ig.directThread.broadcast({
|
|
2044
|
+
userIds: ['25025320'],
|
|
2045
|
+
item: 'text',
|
|
2046
|
+
form: { text: 'hey there!' },
|
|
2047
|
+
});
|
|
2048
|
+
```
|
|
2049
|
+
|
|
2050
|
+
#### Send Photo / Video / Link via REST
|
|
2051
|
+
|
|
2052
|
+
All convenience methods use `to` (Instagram username) to identify the recipient:
|
|
2053
|
+
|
|
2054
|
+
```javascript
|
|
2055
|
+
await ig.direct.sendImage({
|
|
2056
|
+
to: 'target_username',
|
|
2057
|
+
imagePath: './photo.jpg',
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
await ig.direct.sendVideo({
|
|
2061
|
+
to: 'target_username',
|
|
2062
|
+
videoPath: './video.mp4',
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
await ig.direct.sendLink({
|
|
2066
|
+
to: 'target_username',
|
|
2067
|
+
text: 'check this out',
|
|
2068
|
+
urls: ['https://example.com'],
|
|
2069
|
+
});
|
|
2070
|
+
```
|
|
2071
|
+
|
|
2072
|
+
#### Share Media / Profile / Hashtag / Location
|
|
2073
|
+
|
|
2074
|
+
```javascript
|
|
2075
|
+
await ig.direct.sendMediaShare({ to: 'target_username', mediaId: '3193593212003331660' });
|
|
2076
|
+
await ig.direct.sendProfile({ to: 'target_username', profileUserId: '25025320' });
|
|
2077
|
+
await ig.direct.sendHashtag({ to: 'target_username', hashtag: 'photography' });
|
|
2078
|
+
await ig.direct.sendLocation({ to: 'target_username', locationId: '213385402' });
|
|
2079
|
+
```
|
|
2080
|
+
|
|
2081
|
+
#### Create a Group Thread
|
|
2082
|
+
|
|
2083
|
+
```javascript
|
|
2084
|
+
const group = await ig.direct.createGroupThread(['user_id_1', 'user_id_2'], 'Project Team');
|
|
2085
|
+
console.log('Thread ID:', group.thread_id);
|
|
2086
|
+
```
|
|
2087
|
+
|
|
2088
|
+
#### Ranked Recipients (Who to Message)
|
|
2089
|
+
|
|
2090
|
+
```javascript
|
|
2091
|
+
const recipients = await ig.direct.rankedRecipients('raven', 'john');
|
|
2092
|
+
```
|
|
2093
|
+
|
|
2094
|
+
#### Get Presence
|
|
2095
|
+
|
|
2096
|
+
```javascript
|
|
2097
|
+
const presence = await ig.direct.getPresence();
|
|
2098
|
+
```
|
|
2099
|
+
|
|
2100
|
+
#### Mark as Seen / Hide Thread
|
|
2101
|
+
|
|
2102
|
+
```javascript
|
|
2103
|
+
await ig.direct.markAsSeen(threadId, itemId);
|
|
2104
|
+
await ig.direct.hideThread(threadId);
|
|
2105
|
+
```
|
|
2106
|
+
|
|
2107
|
+
#### Send Raven (View-Once) Photo / Video via REST
|
|
2108
|
+
|
|
2109
|
+
Send disappearing photos and videos in DMs. These use the REST endpoint, no MQTT needed. The helpers handle the full flow — upload to `rupload.facebook.com/messenger_image/` + broadcast via `/direct_v2/threads/broadcast/raven_attachment/` — in a single call.
|
|
2110
|
+
|
|
2111
|
+
```javascript
|
|
2112
|
+
const { sendRavenPhotoOnce, sendRavenPhotoReplayable, sendRavenVideoOnce } = require('nodejs-insta-private-api-mqt');
|
|
2113
|
+
const fs = require('fs');
|
|
2114
|
+
|
|
2115
|
+
// view-once photo (disappears after one view)
|
|
2116
|
+
const photoBuffer = fs.readFileSync('./secret.jpg');
|
|
2117
|
+
await sendRavenPhotoOnce(ig, photoBuffer, {
|
|
2118
|
+
threadId: threadId
|
|
2119
|
+
});
|
|
2120
|
+
|
|
2121
|
+
// replayable photo (can replay, still disappears)
|
|
2122
|
+
await sendRavenPhotoReplayable(ig, photoBuffer, {
|
|
2123
|
+
threadId: threadId
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
// view-once video
|
|
2127
|
+
const videoBuffer = fs.readFileSync('./secret_clip.mp4');
|
|
2128
|
+
await sendRavenVideoOnce(ig, videoBuffer, {
|
|
2129
|
+
threadId: threadId,
|
|
2130
|
+
duration: 8,
|
|
2131
|
+
width: 720,
|
|
2132
|
+
height: 1280
|
|
2133
|
+
});
|
|
2134
|
+
```
|
|
2135
|
+
|
|
2136
|
+
---
|
|
2137
|
+
|
|
2138
|
+
### Friendship Management
|
|
2139
|
+
|
|
2140
|
+
#### Follow / Unfollow
|
|
2141
|
+
|
|
2142
|
+
```javascript
|
|
2143
|
+
await ig.friendship.create('25025320'); // follow
|
|
2144
|
+
await ig.friendship.destroy('25025320'); // unfollow
|
|
2145
|
+
```
|
|
2146
|
+
|
|
2147
|
+
#### Check Friendship Status
|
|
2148
|
+
|
|
2149
|
+
```javascript
|
|
2150
|
+
const status = await ig.friendship.show('25025320');
|
|
2151
|
+
console.log('Following:', status.following);
|
|
2152
|
+
console.log('Followed by:', status.followed_by);
|
|
2153
|
+
console.log('Blocking:', status.blocking);
|
|
2154
|
+
|
|
2155
|
+
// bulk check
|
|
2156
|
+
const many = await ig.friendship.showMany(['12345', '67890']);
|
|
2157
|
+
```
|
|
2158
|
+
|
|
2159
|
+
#### Approve / Ignore Follow Requests
|
|
2160
|
+
|
|
2161
|
+
```javascript
|
|
2162
|
+
await ig.friendship.approve('25025320');
|
|
2163
|
+
await ig.friendship.ignore('25025320');
|
|
2164
|
+
|
|
2165
|
+
// list pending requests
|
|
2166
|
+
const pending = await ig.friendship.getPendingRequests();
|
|
2167
|
+
```
|
|
2168
|
+
|
|
2169
|
+
#### Block / Unblock
|
|
2170
|
+
|
|
2171
|
+
```javascript
|
|
2172
|
+
await ig.friendship.block('25025320');
|
|
2173
|
+
await ig.friendship.unblock('25025320');
|
|
2174
|
+
|
|
2175
|
+
const blocked = await ig.friendship.getBlockedUsers();
|
|
2176
|
+
```
|
|
2177
|
+
|
|
2178
|
+
#### Restrict / Unrestrict
|
|
2179
|
+
|
|
2180
|
+
```javascript
|
|
2181
|
+
await ig.friendship.restrict('25025320');
|
|
2182
|
+
await ig.friendship.unrestrict('25025320');
|
|
2183
|
+
```
|
|
2184
|
+
|
|
2185
|
+
#### Mute / Unmute
|
|
2186
|
+
|
|
2187
|
+
```javascript
|
|
2188
|
+
await ig.friendship.mute('25025320', { muteStories: true, mutePosts: true });
|
|
2189
|
+
await ig.friendship.unmute('25025320', { unmuteStories: true, unmutePosts: true });
|
|
2190
|
+
```
|
|
2191
|
+
|
|
2192
|
+
#### Close Friends / Besties
|
|
2193
|
+
|
|
2194
|
+
```javascript
|
|
2195
|
+
// add someone to close friends
|
|
2196
|
+
await ig.friendship.setCloseFriend('25025320', true);
|
|
2197
|
+
// remove
|
|
2198
|
+
await ig.friendship.setCloseFriend('25025320', false);
|
|
2199
|
+
|
|
2200
|
+
// bulk update close friends list
|
|
2201
|
+
await ig.friendship.setBesties(['user1', 'user2'], ['user3']); // add, remove
|
|
2202
|
+
```
|
|
2203
|
+
|
|
2204
|
+
#### Favorites
|
|
2205
|
+
|
|
2206
|
+
```javascript
|
|
2207
|
+
await ig.friendship.setFavorite('25025320');
|
|
2208
|
+
await ig.friendship.unsetFavorite('25025320');
|
|
2209
|
+
|
|
2210
|
+
const favorites = await ig.friendship.getFavoriteFriends();
|
|
2211
|
+
```
|
|
2212
|
+
|
|
2213
|
+
#### Remove Follower
|
|
2214
|
+
|
|
2215
|
+
```javascript
|
|
2216
|
+
await ig.friendship.removeFollower('25025320');
|
|
2217
|
+
```
|
|
2218
|
+
|
|
2219
|
+
#### Get Followers / Following
|
|
2220
|
+
|
|
2221
|
+
```javascript
|
|
2222
|
+
const followers = await ig.friendship.getFollowers('25025320');
|
|
2223
|
+
const following = await ig.friendship.getFollowing('25025320');
|
|
2224
|
+
const mutual = await ig.friendship.getMutuafFollowers('25025320');
|
|
2225
|
+
```
|
|
2226
|
+
|
|
2227
|
+
---
|
|
2228
|
+
|
|
2229
|
+
### Search (FBSearch)
|
|
2230
|
+
|
|
2231
|
+
Instagram's unified search endpoint. Covers users, hashtags, places, and music.
|
|
2232
|
+
|
|
2233
|
+
#### Top Search (All Types)
|
|
2234
|
+
|
|
2235
|
+
```javascript
|
|
2236
|
+
const results = await ig.fbsearch.topSearch('coffee shop');
|
|
2237
|
+
console.log('Users:', results.users?.length);
|
|
2238
|
+
console.log('Places:', results.places?.length);
|
|
2239
|
+
console.log('Hashtags:', results.hashtags?.length);
|
|
2240
|
+
|
|
2241
|
+
// flat version (simpler output)
|
|
2242
|
+
const flat = await ig.fbsearch.topSearchFlat('coffee', 30);
|
|
2243
|
+
```
|
|
2244
|
+
|
|
2245
|
+
#### Search by Type
|
|
2246
|
+
|
|
2247
|
+
```javascript
|
|
2248
|
+
const users = await ig.fbsearch.searchUsers('johndoe', 20);
|
|
2249
|
+
const hashtags = await ig.fbsearch.searchHashtags('photography', 20);
|
|
2250
|
+
const places = await ig.fbsearch.searchPlaces('new york');
|
|
2251
|
+
const music = await ig.fbsearch.searchMusic('trending');
|
|
2252
|
+
```
|
|
2253
|
+
|
|
2254
|
+
#### Search History
|
|
2255
|
+
|
|
2256
|
+
```javascript
|
|
2257
|
+
const recent = await ig.fbsearch.getRecentSearches();
|
|
2258
|
+
|
|
2259
|
+
// clear it
|
|
2260
|
+
await ig.fbsearch.clearRecentSearches();
|
|
2261
|
+
|
|
2262
|
+
// manually add something to recent searches
|
|
2263
|
+
await ig.fbsearch.registerRecentSearch('25025320', 'user');
|
|
2264
|
+
```
|
|
2265
|
+
|
|
2266
|
+
#### Suggested Searches
|
|
2267
|
+
|
|
2268
|
+
```javascript
|
|
2269
|
+
const suggested = await ig.fbsearch.getSuggestedSearches('users');
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
#### Null State (Default Explore)
|
|
2273
|
+
|
|
2274
|
+
```javascript
|
|
2275
|
+
const nullState = await ig.fbsearch.nullStateDynamic();
|
|
2276
|
+
```
|
|
2277
|
+
|
|
2278
|
+
---
|
|
2279
|
+
|
|
2280
|
+
### Explore
|
|
2281
|
+
|
|
2282
|
+
#### Topical Explore
|
|
2283
|
+
|
|
2284
|
+
```javascript
|
|
2285
|
+
const explore = await ig.explore.topicalExplore({
|
|
2286
|
+
module: 'explore_popular',
|
|
2287
|
+
cluster_id: 'explore_all:0',
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
// basic explore
|
|
2291
|
+
const basic = await ig.explore.explore();
|
|
2292
|
+
// paginate
|
|
2293
|
+
const page2 = await ig.explore.explore(basic.next_max_id);
|
|
2294
|
+
```
|
|
2295
|
+
|
|
2296
|
+
#### Report Explore Media
|
|
2297
|
+
|
|
2298
|
+
```javascript
|
|
2299
|
+
await ig.explore.reportExploreMedia('3193593212003331660', 1);
|
|
2300
|
+
```
|
|
2301
|
+
|
|
2302
|
+
#### Mark Explore as Seen
|
|
2303
|
+
|
|
2304
|
+
```javascript
|
|
2305
|
+
await ig.explore.markAsSeen();
|
|
2306
|
+
```
|
|
2307
|
+
|
|
2308
|
+
---
|
|
2309
|
+
|
|
2310
|
+
### Notes
|
|
2311
|
+
|
|
2312
|
+
Instagram Notes — the little status messages that show up in the DM tab.
|
|
2313
|
+
|
|
2314
|
+
```javascript
|
|
2315
|
+
// get all notes from people you follow
|
|
2316
|
+
const notes = await ig.note.getNotes();
|
|
2317
|
+
const followingNotes = await ig.note.getNotesFollowing();
|
|
2318
|
+
|
|
2319
|
+
// create a note (audience: 0 = followers, 1 = close friends)
|
|
2320
|
+
await ig.note.createNote('currently building something cool', 0);
|
|
2321
|
+
|
|
2322
|
+
// delete your note
|
|
2323
|
+
await ig.note.deleteNote('note_id_here');
|
|
2324
|
+
|
|
2325
|
+
// mark notes as seen
|
|
2326
|
+
await ig.note.lastSeenUpdateNote();
|
|
2327
|
+
```
|
|
2328
|
+
|
|
2329
|
+
---
|
|
2330
|
+
|
|
2331
|
+
### Insights (Business/Creator Accounts)
|
|
2332
|
+
|
|
2333
|
+
Requires a business or creator account.
|
|
2334
|
+
|
|
2335
|
+
```javascript
|
|
2336
|
+
// account-level insights
|
|
2337
|
+
const accountInsights = await ig.insights.account({
|
|
2338
|
+
ig_drop_table: 'is_feed',
|
|
2339
|
+
follower_type: 'followers',
|
|
2340
|
+
timeframe: 'one_week',
|
|
2341
|
+
query_params: JSON.stringify({ access_token: '', id: '' })
|
|
2342
|
+
});
|
|
2343
|
+
|
|
2344
|
+
// insights for a specific post
|
|
2345
|
+
const mediaInsights = await ig.insights.media('3193593212003331660');
|
|
2346
|
+
|
|
2347
|
+
// reel insights
|
|
2348
|
+
const reelInsights = await ig.insights.reelInsights('3193593212003331660');
|
|
2349
|
+
|
|
2350
|
+
// story insights
|
|
2351
|
+
const storyInsights = await ig.insights.storyInsights('3193593212003331660');
|
|
2352
|
+
|
|
2353
|
+
// all media feed insights
|
|
2354
|
+
const allMedia = await ig.insights.mediaFeedAll({ count: 20 });
|
|
2355
|
+
```
|
|
2356
|
+
|
|
2357
|
+
---
|
|
2358
|
+
|
|
2359
|
+
### Notifications
|
|
2360
|
+
|
|
2361
|
+
Fine-grained control over push notification settings.
|
|
2362
|
+
|
|
2363
|
+
```javascript
|
|
2364
|
+
// mute all notifications for 8 hours
|
|
2365
|
+
await ig.notification.muteAll('8_hour');
|
|
2366
|
+
|
|
2367
|
+
// disable all notifications
|
|
2368
|
+
await ig.notification.disableAll();
|
|
2369
|
+
|
|
2370
|
+
// control individual notification types
|
|
2371
|
+
await ig.notification.likes('off');
|
|
2372
|
+
await ig.notification.comments('off');
|
|
2373
|
+
await ig.notification.newFollower('off');
|
|
2374
|
+
await ig.notification.commentLikes('off');
|
|
2375
|
+
await ig.notification.directShareActivity('off');
|
|
2376
|
+
await ig.notification.login('off');
|
|
2377
|
+
await ig.notification.reminders('off');
|
|
2378
|
+
|
|
2379
|
+
// and many more: userTagged, firstPost, followRequestAccepted,
|
|
2380
|
+
// connection, taggedInBio, pendingDirectShare, directGroupRequests,
|
|
2381
|
+
// fundraiserSupporter, announcements, reportUpdated...
|
|
2382
|
+
```
|
|
2383
|
+
|
|
2384
|
+
---
|
|
2385
|
+
|
|
2386
|
+
### Music / Audio Tracks
|
|
2387
|
+
|
|
2388
|
+
```javascript
|
|
2389
|
+
// search for music
|
|
2390
|
+
const tracks = await ig.track.search('trending pop');
|
|
2391
|
+
|
|
2392
|
+
// get track info
|
|
2393
|
+
const trackInfo = await ig.track.infoById('track_id_here');
|
|
2394
|
+
const trackByCanonical = await ig.track.infoByCanonicalId('canonical_id');
|
|
2395
|
+
|
|
2396
|
+
// download audio
|
|
2397
|
+
const audioBuffer = await ig.track.downloadByUrl('https://instagram.cdnurl.com/...');
|
|
2398
|
+
```
|
|
2399
|
+
|
|
2400
|
+
---
|
|
2401
|
+
|
|
2402
|
+
### Signup (Account Creation)
|
|
2403
|
+
|
|
2404
|
+
Programmatic account creation flow.
|
|
2405
|
+
|
|
2406
|
+
```javascript
|
|
2407
|
+
// check if email/username is available
|
|
2408
|
+
const emailCheck = await ig.signup.checkEmail('user@example.com');
|
|
2409
|
+
const usernameCheck = await ig.signup.checkUsername('desired_username');
|
|
2410
|
+
|
|
2411
|
+
// get signup config
|
|
2412
|
+
const config = await ig.signup.getSignupConfig();
|
|
2413
|
+
|
|
2414
|
+
// check age eligibility
|
|
2415
|
+
await ig.signup.checkAgeEligibility(1995, 6, 15);
|
|
2416
|
+
|
|
2417
|
+
// send verification email and confirm
|
|
2418
|
+
await ig.signup.sendVerifyEmail('user@example.com');
|
|
2419
|
+
await ig.signup.checkConfirmationCode('user@example.com', '123456');
|
|
2420
|
+
|
|
2421
|
+
// phone-based signup
|
|
2422
|
+
await ig.signup.sendSignupSmsCode('+1234567890');
|
|
2423
|
+
await ig.signup.validateSignupSmsCode('+1234567890', '123456');
|
|
2424
|
+
|
|
2425
|
+
// get username suggestions
|
|
2426
|
+
const suggestions = await ig.signup.getSuggestedUsernames('John Doe', 'john@example.com');
|
|
2427
|
+
|
|
2428
|
+
// create the account
|
|
2429
|
+
const newAccount = await ig.signup.accountsCreate({
|
|
2430
|
+
username: 'johndoe_2026',
|
|
2431
|
+
password: 'securepassword123',
|
|
2432
|
+
email: 'john@example.com',
|
|
2433
|
+
first_name: 'John',
|
|
2434
|
+
});
|
|
2435
|
+
```
|
|
2436
|
+
|
|
2437
|
+
---
|
|
2438
|
+
|
|
2439
|
+
### Multiple Accounts
|
|
2440
|
+
|
|
2441
|
+
```javascript
|
|
2442
|
+
const family = await ig.multipleAccounts.getAccountFamily();
|
|
2443
|
+
const featured = await ig.multipleAccounts.getFeaturedAccounts();
|
|
2444
|
+
const info = await ig.multipleAccounts.getAccountInfo();
|
|
2445
|
+
|
|
2446
|
+
// switch to another logged-in account
|
|
2447
|
+
await ig.multipleAccounts.switchAccount('other_user_id');
|
|
2448
|
+
```
|
|
2449
|
+
|
|
2450
|
+
---
|
|
2451
|
+
|
|
2452
|
+
### Fundraiser
|
|
2453
|
+
|
|
2454
|
+
```javascript
|
|
2455
|
+
// get fundraiser info
|
|
2456
|
+
const info = await ig.fundraiser.standaloneFundraiserInfo('fundraiser_pk');
|
|
2457
|
+
|
|
2458
|
+
// create a charity fundraiser
|
|
2459
|
+
const fundraiser = await ig.fundraiser.createCharityFundraiser({
|
|
2460
|
+
title: 'Help Local School',
|
|
2461
|
+
description: 'Raising funds for supplies',
|
|
2462
|
+
charity_id: 'charity_pk',
|
|
2463
|
+
goal_amount: 5000,
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
// donate
|
|
2467
|
+
await ig.fundraiser.donateFundraiser('fundraiser_pk', 25);
|
|
2468
|
+
```
|
|
2469
|
+
|
|
2470
|
+
---
|
|
2471
|
+
|
|
2472
|
+
### Captcha / Challenge Forms
|
|
2473
|
+
|
|
2474
|
+
```javascript
|
|
2475
|
+
// get challenge form (when Instagram shows a captcha)
|
|
2476
|
+
const form = await ig.captcha.getChallengeForm('/api/v1/challenge/1234/');
|
|
2477
|
+
|
|
2478
|
+
// submit reCAPTCHA or hCaptcha response
|
|
2479
|
+
await ig.captcha.submitRecaptchaResponse('/api/v1/challenge/1234/', 'recaptcha_token');
|
|
2480
|
+
await ig.captcha.submitHCaptchaResponse('/api/v1/challenge/1234/', 'hcaptcha_token');
|
|
2481
|
+
```
|
|
2482
|
+
|
|
2483
|
+
---
|
|
2484
|
+
|
|
2485
|
+
### Share / URL Parsing
|
|
2486
|
+
|
|
2487
|
+
```javascript
|
|
2488
|
+
// decode a share code (from QR codes, NFC tags, etc.)
|
|
2489
|
+
const info = ig.share.shareInfo('base64_encoded_code');
|
|
2490
|
+
// returns { type: 'user', pk: '25025320' }
|
|
2491
|
+
|
|
2492
|
+
// parse from URL
|
|
2493
|
+
const fromUrl = ig.share.shareInfoByUrl('https://www.instagram.com/share/abc123');
|
|
2494
|
+
|
|
2495
|
+
// extract share code from URL
|
|
2496
|
+
const code = ig.share.shareCodeFromUrl('https://www.instagram.com/share/abc123');
|
|
2497
|
+
```
|
|
2498
|
+
|
|
2499
|
+
---
|
|
2500
|
+
|
|
2501
|
+
### Bloks (Instagram Bloks Engine)
|
|
2502
|
+
|
|
2503
|
+
Low-level access to Instagram's Bloks framework. Used internally by some flows.
|
|
2504
|
+
|
|
2505
|
+
```javascript
|
|
2506
|
+
await ig.bloks.action({
|
|
2507
|
+
action_name: 'some.action.name',
|
|
2508
|
+
params: { key: 'value' }
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2511
|
+
const layout = await ig.bloks.getLayoutData({
|
|
2512
|
+
layout_name: 'layout.name',
|
|
2513
|
+
params: {}
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
// bloks-based password change
|
|
2517
|
+
await ig.bloks.changePassword('old_pass', 'new_pass');
|
|
2518
|
+
```
|
|
2519
|
+
|
|
2520
|
+
---
|
|
2521
|
+
|
|
2522
|
+
### Complete REST Repository Reference
|
|
2523
|
+
|
|
2524
|
+
Here's every repository and what it covers at a glance.
|
|
2525
|
+
|
|
2526
|
+
| Repository | Access | What it does |
|
|
2527
|
+
|------------|--------|--------------|
|
|
2528
|
+
| `ig.account` | Account management | Login, 2FA, edit profile, change password, privacy |
|
|
2529
|
+
| `ig.user` | User operations | Info, search, follow, block, mute, get medias/reels/stories |
|
|
2530
|
+
| `ig.media` | Media operations | Like, comment, pin, delete, save, archive, download |
|
|
2531
|
+
| `ig.clip` | Reels / Clips | Upload, discover, download reels |
|
|
2532
|
+
| `ig.story` | Stories | Upload, view, react, highlights |
|
|
2533
|
+
| `ig.highlights` | Highlights | Create, edit, delete, manage cover |
|
|
2534
|
+
| `ig.feed` | Content feeds | Timeline, hashtag, location, saved, liked, explore |
|
|
2535
|
+
| `ig.timeline` | Timeline reels | Reels feed, explore reels |
|
|
2536
|
+
| `ig.upload` | Upload & configure | Photo/video upload, configure to feed/story/clips |
|
|
2537
|
+
| `ig.direct` | Direct messages | Inbox, send text/media/links, raven (view-once), group threads |
|
|
2538
|
+
| `ig.friendship` | Relationships | Follow, block, restrict, close friends, favorites |
|
|
2539
|
+
| `ig.fbsearch` | Search | Users, hashtags, places, music, search history |
|
|
2540
|
+
| `ig.explore` | Explore page | Topical explore, report, mark seen |
|
|
2541
|
+
| `ig.insights` | Analytics | Account, media, reel, story insights |
|
|
2542
|
+
| `ig.note` | Notes | Create, delete, view notes |
|
|
2543
|
+
| `ig.notification` | Notification settings | Enable/disable per-type notifications |
|
|
2544
|
+
| `ig.totp` | 2FA management | TOTP setup, SMS 2FA, backup codes |
|
|
2545
|
+
| `ig.challenge` | Challenge resolver | Auto-resolve, verify methods, security codes |
|
|
2546
|
+
| `ig.signup` | Account creation | Email/phone verification, username check, create account |
|
|
2547
|
+
| `ig.track` | Music / Audio | Search tracks, get info, download |
|
|
2548
|
+
| `ig.share` | Share codes | Decode QR/NFC share codes, parse URLs |
|
|
2549
|
+
| `ig.bloks` | Bloks engine | Low-level Instagram UI actions |
|
|
2550
|
+
| `ig.fundraiser` | Fundraisers | Create, donate, get info |
|
|
2551
|
+
| `ig.multipleAccounts` | Multi-account | Switch accounts, account family |
|
|
2552
|
+
| `ig.captcha` | Captcha handling | reCAPTCHA / hCaptcha submission |
|
|
2553
|
+
| `ig.location` | Locations | Location search and info |
|
|
2554
|
+
| `ig.hashtag` | Hashtags | Hashtag info and feed |
|
|
2555
|
+
| `ig.news` | Activity feed | Activity inbox |
|
|
2556
|
+
| `ig.collection` | Collections | Saved collections management |
|
|
2557
|
+
| `ig.closeFriends` | Close friends | Close friends list management |
|
|
2558
|
+
|
|
2559
|
+
---
|
|
2560
|
+
|
|
2561
|
+
### RealtimeClient Events
|
|
2562
|
+
|
|
2563
|
+
| Event | Description |
|
|
2564
|
+
|-------|-------------|
|
|
2565
|
+
| `connected` | MQTT connected |
|
|
2566
|
+
| `disconnected` | MQTT disconnected |
|
|
2567
|
+
| `message` | New message received |
|
|
2568
|
+
| `message_live` | Live message with parsed data |
|
|
2569
|
+
| `typing` | Typing indicator |
|
|
2570
|
+
| `presence` | User presence update |
|
|
2571
|
+
| `error` | Connection error |
|
|
2572
|
+
| `warning` | Non-fatal issue (payload errors, etc.) |
|
|
2573
|
+
| `reconnected` | Successfully reconnected after a drop |
|
|
2574
|
+
| `reconnect_failed` | All reconnect attempts exhausted |
|
|
2575
|
+
| `auth_failure` | 3+ consecutive authentication errors (credentials likely expired) |
|
|
2576
|
+
|
|
2577
|
+
### Advanced Real-time Events (instagram_mqtt compatible)
|
|
2578
|
+
|
|
2579
|
+
These events provide deeper access to Instagram's MQTT protocol. They were added for full compatibility with the instagram_mqtt library.
|
|
2580
|
+
|
|
2581
|
+
| Event | Description |
|
|
2582
|
+
|-------|-------------|
|
|
2583
|
+
| `realtimeSub` | Raw realtime subscription data (all MQTT messages) |
|
|
2584
|
+
| `direct` | Direct message events with parsed data |
|
|
2585
|
+
| `subscription` | Legacy subscription event (backwards compatible) |
|
|
2586
|
+
| `directTyping` | When someone is typing in a DM thread |
|
|
2587
|
+
| `appPresence` | User online/offline status updates |
|
|
2588
|
+
| `directStatus` | DM thread status changes |
|
|
2589
|
+
| `liveWave` | Instagram Live wave notifications |
|
|
2590
|
+
| `liveRealtimeComments` | Real-time comments on Instagram Live |
|
|
2591
|
+
| `liveTypingIndicator` | Typing indicator in Live comments |
|
|
2592
|
+
| `mediaFeedback` | Media engagement feedback |
|
|
2593
|
+
| `clientConfigUpdate` | Client configuration updates |
|
|
2594
|
+
|
|
2595
|
+
#### Using the realtimeSub Event
|
|
2596
|
+
|
|
2597
|
+
The `realtimeSub` event gives you access to all raw MQTT messages. This is useful for debugging or implementing custom message handling:
|
|
2598
|
+
|
|
2599
|
+
```javascript
|
|
2600
|
+
realtime.on('realtimeSub', ({ data, topic }) => {
|
|
2601
|
+
console.log('Raw MQTT data received:', data);
|
|
2602
|
+
console.log('Topic:', topic);
|
|
2603
|
+
});
|
|
2604
|
+
```
|
|
2605
|
+
|
|
2606
|
+
#### Using the direct Event
|
|
2607
|
+
|
|
2608
|
+
The `direct` event provides parsed direct message updates with automatic JSON parsing of nested values:
|
|
2609
|
+
|
|
2610
|
+
```javascript
|
|
2611
|
+
realtime.on('direct', (data) => {
|
|
2612
|
+
console.log('Direct update:', data);
|
|
2613
|
+
|
|
2614
|
+
// data.op contains the operation type (e.g., 'add', 'replace', 'remove')
|
|
2615
|
+
// data.path contains the affected path
|
|
2616
|
+
// data.value contains the parsed message data
|
|
2617
|
+
|
|
2618
|
+
if (data.op === 'add' && data.value) {
|
|
2619
|
+
console.log('New message:', data.value.text);
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
```
|
|
2623
|
+
|
|
2624
|
+
#### Using QueryID-based Events
|
|
2625
|
+
|
|
2626
|
+
These events are automatically emitted when Instagram sends specific subscription updates:
|
|
2627
|
+
|
|
2628
|
+
```javascript
|
|
2629
|
+
// Listen for typing indicators
|
|
2630
|
+
realtime.on('directTyping', (data) => {
|
|
2631
|
+
console.log('User is typing:', data);
|
|
2632
|
+
});
|
|
2633
|
+
|
|
2634
|
+
// Listen for presence updates (online/offline status)
|
|
2635
|
+
realtime.on('appPresence', (data) => {
|
|
2636
|
+
console.log('Presence update:', data);
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2639
|
+
// Listen for DM status changes
|
|
2640
|
+
realtime.on('directStatus', (data) => {
|
|
2641
|
+
console.log('Direct status changed:', data);
|
|
2642
|
+
});
|
|
2643
|
+
|
|
2644
|
+
// Listen for Instagram Live comments
|
|
2645
|
+
realtime.on('liveRealtimeComments', (data) => {
|
|
2646
|
+
console.log('Live comment:', data);
|
|
2647
|
+
});
|
|
2648
|
+
```
|
|
2649
|
+
|
|
2650
|
+
#### Complete Example: Multi-Event Listener
|
|
2651
|
+
|
|
2652
|
+
Here's a complete example showing how to listen to multiple events:
|
|
2653
|
+
|
|
2654
|
+
```javascript
|
|
2655
|
+
const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqt');
|
|
2656
|
+
|
|
2657
|
+
async function startAdvancedBot() {
|
|
2658
|
+
const ig = new IgApiClient();
|
|
2659
|
+
const auth = await useMultiFileAuthState('./auth_info_ig');
|
|
2660
|
+
|
|
2661
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
2662
|
+
|
|
2663
|
+
const realtime = new RealtimeClient(ig);
|
|
2664
|
+
|
|
2665
|
+
// Standard message handling
|
|
2666
|
+
realtime.on('message_live', (msg) => {
|
|
2667
|
+
console.log(`[${msg.username}]: ${msg.text}`);
|
|
2668
|
+
});
|
|
2669
|
+
|
|
2670
|
+
// Advanced: Raw MQTT data (useful for debugging)
|
|
2671
|
+
realtime.on('realtimeSub', ({ data }) => {
|
|
2672
|
+
console.log('[Debug] Raw MQTT:', JSON.stringify(data).substring(0, 200));
|
|
2673
|
+
});
|
|
2674
|
+
|
|
2675
|
+
// Direct message updates with parsed data
|
|
2676
|
+
realtime.on('direct', (data) => {
|
|
2677
|
+
if (data.op === 'add') {
|
|
2678
|
+
console.log('[Direct] New item added');
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
|
|
2682
|
+
// Typing indicators
|
|
2683
|
+
realtime.on('directTyping', (data) => {
|
|
2684
|
+
console.log('[Typing] Someone is typing...');
|
|
2685
|
+
});
|
|
2686
|
+
|
|
2687
|
+
// User presence (online/offline)
|
|
2688
|
+
realtime.on('appPresence', (data) => {
|
|
2689
|
+
console.log('[Presence] User status changed');
|
|
2690
|
+
});
|
|
2691
|
+
|
|
2692
|
+
// Login and connect
|
|
2693
|
+
if (!auth.hasSession()) {
|
|
2694
|
+
await ig.login({
|
|
2695
|
+
username: 'your_username',
|
|
2696
|
+
password: 'your_password'
|
|
2697
|
+
});
|
|
2698
|
+
await auth.saveCreds(ig);
|
|
2699
|
+
} else {
|
|
2700
|
+
await auth.loadCreds(ig);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
await realtime.startRealTimeListener();
|
|
2704
|
+
|
|
2705
|
+
console.log('Advanced bot with full MQTT events is running!');
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
startAdvancedBot().catch(console.error);
|
|
2709
|
+
```
|
|
2710
|
+
|
|
2711
|
+
---
|
|
2712
|
+
|
|
2713
|
+
## Important Notes
|
|
2714
|
+
|
|
2715
|
+
### Media Uploads
|
|
2716
|
+
|
|
2717
|
+
Media files (photos, videos) are always uploaded via HTTP rupload first. MQTT only sends metadata/references, never the raw bytes. The `sendPhoto()` and `sendVideo()` methods handle this automatically. Raven (view-once) media uses a different upload path — `rupload.facebook.com/messenger_image/` instead of `rupload_igphoto/` — and then broadcasts via `/direct_v2/threads/broadcast/raven_attachment/`. The `sendRavenPhoto()` / `sendRavenVideo()` helpers handle the entire flow.
|
|
2718
|
+
|
|
2719
|
+
### Rate Limiting
|
|
2720
|
+
|
|
2721
|
+
Instagram has strict rate limits. Add delays between rapid-fire messages to avoid temporary bans.
|
|
2722
|
+
|
|
2723
|
+
### Session Persistence
|
|
2724
|
+
|
|
2725
|
+
Always save your session after login to avoid repeated logins which can trigger verification.
|
|
2726
|
+
|
|
2727
|
+
---
|
|
2728
|
+
|
|
2729
|
+
## February 2026 Update - Stability & Compatibility Overhaul
|
|
2730
|
+
|
|
2731
|
+
This update focuses on long-term MQTT stability. If you've been getting random disconnections or rate limit bans, most of that should be gone now. Here's what changed and how to use the new stuff.
|
|
2732
|
+
|
|
2733
|
+
### What's New
|
|
2734
|
+
|
|
2735
|
+
- **Smart error handling** - errors are now classified (rate limit, auth, network, etc.) and each type gets its own backoff strategy
|
|
2736
|
+
- **Reconnect manager** - smarter reconnection that knows *why* it disconnected and adjusts accordingly
|
|
2737
|
+
- **Session persistence helpers** - `IgApiClientExt` with `exportState()` / `importState()` for dead-simple session save/restore
|
|
2738
|
+
- **Convenience wrappers** - `withRealtime()`, `withFbns()`, `withFbnsAndRealtime()` to set up clients in one line
|
|
2739
|
+
- **Message ordering** - automatic per-thread message queue so multi-message sends always arrive in order
|
|
2740
|
+
- **Subscription exports** - `GraphQLSubscriptions`, `SkywalkerSubscriptions`, `QueryIDs` now exported for custom subscription setups
|
|
2741
|
+
- **Topic listener cleanup** - `listen()` now returns an unsubscribe function
|
|
2742
|
+
- **FBNS push notifications** - `FbnsClient` fully working out of the box (no extra packages needed)
|
|
2743
|
+
- **Low-level MQTT access** - `MQTToTClient`, `MQTToTConnection`, `mqttotConnectFlow` exported for advanced use
|
|
2744
|
+
- **Updated fingerprint** - Instagram version `415.0.0.36.76`, Samsung Galaxy S24 device profile
|
|
2745
|
+
- **Keepalive rewrite** - from 4 overlapping timers down to 2 + a watchdog, way less suspicious traffic
|
|
2746
|
+
|
|
2747
|
+
---
|
|
2748
|
+
|
|
2749
|
+
## Intelligent Error Handling
|
|
2750
|
+
|
|
2751
|
+
The old error handler just retried everything with the same delay. The new one actually looks at *what* went wrong and reacts differently depending on the error type.
|
|
2752
|
+
|
|
2753
|
+
### Error Types
|
|
2754
|
+
|
|
2755
|
+
| Type | Triggers On | Base Delay | Max Delay | Multiplier |
|
|
2756
|
+
|------|-------------|------------|-----------|------------|
|
|
2757
|
+
| `rate_limit` | "too many requests", "action blocked", 429 | 60s | 10 min | 1.5x |
|
|
2758
|
+
| `auth_failure` | "login_required", "checkpoint", 401, 403 | 10s | 2 min | 2x |
|
|
2759
|
+
| `network` | ECONNRESET, ETIMEDOUT, DNS failures | 2s | 1 min | 2x |
|
|
2760
|
+
| `server` | 500, 502, 503 | 5s | 2 min | 2x |
|
|
2761
|
+
| `protocol` | Thrift parse errors, CONNACK issues | 5s | 1 min | 2x |
|
|
2762
|
+
|
|
2763
|
+
All delays include random jitter (0-2s) to prevent multiple bots from hammering the server at the same time.
|
|
2764
|
+
|
|
2765
|
+
### Listening for Error Events
|
|
2766
|
+
|
|
2767
|
+
```javascript
|
|
2768
|
+
const { IgApiClient, RealtimeClient } = require('nodejs-insta-private-api-mqt');
|
|
2769
|
+
|
|
2770
|
+
const ig = new IgApiClient();
|
|
2771
|
+
const realtime = new RealtimeClient(ig);
|
|
2772
|
+
|
|
2773
|
+
// fires after 3 consecutive auth failures - probably time to re-login
|
|
2774
|
+
realtime.on('auth_failure', ({ count, error }) => {
|
|
2775
|
+
console.log(`Auth failed ${count} times: ${error}`);
|
|
2776
|
+
console.log('Session is probably expired, need to login again');
|
|
2777
|
+
// your re-login logic here
|
|
2778
|
+
});
|
|
2779
|
+
|
|
2780
|
+
// fires when all 15 retry attempts are used up
|
|
2781
|
+
realtime.on('error', (err) => {
|
|
2782
|
+
if (err.message.includes('Max retries')) {
|
|
2783
|
+
console.log('Gave up reconnecting. Maybe restart the process.');
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
|
|
2787
|
+
// non-fatal stuff - good to log but usually not actionable
|
|
2788
|
+
realtime.on('warning', ({ type, topic, error }) => {
|
|
2789
|
+
console.log(`Warning [${type}] on ${topic}: ${error}`);
|
|
2790
|
+
});
|
|
2791
|
+
```
|
|
2792
|
+
|
|
2793
|
+
### Checking Error Stats
|
|
2794
|
+
|
|
2795
|
+
The error handler keeps a rolling history of the last 50 errors. Handy for dashboards or diagnostics:
|
|
2796
|
+
|
|
2797
|
+
```javascript
|
|
2798
|
+
// after connecting...
|
|
2799
|
+
const stats = realtime.errorHandler.getErrorStats();
|
|
2800
|
+
console.log('Total errors:', stats.errorCount);
|
|
2801
|
+
console.log('Can still retry:', stats.canRetry);
|
|
2802
|
+
console.log('Currently rate limited:', stats.isRateLimited);
|
|
2803
|
+
console.log('Rate limit expires in:', stats.rateLimitRemainingMs, 'ms');
|
|
2804
|
+
console.log('Breakdown by type:', stats.typeBreakdown);
|
|
2805
|
+
// e.g. { network: 2, rate_limit: 1 }
|
|
2806
|
+
```
|
|
2807
|
+
|
|
2808
|
+
---
|
|
2809
|
+
|
|
2810
|
+
## Smart Reconnection
|
|
2811
|
+
|
|
2812
|
+
The `ReconnectManager` works together with the error handler. When the connection drops, it doesn't just blindly retry - it checks what kind of error caused the disconnect and adjusts the backoff.
|
|
2813
|
+
|
|
2814
|
+
- **Rate limit errors**: 3x multiplier (backs off aggressively to avoid making things worse)
|
|
2815
|
+
- **Auth failures**: 2.5x multiplier (slower, gives time for token refresh)
|
|
2816
|
+
- **Everything else**: 2x multiplier (standard exponential backoff)
|
|
2817
|
+
|
|
2818
|
+
All delays include up to 30% jitter so if you're running multiple bots they don't all reconnect at the exact same second.
|
|
2819
|
+
|
|
2820
|
+
```javascript
|
|
2821
|
+
realtime.on('reconnected', () => {
|
|
2822
|
+
console.log('Back online after a disconnect');
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
realtime.on('reconnect_failed', () => {
|
|
2826
|
+
console.log('Could not reconnect after all attempts');
|
|
2827
|
+
// maybe send yourself a notification, restart the process, etc.
|
|
2828
|
+
});
|
|
2829
|
+
```
|
|
2830
|
+
|
|
2831
|
+
You don't need to configure any of this - it's all automatic. The RealtimeClient uses the ErrorHandler + ReconnectManager for high-level reconnection, while MQTToTClient has its own low-level reconnect loop for transport-level drops. They work independently but cover different failure scenarios.
|
|
2832
|
+
|
|
2833
|
+
---
|
|
2834
|
+
|
|
2835
|
+
## Session Persistence with IgApiClientExt
|
|
2836
|
+
|
|
2837
|
+
If you're coming from `instagram_mqtt`, you might be used to `withRealtime()` and `exportState()`. We've got those now.
|
|
2838
|
+
|
|
2839
|
+
### Basic Usage - exportState / importState
|
|
2840
|
+
|
|
2841
|
+
```javascript
|
|
2842
|
+
const { IgApiClientExt } = require('nodejs-insta-private-api-mqt');
|
|
2843
|
+
const fs = require('fs');
|
|
2844
|
+
|
|
2845
|
+
const ig = new IgApiClientExt();
|
|
2846
|
+
|
|
2847
|
+
// first run - login and save
|
|
2848
|
+
await ig.login({ username: 'myuser', password: 'mypass' });
|
|
2849
|
+
const state = await ig.exportState();
|
|
2850
|
+
fs.writeFileSync('saved_state.json', state);
|
|
2851
|
+
|
|
2852
|
+
// next run - restore from file
|
|
2853
|
+
const ig2 = new IgApiClientExt();
|
|
2854
|
+
const saved = fs.readFileSync('saved_state.json', 'utf8');
|
|
2855
|
+
await ig2.importState(saved);
|
|
2856
|
+
// ig2 is now logged in, no need to call login() again
|
|
2857
|
+
```
|
|
2858
|
+
|
|
2859
|
+
`exportState()` serializes the entire client state (cookies, tokens, device info) through a hook system. You can add your own hooks if you need to persist additional data:
|
|
2860
|
+
|
|
2861
|
+
```javascript
|
|
2862
|
+
ig.addStateHook({
|
|
2863
|
+
name: 'myCustomData',
|
|
2864
|
+
onExport: async (client) => {
|
|
2865
|
+
return { lastSyncTime: Date.now(), someConfig: 'value' };
|
|
2866
|
+
},
|
|
2867
|
+
onImport: async (data, client) => {
|
|
2868
|
+
console.log('Restoring custom data:', data);
|
|
2869
|
+
// do whatever you need with the restored data
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
```
|
|
2873
|
+
|
|
2874
|
+
### withRealtime() - Attach RealtimeClient in One Line
|
|
2875
|
+
|
|
2876
|
+
```javascript
|
|
2877
|
+
const { IgApiClient, withRealtime } = require('nodejs-insta-private-api-mqt');
|
|
2878
|
+
|
|
2879
|
+
const ig = new IgApiClient();
|
|
2880
|
+
// this upgrades ig to IgApiClientExt and adds a lazy .realtime property
|
|
2881
|
+
const client = withRealtime(ig);
|
|
2882
|
+
|
|
2883
|
+
await client.login({ username: 'myuser', password: 'mypass' });
|
|
2884
|
+
|
|
2885
|
+
// .realtime is created lazily - only when you first access it
|
|
2886
|
+
client.realtime.on('message_live', (msg) => {
|
|
2887
|
+
console.log(`${msg.username}: ${msg.text}`);
|
|
2888
|
+
});
|
|
2889
|
+
|
|
2890
|
+
await client.realtime.startRealTimeListener();
|
|
2891
|
+
|
|
2892
|
+
// and since it's an IgApiClientExt, you can export the full state
|
|
2893
|
+
const state = await client.exportState();
|
|
2894
|
+
```
|
|
2895
|
+
|
|
2896
|
+
**Note:** `withRealtime()` doesn't create the RealtimeClient immediately. It sets up a lazy getter, so the client is only instantiated when you first access `client.realtime`. No surprise connections.
|
|
2897
|
+
|
|
2898
|
+
### withFbns() - Push Notifications
|
|
2899
|
+
|
|
2900
|
+
Attach FBNS (Facebook Notification Service) for push notifications. The FBNS auth state is automatically included when you call `exportState()`, so you don't need to re-authenticate every time you restart:
|
|
2901
|
+
|
|
2902
|
+
```javascript
|
|
2903
|
+
const { IgApiClient, withFbns } = require('nodejs-insta-private-api-mqt');
|
|
2904
|
+
|
|
2905
|
+
const ig = new IgApiClient();
|
|
2906
|
+
const client = withFbns(ig);
|
|
2907
|
+
|
|
2908
|
+
// client.fbns is now available
|
|
2909
|
+
// FBNS auth state is automatically included in exportState()
|
|
2910
|
+
```
|
|
2911
|
+
|
|
2912
|
+
### withFbnsAndRealtime() - Everything at Once
|
|
2913
|
+
|
|
2914
|
+
```javascript
|
|
2915
|
+
const { IgApiClient, withFbnsAndRealtime } = require('nodejs-insta-private-api-mqt');
|
|
2916
|
+
|
|
2917
|
+
const ig = new IgApiClient();
|
|
2918
|
+
const client = withFbnsAndRealtime(ig);
|
|
2919
|
+
|
|
2920
|
+
// client.realtime -> RealtimeClient (lazy)
|
|
2921
|
+
// client.fbns -> FbnsClient (ready to use, no extra packages needed)
|
|
2922
|
+
// client.exportState() / client.importState() -> full state persistence
|
|
2923
|
+
```
|
|
2924
|
+
|
|
2925
|
+
### Upgrading an Existing IgApiClient
|
|
2926
|
+
|
|
2927
|
+
All the `with*` functions work on existing `IgApiClient` instances. They use `Object.setPrototypeOf` internally, so your existing event listeners, state, and references are all preserved - nothing gets copied or lost.
|
|
2928
|
+
|
|
2929
|
+
```javascript
|
|
2930
|
+
const ig = new IgApiClient();
|
|
2931
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
2932
|
+
await ig.login({ username: 'user', password: 'pass' });
|
|
2933
|
+
|
|
2934
|
+
// this is safe - ig keeps all its internal state
|
|
2935
|
+
const upgraded = withRealtime(ig);
|
|
2936
|
+
// upgraded === ig (same object, just with extra methods now)
|
|
2937
|
+
```
|
|
2938
|
+
|
|
2939
|
+
---
|
|
2940
|
+
|
|
2941
|
+
## Automatic Message Ordering (Transactions)
|
|
2942
|
+
|
|
2943
|
+
Ever sent 3 messages in a row and they showed up in the wrong order on Instagram? That's because MQTT doesn't guarantee delivery order. This library fixes that automatically.
|
|
2944
|
+
|
|
2945
|
+
When RealtimeClient starts up, it wraps `directCommands` methods with per-thread queues (using `p-queue` with `concurrency: 1`). So if you do:
|
|
2946
|
+
|
|
2947
|
+
```javascript
|
|
2948
|
+
await realtime.directCommands.sendText({ threadId: thread, text: 'Hey' });
|
|
2949
|
+
await realtime.directCommands.sendText({ threadId: thread, text: 'How are you?' });
|
|
2950
|
+
await realtime.directCommands.sendText({ threadId: thread, text: 'Check this out' });
|
|
2951
|
+
```
|
|
2952
|
+
|
|
2953
|
+
They will **always** arrive in that exact order on Instagram's side, even if some of them take longer to deliver than others. This happens transparently - you don't need to write any extra code.
|
|
2954
|
+
|
|
2955
|
+
The queue is per-thread, so messages to different threads can still go out in parallel. Only messages within the same thread are serialized.
|
|
2956
|
+
|
|
2957
|
+
**Methods that are queued:** `sendText`, `sendLink`, `sendPhoto`, `sendVideo`, `sendVoice`, `sendLike`, `sendPost`
|
|
2958
|
+
|
|
2959
|
+
---
|
|
2960
|
+
|
|
2961
|
+
## GraphQL & Skywalker Subscriptions
|
|
2962
|
+
|
|
2963
|
+
These are now exported so you can build custom subscription configurations instead of relying on the defaults.
|
|
2964
|
+
|
|
2965
|
+
```javascript
|
|
2966
|
+
const {
|
|
2967
|
+
GraphQLSubscriptions,
|
|
2968
|
+
SkywalkerSubscriptions,
|
|
2969
|
+
QueryIDs
|
|
2970
|
+
} = require('nodejs-insta-private-api-mqt');
|
|
2971
|
+
|
|
2972
|
+
// GraphQLSubscriptions has factory methods for each subscription type
|
|
2973
|
+
const directSub = GraphQLSubscriptions.getDirectTypingSubscription('your_user_id');
|
|
2974
|
+
const presenceSub = GraphQLSubscriptions.getAppPresenceSubscription();
|
|
2975
|
+
|
|
2976
|
+
// SkywalkerSubscriptions works the same way
|
|
2977
|
+
const liveSub = SkywalkerSubscriptions.directSub('your_user_id');
|
|
2978
|
+
|
|
2979
|
+
// QueryIDs contains the numeric IDs Instagram uses internally
|
|
2980
|
+
// useful if you need to match incoming messages to subscription types
|
|
2981
|
+
console.log(QueryIDs);
|
|
2982
|
+
```
|
|
2983
|
+
|
|
2984
|
+
### Using Custom Subscriptions with connect()
|
|
2985
|
+
|
|
2986
|
+
```javascript
|
|
2987
|
+
const realtime = new RealtimeClient(ig);
|
|
2988
|
+
|
|
2989
|
+
await realtime.connect({
|
|
2990
|
+
graphQlSubs: [
|
|
2991
|
+
GraphQLSubscriptions.getDirectTypingSubscription(ig.state.userId),
|
|
2992
|
+
GraphQLSubscriptions.getAppPresenceSubscription(),
|
|
2993
|
+
],
|
|
2994
|
+
skywalkerSubs: [
|
|
2995
|
+
SkywalkerSubscriptions.directSub(ig.state.userId),
|
|
2996
|
+
],
|
|
2997
|
+
irisData: inbox
|
|
2998
|
+
});
|
|
2999
|
+
```
|
|
3000
|
+
|
|
3001
|
+
---
|
|
3002
|
+
|
|
3003
|
+
## Topic Listeners with Cleanup
|
|
3004
|
+
|
|
3005
|
+
The low-level `listen()` method on MQTToTClient now returns a function you can call to stop listening. This prevents memory leaks when you only need to listen temporarily.
|
|
3006
|
+
|
|
3007
|
+
```javascript
|
|
3008
|
+
// listen with a topic config object (applies transformer)
|
|
3009
|
+
const remove = realtime.mqtt.listen(
|
|
3010
|
+
{ topic: '/ig_message_sync', transformer: myTransformer },
|
|
3011
|
+
(data) => {
|
|
3012
|
+
console.log('Got transformed data:', data);
|
|
3013
|
+
}
|
|
3014
|
+
);
|
|
3015
|
+
|
|
3016
|
+
// or listen to a raw topic string (no transformer, just raw messages)
|
|
3017
|
+
const removeRaw = realtime.mqtt.listen('/ig_send_message_response', (msg) => {
|
|
3018
|
+
console.log('Raw message on topic:', msg.payload);
|
|
3019
|
+
});
|
|
3020
|
+
|
|
3021
|
+
// later, when you're done:
|
|
3022
|
+
remove();
|
|
3023
|
+
removeRaw();
|
|
3024
|
+
```
|
|
3025
|
+
|
|
3026
|
+
---
|
|
3027
|
+
|
|
3028
|
+
## FBNS Push Notifications (FbnsClient)
|
|
3029
|
+
|
|
3030
|
+
`FbnsClient` handles Facebook Notification Service - the push notification system that Instagram uses under the hood. Even when the official app is "in the background", FBNS is what delivers those new follower, like, comment, and DM notifications. This library now has full FBNS support built in - no extra packages needed.
|
|
3031
|
+
|
|
3032
|
+
For most DM bot use cases you don't need FBNS because `RealtimeClient` already handles real-time message delivery. But if you want to receive push-style notifications for things like new followers, comments on your posts, story mentions, or live broadcast alerts - FBNS is the way to go. You can also run both at the same time.
|
|
3033
|
+
|
|
3034
|
+
### Basic FBNS Setup
|
|
3035
|
+
|
|
3036
|
+
```javascript
|
|
3037
|
+
const { IgApiClient, FbnsClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqt');
|
|
3038
|
+
|
|
3039
|
+
async function startFbns() {
|
|
3040
|
+
const ig = new IgApiClient();
|
|
3041
|
+
const auth = await useMultiFileAuthState('./auth_session');
|
|
3042
|
+
|
|
3043
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
3044
|
+
|
|
3045
|
+
// login or restore session
|
|
3046
|
+
if (auth.hasSession()) {
|
|
3047
|
+
await auth.loadCreds(ig);
|
|
3048
|
+
} else {
|
|
3049
|
+
await ig.login({ username: 'your_username', password: 'your_password' });
|
|
3050
|
+
await auth.saveCreds(ig);
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
// create the FBNS client and wire up events
|
|
3054
|
+
const fbns = new FbnsClient(ig);
|
|
3055
|
+
|
|
3056
|
+
fbns.on('push', (notification) => {
|
|
3057
|
+
console.log('Got push notification!');
|
|
3058
|
+
console.log('Title:', notification.title);
|
|
3059
|
+
console.log('Message:', notification.message);
|
|
3060
|
+
console.log('Type:', notification.collapseKey);
|
|
3061
|
+
});
|
|
3062
|
+
|
|
3063
|
+
fbns.on('error', (err) => {
|
|
3064
|
+
console.error('FBNS error:', err.message);
|
|
3065
|
+
});
|
|
3066
|
+
|
|
3067
|
+
// connect - this handles everything: TLS handshake, device auth,
|
|
3068
|
+
// token registration, and push subscription
|
|
3069
|
+
await fbns.connect();
|
|
3070
|
+
console.log('FBNS connected and listening for push notifications');
|
|
3071
|
+
|
|
3072
|
+
// keep the process alive
|
|
3073
|
+
await new Promise(() => {});
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
startFbns().catch(console.error);
|
|
3077
|
+
```
|
|
3078
|
+
|
|
3079
|
+
That's it. Once `connect()` resolves, you're registered for push notifications and the `push` event will fire whenever Instagram sends one.
|
|
3080
|
+
|
|
3081
|
+
### FBNS Events
|
|
3082
|
+
|
|
3083
|
+
| Event | Payload | When it fires |
|
|
3084
|
+
|-------|---------|---------------|
|
|
3085
|
+
| `push` | `{ title, message, collapseKey, igAction, pushId, pushCategory, badgeCount }` | New push notification received |
|
|
3086
|
+
| `auth` | `{ clientId, deviceId, userId, deviceSecret }` | Device authenticated with FBNS servers |
|
|
3087
|
+
| `message` | Raw FBNS message payload | Any FBNS message (low-level) |
|
|
3088
|
+
| `logging` | Experiment/logging data from Instagram | Instagram sends telemetry config |
|
|
3089
|
+
| `error` | Error object | Connection or protocol error |
|
|
3090
|
+
| `warning` | Warning info | Non-fatal issues (empty payloads, etc.) |
|
|
3091
|
+
| `disconnect` | Disconnect reason string | FBNS connection dropped |
|
|
3092
|
+
|
|
3093
|
+
### Push Notification Types (collapseKey values)
|
|
3094
|
+
|
|
3095
|
+
The `collapseKey` field tells you what kind of notification it is. Here are the common ones:
|
|
3096
|
+
|
|
3097
|
+
| collapseKey | What it means |
|
|
3098
|
+
|-------------|---------------|
|
|
3099
|
+
| `direct_v2_message` | New DM received |
|
|
3100
|
+
| `direct_v2_group` | New group DM |
|
|
3101
|
+
| `follow` | Someone followed you |
|
|
3102
|
+
| `like` | Someone liked your post |
|
|
3103
|
+
| `comment` | Someone commented on your post |
|
|
3104
|
+
| `mention` | Someone mentioned you |
|
|
3105
|
+
| `story_mention` | Someone mentioned you in their story |
|
|
3106
|
+
| `live_broadcast` | Someone you follow went live |
|
|
3107
|
+
| `story_reshare` | Someone reshared your story |
|
|
3108
|
+
| `comment_like` | Someone liked your comment |
|
|
3109
|
+
| `tagged_in_photo` | Someone tagged you in a photo |
|
|
3110
|
+
|
|
3111
|
+
### Handling Different Notification Types
|
|
3112
|
+
|
|
3113
|
+
```javascript
|
|
3114
|
+
fbns.on('push', (notification) => {
|
|
3115
|
+
switch (notification.collapseKey) {
|
|
3116
|
+
case 'direct_v2_message':
|
|
3117
|
+
console.log('New DM! Badge:', notification.badgeCount?.direct);
|
|
3118
|
+
// notification.igAction contains the thread URL you can parse
|
|
3119
|
+
// e.g. "direct_v2?id=34028236...&x=32658200..."
|
|
3120
|
+
break;
|
|
3121
|
+
|
|
3122
|
+
case 'follow':
|
|
3123
|
+
console.log('New follower!', notification.message);
|
|
3124
|
+
break;
|
|
3125
|
+
|
|
3126
|
+
case 'like':
|
|
3127
|
+
case 'comment':
|
|
3128
|
+
console.log('Engagement:', notification.message);
|
|
3129
|
+
break;
|
|
3130
|
+
|
|
3131
|
+
case 'live_broadcast':
|
|
3132
|
+
console.log('Someone went live:', notification.message);
|
|
3133
|
+
break;
|
|
3134
|
+
|
|
3135
|
+
default:
|
|
3136
|
+
console.log(`[${notification.collapseKey}]`, notification.message || notification.title);
|
|
3137
|
+
}
|
|
3138
|
+
});
|
|
3139
|
+
```
|
|
3140
|
+
|
|
3141
|
+
### Running FBNS + Realtime Together
|
|
3142
|
+
|
|
3143
|
+
You can run both FBNS (push notifications) and RealtimeClient (DM messaging) side by side. They use different MQTT connections - Realtime connects to `edge-mqtt.facebook.com` for DMs, while FBNS connects to `mqtt-mini.facebook.com` for push notifications.
|
|
3144
|
+
|
|
3145
|
+
```javascript
|
|
3146
|
+
const { IgApiClient, RealtimeClient, FbnsClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqt');
|
|
3147
|
+
|
|
3148
|
+
async function startBot() {
|
|
3149
|
+
const ig = new IgApiClient();
|
|
3150
|
+
const auth = await useMultiFileAuthState('./auth_session');
|
|
3151
|
+
|
|
3152
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
3153
|
+
|
|
3154
|
+
if (auth.hasSession()) {
|
|
3155
|
+
await auth.loadCreds(ig);
|
|
3156
|
+
} else {
|
|
3157
|
+
await ig.login({ username: 'your_username', password: 'your_password' });
|
|
3158
|
+
await auth.saveCreds(ig);
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
// start realtime for DMs
|
|
3162
|
+
const realtime = new RealtimeClient(ig);
|
|
3163
|
+
realtime.on('message_live', (msg) => {
|
|
3164
|
+
console.log(`DM from ${msg.username}: ${msg.text}`);
|
|
3165
|
+
});
|
|
3166
|
+
await realtime.startRealTimeListener();
|
|
3167
|
+
|
|
3168
|
+
// start FBNS for push notifications
|
|
3169
|
+
const fbns = new FbnsClient(ig);
|
|
3170
|
+
fbns.on('push', (notif) => {
|
|
3171
|
+
console.log(`Push [${notif.collapseKey}]:`, notif.message || notif.title);
|
|
3172
|
+
});
|
|
3173
|
+
await fbns.connect();
|
|
3174
|
+
|
|
3175
|
+
console.log('Both Realtime and FBNS are running!');
|
|
3176
|
+
await new Promise(() => {});
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
startBot().catch(console.error);
|
|
3180
|
+
```
|
|
3181
|
+
|
|
3182
|
+
### FBNS with Session Persistence (withFbns)
|
|
3183
|
+
|
|
3184
|
+
If you're using `IgApiClientExt` for state management, `withFbns()` automatically includes the FBNS auth state in `exportState()` / `importState()`. This means you don't have to re-authenticate with FBNS servers every time you restart.
|
|
3185
|
+
|
|
3186
|
+
```javascript
|
|
3187
|
+
const { IgApiClient, withFbnsAndRealtime } = require('nodejs-insta-private-api-mqt');
|
|
3188
|
+
const fs = require('fs');
|
|
3189
|
+
|
|
3190
|
+
const ig = new IgApiClient();
|
|
3191
|
+
const client = withFbnsAndRealtime(ig);
|
|
3192
|
+
|
|
3193
|
+
// first run - login and save everything
|
|
3194
|
+
await client.login({ username: 'user', password: 'pass' });
|
|
3195
|
+
const state = await client.exportState();
|
|
3196
|
+
fs.writeFileSync('full_state.json', state);
|
|
3197
|
+
|
|
3198
|
+
// next run - restore from file, FBNS auth included
|
|
3199
|
+
const saved = fs.readFileSync('full_state.json', 'utf8');
|
|
3200
|
+
await client.importState(saved);
|
|
3201
|
+
|
|
3202
|
+
// client.fbns is ready to connect with saved device credentials
|
|
3203
|
+
client.fbns.on('push', (notif) => console.log('Push:', notif.message));
|
|
3204
|
+
await client.fbns.connect();
|
|
3205
|
+
```
|
|
3206
|
+
|
|
3207
|
+
### FBNS Connection Options
|
|
3208
|
+
|
|
3209
|
+
```javascript
|
|
3210
|
+
await fbns.connect({
|
|
3211
|
+
autoReconnect: true, // auto-reconnect on disconnect (default: true)
|
|
3212
|
+
enableTrace: false, // enable MQTT packet tracing for debugging
|
|
3213
|
+
});
|
|
3214
|
+
```
|
|
3215
|
+
|
|
3216
|
+
### Disconnecting FBNS
|
|
3217
|
+
|
|
3218
|
+
```javascript
|
|
3219
|
+
// graceful disconnect
|
|
3220
|
+
await fbns.disconnect();
|
|
3221
|
+
|
|
3222
|
+
// if you need to reconnect later
|
|
3223
|
+
await fbns.connect();
|
|
3224
|
+
```
|
|
3225
|
+
|
|
3226
|
+
### How FBNS Works Internally
|
|
3227
|
+
|
|
3228
|
+
For anyone curious about what happens under the hood:
|
|
3229
|
+
|
|
3230
|
+
1. `FbnsClient` creates a TLS connection to `mqtt-mini.facebook.com:443` using Instagram's MQTToT protocol (MQTT over Thrift)
|
|
3231
|
+
2. The CONNECT packet includes a device auth payload - either fresh credentials or saved ones from a previous session
|
|
3232
|
+
3. Instagram responds with a CONNACK that contains a `deviceId`, `clientId`, and `deviceSecret` for this device
|
|
3233
|
+
4. The client then publishes a registration request (FBNS_REG_REQ) with the Instagram package name
|
|
3234
|
+
5. Instagram responds with a push token (FBNS_REG_RESP)
|
|
3235
|
+
6. The client registers this token with Instagram's push API
|
|
3236
|
+
7. From this point on, any push notification targeted at this account gets delivered via the MQTT connection
|
|
3237
|
+
|
|
3238
|
+
The whole flow takes about 1-2 seconds. The registration response can arrive very fast (sometimes before `connect()` even returns), so the library sets up the response listener before connecting to avoid any timing issues.
|
|
3239
|
+
|
|
3240
|
+
---
|
|
3241
|
+
|
|
3242
|
+
## Low-Level MQTToT Access
|
|
3243
|
+
|
|
3244
|
+
For advanced users who want to work directly with Instagram's MQTT-over-Thrift protocol:
|
|
3245
|
+
|
|
3246
|
+
```javascript
|
|
3247
|
+
const {
|
|
3248
|
+
MQTToTClient,
|
|
3249
|
+
MQTToTConnection,
|
|
3250
|
+
mqttotConnectFlow,
|
|
3251
|
+
INSTAGRAM_VERSION
|
|
3252
|
+
} = require('nodejs-insta-private-api-mqt');
|
|
3253
|
+
|
|
3254
|
+
console.log('Current Instagram version:', INSTAGRAM_VERSION);
|
|
3255
|
+
// '415.0.0.36.76'
|
|
3256
|
+
|
|
3257
|
+
// MQTToTClient extends mqtts.MqttClient with Instagram-specific behavior:
|
|
3258
|
+
// - automatic keepalive pinging (every 8 min + jitter)
|
|
3259
|
+
// - consecutive ping failure detection (3 fails = forced reconnect)
|
|
3260
|
+
// - reconnect loop with rate limit awareness
|
|
3261
|
+
// - listen() with cleanup function
|
|
3262
|
+
|
|
3263
|
+
// MQTToTConnection is used to build the Thrift connection payload
|
|
3264
|
+
// that Instagram expects in the CONNECT packet
|
|
3265
|
+
|
|
3266
|
+
// mqttotConnectFlow creates the CONNECT/CONNACK handshake flow
|
|
3267
|
+
// with keepAlive: 60 and proper error handling
|
|
3268
|
+
```
|
|
3269
|
+
|
|
3270
|
+
---
|
|
3271
|
+
|
|
3272
|
+
## Keepalive Configuration (February 2026)
|
|
3273
|
+
|
|
3274
|
+
The previous version had 4 overlapping timers sending various keepalive signals, which generated a lot of unnecessary traffic. Instagram could potentially flag this as suspicious bot behavior. This has been streamlined:
|
|
3275
|
+
|
|
3276
|
+
| Timer | Old Value | New Value | Purpose |
|
|
3277
|
+
|-------|-----------|-----------|---------|
|
|
3278
|
+
| Foreground pulse | 15s | 60s + 0-5s jitter | Tells Instagram the app is in foreground |
|
|
3279
|
+
| GraphQL sync | 45s | 5 min + 0-10s jitter | Refreshes subscription data |
|
|
3280
|
+
| Traffic watchdog | 60s check / 60s threshold | 60s check / 5 min threshold | Detects dead connections, tries ping before reconnecting |
|
|
3281
|
+
| MQTT heartbeat | 15s | 4 min + 0-5s jitter | MQTT-level ping to keep the TCP connection alive |
|
|
3282
|
+
| MQTToT keepalive | 10 min | 8 min + 0-30s jitter | Low-level protocol keepalive |
|
|
3283
|
+
| Active query | 25s | **Disabled** | Was redundant with the above |
|
|
3284
|
+
|
|
3285
|
+
The result is roughly 10x less keepalive traffic, which means less chance of Instagram flagging your connection as automated.
|
|
3286
|
+
|
|
3287
|
+
---
|
|
3288
|
+
|
|
3289
|
+
## Changelog
|
|
3290
|
+
|
|
3291
|
+
### v5.67.0 (February 2026) - Raven (View-Once) Media Sending
|
|
3292
|
+
|
|
3293
|
+
**Send disappearing photos and videos in DMs using Instagram's raven protocol:**
|
|
3294
|
+
- `sendRavenPhoto()` in EnhancedDirectCommands — full upload + REST broadcast flow (accessible via directCommands)
|
|
3295
|
+
- `sendRavenVideo()` in EnhancedDirectCommands — same flow for video with duration/dimensions
|
|
3296
|
+
- `broadcastRaven()` in DirectThreadRepository — low-level REST broadcast to `/direct_v2/threads/broadcast/raven_attachment/`
|
|
3297
|
+
- `sendmedia.sendRavenPhoto()` / `sendmedia.sendRavenVideo()` — standalone helpers that work without MQTT
|
|
3298
|
+
- Two modes: view-once (`ephemeralMediaViewMode: 0`) and replayable (`ephemeralMediaViewMode: 1`)
|
|
3299
|
+
- Upload uses rupload with `direct_v2: '1'` flag to mark media as DM content
|
|
3300
|
+
- Confirmed working endpoint from Instagram's smali decompilation — this is the same endpoint the official app uses
|
|
3301
|
+
|
|
3302
|
+
### v5.66.0 (February 2026) - Complete REST API Parity with instagrapi
|
|
3303
|
+
|
|
3304
|
+
**32 REST API Repositories — full coverage of every Instagram private endpoint:**
|
|
3305
|
+
|
|
3306
|
+
New repositories added:
|
|
3307
|
+
- `ClipRepository` - Upload, configure, discover, and download Reels/Clips
|
|
3308
|
+
- `TimelineRepository` - Timeline reels feed and explore reels
|
|
3309
|
+
- `InsightsRepository` - Account, media, reel, and story insights for business/creator accounts
|
|
3310
|
+
- `NoteRepository` - Create, delete, and view Instagram Notes
|
|
3311
|
+
- `NotificationRepository` - Fine-grained per-type notification settings
|
|
3312
|
+
- `SignupRepository` - Full account creation flow with email/SMS verification
|
|
3313
|
+
- `TOTPRepository` - TOTP 2FA setup, SMS 2FA, backup codes
|
|
3314
|
+
- `BloksRepository` - Low-level Bloks engine actions
|
|
3315
|
+
- `ChallengeRepository` - Auto-resolve challenges, verify methods, security codes
|
|
3316
|
+
- `ShareRepository` - Decode QR/NFC share codes, parse share URLs
|
|
3317
|
+
- `TrackRepository` - Music/audio search, info, download
|
|
3318
|
+
- `ExploreRepository` - Topical explore, report, mark seen
|
|
3319
|
+
- `FBSearchRepository` - Unified search (users, hashtags, places, music), search history
|
|
3320
|
+
- `FundraiserRepository` - Create, donate, get fundraiser info
|
|
3321
|
+
- `MultipleAccountsRepository` - Account family, switch accounts
|
|
3322
|
+
- `CaptchaRepository` - reCAPTCHA / hCaptcha submission for challenges
|
|
3323
|
+
- `HighlightsRepository` - Create, edit, delete highlights, manage stories and covers
|
|
3324
|
+
|
|
3325
|
+
Enhanced existing repositories:
|
|
3326
|
+
- `AccountRepository` - Added editProfile, setBiography, setExternalUrl, removeBioLinks, setGender, setPrivate/Public, profilePictureChange/Remove, passwordPublicKeys, sendRecoveryFlowEmail/Sms, encryptPassword
|
|
3327
|
+
- `UserRepository` - Added getUserMedias/getUserReels/getUserClips with pagination, userIdFromUsername, usernameFromUserId, block/unblock, mute/unmute, removeFollower, getBlockedUsers, getMutualFollowers
|
|
3328
|
+
- `MediaRepository` - Added pinComment/unpinComment, bulkDeleteComments, replyToComment, likeComment/unlikeComment, save/unsave, archive/unarchive, disableComments/enableComments, commentThreadComments, downloadPhoto/downloadVideo, oembed, static mediaPkFromCode/mediaCodeFromPk/mediaPkFromUrl
|
|
3329
|
+
- `FriendshipRepository` - Added restrict/unrestrict, setCloseFriend, setBesties, getFavoriteFriends, setFavorite/unsetFavorite, getPendingRequests, getMutuafFollowers, getBlockedUsers
|
|
3330
|
+
- `FeedRepository` - Added uploadCarousel, reelsMedia, reelsTray, getReelsFeed, getUserReelsFeed, getExploreFeed
|
|
3331
|
+
- `StoryRepository` - Added createHighlight/editHighlight/deleteHighlight, uploadVideo, configureStoryVideo, react
|
|
3332
|
+
- `DirectRepository` - Added sendPhoto/sendVideo/sendLink/sendMediaShare/sendProfile/sendHashtag/sendLocation, getPendingInbox, createGroupThread, hideThread
|
|
3333
|
+
- `UploadRepository` - Added configureToClips, configurePhoto, configureVideo, configureToStory
|
|
3334
|
+
|
|
3335
|
+
All 32 repositories registered in client.js and exported from index.js.
|
|
3336
|
+
|
|
3337
|
+
### v5.65.0 (February 2026) - Full FBNS Push Notifications
|
|
3338
|
+
|
|
3339
|
+
**FbnsClient now fully working out of the box:**
|
|
3340
|
+
- Fixed critical timing bug where FBNS registration response arrived before the listener was set up, causing a 30s timeout on every connect
|
|
3341
|
+
- Created built-in `mqtt-shim.js` bridge module so FBNS no longer requires any external MQTT packages
|
|
3342
|
+
- Added missing shared functions (`createFbnsUserAgent`, `notUndefined`, `listenOnce`) that FBNS depends on
|
|
3343
|
+
- FBNS connects to `mqtt-mini.facebook.com:443` via MQTToT, handles device auth, token registration, and push delivery
|
|
3344
|
+
- Receives all push notification types: DMs, follows, likes, comments, story mentions, live broadcasts, and more
|
|
3345
|
+
- Full event system: `push`, `auth`, `message`, `logging`, `error`, `warning`, `disconnect`
|
|
3346
|
+
- Works alongside RealtimeClient (different MQTT connections, no conflicts)
|
|
3347
|
+
- Session persistence via `withFbns()` / `withFbnsAndRealtime()` includes FBNS device auth in `exportState()`
|
|
3348
|
+
- Auto-reconnect support with configurable options
|
|
3349
|
+
- Comprehensive README documentation with working code examples
|
|
3350
|
+
|
|
3351
|
+
### v5.64.0 (February 2026) - Long-Term Stability
|
|
3352
|
+
|
|
3353
|
+
**SessionHealthMonitor** (native in RealtimeClient):
|
|
3354
|
+
- Periodic session validation via `/api/v1/accounts/current_user/` (every 30min + jitter)
|
|
3355
|
+
- Auto-relogin when session expires (configurable with credentials)
|
|
3356
|
+
- Detects: auth errors (401/403), login_required, rate limits (429), network errors
|
|
3357
|
+
- Smart consecutive failure tracking (2+ failures = session expired)
|
|
3358
|
+
- After successful relogin: saves credentials + triggers MQTT reconnect
|
|
3359
|
+
- Events: `health_check`, `session_expired`, `relogin_start`, `relogin_success`, `relogin_failed`, `relogin_challenge`, `relogin_needed`
|
|
3360
|
+
- Uptime statistics: total runtime, uptime percent, session segments, reconnect count, longest session
|
|
3361
|
+
|
|
3362
|
+
**PersistentLogger** (native in RealtimeClient):
|
|
3363
|
+
- File-based logging with automatic rotation (10MB per file, 5 files max)
|
|
3364
|
+
- All MQTT events, errors, reconnects, health checks logged to disk
|
|
3365
|
+
- Configurable log level (debug/info/warn/error)
|
|
3366
|
+
- Buffer-based flushing (every 30s or 50 lines)
|
|
3367
|
+
- `getRecentLines(count)` for quick debugging
|
|
3368
|
+
- Logs survive process restarts for post-mortem analysis
|
|
3369
|
+
|
|
3370
|
+
**Integration:**
|
|
3371
|
+
- `realtime.enableHealthMonitor({ credentials, autoRelogin: true })` - start monitoring
|
|
3372
|
+
- `realtime.enablePersistentLogger({ logDir: './logs' })` - start file logging
|
|
3373
|
+
- `realtime.getHealthStats()` - get uptime/session stats
|
|
3374
|
+
- `realtime.getLoggerStats()` - get logger stats
|
|
3375
|
+
- Auto-enabled via `startRealTimeListener()` options
|
|
3376
|
+
- `SessionHealthMonitor` and `PersistentLogger` exported from index
|
|
3377
|
+
|
|
3378
|
+
**Usage:**
|
|
3379
|
+
```js
|
|
3380
|
+
const realtime = new RealtimeClient(ig);
|
|
3381
|
+
|
|
3382
|
+
// Option A: Auto via startRealTimeListener
|
|
3383
|
+
await realtime.startRealTimeListener({
|
|
3384
|
+
credentials: { username: 'user', password: 'pass' },
|
|
3385
|
+
enablePersistentLogger: true,
|
|
3386
|
+
logDir: './mqtt-logs',
|
|
3387
|
+
});
|
|
3388
|
+
|
|
3389
|
+
// Option B: Manual enable
|
|
3390
|
+
realtime.enableHealthMonitor({
|
|
3391
|
+
credentials: { username: 'user', password: 'pass' },
|
|
3392
|
+
autoRelogin: true,
|
|
3393
|
+
checkIntervalMs: 30 * 60 * 1000,
|
|
3394
|
+
});
|
|
3395
|
+
realtime.enablePersistentLogger({ logDir: './mqtt-logs' });
|
|
3396
|
+
|
|
3397
|
+
// Monitor events
|
|
3398
|
+
realtime.on('health_check', ({ status, stats }) => {
|
|
3399
|
+
console.log(`Session ${status}, uptime: ${stats.totalUptimeHuman}`);
|
|
3400
|
+
});
|
|
3401
|
+
realtime.on('session_expired', () => console.log('Session expired!'));
|
|
3402
|
+
realtime.on('relogin_success', () => console.log('Re-logged in!'));
|
|
3403
|
+
|
|
3404
|
+
// Get stats anytime
|
|
3405
|
+
const stats = realtime.getHealthStats();
|
|
3406
|
+
console.log(`Uptime: ${stats.uptimePercent}%, ${stats.reconnects} reconnects`);
|
|
3407
|
+
```
|
|
3408
|
+
|
|
3409
|
+
### v5.63.0 (February 2026) - Full Protocol Expansion
|
|
3410
|
+
|
|
3411
|
+
**14 New MQTT Topics:**
|
|
3412
|
+
- `/ig_msg_dr` - Delivery receipts (emits `deliveryReceipt`)
|
|
3413
|
+
- `/ig_conn_update` - Connection status updates (emits `connectionUpdate`)
|
|
3414
|
+
- `/notify_disconnect` - Server-initiated disconnects (emits `notifyDisconnect`)
|
|
3415
|
+
- `/t_thread_typing` - Per-thread typing indicators (emits `threadTyping`)
|
|
3416
|
+
- `/iris_server_reset` - Iris state reset with auto-resubscribe (emits `irisServerReset`)
|
|
3417
|
+
- `/t_ig_family_navigation_badge` - Badge/notification counts (emits `badgeCount`)
|
|
3418
|
+
- `/t_entity_presence` - User presence status (emits `entityPresence`)
|
|
3419
|
+
- `/opened_thread` - Thread open tracking (emits `threadOpened`)
|
|
3420
|
+
- `/buddy_list` - Online buddy list (emits `buddyList`)
|
|
3421
|
+
- `/webrtc`, `/webrtc_response`, `/onevc` - Call events (emits `callEvent`)
|
|
3422
|
+
|
|
3423
|
+
**GraphQL Live Subscriptions:**
|
|
3424
|
+
- Live broadcast comments, likes, waves, typing, viewer count
|
|
3425
|
+
- Media feedback (like/comment/save actions)
|
|
3426
|
+
- Direct typing via GraphQL
|
|
3427
|
+
- App-level presence events
|
|
3428
|
+
- Video call state changes
|
|
3429
|
+
- Interactivity events for live broadcasts
|
|
3430
|
+
- Emits: `liveComment`, `liveLikeCount`, `liveWave`, `liveTyping`, `liveViewerCount`, `mediaFeedback`, `directTyping`, `appPresence`, `callStateChange`, `liveInteractivity`, `graphqlEvent`
|
|
3431
|
+
|
|
3432
|
+
**Pubsub Event Handler:**
|
|
3433
|
+
- Direct thread patch operations (emits `direct`)
|
|
3434
|
+
- Typing indicators via pubsub (emits `directTyping`)
|
|
3435
|
+
- Duplicate message filtering
|
|
3436
|
+
- Emits: `pubsubEvent`
|
|
3437
|
+
|
|
3438
|
+
**New REST API Repositories:**
|
|
3439
|
+
- `NewsRepository` - Activity feed inbox, follow requests, mark as seen
|
|
3440
|
+
- `CollectionRepository` - List, create, edit, delete saved collections; collection feed
|
|
3441
|
+
- `CloseFriendsRepository` - List, add/remove besties, suggestions
|
|
3442
|
+
|
|
3443
|
+
**Enhanced MediaRepository:**
|
|
3444
|
+
- `replyToComment(mediaId, commentId, text)` - Reply to specific comments
|
|
3445
|
+
- `likeComment(mediaId, commentId)` / `unlikeComment()` - Comment likes
|
|
3446
|
+
- `bulkDeleteComments(mediaId, commentIds[])` - Bulk comment deletion
|
|
3447
|
+
- `save(mediaId, collectionId?)` / `unsave(mediaId)` - Save/unsave media
|
|
3448
|
+
- `archive(mediaId)` / `unarchive(mediaId)` - Archive/unarchive media
|
|
3449
|
+
- `disableComments(mediaId)` / `enableComments(mediaId)` - Toggle comments
|
|
3450
|
+
- `commentThreadComments(mediaId, commentId)` - Get reply thread
|
|
3451
|
+
|
|
3452
|
+
**Enhanced UserRepository:**
|
|
3453
|
+
- `getFriendshipStatuses(userIds[])` - Bulk friendship status check
|
|
3454
|
+
- `getReelsTrayFeed()` - Stories tray feed
|
|
3455
|
+
- `getUserTags(userId)` - Tagged photos feed
|
|
3456
|
+
- `setSelfBio(biography)` - Update own biography
|
|
3457
|
+
- `report(userId, reason)` - Report user
|
|
3458
|
+
- `getSuggested()` - Suggested users feed
|
|
3459
|
+
|
|
3460
|
+
**Exports:**
|
|
3461
|
+
- `NewsRepository`, `CollectionRepository`, `CloseFriendsRepository`, `MediaRepository`, `UserRepository` now exported from index
|
|
3462
|
+
|
|
3463
|
+
### v5.62.0 (February 2026)
|
|
3464
|
+
- Intelligent error classification with 5 error types and type-specific backoff
|
|
3465
|
+
- Smart ReconnectManager with error-type-aware delays
|
|
3466
|
+
- IgApiClientExt with exportState() / importState() for session persistence
|
|
3467
|
+
- withRealtime(), withFbns(), withFbnsAndRealtime() convenience wrappers
|
|
3468
|
+
- Automatic per-thread message ordering via p-queue
|
|
3469
|
+
- GraphQLSubscriptions, SkywalkerSubscriptions, QueryIDs now exported
|
|
3470
|
+
- listen() returns unsubscribe function for proper cleanup
|
|
3471
|
+
- FbnsClient fully functional with built-in mqtt-shim bridge
|
|
3472
|
+
- MQTToTClient, MQTToTConnection, mqttotConnectFlow exported for advanced use
|
|
3473
|
+
- INSTAGRAM_VERSION constant exported (415.0.0.36.76)
|
|
3474
|
+
- Keepalive timers reduced from 4 to 2+1, traffic reduced ~10x
|
|
3475
|
+
- Instagram version updated to 415.0.0.36.76 with Samsung Galaxy S24 fingerprint
|
|
3476
|
+
- CONNACK keepAlive increased from 20 to 60 seconds
|
|
3477
|
+
- clientCapabilities updated from 183 to 439
|
|
3478
|
+
- Max error retries increased from 5 to 15
|
|
3479
|
+
- New events: reconnected, reconnect_failed, auth_failure, warning
|
|
3480
|
+
|
|
3481
|
+
### v5.61.11
|
|
3482
|
+
- Full iOS support with 21 device presets
|
|
3483
|
+
- Full Android support with 12 device presets
|
|
3484
|
+
- switchPlatform() for easy platform switching
|
|
3485
|
+
|
|
3486
|
+
### v5.60.8
|
|
3487
|
+
- downloadContentFromMessage() - Baileys-style media download
|
|
3488
|
+
- View-once media extraction support
|
|
3489
|
+
- downloadMediaBuffer() and extractMediaUrls()
|
|
3490
|
+
|
|
3491
|
+
### v5.60.7
|
|
3492
|
+
- Custom Device Emulation with 12 preset devices
|
|
3493
|
+
- setCustomDevice() and usePresetDevice() methods
|
|
3494
|
+
|
|
3495
|
+
### v5.60.3
|
|
3496
|
+
- sendPhoto() and sendVideo() for media uploads
|
|
3497
|
+
|
|
3498
|
+
### v5.60.2
|
|
3499
|
+
- useMultiFileAuthState() - Baileys-style session persistence
|
|
3500
|
+
- connectFromSavedSession() method
|
|
3501
|
+
|
|
3502
|
+
### v5.60.0
|
|
3503
|
+
- Full MQTT integration with EnhancedDirectCommands
|
|
3504
|
+
- Real-time messaging with <500ms latency
|
|
3505
|
+
|
|
3506
|
+
---
|
|
3507
|
+
|
|
3508
|
+
## License
|
|
3509
|
+
|
|
3510
|
+
MIT
|
|
3511
|
+
|
|
3512
|
+
## Support
|
|
3513
|
+
|
|
3514
|
+
For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-insta-private-api/issues
|
|
3515
|
+
|
|
3516
|
+
Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api
|
|
3517
|
+
|
|
3518
|
+
|
|
3519
|
+
---
|
|
3520
|
+
## Enhanced Location Usage — Practical Examples (English)
|
|
3521
|
+
|
|
3522
|
+
The `EnhancedDirectCommands` class (see `enhanced.direct.commands.js`) implements robust location sending for Instagram Direct by trying **story-with-location-sticker → share story to thread → fallback to link**. Below are ready-to-copy examples showing how to use the location-related methods and payloads exposed by the class.
|
|
3523
|
+
|
|
3524
|
+
> These examples assume:
|
|
3525
|
+
> - `realtime` is an instance of `RealtimeClient`.
|
|
3526
|
+
> - `realtime.directCommands` is an instance of `EnhancedDirectCommands`.
|
|
3527
|
+
> - You have a valid `threadId` (target DM thread).
|
|
3528
|
+
> - `realtime.ig` exists when examples require publishing a story via the private IG client.
|
|
3529
|
+
|
|
3530
|
+
### 1) Send a location when you already have a `venue` object (recommended)
|
|
3531
|
+
|
|
3532
|
+
```javascript
|
|
3533
|
+
// venue shape expected by sendLocation:
|
|
3534
|
+
// { id, name, address, lat, lng, facebook_places_id, external_source }
|
|
3535
|
+
const venue = {
|
|
3536
|
+
id: "213385402",
|
|
3537
|
+
name: "McDonald's Unirii",
|
|
3538
|
+
address: "Piața Unirii, Bucharest",
|
|
3539
|
+
lat: 44.4268,
|
|
3540
|
+
lng: 26.1025,
|
|
3541
|
+
facebook_places_id: "213385402",
|
|
3542
|
+
external_source: "facebook_places"
|
|
3543
|
+
};
|
|
3544
|
+
|
|
3545
|
+
await realtime.directCommands.sendLocation({
|
|
3546
|
+
threadId: "340282366841710300949128114477782749726",
|
|
3547
|
+
venue,
|
|
3548
|
+
text: "Meet me here at 18:00"
|
|
3549
|
+
});
|
|
3550
|
+
```
|
|
3551
|
+
|
|
3552
|
+
**What happens:**
|
|
3553
|
+
1. The method attempts to publish a Story with a location sticker using `realtime.ig.publish.story`.
|
|
3554
|
+
2. If the Story publish succeeds and a `storyId` is returned, `sendUserStory` (reel_share) is used to share the story to the thread.
|
|
3555
|
+
3. If either step fails, the method falls back to sending a link to `https://www.instagram.com/explore/locations/{placeId}/` via `itemType: 'link'`.
|
|
3556
|
+
|
|
3557
|
+
---
|
|
3558
|
+
|
|
3559
|
+
### 2) Search for a place (instagram private search) and send it
|
|
3560
|
+
|
|
3561
|
+
Use `searchAndSendLocation()` when you only have a search query or coordinates:
|
|
3562
|
+
|
|
3563
|
+
```javascript
|
|
3564
|
+
await realtime.directCommands.searchAndSendLocation({
|
|
3565
|
+
threadId: "340282366841710300949128114477782749726",
|
|
3566
|
+
query: "Starbucks Piata Unirii",
|
|
3567
|
+
lat: 44.4268,
|
|
3568
|
+
lng: 26.1025
|
|
3569
|
+
});
|
|
3570
|
+
```
|
|
3571
|
+
|
|
3572
|
+
This helper calls Instagram's `/fbsearch/places/` private endpoint (via `realtime.ig.request`) and then normalizes the first result into the `venue` shape before calling `sendLocation()`.
|
|
3573
|
+
|
|
3574
|
+
---
|
|
3575
|
+
|
|
3576
|
+
### 3) Build a location sticker manually & publish story (advanced)
|
|
3577
|
+
|
|
3578
|
+
If you want direct control over the sticker object or to publish your own image, you can use `createLocationStickerFromVenue()` and `realtime.ig.publish.story()` directly:
|
|
3579
|
+
|
|
3580
|
+
```javascript
|
|
3581
|
+
const venue = {
|
|
3582
|
+
id: "213385402",
|
|
3583
|
+
name: "McDonald's Unirii",
|
|
3584
|
+
address: "Piața Unirii, Bucharest",
|
|
3585
|
+
lat: 44.4268,
|
|
3586
|
+
lng: 26.1025,
|
|
3587
|
+
facebook_places_id: "213385402"
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3590
|
+
// create sticker compatible with publish.story helpers
|
|
3591
|
+
const sticker = realtime.directCommands.createLocationStickerFromVenue(venue);
|
|
3592
|
+
|
|
3593
|
+
// create a tiny placeholder image (1x1 PNG) or your real photo buffer
|
|
3594
|
+
const SINGLE_PIXEL_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=";
|
|
3595
|
+
const photoBuffer = Buffer.from(SINGLE_PIXEL_PNG_BASE64, 'base64');
|
|
3596
|
+
|
|
3597
|
+
// publish story with the sticker (if realtime.ig.publish.story exists)
|
|
3598
|
+
const publishResult = await realtime.ig.publish.story({
|
|
3599
|
+
file: photoBuffer,
|
|
3600
|
+
stickers: [sticker]
|
|
3601
|
+
});
|
|
3602
|
+
|
|
3603
|
+
// try to resolve returned story id and then share it
|
|
3604
|
+
const storyId = publishResult?.media?.pk || publishResult?.item_id || publishResult?.upload_id;
|
|
3605
|
+
if (storyId) {
|
|
3606
|
+
await realtime.directCommands.sendUserStory({
|
|
3607
|
+
threadId: "340282366841710300949128114477782749726",
|
|
3608
|
+
storyId,
|
|
3609
|
+
text: "Location for tonight"
|
|
3610
|
+
});
|
|
3611
|
+
} else {
|
|
3612
|
+
// fallback: send explore link manually
|
|
3613
|
+
await realtime.directCommands.sendLink({
|
|
3614
|
+
threadId: "340282366841710300949128114477782749726",
|
|
3615
|
+
link: `https://www.instagram.com/explore/locations/${venue.facebook_places_id || venue.id}/`,
|
|
3616
|
+
text: venue.name
|
|
3617
|
+
});
|
|
3618
|
+
}
|
|
3619
|
+
```
|
|
3620
|
+
|
|
3621
|
+
---
|
|
3622
|
+
|
|
3623
|
+
### 4) Force sending the explore-location link (explicit fallback)
|
|
3624
|
+
|
|
3625
|
+
If you don't want to publish a story and only need the location link in DM:
|
|
3626
|
+
|
|
3627
|
+
```javascript
|
|
3628
|
+
const placeId = "213385402";
|
|
3629
|
+
await realtime.directCommands.sendLink({
|
|
3630
|
+
threadId: "340282366841710300949128114477782749726",
|
|
3631
|
+
link: `https://www.instagram.com/explore/locations/${placeId}/`,
|
|
3632
|
+
text: "Meet here"
|
|
3633
|
+
});
|
|
3634
|
+
```
|
|
3635
|
+
|
|
3636
|
+
---
|
|
3637
|
+
|
|
3638
|
+
### 5) Error handling & debug tips
|
|
3639
|
+
|
|
3640
|
+
```javascript
|
|
3641
|
+
try {
|
|
3642
|
+
await realtime.directCommands.sendLocation({ threadId, venue, text: "See you" });
|
|
3643
|
+
console.log("Location sent!");
|
|
3644
|
+
} catch (err) {
|
|
3645
|
+
console.error("Failed to send location:", err);
|
|
3646
|
+
// fallback to explicit link if needed
|
|
3647
|
+
await realtime.directCommands.sendLink({
|
|
3648
|
+
threadId,
|
|
3649
|
+
link: `https://www.instagram.com/explore/locations/${venue.facebook_places_id || venue.id}/`,
|
|
3650
|
+
text: venue.name || "Location"
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
```
|
|
3654
|
+
|
|
3655
|
+
If you need verbose logs, enable debug for the realtime/enhanced module:
|
|
3656
|
+
|
|
3657
|
+
```bash
|
|
3658
|
+
# in your environment (example)
|
|
3659
|
+
DEBUG="realtime:enhanced-commands" node your_bot.js
|
|
3660
|
+
# or to see broader realtime logs
|
|
3661
|
+
DEBUG="realtime:*" node your_bot.js
|
|
3662
|
+
```
|
|
3663
|
+
|
|
3664
|
+
---
|
|
3665
|
+
|
|
3666
|
+
### 6) Quick checklist (what the library needs to make story-with-sticker work)
|
|
3667
|
+
|
|
3668
|
+
- `realtime.ig` must exist and expose `publish.story(...)` (a private client publish helper).
|
|
3669
|
+
- The `venue` must include either `facebook_places_id` or `id`.
|
|
3670
|
+
- If `realtime.ig.publish.story` is unavailable or Instagram rejects the sticker, the library **automatically falls back** to sending a DM link to the Explore locations page.
|
|
3671
|
+
|
|
3672
|
+
---
|
|
3673
|
+
|
|
3674
|
+
## End of Location Examples
|
|
3675
|
+
|
|
3676
|
+
These examples are appended to the original README in order to keep the whole original file intact while adding clear, English examples for location flows.
|
|
3677
|
+
|