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
@@ -0,0 +1,1731 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnhancedDirectCommands = void 0;
4
+
5
+ const shared_1 = require("../../shared");
6
+ const uuid_1 = require("uuid");
7
+ const constants_1 = require("../../constants");
8
+ const thrift_1 = require("../../thrift");
9
+
10
+ /**
11
+ * EnhancedDirectCommands
12
+ *
13
+ * - Full, self-contained class that publishes correctly-formatted payloads to Instagram's
14
+ * Direct MQTT (Thrift + compressed payloads).
15
+ * - Updated sendLocation implementation:
16
+ * 1) try publish a story with a Location sticker (preferred, matches APK behavior)
17
+ * 2) share that story to the thread (reel/media_share)
18
+ * 3) fallback: send a link to /explore/locations/{placeId}/ if (1) fails
19
+ *
20
+ * Note: server-side validation may still reject location stickers in some contexts.
21
+ */
22
+ class EnhancedDirectCommands {
23
+ constructor(client) {
24
+ this.realtimeClient = client;
25
+ this.enhancedDebug = (0, shared_1.debugChannel)('realtime', 'enhanced-commands');
26
+
27
+ // Foreground state config for Thrift encoding (matching instagram_mqtt)
28
+ this.foregroundStateConfig = [
29
+ thrift_1.ThriftDescriptors.boolean('inForegroundApp', 1),
30
+ thrift_1.ThriftDescriptors.boolean('inForegroundDevice', 2),
31
+ thrift_1.ThriftDescriptors.int32('keepAliveTimeout', 3),
32
+ thrift_1.ThriftDescriptors.listOfBinary('subscribeTopics', 4),
33
+ thrift_1.ThriftDescriptors.listOfBinary('subscribeGenericTopics', 5),
34
+ thrift_1.ThriftDescriptors.listOfBinary('unsubscribeTopics', 6),
35
+ thrift_1.ThriftDescriptors.listOfBinary('unsubscribeGenericTopics', 7),
36
+ thrift_1.ThriftDescriptors.int64('requestId', 8),
37
+ ];
38
+ }
39
+
40
+ /**
41
+ * Attempt to locate the MQTT client object on the realtime client.
42
+ * Many wrappers expose mqtt under different property names.
43
+ */
44
+ getMqtt() {
45
+ const candidates = [
46
+ 'mqtt',
47
+ '_mqtt',
48
+ 'client',
49
+ '_client',
50
+ 'connection',
51
+ 'mqttClient',
52
+ ];
53
+ let mqtt = null;
54
+ for (const key of candidates) {
55
+ if (this.realtimeClient && Object.prototype.hasOwnProperty.call(this.realtimeClient, key) && this.realtimeClient[key]) {
56
+ mqtt = this.realtimeClient[key];
57
+ break;
58
+ }
59
+ }
60
+ // fallback: maybe the realtimeClient itself *is* the mqtt client
61
+ if (!mqtt && this.realtimeClient && typeof this.realtimeClient.publish === 'function') {
62
+ mqtt = this.realtimeClient;
63
+ }
64
+
65
+ if (!mqtt || typeof mqtt.publish !== 'function') {
66
+ throw new Error('MQTT client not available or does not expose publish(). Found client keys: ' +
67
+ (this.realtimeClient ? Object.keys(this.realtimeClient).join(',') : 'none'));
68
+ }
69
+ return mqtt;
70
+ }
71
+
72
+ /**
73
+ * Robust mqtt publish wrapper - handles both:
74
+ * - mqtt.publish({ topic, payload, qosLevel }) returning a Promise or using callback
75
+ * - mqtt.publish(topic, payload, { qos }, cb)
76
+ */
77
+ async publishToMqtt(mqtt, publishObj) {
78
+ const topic = publishObj.topic;
79
+ const payload = publishObj.payload;
80
+ const qosLevel = typeof publishObj.qosLevel !== 'undefined' ? publishObj.qosLevel : 1;
81
+
82
+ // Try object-style publish first (some wrappers expect object)
83
+ try {
84
+ const maybePromise = mqtt.publish({
85
+ topic,
86
+ payload,
87
+ qosLevel,
88
+ });
89
+ if (maybePromise && typeof maybePromise.then === 'function') {
90
+ return await maybePromise;
91
+ }
92
+ // if it returned synchronously, maybe it still used callback style
93
+ return await new Promise((resolve, reject) => {
94
+ try {
95
+ mqtt.publish({ topic, payload, qosLevel }, (err, res) => {
96
+ if (err)
97
+ return reject(err);
98
+ return resolve(res);
99
+ });
100
+ } catch (err) {
101
+ reject(err);
102
+ }
103
+ });
104
+ } catch (e) {
105
+ // fallthrough to positional try
106
+ }
107
+
108
+ // Try positional-style publish (topic, payload, options, callback)
109
+ try {
110
+ return await new Promise((resolve, reject) => {
111
+ try {
112
+ mqtt.publish(topic, payload, { qos: qosLevel }, (err, res) => {
113
+ if (err)
114
+ return reject(err);
115
+ return resolve(res);
116
+ });
117
+ } catch (err) {
118
+ reject(err);
119
+ }
120
+ });
121
+ } catch (e) {
122
+ // final fallback: some clients return synchronously or throw - try positional without callback
123
+ try {
124
+ const res = mqtt.publish(topic, payload, { qos: qosLevel });
125
+ if (res && typeof res.then === 'function') {
126
+ return await res;
127
+ }
128
+ // last attempt: resolve with returned value
129
+ return res;
130
+ } catch (err) {
131
+ // give clear error
132
+ throw new Error(`MQTT publish failed: no known publish signature worked. Errors: ${err && err.message ? err.message : String(err)}`);
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Send foreground state via MQTT with Thrift encoding (matching instagram_mqtt)
139
+ */
140
+ async sendForegroundState(state) {
141
+ this.enhancedDebug(`Updated foreground state: ${JSON.stringify(state)}`);
142
+
143
+ try {
144
+ const mqtt = this.getMqtt();
145
+
146
+ const thriftBuffer = (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig);
147
+ const concat = Buffer.concat([
148
+ Buffer.alloc(1, 0),
149
+ thriftBuffer
150
+ ]);
151
+
152
+ // ensure we pass Buffer to compressDeflate
153
+ const payload = await (0, shared_1.compressDeflate)(concat);
154
+
155
+ const result = await this.publishToMqtt(mqtt, {
156
+ topic: constants_1.Topics.FOREGROUND_STATE.id,
157
+ payload: payload,
158
+ qosLevel: 1,
159
+ });
160
+
161
+ // Update keepAlive if provided
162
+ if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
163
+ mqtt.keepAlive = state.keepAliveTimeout;
164
+ }
165
+
166
+ this.enhancedDebug(`✅ Foreground state updated via MQTT!`);
167
+ return result;
168
+ } catch (err) {
169
+ this.enhancedDebug(`Foreground state failed: ${err && err.message ? err.message : String(err)}`);
170
+ throw err;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Base command sender (matching instagram_mqtt format)
176
+ * It encodes the command as JSON, compresses, and publishes to SEND_MESSAGE topic.
177
+ */
178
+ async sendCommand({ action, data, threadId, clientContext }) {
179
+ try {
180
+ const mqtt = this.getMqtt();
181
+
182
+ if (clientContext) {
183
+ data.client_context = clientContext;
184
+ }
185
+
186
+ const json = JSON.stringify({
187
+ action,
188
+ thread_id: threadId,
189
+ ...data,
190
+ });
191
+
192
+ // ensure Buffer (some compress implementations expect Buffer)
193
+ const payload = await (0, shared_1.compressDeflate)(Buffer.from(json));
194
+
195
+ return this.publishToMqtt(mqtt, {
196
+ topic: constants_1.Topics.SEND_MESSAGE.id,
197
+ qosLevel: 1,
198
+ payload: payload,
199
+ });
200
+ } catch (err) {
201
+ this.enhancedDebug(`sendCommand failed: ${err && err.message ? err.message : String(err)}`);
202
+ throw err;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Base item sender (matching instagram_mqtt format)
208
+ */
209
+ async sendItem({ threadId, itemType, data, clientContext }) {
210
+ return this.sendCommand({
211
+ action: 'send_item',
212
+ threadId,
213
+ clientContext: clientContext || (0, uuid_1.v4)(),
214
+ data: {
215
+ item_type: itemType,
216
+ ...data,
217
+ },
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Send text via MQTT
223
+ */
224
+ async sendText({ text, clientContext, threadId }) {
225
+ this.enhancedDebug(`Sending text to ${threadId}: "${text}"`);
226
+
227
+ const result = await this.sendItem({
228
+ itemType: 'text',
229
+ threadId,
230
+ clientContext,
231
+ data: {
232
+ text,
233
+ },
234
+ });
235
+
236
+ this.enhancedDebug(`✅ Text sent via MQTT!`);
237
+ return result;
238
+ }
239
+
240
+ /**
241
+ * Alias for sendText
242
+ */
243
+ async sendTextViaRealtime(threadId, text, clientContext) {
244
+ return this.sendText({
245
+ text,
246
+ threadId,
247
+ clientContext,
248
+ });
249
+ }
250
+
251
+ /**
252
+ * Send hashtag via MQTT
253
+ */
254
+ async sendHashtag({ text, threadId, hashtag, clientContext }) {
255
+ this.enhancedDebug(`Sending hashtag #${hashtag} to ${threadId}`);
256
+
257
+ const result = await this.sendItem({
258
+ itemType: 'hashtag',
259
+ threadId,
260
+ clientContext,
261
+ data: {
262
+ text: text || '',
263
+ hashtag,
264
+ item_id: hashtag,
265
+ },
266
+ });
267
+
268
+ this.enhancedDebug(`✅ Hashtag sent via MQTT!`);
269
+ return result;
270
+ }
271
+
272
+ /**
273
+ * Send like via MQTT
274
+ */
275
+ async sendLike({ threadId, clientContext }) {
276
+ this.enhancedDebug(`Sending like in thread ${threadId}`);
277
+
278
+ const result = await this.sendItem({
279
+ itemType: 'like',
280
+ threadId,
281
+ clientContext,
282
+ data: {},
283
+ });
284
+
285
+ this.enhancedDebug(`✅ Like sent via MQTT!`);
286
+ return result;
287
+ }
288
+
289
+ /**
290
+ * Send location via MQTT (reworked)
291
+ *
292
+ * Now:
293
+ * - Tries to publish a Story with a Location sticker (preferred; matches APK behavior)
294
+ * - Shares that Story to the thread (reel_share / media_share)
295
+ * - If any step fails, falls back to sending a link to /explore/locations/{placeId}/
296
+ *
297
+ * venue shape expected:
298
+ * { id, name, address, lat, lng, facebook_places_id, external_source }
299
+ */
300
+ async sendLocation({ threadId, clientContext, venue, text = '' }) {
301
+ this.enhancedDebug(`Attempting to send location to ${threadId}. Venue: ${venue ? JSON.stringify(venue) : 'none'}`);
302
+
303
+ // Basic validation - need at least an id (or facebook_places_id)
304
+ const hasCoords = venue && typeof venue.lat === 'number' && typeof venue.lng === 'number';
305
+ const hasId = venue && (venue.facebook_places_id || venue.id);
306
+
307
+ // prefer facebook_places_id if present
308
+ const placeId = venue && (venue.facebook_places_id || venue.id);
309
+
310
+ // create sticker structure (format used by many reverse-engineered clients)
311
+ const sticker = this.createLocationStickerFromVenue(venue);
312
+
313
+ // If we have an ig client capable of publishing stories, attempt the sticker flow.
314
+ const ig = this.realtimeClient && this.realtimeClient.ig;
315
+ if (ig && typeof ig.publish === 'object' && typeof ig.publish.story === 'function') {
316
+ try {
317
+ // Use a tiny placeholder image if caller didn't provide one (1x1 PNG)
318
+ const SINGLE_PIXEL_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=";
319
+ const photoBuffer = Buffer.from(SINGLE_PIXEL_PNG_BASE64, 'base64');
320
+
321
+ this.enhancedDebug(`Publishing story with location sticker (venue id: ${placeId})...`);
322
+
323
+ // Build publish params. Many clients accept `file` and `stickers` array like this.
324
+ const publishParams = {
325
+ file: photoBuffer,
326
+ stickers: [sticker],
327
+ // optional: caption/text not always supported on story publish in the same way; keep minimal
328
+ };
329
+
330
+ const publishResult = await ig.publish.story(publishParams);
331
+
332
+ this.enhancedDebug(`Story publish result: ${publishResult ? JSON.stringify(publishResult).slice(0, 400) : 'no result'}`);
333
+
334
+ // Try to resolve story media id
335
+ let storyId = null;
336
+ if (publishResult) {
337
+ // common fields returned by private clients
338
+ storyId = publishResult.media && (publishResult.media.pk || publishResult.media.id || publishResult.media_id) ||
339
+ publishResult.item_id || publishResult.upload_id || publishResult.media_id;
340
+ }
341
+
342
+ // If we didn't get a story id, try common fallback fields
343
+ if (!storyId && publishResult && publishResult.params && publishResult.params.upload_id) {
344
+ storyId = publishResult.params.upload_id;
345
+ }
346
+
347
+ if (!storyId) {
348
+ // If publish succeeded but no usable id found, still attempt best-effort: some clients return a "media" object later on pubsub
349
+ this.enhancedDebug(`Could not determine story id from publish result; continuing to fallback to link/share attempt.`);
350
+ throw new Error('Could not determine story id from publish result.');
351
+ }
352
+
353
+ // Now share the story to the thread (reel_share/media_share)
354
+ // Use the existing helper sendUserStory if available (it uses itemType: 'reel_share')
355
+ this.enhancedDebug(`Sharing published story ${storyId} to thread ${threadId}...`);
356
+ try {
357
+ const shareResult = await this.sendUserStory({
358
+ text: text || '',
359
+ storyId: storyId,
360
+ threadId,
361
+ clientContext: clientContext || (0, uuid_1.v4)(),
362
+ });
363
+ this.enhancedDebug(`✅ Location story shared to thread via MQTT! (storyId=${storyId})`);
364
+ return shareResult;
365
+ } catch (shareErr) {
366
+ // If sharing via MQTT fails, try a fallback: some clients expose direct send helper
367
+ this.enhancedDebug(`Sharing story to thread failed: ${shareErr && shareErr.message ? shareErr.message : String(shareErr)}`);
368
+ // fall through to fallback link below
369
+ throw shareErr;
370
+ }
371
+ } catch (err) {
372
+ this.enhancedDebug(`Story-with-sticker attempt failed: ${err && err.message ? err.message : String(err)} - falling back to link`);
373
+ // fallthrough to fallback block below
374
+ }
375
+ } else {
376
+ this.enhancedDebug(`ig.publish.story not available on realtimeClient.ig — will use fallback link if possible.`);
377
+ }
378
+
379
+ // Fallback: send as a link to the location explore page (guaranteed to render in DM)
380
+ if (hasId) {
381
+ const link = `https://www.instagram.com/explore/locations/${placeId}/`;
382
+ this.enhancedDebug(`Sending location fallback link: ${link}`);
383
+
384
+ try {
385
+ const fallback = await this.sendItem({
386
+ itemType: 'link',
387
+ threadId,
388
+ clientContext: clientContext || (0, uuid_1.v4)(),
389
+ data: {
390
+ link_text: text || (venue && venue.name) || 'Location',
391
+ link_urls: [link],
392
+ },
393
+ });
394
+ this.enhancedDebug(`✅ Location fallback link sent via MQTT!`);
395
+ return fallback;
396
+ } catch (err) {
397
+ this.enhancedDebug(`Fallback link send failed: ${err && err.message ? err.message : String(err)}`);
398
+ throw err;
399
+ }
400
+ }
401
+
402
+ // If we don't have any usable info, throw an error
403
+ throw new Error('sendLocation requires a venue object with at least id (or facebook_places_id).');
404
+ }
405
+
406
+ /**
407
+ * Helper that returns a "location sticker" object compatible with many reverse-engineered
408
+ * clients / the publish.story helper. This mirrors the "LocationStickerClientModel" semantics.
409
+ *
410
+ * Format produced:
411
+ * {
412
+ * type: 'location',
413
+ * location_id: '12345',
414
+ * location: { lat, lng, name, address, external_source, facebook_places_id },
415
+ * x: 0.5,
416
+ * y: 0.5,
417
+ * width: 0.7,
418
+ * height: 0.15,
419
+ * rotation: 0,
420
+ * is_pinned: false
421
+ * }
422
+ */
423
+ createLocationStickerFromVenue(venue) {
424
+ // Defensive defaults
425
+ if (!venue) {
426
+ throw new Error('venue required to create location sticker');
427
+ }
428
+ const placeId = venue.facebook_places_id || String(venue.id || '');
429
+ const lat = (typeof venue.lat === 'number') ? venue.lat : (venue.location && venue.location.lat) || null;
430
+ const lng = (typeof venue.lng === 'number') ? venue.lng : (venue.location && venue.location.lng) || null;
431
+
432
+ const locationObj = {
433
+ lat: lat,
434
+ lng: lng,
435
+ name: venue.name || '',
436
+ address: venue.address || '',
437
+ external_source: venue.external_source || 'facebook_places',
438
+ facebook_places_id: placeId || '',
439
+ };
440
+
441
+ // Sticker appearance / position defaults - caller may tweak later if needed
442
+ const sticker = {
443
+ type: 'location',
444
+ // some clients expect locationId, some expect venue_id or location_id
445
+ locationId: placeId,
446
+ venue_id: placeId,
447
+ location: locationObj,
448
+ x: 0.5,
449
+ y: 0.5,
450
+ width: 0.7,
451
+ height: 0.15,
452
+ rotation: 0,
453
+ isPinned: false,
454
+ };
455
+
456
+ return sticker;
457
+ }
458
+
459
+ /**
460
+ * Helper: search places via the Instagram client (optional).
461
+ * If your realtimeClient has an .ig.request helper, this will call the appropriate
462
+ * endpoint to fetch place metadata, and then call sendLocation with the full venue.
463
+ *
464
+ * This is optional — you can call sendLocation yourself with the venue object you already have.
465
+ */
466
+ async searchAndSendLocation({ threadId, query, lat, lng, clientContext }) {
467
+ const ig = this.realtimeClient && this.realtimeClient.ig;
468
+ if (!ig || !ig.request) {
469
+ throw new Error('Instagram client (ig.request) not available on realtimeClient. Provide `venue` directly to sendLocation instead.');
470
+ }
471
+
472
+ this.enhancedDebug(`Searching location: ${query} at ${lat},${lng}`);
473
+
474
+ // Example endpoint - private API endpoints vary. If your client has a helper method,
475
+ // prefer that. This tries a common private endpoint pattern.
476
+ const url = '/fbsearch/places/';
477
+ const params = {
478
+ search_media_creation: false,
479
+ rank_token: (0, uuid_1.v4)(),
480
+ query: query,
481
+ latitude: lat,
482
+ longitude: lng,
483
+ };
484
+
485
+ try {
486
+ const res = await ig.request.send({
487
+ url: url,
488
+ method: 'GET',
489
+ qs: params,
490
+ });
491
+
492
+ // Parse response - different private API clients return different shapes.
493
+ // We try to find the first usable place with id/lat/lng/name.
494
+ const places = (res && (res.places || res.items || res.results)) || [];
495
+ const place = places.find(p => p && (p.pk || p.place || p.location || p.facebook_places_id)) || places[0];
496
+
497
+ if (!place) {
498
+ throw new Error('No places found from search.');
499
+ }
500
+
501
+ // Normalize to `venue` shape our sendLocation expects
502
+ const venue = {
503
+ id: String(place.pk || (place.place && place.place.id) || place.id || place.facebook_places_id || ''),
504
+ name: place.name || (place.place && place.place.name) || '',
505
+ address: place.address || (place.place && place.place.address) || '',
506
+ lat: (place.location && (place.location.lat || place.location.latitude)) || place.lat || null,
507
+ lng: (place.location && (place.location.lng || place.location.longitude)) || place.lng || null,
508
+ facebook_places_id: place.facebook_places_id || (place.place && place.place.id) || String(place.pk || ''),
509
+ external_source: place.external_source || 'facebook_places',
510
+ };
511
+
512
+ return await this.sendLocation({ threadId, clientContext, venue });
513
+ } catch (err) {
514
+ this.enhancedDebug(`place search/send failed: ${err && err.message ? err.message : String(err)}`);
515
+ throw err;
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Send media via MQTT (media_share)
521
+ */
522
+ async sendMedia({ text, mediaId, threadId, clientContext }) {
523
+ this.enhancedDebug(`Sending media ${mediaId} to ${threadId}`);
524
+
525
+ const result = await this.sendItem({
526
+ itemType: 'media_share',
527
+ threadId,
528
+ clientContext,
529
+ data: {
530
+ text: text || '',
531
+ media_id: mediaId,
532
+ },
533
+ });
534
+
535
+ this.enhancedDebug(`✅ Media sent via MQTT!`);
536
+ return result;
537
+ }
538
+
539
+ /**
540
+ * Send profile via MQTT
541
+ */
542
+ async sendProfile({ text, userId, threadId, clientContext }) {
543
+ this.enhancedDebug(`Sending profile ${userId} to ${threadId}`);
544
+
545
+ const result = await this.sendItem({
546
+ itemType: 'profile',
547
+ threadId,
548
+ clientContext,
549
+ data: {
550
+ text: text || '',
551
+ profile_user_id: userId,
552
+ item_id: userId,
553
+ },
554
+ });
555
+
556
+ this.enhancedDebug(`✅ Profile sent via MQTT!`);
557
+ return result;
558
+ }
559
+
560
+ /**
561
+ * Send reaction via MQTT
562
+ */
563
+ async sendReaction({ itemId, reactionType, clientContext, threadId, reactionStatus, targetItemType, emoji }) {
564
+ this.enhancedDebug(`Sending ${reactionType || 'like'} reaction to message ${itemId}`);
565
+
566
+ const result = await this.sendItem({
567
+ itemType: 'reaction',
568
+ threadId,
569
+ clientContext,
570
+ data: {
571
+ item_id: itemId,
572
+ node_type: 'item',
573
+ reaction_type: reactionType || (emoji ? 'emoji' : 'like'),
574
+ reaction_status: reactionStatus || 'created',
575
+ target_item_type: targetItemType,
576
+ emoji: emoji || '',
577
+ },
578
+ });
579
+
580
+ this.enhancedDebug(`✅ Reaction sent via MQTT!`);
581
+ return result;
582
+ }
583
+
584
+ /**
585
+ * Send user story via MQTT (reel_share)
586
+ */
587
+ async sendUserStory({ text, storyId, threadId, clientContext }) {
588
+ this.enhancedDebug(`Sending story ${storyId} to ${threadId}`);
589
+
590
+ const result = await this.sendItem({
591
+ itemType: 'reel_share',
592
+ threadId,
593
+ clientContext,
594
+ data: {
595
+ text: text || '',
596
+ item_id: storyId,
597
+ media_id: storyId,
598
+ },
599
+ });
600
+
601
+ this.enhancedDebug(`✅ Story sent via MQTT!`);
602
+ return result;
603
+ }
604
+
605
+ /**
606
+ * Mark as seen via REST API (with MQTT fallback)
607
+ *
608
+ * Instagram requires the REST endpoint /api/v1/direct_v2/threads/{threadId}/items/{itemId}/seen/
609
+ * for marking messages as seen. The MQTT mark_seen action on topic 132 is not processed
610
+ * by Instagram's servers. This method uses the ig client's REST API which is accessible
611
+ * through the realtime client.
612
+ */
613
+ async markAsSeen({ threadId, itemId }) {
614
+ this.enhancedDebug(`Marking message ${itemId} as seen in thread ${threadId}`);
615
+
616
+ const ig = this.realtimeClient && this.realtimeClient.ig;
617
+ if (ig && ig.request) {
618
+ try {
619
+ const clientContext = (0, uuid_1.v4)();
620
+ const form = {
621
+ _uuid: ig.state.uuid,
622
+ device_id: ig.state.deviceId,
623
+ use_unified_inbox: true,
624
+ action: 'mark_seen',
625
+ thread_id: threadId,
626
+ item_id: itemId,
627
+ client_context: clientContext,
628
+ mutation_token: clientContext,
629
+ };
630
+
631
+ const response = await ig.request.send({
632
+ url: `/api/v1/direct_v2/threads/${threadId}/items/${itemId}/seen/`,
633
+ method: 'POST',
634
+ form: form,
635
+ });
636
+
637
+ const body = response.body || response.data || response;
638
+ const parsed = typeof body === 'string' ? JSON.parse(body) : body;
639
+
640
+ if (parsed && parsed.status === 'ok') {
641
+ this.enhancedDebug(`✅ Message marked as seen via REST API! Status: ok`);
642
+ return parsed;
643
+ }
644
+
645
+ this.enhancedDebug(`REST mark_seen response: ${JSON.stringify(parsed).slice(0, 300)}`);
646
+ return parsed;
647
+ } catch (restErr) {
648
+ this.enhancedDebug(`REST mark_seen failed: ${restErr && restErr.message ? restErr.message : String(restErr)}, falling back to MQTT`);
649
+ }
650
+ } else {
651
+ this.enhancedDebug(`ig.request not available, using MQTT fallback for mark_seen`);
652
+ }
653
+
654
+ const result = await this.sendCommand({
655
+ action: 'mark_seen',
656
+ threadId,
657
+ data: {
658
+ item_id: itemId,
659
+ },
660
+ });
661
+
662
+ this.enhancedDebug(`⚠️ Message mark_seen sent via MQTT (fallback - may not be processed by Instagram)`);
663
+ return result;
664
+ }
665
+
666
+ /**
667
+ * Indicate activity (typing) via MQTT (activity_status)
668
+ */
669
+ async indicateActivity({ threadId, isActive, clientContext }) {
670
+ const active = typeof isActive === 'undefined' ? true : isActive;
671
+ this.enhancedDebug(`Indicating ${active ? 'typing' : 'stopped'} in thread ${threadId}`);
672
+
673
+ const result = await this.sendCommand({
674
+ action: 'indicate_activity',
675
+ threadId,
676
+ clientContext: clientContext || (0, uuid_1.v4)(),
677
+ data: {
678
+ activity_status: active ? '1' : '0',
679
+ },
680
+ });
681
+
682
+ this.enhancedDebug(`✅ Activity indicator sent via MQTT!`);
683
+ return result;
684
+ }
685
+
686
+ /**
687
+ * Delete message via MQTT
688
+ */
689
+ async deleteMessage(threadId, itemId) {
690
+ this.enhancedDebug(`Deleting message ${itemId} from thread ${threadId}`);
691
+
692
+ const result = await this.sendCommand({
693
+ action: 'delete_item',
694
+ threadId,
695
+ clientContext: (0, uuid_1.v4)(),
696
+ data: {
697
+ item_id: itemId,
698
+ },
699
+ });
700
+
701
+ this.enhancedDebug(`✅ Message deleted via MQTT!`);
702
+ return result;
703
+ }
704
+
705
+ /**
706
+ * Edit message via MQTT
707
+ */
708
+ async editMessage(threadId, itemId, newText) {
709
+ this.enhancedDebug(`Editing message ${itemId}: "${newText}"`);
710
+
711
+ const result = await this.sendCommand({
712
+ action: 'edit_item',
713
+ threadId,
714
+ clientContext: (0, uuid_1.v4)(),
715
+ data: {
716
+ item_id: itemId,
717
+ text: newText,
718
+ },
719
+ });
720
+
721
+ this.enhancedDebug(`✅ Message edited via MQTT!`);
722
+ return result;
723
+ }
724
+
725
+ /**
726
+ * Reply to message via MQTT (Quote Reply)
727
+ */
728
+ async replyToMessage(threadId, messageId, replyText) {
729
+ this.enhancedDebug(`Replying to ${messageId} in thread ${threadId}: "${replyText}"`);
730
+
731
+ const result = await this.sendItem({
732
+ itemType: 'text',
733
+ threadId,
734
+ clientContext: (0, uuid_1.v4)(),
735
+ data: {
736
+ text: replyText,
737
+ replied_to_item_id: messageId,
738
+ },
739
+ });
740
+
741
+ this.enhancedDebug(`✅ Reply sent via MQTT!`);
742
+ return result;
743
+ }
744
+
745
+ /**
746
+ * Add member to thread via MQTT
747
+ */
748
+ async addMemberToThread(threadId, userId) {
749
+ this.enhancedDebug(`Adding user ${userId} to thread ${threadId}`);
750
+
751
+ const result = await this.sendCommand({
752
+ action: 'add_users',
753
+ threadId,
754
+ clientContext: (0, uuid_1.v4)(),
755
+ data: {
756
+ user_ids: Array.isArray(userId) ? userId : [userId],
757
+ },
758
+ });
759
+
760
+ this.enhancedDebug(`✅ Member added to thread via MQTT!`);
761
+ return result;
762
+ }
763
+
764
+ /**
765
+ * Remove member from thread via MQTT
766
+ */
767
+ async removeMemberFromThread(threadId, userId) {
768
+ this.enhancedDebug(`Removing user ${userId} from thread ${threadId}`);
769
+
770
+ const result = await this.sendCommand({
771
+ action: 'remove_users',
772
+ threadId,
773
+ clientContext: (0, uuid_1.v4)(),
774
+ data: {
775
+ user_ids: Array.isArray(userId) ? userId : [userId],
776
+ },
777
+ });
778
+
779
+ this.enhancedDebug(`✅ Member removed from thread via MQTT!`);
780
+ return result;
781
+ }
782
+
783
+ /**
784
+ * Leave thread via MQTT
785
+ */
786
+ async leaveThread(threadId) {
787
+ this.enhancedDebug(`Leaving thread ${threadId}`);
788
+
789
+ const result = await this.sendCommand({
790
+ action: 'leave',
791
+ threadId,
792
+ clientContext: (0, uuid_1.v4)(),
793
+ data: {},
794
+ });
795
+
796
+ this.enhancedDebug(`✅ Left thread via MQTT!`);
797
+ return result;
798
+ }
799
+
800
+ /**
801
+ * Mute thread via MQTT
802
+ */
803
+ async muteThread(threadId, muteUntil = null) {
804
+ this.enhancedDebug(`Muting thread ${threadId}`);
805
+
806
+ const result = await this.sendCommand({
807
+ action: 'mute',
808
+ threadId,
809
+ clientContext: (0, uuid_1.v4)(),
810
+ data: {
811
+ mute_until: muteUntil,
812
+ },
813
+ });
814
+
815
+ this.enhancedDebug(`✅ Thread muted via MQTT!`);
816
+ return result;
817
+ }
818
+
819
+ /**
820
+ * Unmute thread via MQTT
821
+ */
822
+ async unmuteThread(threadId) {
823
+ this.enhancedDebug(`Unmuting thread ${threadId}`);
824
+
825
+ const result = await this.sendCommand({
826
+ action: 'unmute',
827
+ threadId,
828
+ clientContext: (0, uuid_1.v4)(),
829
+ data: {},
830
+ });
831
+
832
+ this.enhancedDebug(`✅ Thread unmuted via MQTT!`);
833
+ return result;
834
+ }
835
+
836
+ /**
837
+ * Update thread title via MQTT
838
+ */
839
+ async updateThreadTitle(threadId, title) {
840
+ this.enhancedDebug(`Updating thread ${threadId} title to: "${title}"`);
841
+
842
+ const result = await this.sendCommand({
843
+ action: 'update_title',
844
+ threadId,
845
+ clientContext: (0, uuid_1.v4)(),
846
+ data: {
847
+ title: title,
848
+ },
849
+ });
850
+
851
+ this.enhancedDebug(`✅ Thread title updated via MQTT!`);
852
+ return result;
853
+ }
854
+
855
+ /**
856
+ * Send link via MQTT
857
+ */
858
+ async sendLink({ link, text, threadId, clientContext }) {
859
+ this.enhancedDebug(`Sending link ${link} to ${threadId}`);
860
+
861
+ const result = await this.sendItem({
862
+ itemType: 'link',
863
+ threadId,
864
+ clientContext,
865
+ data: {
866
+ link_text: text || '',
867
+ // use array (not JSON string) to match instagram_mqtt expectations
868
+ link_urls: [link],
869
+ },
870
+ });
871
+
872
+ this.enhancedDebug(`✅ Link sent via MQTT!`);
873
+ return result;
874
+ }
875
+
876
+ /**
877
+ * Send animated media (GIF/sticker) via MQTT
878
+ */
879
+ async sendAnimatedMedia({ id, isSticker, threadId, clientContext }) {
880
+ this.enhancedDebug(`Sending animated media ${id} to ${threadId}`);
881
+
882
+ const result = await this.sendItem({
883
+ itemType: 'animated_media',
884
+ threadId,
885
+ clientContext,
886
+ data: {
887
+ id: id,
888
+ is_sticker: isSticker || false,
889
+ },
890
+ });
891
+
892
+ this.enhancedDebug(`✅ Animated media sent via MQTT!`);
893
+ return result;
894
+ }
895
+
896
+ /**
897
+ * Send voice message via MQTT (after upload)
898
+ */
899
+ async sendVoice({ uploadId, waveform, waveformSamplingFrequencyHz, threadId, clientContext }) {
900
+ this.enhancedDebug(`Sending voice ${uploadId} to ${threadId}`);
901
+
902
+ const result = await this.sendItem({
903
+ itemType: 'voice_media',
904
+ threadId,
905
+ clientContext,
906
+ data: {
907
+ upload_id: uploadId,
908
+ waveform: waveform,
909
+ waveform_sampling_frequency_hz: waveformSamplingFrequencyHz || 10,
910
+ },
911
+ });
912
+
913
+ this.enhancedDebug(`✅ Voice sent via MQTT!`);
914
+ return result;
915
+ }
916
+
917
+ /**
918
+ * Send photo via Realtime (Upload + Broadcast)
919
+ * Note: depends on realtimeClient.ig.request for uploading
920
+ */
921
+ async sendPhotoViaRealtime({ photoBuffer, threadId, caption = '', mimeType = 'image/jpeg', clientContext }) {
922
+ this.enhancedDebug(`Sending photo to thread ${threadId} via Realtime`);
923
+
924
+ try {
925
+ if (!photoBuffer || !Buffer.isBuffer(photoBuffer) || photoBuffer.length === 0) {
926
+ throw new Error('photoBuffer must be a non-empty Buffer');
927
+ }
928
+ if (!threadId) {
929
+ throw new Error('threadId is required');
930
+ }
931
+
932
+ const ig = this.realtimeClient.ig;
933
+ if (!ig || !ig.request) {
934
+ throw new Error('Instagram client not available. Make sure you are logged in.');
935
+ }
936
+
937
+ this.enhancedDebug(`Step 1: Uploading photo (${photoBuffer.length} bytes)...`);
938
+
939
+ const uploadId = Date.now().toString();
940
+ const objectName = `${(0, uuid_1.v4)()}.${mimeType === 'image/png' ? 'png' : 'jpg'}`;
941
+
942
+ const isJpeg = mimeType === 'image/jpeg' || mimeType === 'image/jpg';
943
+ const compression = isJpeg
944
+ ? '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}'
945
+ : '{"lib_name":"png","lib_version":"1.0","quality":"100"}';
946
+
947
+ const ruploadParams = {
948
+ upload_id: uploadId,
949
+ media_type: 1,
950
+ image_compression: compression,
951
+ xsharing_user_ids: JSON.stringify([]),
952
+ is_clips_media: false,
953
+ };
954
+
955
+ const uploadHeaders = {
956
+ 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
957
+ 'Content-Type': mimeType,
958
+ 'X_FB_PHOTO_WATERFALL_ID': (0, uuid_1.v4)(),
959
+ 'X-Entity-Type': mimeType,
960
+ 'X-Entity-Length': String(photoBuffer.length),
961
+ 'Content-Length': String(photoBuffer.length),
962
+ };
963
+
964
+ const uploadUrl = `/rupload_igphoto/${objectName}`;
965
+
966
+ let serverUploadId = uploadId;
967
+ try {
968
+ const uploadResponse = await ig.request.send({
969
+ url: uploadUrl,
970
+ method: 'POST',
971
+ headers: uploadHeaders,
972
+ body: photoBuffer,
973
+ });
974
+
975
+ if (uploadResponse && typeof uploadResponse === 'object' && uploadResponse.upload_id) {
976
+ serverUploadId = uploadResponse.upload_id;
977
+ }
978
+ this.enhancedDebug(`✅ Photo uploaded! upload_id: ${serverUploadId}`);
979
+ } catch (uploadErr) {
980
+ this.enhancedDebug(`Upload error: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
981
+ throw new Error(`Photo upload failed: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
982
+ }
983
+
984
+ this.enhancedDebug(`Step 2: Broadcasting photo to thread ${threadId}...`);
985
+
986
+ const broadcastForm = {
987
+ upload_id: serverUploadId,
988
+ action: 'send_item',
989
+ thread_ids: JSON.stringify([String(threadId)]),
990
+ };
991
+
992
+ if (caption) {
993
+ broadcastForm.caption = caption;
994
+ }
995
+
996
+ try {
997
+ const broadcastResponse = await ig.request.send({
998
+ url: '/direct_v2/threads/broadcast/upload_photo/',
999
+ method: 'POST',
1000
+ form: broadcastForm,
1001
+ });
1002
+
1003
+ this.enhancedDebug(`✅ Photo sent successfully to thread ${threadId}!`);
1004
+ return broadcastResponse;
1005
+ } catch (broadcastErr) {
1006
+ this.enhancedDebug(`Broadcast error: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
1007
+ throw new Error(`Photo broadcast failed: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
1008
+ }
1009
+
1010
+ } catch (err) {
1011
+ this.enhancedDebug(`sendPhotoViaRealtime failed: ${err && err.message ? err.message : String(err)}`);
1012
+ throw err;
1013
+ }
1014
+ }
1015
+
1016
+ /**
1017
+ * Alias for sendPhotoViaRealtime
1018
+ */
1019
+ async sendPhoto(options) {
1020
+ return this.sendPhotoViaRealtime(options);
1021
+ }
1022
+
1023
+ /**
1024
+ * Send video via Realtime (Upload + Broadcast)
1025
+ * Note: depends on realtimeClient.ig.request for uploading
1026
+ */
1027
+ async sendVideoViaRealtime({ videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280, clientContext }) {
1028
+ this.enhancedDebug(`Sending video to thread ${threadId} via Realtime`);
1029
+
1030
+ try {
1031
+ if (!videoBuffer || !Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {
1032
+ throw new Error('videoBuffer must be a non-empty Buffer');
1033
+ }
1034
+ if (!threadId) {
1035
+ throw new Error('threadId is required');
1036
+ }
1037
+
1038
+ const ig = this.realtimeClient.ig;
1039
+ if (!ig || !ig.request) {
1040
+ throw new Error('Instagram client not available. Make sure you are logged in.');
1041
+ }
1042
+
1043
+ this.enhancedDebug(`Step 1: Uploading video (${videoBuffer.length} bytes)...`);
1044
+
1045
+ const uploadId = Date.now().toString();
1046
+ const objectName = `${(0, uuid_1.v4)()}.mp4`;
1047
+
1048
+ const ruploadParams = {
1049
+ upload_id: uploadId,
1050
+ media_type: 2,
1051
+ xsharing_user_ids: JSON.stringify([]),
1052
+ upload_media_duration_ms: Math.round(duration * 1000),
1053
+ upload_media_width: width,
1054
+ upload_media_height: height,
1055
+ };
1056
+
1057
+ const uploadHeaders = {
1058
+ 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
1059
+ 'Content-Type': 'video/mp4',
1060
+ 'X_FB_VIDEO_WATERFALL_ID': (0, uuid_1.v4)(),
1061
+ 'X-Entity-Type': 'video/mp4',
1062
+ 'X-Entity-Length': String(videoBuffer.length),
1063
+ 'Content-Length': String(videoBuffer.length),
1064
+ 'Offset': '0',
1065
+ };
1066
+
1067
+ const uploadUrl = `/rupload_igvideo/${objectName}`;
1068
+
1069
+ let serverUploadId = uploadId;
1070
+ try {
1071
+ const uploadResponse = await ig.request.send({
1072
+ url: uploadUrl,
1073
+ method: 'POST',
1074
+ headers: uploadHeaders,
1075
+ body: videoBuffer,
1076
+ });
1077
+
1078
+ if (uploadResponse && typeof uploadResponse === 'object' && uploadResponse.upload_id) {
1079
+ serverUploadId = uploadResponse.upload_id;
1080
+ }
1081
+ this.enhancedDebug(`✅ Video uploaded! upload_id: ${serverUploadId}`);
1082
+ } catch (uploadErr) {
1083
+ this.enhancedDebug(`Video upload error: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
1084
+ throw new Error(`Video upload failed: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
1085
+ }
1086
+
1087
+ this.enhancedDebug(`Step 2: Broadcasting video to thread ${threadId}...`);
1088
+
1089
+ const broadcastForm = {
1090
+ upload_id: serverUploadId,
1091
+ action: 'send_item',
1092
+ thread_ids: JSON.stringify([String(threadId)]),
1093
+ video_result: '',
1094
+ };
1095
+
1096
+ if (caption) {
1097
+ broadcastForm.caption = caption;
1098
+ }
1099
+
1100
+ try {
1101
+ const broadcastResponse = await ig.request.send({
1102
+ url: '/direct_v2/threads/broadcast/upload_video/',
1103
+ method: 'POST',
1104
+ form: broadcastForm,
1105
+ });
1106
+
1107
+ this.enhancedDebug(`✅ Video sent successfully to thread ${threadId}!`);
1108
+ return broadcastResponse;
1109
+ } catch (broadcastErr) {
1110
+ this.enhancedDebug(`Video broadcast error: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
1111
+ throw new Error(`Video broadcast failed: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
1112
+ }
1113
+
1114
+ } catch (err) {
1115
+ this.enhancedDebug(`sendVideoViaRealtime failed: ${err && err.message ? err.message : String(err)}`);
1116
+ throw err;
1117
+ }
1118
+ }
1119
+
1120
+ /**
1121
+ * Alias for sendVideoViaRealtime
1122
+ */
1123
+ async sendVideo(options) {
1124
+ return this.sendVideoViaRealtime(options);
1125
+ }
1126
+
1127
+ /**
1128
+ * Approve pending thread via MQTT
1129
+ */
1130
+ async approveThread(threadId) {
1131
+ this.enhancedDebug(`Approving thread ${threadId}`);
1132
+
1133
+ const result = await this.sendCommand({
1134
+ action: 'approve',
1135
+ threadId,
1136
+ clientContext: (0, uuid_1.v4)(),
1137
+ data: {},
1138
+ });
1139
+
1140
+ this.enhancedDebug(`✅ Thread approved via MQTT!`);
1141
+ return result;
1142
+ }
1143
+
1144
+ /**
1145
+ * Decline pending thread via MQTT
1146
+ */
1147
+ async declineThread(threadId) {
1148
+ this.enhancedDebug(`Declining thread ${threadId}`);
1149
+
1150
+ const result = await this.sendCommand({
1151
+ action: 'decline',
1152
+ threadId,
1153
+ clientContext: (0, uuid_1.v4)(),
1154
+ data: {},
1155
+ });
1156
+
1157
+ this.enhancedDebug(`✅ Thread declined via MQTT!`);
1158
+ return result;
1159
+ }
1160
+
1161
+ /**
1162
+ * Block user in thread via MQTT
1163
+ */
1164
+ async blockUserInThread(threadId, userId) {
1165
+ this.enhancedDebug(`Blocking user ${userId} in thread ${threadId}`);
1166
+
1167
+ const result = await this.sendCommand({
1168
+ action: 'block',
1169
+ threadId,
1170
+ clientContext: (0, uuid_1.v4)(),
1171
+ data: {
1172
+ user_id: userId,
1173
+ },
1174
+ });
1175
+
1176
+ this.enhancedDebug(`✅ User blocked in thread via MQTT!`);
1177
+ return result;
1178
+ }
1179
+
1180
+ /**
1181
+ * Report thread via MQTT
1182
+ */
1183
+ async reportThread(threadId, reason) {
1184
+ this.enhancedDebug(`Reporting thread ${threadId}`);
1185
+
1186
+ const result = await this.sendCommand({
1187
+ action: 'report',
1188
+ threadId,
1189
+ clientContext: (0, uuid_1.v4)(),
1190
+ data: {
1191
+ reason: reason || 'spam',
1192
+ },
1193
+ });
1194
+
1195
+ this.enhancedDebug(`✅ Thread reported via MQTT!`);
1196
+ return result;
1197
+ }
1198
+
1199
+ /**
1200
+ * Remove reaction via MQTT
1201
+ */
1202
+ async removeReaction({ itemId, threadId, clientContext }) {
1203
+ this.enhancedDebug(`Removing reaction from message ${itemId}`);
1204
+
1205
+ const result = await this.sendItem({
1206
+ itemType: 'reaction',
1207
+ threadId,
1208
+ clientContext,
1209
+ data: {
1210
+ item_id: itemId,
1211
+ node_type: 'item',
1212
+ reaction_type: 'like',
1213
+ reaction_status: 'deleted',
1214
+ },
1215
+ });
1216
+
1217
+ this.enhancedDebug(`✅ Reaction removed via MQTT!`);
1218
+ return result;
1219
+ }
1220
+
1221
+ /**
1222
+ * Send disappearing photo via MQTT (broadcast only - requires pre-uploaded media)
1223
+ */
1224
+ async sendDisappearingPhoto({ uploadId, threadId, viewMode = 'once', clientContext }) {
1225
+ this.enhancedDebug(`Sending disappearing photo to ${threadId}`);
1226
+
1227
+ const ephemeralViewMode = (viewMode === 'replayable') ? 2 : 1;
1228
+
1229
+ const result = await this.sendItem({
1230
+ itemType: 'raven_media',
1231
+ threadId,
1232
+ clientContext,
1233
+ data: {
1234
+ upload_id: uploadId,
1235
+ view_mode: viewMode,
1236
+ ephemeral_media_view_mode: ephemeralViewMode,
1237
+ allow_full_aspect_ratio: true,
1238
+ send_attribution: 'direct_composer',
1239
+ },
1240
+ });
1241
+
1242
+ this.enhancedDebug(`✅ Disappearing photo sent via MQTT!`);
1243
+ return result;
1244
+ }
1245
+
1246
+ /**
1247
+ * Send disappearing video via MQTT (broadcast only - requires pre-uploaded media)
1248
+ */
1249
+ async sendDisappearingVideo({ uploadId, threadId, viewMode = 'once', clientContext }) {
1250
+ this.enhancedDebug(`Sending disappearing video to ${threadId}`);
1251
+
1252
+ const ephemeralViewMode = (viewMode === 'replayable') ? 2 : 1;
1253
+
1254
+ const result = await this.sendItem({
1255
+ itemType: 'raven_media',
1256
+ threadId,
1257
+ clientContext,
1258
+ data: {
1259
+ upload_id: uploadId,
1260
+ view_mode: viewMode,
1261
+ ephemeral_media_view_mode: ephemeralViewMode,
1262
+ allow_full_aspect_ratio: true,
1263
+ media_type: 2,
1264
+ send_attribution: 'direct_composer',
1265
+ },
1266
+ });
1267
+
1268
+ this.enhancedDebug(`✅ Disappearing video sent via MQTT!`);
1269
+ return result;
1270
+ }
1271
+
1272
+ /**
1273
+ * Send raven (view-once) photo - COMPLETE flow:
1274
+ * 1. Upload photo to rupload.facebook.com/messenger_image/ (required for raven)
1275
+ * 2. Broadcast via REST to /direct_v2/threads/broadcast/raven_attachment/
1276
+ *
1277
+ * @param {Object} options
1278
+ * @param {Buffer} options.photoBuffer - The photo as a Buffer
1279
+ * @param {string} options.threadId - Thread ID to send to
1280
+ * @param {string} [options.viewMode='once'] - 'once' or 'replayable'
1281
+ * @param {string} [options.mimeType='image/jpeg'] - MIME type of the image
1282
+ * @returns {Promise<Object>} - REST broadcast response
1283
+ */
1284
+ async sendRavenPhoto({ photoBuffer, threadId, viewMode = 'once', mimeType = 'image/jpeg' }) {
1285
+ this.enhancedDebug(`Sending raven photo to thread ${threadId} (viewMode: ${viewMode})`);
1286
+
1287
+ try {
1288
+ if (!photoBuffer || !Buffer.isBuffer(photoBuffer) || photoBuffer.length === 0) {
1289
+ throw new Error('photoBuffer must be a non-empty Buffer');
1290
+ }
1291
+ if (!threadId) {
1292
+ throw new Error('threadId is required');
1293
+ }
1294
+
1295
+ const ig = this.realtimeClient.ig;
1296
+ if (!ig || !ig.request) {
1297
+ throw new Error('Instagram client not available. Make sure you are logged in.');
1298
+ }
1299
+
1300
+ const axios = require('axios');
1301
+
1302
+ this.enhancedDebug(`Step 1: Uploading raven photo to messenger_image (${photoBuffer.length} bytes)...`);
1303
+
1304
+ const uploadId = Date.now().toString();
1305
+ const randomSuffix = Math.floor(Math.random() * (9999999999 - 1000000000) + 1000000000);
1306
+ const name = `${uploadId}_0_${randomSuffix}`;
1307
+ const waterfallId = (0, uuid_1.v4)();
1308
+
1309
+ const ruploadParams = {
1310
+ retry_context: '{"num_step_auto_retry":0,"num_reupload":0,"num_step_manual_retry":0}',
1311
+ media_type: '1',
1312
+ upload_id: uploadId,
1313
+ image_compression: '{"lib_name":"moz","lib_version":"3.1.m","quality":"95"}',
1314
+ xsharing_user_ids: JSON.stringify([]),
1315
+ direct_v2: '1',
1316
+ is_optimistic_upload: 'false',
1317
+ };
1318
+
1319
+ const defaultHeaders = ig.request.getDefaultHeaders();
1320
+ const messengerUploadHeaders = {
1321
+ ...defaultHeaders,
1322
+ 'X_FB_PHOTO_WATERFALL_ID': waterfallId,
1323
+ 'X-Entity-Type': mimeType,
1324
+ 'Offset': '0',
1325
+ 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
1326
+ 'X-Entity-Name': name,
1327
+ 'X-Entity-Length': String(photoBuffer.length),
1328
+ 'Content-Type': 'application/octet-stream',
1329
+ 'Content-Length': String(photoBuffer.length),
1330
+ 'ephemeral_media_view_mode': viewMode === 'replayable' ? '1' : '0',
1331
+ 'ig_raven_metadata': '{}',
1332
+ 'image_type': 'FILE_ATTACHMENT',
1333
+ 'Host': 'rupload.facebook.com',
1334
+ };
1335
+
1336
+ let serverUploadId = uploadId;
1337
+ let attachmentFbid = null;
1338
+ try {
1339
+ const uploadResp = await axios({
1340
+ url: `https://rupload.facebook.com/messenger_image/${name}`,
1341
+ method: 'POST',
1342
+ headers: messengerUploadHeaders,
1343
+ data: photoBuffer,
1344
+ maxBodyLength: Infinity,
1345
+ maxContentLength: Infinity,
1346
+ transformRequest: [(d) => d],
1347
+ });
1348
+ const uploadParsed = uploadResp.data;
1349
+ serverUploadId = (uploadParsed.upload_id || uploadId).toString();
1350
+ attachmentFbid = uploadParsed.media_id
1351
+ || (uploadParsed.media && uploadParsed.media.pk)
1352
+ || null;
1353
+ this.enhancedDebug(`Raven photo uploaded! upload_id: ${serverUploadId}, media_id: ${attachmentFbid}`);
1354
+ } catch (uploadErr) {
1355
+ const msg = uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr);
1356
+ this.enhancedDebug(`Raven photo upload error: ${msg}`);
1357
+ throw new Error(`Raven photo upload failed: ${msg}`);
1358
+ }
1359
+
1360
+ this.enhancedDebug(`Step 2: Broadcasting via REST to raven_attachment/...`);
1361
+
1362
+ const clientContext = BigInt(Math.floor(Math.random() * 2**62)).toString();
1363
+ const compositionId = (0, uuid_1.v4)();
1364
+ const cameraSessionId = (0, uuid_1.v4)();
1365
+ const now = Math.floor(Date.now() / 1000);
1366
+
1367
+ const form = {
1368
+ allow_multi_configures: '1',
1369
+ recipient_users: '[]',
1370
+ view_mode: viewMode,
1371
+ is_shh_mode: '0',
1372
+ camera_entry_point: '3',
1373
+ thread_ids: JSON.stringify([String(threadId)]),
1374
+ reshare_mode: 'allow_reshare',
1375
+ original_media_type: '1',
1376
+ send_attribution: 'direct_thread_camera',
1377
+ client_context: clientContext,
1378
+ camera_session_id: cameraSessionId,
1379
+ include_e2ee_mentioned_user_list: '1',
1380
+ hide_from_profile_grid: 'false',
1381
+ scene_capture_type: '',
1382
+ timezone_offset: String(new Date().getTimezoneOffset() * -60),
1383
+ client_shared_at: String(now),
1384
+ configure_mode: '2',
1385
+ source_type: '4',
1386
+ camera_position: 'unknown',
1387
+ _uid: String(ig.state.cookieUserId || ig.state.igUserId),
1388
+ device_id: ig.state.deviceId,
1389
+ composition_id: compositionId,
1390
+ mutation_token: clientContext,
1391
+ _uuid: ig.state.uuid,
1392
+ creation_tool_info: '[]',
1393
+ creation_surface: 'camera',
1394
+ capture_type: 'normal',
1395
+ audience: 'default',
1396
+ upload_id: serverUploadId,
1397
+ client_timestamp: String(now),
1398
+ sampled: 'true',
1399
+ media_transformation_info: JSON.stringify({
1400
+ width: '720', height: '1280',
1401
+ x_transform: '0', y_transform: '0',
1402
+ zoom: '1.0', rotation: '0.0', background_coverage: '0.0',
1403
+ }),
1404
+ edits: JSON.stringify({ filter_type: 0, filter_strength: 0.5, crop_original_size: [720.0, 1280.0] }),
1405
+ extra: JSON.stringify({ source_width: 720, source_height: 1280 }),
1406
+ device: JSON.stringify({
1407
+ manufacturer: ig.state.devicePayload?.manufacturer || 'samsung',
1408
+ model: ig.state.devicePayload?.model || 'SM-S938B',
1409
+ android_version: ig.state.devicePayload?.android_version || 35,
1410
+ android_release: ig.state.devicePayload?.android_release || '15',
1411
+ }),
1412
+ };
1413
+
1414
+ if (attachmentFbid) {
1415
+ form.attachment_fbid = String(attachmentFbid);
1416
+ }
1417
+
1418
+ const payloadForm = (ig.request && typeof ig.request.sign === 'function')
1419
+ ? ig.request.sign(form)
1420
+ : form;
1421
+
1422
+ const result = await ig.request.send({
1423
+ url: '/api/v1/direct_v2/threads/broadcast/raven_attachment/',
1424
+ method: 'POST',
1425
+ form: payloadForm,
1426
+ qs: { use_unified_inbox: true },
1427
+ });
1428
+
1429
+ const body = result && (result.body || result.data || result);
1430
+ this.enhancedDebug(`Raven photo broadcast SUCCESS`);
1431
+ return typeof body === 'string' ? JSON.parse(body) : body;
1432
+
1433
+ } catch (err) {
1434
+ this.enhancedDebug(`sendRavenPhoto failed: ${err && err.message ? err.message : String(err)}`);
1435
+ throw err;
1436
+ }
1437
+ }
1438
+
1439
+ /**
1440
+ * Send raven photo (view once) - convenience wrapper
1441
+ */
1442
+ async sendRavenPhotoOnce(options) {
1443
+ return this.sendRavenPhoto({ ...options, viewMode: 'once' });
1444
+ }
1445
+
1446
+ /**
1447
+ * Send raven photo (replayable) - convenience wrapper
1448
+ */
1449
+ async sendRavenPhotoReplayable(options) {
1450
+ return this.sendRavenPhoto({ ...options, viewMode: 'replayable' });
1451
+ }
1452
+
1453
+ /**
1454
+ * Send raven (view-once) video - COMPLETE flow:
1455
+ * 1. Upload video to rupload.facebook.com/messenger_image/ (required for raven)
1456
+ * 2. Broadcast via REST to /direct_v2/threads/broadcast/raven_attachment/
1457
+ *
1458
+ * @param {Object} options
1459
+ * @param {Buffer} options.videoBuffer - The video as a Buffer
1460
+ * @param {string} options.threadId - Thread ID to send to
1461
+ * @param {string} [options.viewMode='once'] - 'once' or 'replayable'
1462
+ * @param {number} [options.duration=0] - Duration in seconds
1463
+ * @param {number} [options.width=720] - Video width
1464
+ * @param {number} [options.height=1280] - Video height
1465
+ * @returns {Promise<Object>} - REST broadcast response
1466
+ */
1467
+ async sendRavenVideo({ videoBuffer, threadId, viewMode = 'once', duration = 0, width = 720, height = 1280 }) {
1468
+ this.enhancedDebug(`Sending raven video to thread ${threadId} (viewMode: ${viewMode})`);
1469
+
1470
+ try {
1471
+ if (!videoBuffer || !Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {
1472
+ throw new Error('videoBuffer must be a non-empty Buffer');
1473
+ }
1474
+ if (!threadId) {
1475
+ throw new Error('threadId is required');
1476
+ }
1477
+
1478
+ const ig = this.realtimeClient.ig;
1479
+ if (!ig || !ig.request) {
1480
+ throw new Error('Instagram client not available. Make sure you are logged in.');
1481
+ }
1482
+
1483
+ const axios = require('axios');
1484
+
1485
+ this.enhancedDebug(`Step 1: Uploading raven video to messenger_image (${videoBuffer.length} bytes)...`);
1486
+
1487
+ const uploadId = Date.now().toString();
1488
+ const randomSuffix = Math.floor(Math.random() * (9999999999 - 1000000000) + 1000000000);
1489
+ const name = `${uploadId}_0_${randomSuffix}`;
1490
+ const waterfallId = (0, uuid_1.v4)();
1491
+
1492
+ const ruploadParams = {
1493
+ upload_media_height: String(height),
1494
+ upload_media_width: String(width),
1495
+ upload_media_duration_ms: String(Math.round(duration * 1000)),
1496
+ upload_id: uploadId,
1497
+ retry_context: '{"num_step_auto_retry":0,"num_reupload":0,"num_step_manual_retry":0}',
1498
+ media_type: '2',
1499
+ xsharing_user_ids: JSON.stringify([]),
1500
+ direct_v2: '1',
1501
+ };
1502
+
1503
+ const defaultHeaders = ig.request.getDefaultHeaders();
1504
+ const messengerUploadHeaders = {
1505
+ ...defaultHeaders,
1506
+ 'X_FB_VIDEO_WATERFALL_ID': waterfallId,
1507
+ 'X-Entity-Type': 'video/mp4',
1508
+ 'Offset': '0',
1509
+ 'X-Instagram-Rupload-Params': JSON.stringify(ruploadParams),
1510
+ 'X-Entity-Name': name,
1511
+ 'X-Entity-Length': String(videoBuffer.length),
1512
+ 'Content-Type': 'application/octet-stream',
1513
+ 'Content-Length': String(videoBuffer.length),
1514
+ 'ephemeral_media_view_mode': viewMode === 'replayable' ? '1' : '0',
1515
+ 'ig_raven_metadata': '{}',
1516
+ 'Host': 'rupload.facebook.com',
1517
+ };
1518
+
1519
+ let serverUploadId = uploadId;
1520
+ let attachmentFbid = null;
1521
+ try {
1522
+ const uploadResp = await axios({
1523
+ url: `https://rupload.facebook.com/messenger_image/${name}`,
1524
+ method: 'POST',
1525
+ headers: messengerUploadHeaders,
1526
+ data: videoBuffer,
1527
+ maxBodyLength: Infinity,
1528
+ maxContentLength: Infinity,
1529
+ transformRequest: [(d) => d],
1530
+ });
1531
+ const uploadParsed = uploadResp.data;
1532
+ serverUploadId = (uploadParsed.upload_id || uploadId).toString();
1533
+ attachmentFbid = uploadParsed.media_id
1534
+ || (uploadParsed.media && uploadParsed.media.pk)
1535
+ || null;
1536
+ this.enhancedDebug(`Raven video uploaded! upload_id: ${serverUploadId}, media_id: ${attachmentFbid}`);
1537
+ } catch (uploadErr) {
1538
+ const msg = uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr);
1539
+ this.enhancedDebug(`Raven video upload error: ${msg}`);
1540
+ throw new Error(`Raven video upload failed: ${msg}`);
1541
+ }
1542
+
1543
+ this.enhancedDebug(`Step 2: Broadcasting via REST to raven_attachment/...`);
1544
+
1545
+ const clientContext = BigInt(Math.floor(Math.random() * 2**62)).toString();
1546
+ const compositionId = (0, uuid_1.v4)();
1547
+ const cameraSessionId = (0, uuid_1.v4)();
1548
+ const now = Math.floor(Date.now() / 1000);
1549
+
1550
+ const form = {
1551
+ allow_multi_configures: '1',
1552
+ recipient_users: '[]',
1553
+ view_mode: viewMode,
1554
+ is_shh_mode: '0',
1555
+ camera_entry_point: '3',
1556
+ thread_ids: JSON.stringify([String(threadId)]),
1557
+ reshare_mode: 'allow_reshare',
1558
+ original_media_type: '2',
1559
+ send_attribution: 'direct_thread_camera',
1560
+ client_context: clientContext,
1561
+ camera_session_id: cameraSessionId,
1562
+ include_e2ee_mentioned_user_list: '1',
1563
+ hide_from_profile_grid: 'false',
1564
+ scene_capture_type: '',
1565
+ timezone_offset: String(new Date().getTimezoneOffset() * -60),
1566
+ client_shared_at: String(now),
1567
+ configure_mode: '2',
1568
+ source_type: '4',
1569
+ camera_position: 'unknown',
1570
+ _uid: String(ig.state.cookieUserId || ig.state.igUserId),
1571
+ device_id: ig.state.deviceId,
1572
+ composition_id: compositionId,
1573
+ mutation_token: clientContext,
1574
+ _uuid: ig.state.uuid,
1575
+ creation_tool_info: '[]',
1576
+ creation_surface: 'camera',
1577
+ capture_type: 'normal',
1578
+ audience: 'default',
1579
+ upload_id: serverUploadId,
1580
+ client_timestamp: String(now),
1581
+ sampled: 'true',
1582
+ video_result: '',
1583
+ media_type: '2',
1584
+ media_transformation_info: JSON.stringify({
1585
+ width: String(width), height: String(height),
1586
+ x_transform: '0', y_transform: '0',
1587
+ zoom: '1.0', rotation: '0.0', background_coverage: '0.0',
1588
+ }),
1589
+ extra: JSON.stringify({ source_width: width, source_height: height }),
1590
+ device: JSON.stringify({
1591
+ manufacturer: ig.state.devicePayload?.manufacturer || 'samsung',
1592
+ model: ig.state.devicePayload?.model || 'SM-S938B',
1593
+ android_version: ig.state.devicePayload?.android_version || 35,
1594
+ android_release: ig.state.devicePayload?.android_release || '15',
1595
+ }),
1596
+ };
1597
+
1598
+ if (attachmentFbid) {
1599
+ form.attachment_fbid = String(attachmentFbid);
1600
+ }
1601
+
1602
+ const payloadForm = (ig.request && typeof ig.request.sign === 'function')
1603
+ ? ig.request.sign(form)
1604
+ : form;
1605
+
1606
+ const result = await ig.request.send({
1607
+ url: '/api/v1/direct_v2/threads/broadcast/raven_attachment/',
1608
+ method: 'POST',
1609
+ form: payloadForm,
1610
+ qs: { use_unified_inbox: true },
1611
+ });
1612
+
1613
+ const body = result && (result.body || result.data || result);
1614
+ this.enhancedDebug(`Raven video broadcast SUCCESS`);
1615
+ return typeof body === 'string' ? JSON.parse(body) : body;
1616
+
1617
+ } catch (err) {
1618
+ this.enhancedDebug(`sendRavenVideo failed: ${err && err.message ? err.message : String(err)}`);
1619
+ throw err;
1620
+ }
1621
+ }
1622
+
1623
+ /**
1624
+ * Send raven video (view once) - convenience wrapper
1625
+ */
1626
+ async sendRavenVideoOnce(options) {
1627
+ return this.sendRavenVideo({ ...options, viewMode: 'once' });
1628
+ }
1629
+
1630
+ /**
1631
+ * Send raven video (replayable) - convenience wrapper
1632
+ */
1633
+ async sendRavenVideoReplayable(options) {
1634
+ return this.sendRavenVideo({ ...options, viewMode: 'replayable' });
1635
+ }
1636
+
1637
+ /**
1638
+ * Mark visual message as seen via REST API (with MQTT fallback)
1639
+ */
1640
+ async markVisualMessageSeen({ threadId, itemId, clientContext }) {
1641
+ this.enhancedDebug(`Marking visual message ${itemId} as seen`);
1642
+
1643
+ const ig = this.realtimeClient && this.realtimeClient.ig;
1644
+ if (ig && ig.request) {
1645
+ try {
1646
+ const ctx = clientContext || (0, uuid_1.v4)();
1647
+ const form = {
1648
+ _uuid: ig.state.uuid,
1649
+ device_id: ig.state.deviceId,
1650
+ use_unified_inbox: true,
1651
+ action: 'mark_visual_item_seen',
1652
+ thread_id: threadId,
1653
+ item_id: itemId,
1654
+ client_context: ctx,
1655
+ mutation_token: ctx,
1656
+ };
1657
+
1658
+ const response = await ig.request.send({
1659
+ url: `/api/v1/direct_v2/threads/${threadId}/items/${itemId}/seen/`,
1660
+ method: 'POST',
1661
+ form: form,
1662
+ });
1663
+
1664
+ const body = response.body || response.data || response;
1665
+ const parsed = typeof body === 'string' ? JSON.parse(body) : body;
1666
+
1667
+ if (parsed && parsed.status === 'ok') {
1668
+ this.enhancedDebug(`✅ Visual message marked as seen via REST API! Status: ok`);
1669
+ return parsed;
1670
+ }
1671
+
1672
+ this.enhancedDebug(`REST mark_visual_item_seen response: ${JSON.stringify(parsed).slice(0, 300)}`);
1673
+ return parsed;
1674
+ } catch (restErr) {
1675
+ this.enhancedDebug(`REST mark_visual_item_seen failed: ${restErr && restErr.message ? restErr.message : String(restErr)}, falling back to MQTT`);
1676
+ }
1677
+ }
1678
+
1679
+ const result = await this.sendCommand({
1680
+ action: 'mark_visual_item_seen',
1681
+ threadId,
1682
+ clientContext: clientContext || (0, uuid_1.v4)(),
1683
+ data: {
1684
+ item_id: itemId,
1685
+ },
1686
+ });
1687
+
1688
+ this.enhancedDebug(`⚠️ Visual message mark_seen sent via MQTT (fallback - may not be processed by Instagram)`);
1689
+ return result;
1690
+ }
1691
+
1692
+ /**
1693
+ * Screenshot notification via MQTT
1694
+ */
1695
+ async sendScreenshotNotification({ threadId, itemId, clientContext }) {
1696
+ this.enhancedDebug(`Sending screenshot notification for ${itemId}`);
1697
+
1698
+ const result = await this.sendCommand({
1699
+ action: 'screenshot_notification',
1700
+ threadId,
1701
+ clientContext: clientContext || (0, uuid_1.v4)(),
1702
+ data: {
1703
+ item_id: itemId,
1704
+ },
1705
+ });
1706
+
1707
+ this.enhancedDebug(`✅ Screenshot notification sent via MQTT!`);
1708
+ return result;
1709
+ }
1710
+
1711
+ /**
1712
+ * Replay notification via MQTT
1713
+ */
1714
+ async sendReplayNotification({ threadId, itemId, clientContext }) {
1715
+ this.enhancedDebug(`Sending replay notification for ${itemId}`);
1716
+
1717
+ const result = await this.sendCommand({
1718
+ action: 'replay_notification',
1719
+ threadId,
1720
+ clientContext: clientContext || (0, uuid_1.v4)(),
1721
+ data: {
1722
+ item_id: itemId,
1723
+ },
1724
+ });
1725
+
1726
+ this.enhancedDebug(`✅ Replay notification sent via MQTT!`);
1727
+ return result;
1728
+ }
1729
+ }
1730
+
1731
+ exports.EnhancedDirectCommands = EnhancedDirectCommands;