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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3677 -0
  3. package/dist/constants/constants.js +342 -0
  4. package/dist/constants/index.js +58 -0
  5. package/dist/core/client.js +419 -0
  6. package/dist/core/nav-chain.js +282 -0
  7. package/dist/core/repository.js +7 -0
  8. package/dist/core/request.js +390 -0
  9. package/dist/core/state.js +1473 -0
  10. package/dist/core/utils.js +786 -0
  11. package/dist/downloadMedia.js +381 -0
  12. package/dist/errors/index.d.ts +16 -0
  13. package/dist/errors/index.js +38 -0
  14. package/dist/errors/index.js.map +1 -0
  15. package/dist/extend.js +167 -0
  16. package/dist/fbns/fbns.client.d.ts +32 -0
  17. package/dist/fbns/fbns.client.events.d.ts +41 -0
  18. package/dist/fbns/fbns.client.events.js +3 -0
  19. package/dist/fbns/fbns.client.events.js.map +1 -0
  20. package/dist/fbns/fbns.client.js +252 -0
  21. package/dist/fbns/fbns.client.js.map +1 -0
  22. package/dist/fbns/fbns.device-auth.d.ts +17 -0
  23. package/dist/fbns/fbns.device-auth.js +54 -0
  24. package/dist/fbns/fbns.device-auth.js.map +1 -0
  25. package/dist/fbns/fbns.types.d.ts +83 -0
  26. package/dist/fbns/fbns.types.js +3 -0
  27. package/dist/fbns/fbns.types.js.map +1 -0
  28. package/dist/fbns/fbns.utilities.d.ts +2 -0
  29. package/dist/fbns/fbns.utilities.js +79 -0
  30. package/dist/fbns/fbns.utilities.js.map +1 -0
  31. package/dist/fbns/index.d.ts +4 -0
  32. package/dist/fbns/index.js +21 -0
  33. package/dist/fbns/index.js.map +1 -0
  34. package/dist/index.js +139 -0
  35. package/dist/mqtt-shim.d.ts +96 -0
  36. package/dist/mqtt-shim.js +15 -0
  37. package/dist/mqttot/index.d.ts +4 -0
  38. package/dist/mqttot/index.js +21 -0
  39. package/dist/mqttot/index.js.map +1 -0
  40. package/dist/mqttot/mqttot.client.d.ts +39 -0
  41. package/dist/mqttot/mqttot.client.js +318 -0
  42. package/dist/mqttot/mqttot.client.js.map +1 -0
  43. package/dist/mqttot/mqttot.connect.request.packet.d.ts +7 -0
  44. package/dist/mqttot/mqttot.connect.request.packet.js +9 -0
  45. package/dist/mqttot/mqttot.connect.request.packet.js.map +1 -0
  46. package/dist/mqttot/mqttot.connect.response.packet.d.ts +7 -0
  47. package/dist/mqttot/mqttot.connect.response.packet.js +24 -0
  48. package/dist/mqttot/mqttot.connect.response.packet.js.map +1 -0
  49. package/dist/mqttot/mqttot.connection.d.ts +57 -0
  50. package/dist/mqttot/mqttot.connection.js +79 -0
  51. package/dist/mqttot/mqttot.connection.js.map +1 -0
  52. package/dist/package.json +59 -0
  53. package/dist/realtime/commands/commands.d.ts +15 -0
  54. package/dist/realtime/commands/commands.js +71 -0
  55. package/dist/realtime/commands/commands.js.map +1 -0
  56. package/dist/realtime/commands/direct.commands.d.ts +75 -0
  57. package/dist/realtime/commands/direct.commands.js +417 -0
  58. package/dist/realtime/commands/direct.commands.js.map +1 -0
  59. package/dist/realtime/commands/enhanced.direct.commands.js +1731 -0
  60. package/dist/realtime/commands/enhanced.direct.commands.js.bak +967 -0
  61. package/dist/realtime/commands/index.d.ts +2 -0
  62. package/dist/realtime/commands/index.js +20 -0
  63. package/dist/realtime/commands/index.js.map +1 -0
  64. package/dist/realtime/delta-sync.manager.js +293 -0
  65. package/dist/realtime/features/dm-sender.js +88 -0
  66. package/dist/realtime/features/error-handler.js +185 -0
  67. package/dist/realtime/features/gap-handler.js +61 -0
  68. package/dist/realtime/features/persistent-logger.js +186 -0
  69. package/dist/realtime/features/presence.manager.js +66 -0
  70. package/dist/realtime/features/session-health-monitor.js +345 -0
  71. package/dist/realtime/index.js +30 -0
  72. package/dist/realtime/messages/app-presence.event.d.ts +9 -0
  73. package/dist/realtime/messages/app-presence.event.js +3 -0
  74. package/dist/realtime/messages/app-presence.event.js.map +1 -0
  75. package/dist/realtime/messages/index.d.ts +3 -0
  76. package/dist/realtime/messages/index.js +20 -0
  77. package/dist/realtime/messages/index.js.map +1 -0
  78. package/dist/realtime/messages/message-sync.message.d.ts +222 -0
  79. package/dist/realtime/messages/message-sync.message.js +43 -0
  80. package/dist/realtime/messages/message-sync.message.js.map +1 -0
  81. package/dist/realtime/messages/realtime-sub.direct.data.d.ts +11 -0
  82. package/dist/realtime/messages/realtime-sub.direct.data.js +3 -0
  83. package/dist/realtime/messages/realtime-sub.direct.data.js.map +1 -0
  84. package/dist/realtime/messages/thread-update.message.d.ts +68 -0
  85. package/dist/realtime/messages/thread-update.message.js +3 -0
  86. package/dist/realtime/messages/thread-update.message.js.map +1 -0
  87. package/dist/realtime/mixins/index.d.ts +3 -0
  88. package/dist/realtime/mixins/index.js +20 -0
  89. package/dist/realtime/mixins/index.js.map +1 -0
  90. package/dist/realtime/mixins/message-sync.mixin.d.ts +8 -0
  91. package/dist/realtime/mixins/message-sync.mixin.js +596 -0
  92. package/dist/realtime/mixins/message-sync.mixin.js.map +1 -0
  93. package/dist/realtime/mixins/mixin.d.ts +19 -0
  94. package/dist/realtime/mixins/mixin.js +41 -0
  95. package/dist/realtime/mixins/mixin.js.map +1 -0
  96. package/dist/realtime/mixins/presence-typing.mixin.js +33 -0
  97. package/dist/realtime/mixins/realtime-sub.mixin.d.ts +8 -0
  98. package/dist/realtime/mixins/realtime-sub.mixin.js +181 -0
  99. package/dist/realtime/mixins/realtime-sub.mixin.js.map +1 -0
  100. package/dist/realtime/parsers/graphql-parser.js +43 -0
  101. package/dist/realtime/parsers/graphql.parser.d.ts +15 -0
  102. package/dist/realtime/parsers/graphql.parser.js +22 -0
  103. package/dist/realtime/parsers/graphql.parser.js.map +1 -0
  104. package/dist/realtime/parsers/index.d.ts +6 -0
  105. package/dist/realtime/parsers/index.js +23 -0
  106. package/dist/realtime/parsers/index.js.map +1 -0
  107. package/dist/realtime/parsers/iris-parser.js +43 -0
  108. package/dist/realtime/parsers/iris.parser.d.ts +17 -0
  109. package/dist/realtime/parsers/iris.parser.js +10 -0
  110. package/dist/realtime/parsers/iris.parser.js.map +1 -0
  111. package/dist/realtime/parsers/json-parser.js +43 -0
  112. package/dist/realtime/parsers/json.parser.d.ts +6 -0
  113. package/dist/realtime/parsers/json.parser.js +10 -0
  114. package/dist/realtime/parsers/json.parser.js.map +1 -0
  115. package/dist/realtime/parsers/parser.d.ts +9 -0
  116. package/dist/realtime/parsers/parser.js +3 -0
  117. package/dist/realtime/parsers/parser.js.map +1 -0
  118. package/dist/realtime/parsers/region-hint-parser.js +43 -0
  119. package/dist/realtime/parsers/region-hint.parser.d.ts +12 -0
  120. package/dist/realtime/parsers/region-hint.parser.js +15 -0
  121. package/dist/realtime/parsers/region-hint.parser.js.map +1 -0
  122. package/dist/realtime/parsers/skywalker-parser.js +43 -0
  123. package/dist/realtime/parsers/skywalker.parser.d.ts +12 -0
  124. package/dist/realtime/parsers/skywalker.parser.js +15 -0
  125. package/dist/realtime/parsers/skywalker.parser.js.map +1 -0
  126. package/dist/realtime/parsers-advanced.js +158 -0
  127. package/dist/realtime/proto/common.proto +38 -0
  128. package/dist/realtime/proto/direct.proto +65 -0
  129. package/dist/realtime/proto/ig-messages.proto +83 -0
  130. package/dist/realtime/proto/iris.proto +188 -0
  131. package/dist/realtime/proto-parser.js +195 -0
  132. package/dist/realtime/protocols/iris.handshake.js +74 -0
  133. package/dist/realtime/protocols/proto-definitions.js +80 -0
  134. package/dist/realtime/protocols/skywalker.protocol.js +91 -0
  135. package/dist/realtime/realtime.client.events.js +3 -0
  136. package/dist/realtime/realtime.client.js +1915 -0
  137. package/dist/realtime/realtime.service.js +462 -0
  138. package/dist/realtime/reconnect.manager.js +88 -0
  139. package/dist/realtime/session.manager.js +121 -0
  140. package/dist/realtime/subscriptions/graphql.subscription.d.ts +47 -0
  141. package/dist/realtime/subscriptions/graphql.subscription.js +99 -0
  142. package/dist/realtime/subscriptions/graphql.subscription.js.map +1 -0
  143. package/dist/realtime/subscriptions/index.d.ts +2 -0
  144. package/dist/realtime/subscriptions/index.js +19 -0
  145. package/dist/realtime/subscriptions/index.js.map +1 -0
  146. package/dist/realtime/subscriptions/skywalker.subscription.d.ts +4 -0
  147. package/dist/realtime/subscriptions/skywalker.subscription.js +13 -0
  148. package/dist/realtime/subscriptions/skywalker.subscription.js.map +1 -0
  149. package/dist/realtime/topic-map.js +71 -0
  150. package/dist/realtime/topic.js +80 -0
  151. package/dist/repositories/account.repository.js +575 -0
  152. package/dist/repositories/bloks.repository.js +70 -0
  153. package/dist/repositories/captcha.repository.js +44 -0
  154. package/dist/repositories/challenge.repository.js +120 -0
  155. package/dist/repositories/clip.repository.js +165 -0
  156. package/dist/repositories/close-friends.repository.js +46 -0
  157. package/dist/repositories/collection.repository.js +68 -0
  158. package/dist/repositories/direct-thread.repository.js +446 -0
  159. package/dist/repositories/direct.repository.js +232 -0
  160. package/dist/repositories/explore.repository.js +70 -0
  161. package/dist/repositories/fbsearch.repository.js +140 -0
  162. package/dist/repositories/feed.repository.js +245 -0
  163. package/dist/repositories/friendship.repository.js +296 -0
  164. package/dist/repositories/fundraiser.repository.js +49 -0
  165. package/dist/repositories/hashtag.repository.js +99 -0
  166. package/dist/repositories/highlights.repository.js +121 -0
  167. package/dist/repositories/insights.repository.js +82 -0
  168. package/dist/repositories/location.repository.js +84 -0
  169. package/dist/repositories/media.repository.js +395 -0
  170. package/dist/repositories/multiple-accounts.repository.js +41 -0
  171. package/dist/repositories/news.repository.js +35 -0
  172. package/dist/repositories/note.repository.js +57 -0
  173. package/dist/repositories/notification.repository.js +79 -0
  174. package/dist/repositories/share.repository.js +35 -0
  175. package/dist/repositories/signup.repository.js +218 -0
  176. package/dist/repositories/story.repository.js +290 -0
  177. package/dist/repositories/timeline.repository.js +60 -0
  178. package/dist/repositories/totp.repository.js +139 -0
  179. package/dist/repositories/track.repository.js +53 -0
  180. package/dist/repositories/upload.repository.js +204 -0
  181. package/dist/repositories/user.repository.js +360 -0
  182. package/dist/sendmedia/index.js +27 -0
  183. package/dist/sendmedia/sendFile.js +72 -0
  184. package/dist/sendmedia/sendPhoto.js +142 -0
  185. package/dist/sendmedia/sendRavenPhoto.js +153 -0
  186. package/dist/sendmedia/sendRavenVideo.js +158 -0
  187. package/dist/sendmedia/uploadPhoto.js +107 -0
  188. package/dist/sendmedia/uploadfFile.js +130 -0
  189. package/dist/services/live.service.js +139 -0
  190. package/dist/services/search.service.js +115 -0
  191. package/dist/shared/index.js +96 -0
  192. package/dist/shared/shared.js +86 -0
  193. package/dist/thrift/index.d.ts +3 -0
  194. package/dist/thrift/index.js +20 -0
  195. package/dist/thrift/index.js.map +1 -0
  196. package/dist/thrift/thrift.d.ts +59 -0
  197. package/dist/thrift/thrift.js +101 -0
  198. package/dist/thrift/thrift.js.map +1 -0
  199. package/dist/thrift/thrift.reading.d.ts +41 -0
  200. package/dist/thrift/thrift.reading.js +327 -0
  201. package/dist/thrift/thrift.reading.js.map +1 -0
  202. package/dist/thrift/thrift.writing.d.ts +44 -0
  203. package/dist/thrift/thrift.writing.js +342 -0
  204. package/dist/thrift/thrift.writing.js.map +1 -0
  205. package/dist/types/index.js +285 -0
  206. package/dist/useMultiFileAuthState.js +1768 -0
  207. package/dist/utils/helper-1.js +1 -0
  208. package/dist/utils/helper-10.js +1 -0
  209. package/dist/utils/helper-11.js +1 -0
  210. package/dist/utils/helper-12.js +1 -0
  211. package/dist/utils/helper-13.js +1 -0
  212. package/dist/utils/helper-14.js +1 -0
  213. package/dist/utils/helper-15.js +1 -0
  214. package/dist/utils/helper-16.js +1 -0
  215. package/dist/utils/helper-17.js +1 -0
  216. package/dist/utils/helper-18.js +1 -0
  217. package/dist/utils/helper-19.js +1 -0
  218. package/dist/utils/helper-2.js +1 -0
  219. package/dist/utils/helper-20.js +1 -0
  220. package/dist/utils/helper-21.js +1 -0
  221. package/dist/utils/helper-22.js +1 -0
  222. package/dist/utils/helper-23.js +1 -0
  223. package/dist/utils/helper-24.js +1 -0
  224. package/dist/utils/helper-25.js +1 -0
  225. package/dist/utils/helper-26.js +1 -0
  226. package/dist/utils/helper-27.js +1 -0
  227. package/dist/utils/helper-28.js +1 -0
  228. package/dist/utils/helper-29.js +1 -0
  229. package/dist/utils/helper-3.js +1 -0
  230. package/dist/utils/helper-30.js +1 -0
  231. package/dist/utils/helper-4.js +1 -0
  232. package/dist/utils/helper-5.js +1 -0
  233. package/dist/utils/helper-6.js +1 -0
  234. package/dist/utils/helper-7.js +1 -0
  235. package/dist/utils/helper-8.js +1 -0
  236. package/dist/utils/helper-9.js +1 -0
  237. package/dist/utils/index.js +280 -0
  238. package/dist/utils/insta-mqtt-helper.js +128 -0
  239. package/examples/listen-to-messages.js +86 -0
  240. 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
+