nodejs-insta-private-api-mqtt 1.3.39 → 1.3.41

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.
@@ -1,772 +1,808 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EnhancedDirectCommands = void 0;
4
+
4
5
  const shared_1 = require("../../shared");
5
6
  const uuid_1 = require("uuid");
6
7
  const constants_1 = require("../../constants");
8
+ const thrift_1 = require("../../thrift");
7
9
 
8
10
  /**
9
- * Enhanced Direct Commands - sends MQTT directly with proper payload formatting
11
+ * EnhancedDirectCommands
12
+ *
13
+ * - Full, self-contained class that publishes correctly-formatted payloads to Instagram's
14
+ * Direct MQTT (Thrift + compressed payloads).
15
+ * - Includes a robust sendLocation implementation that sends a nested `location` object,
16
+ * and a fallback to sending a link (which reliably appears in chat).
17
+ *
18
+ * IMPORTANT:
19
+ * - Instagram's internal protocol is not public. This implementation matches patterns
20
+ * observed in reverse-engineered clients. Even so, Instagram may silently reject
21
+ * location messages if server-side validation's schema differs. If a message is
22
+ * rejected, fallback sends a link to the location which is visible to users.
10
23
  */
11
24
  class EnhancedDirectCommands {
12
25
  constructor(client) {
13
26
  this.realtimeClient = client;
14
27
  this.enhancedDebug = (0, shared_1.debugChannel)('realtime', 'enhanced-commands');
28
+
29
+ // Foreground state config for Thrift encoding (matching instagram_mqtt)
30
+ this.foregroundStateConfig = [
31
+ thrift_1.ThriftDescriptors.boolean('inForegroundApp', 1),
32
+ thrift_1.ThriftDescriptors.boolean('inForegroundDevice', 2),
33
+ thrift_1.ThriftDescriptors.int32('keepAliveTimeout', 3),
34
+ thrift_1.ThriftDescriptors.listOfBinary('subscribeTopics', 4),
35
+ thrift_1.ThriftDescriptors.listOfBinary('subscribeGenericTopics', 5),
36
+ thrift_1.ThriftDescriptors.listOfBinary('unsubscribeTopics', 6),
37
+ thrift_1.ThriftDescriptors.listOfBinary('unsubscribeGenericTopics', 7),
38
+ thrift_1.ThriftDescriptors.int64('requestId', 8),
39
+ ];
15
40
  }
16
41
 
17
42
  /**
18
- * Send text via MQTT with proper payload format
43
+ * Attempt to locate the MQTT client object on the realtime client.
44
+ * Many wrappers expose mqtt under different property names.
19
45
  */
20
- async sendTextViaRealtime(threadId, text) {
21
- this.enhancedDebug(`Sending text to ${threadId}: "${text}"`);
22
-
23
- try {
24
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
25
- if (!mqtt || typeof mqtt.publish !== 'function') {
26
- throw new Error('MQTT client not available');
46
+ getMqtt() {
47
+ const candidates = [
48
+ 'mqtt',
49
+ '_mqtt',
50
+ 'client',
51
+ '_client',
52
+ 'connection',
53
+ 'mqttClient',
54
+ ];
55
+ let mqtt = null;
56
+ for (const key of candidates) {
57
+ if (this.realtimeClient && Object.prototype.hasOwnProperty.call(this.realtimeClient, key) && this.realtimeClient[key]) {
58
+ mqtt = this.realtimeClient[key];
59
+ break;
27
60
  }
28
-
29
- // Build proper command payload
30
- const clientContext = (0, uuid_1.v4)();
31
- const command = {
32
- action: 'send_item',
33
- thread_id: threadId,
34
- item_type: 'text',
35
- text: text,
36
- timestamp: Date.now(),
37
- client_context: clientContext,
38
- };
39
-
40
- // Compress JSON payload
41
- const json = JSON.stringify(command);
42
- const { compressDeflate } = shared_1;
43
- const payload = await compressDeflate(json);
44
-
45
- // Send to MQTT
46
- this.enhancedDebug(`Publishing to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
47
- const result = await mqtt.publish({
48
- topic: constants_1.Topics.SEND_MESSAGE.id,
49
- qosLevel: 1,
50
- payload: payload,
51
- });
52
-
53
- this.enhancedDebug(`✅ Message sent via MQTT!`);
54
- return result;
55
- } catch (err) {
56
- this.enhancedDebug(`Failed: ${err.message}`);
57
- throw err;
58
61
  }
62
+ // fallback: maybe the realtimeClient itself *is* the mqtt client
63
+ if (!mqtt && this.realtimeClient && typeof this.realtimeClient.publish === 'function') {
64
+ mqtt = this.realtimeClient;
65
+ }
66
+
67
+ if (!mqtt || typeof mqtt.publish !== 'function') {
68
+ throw new Error('MQTT client not available or does not expose publish(). Found client keys: ' +
69
+ (this.realtimeClient ? Object.keys(this.realtimeClient).join(',') : 'none'));
70
+ }
71
+ return mqtt;
59
72
  }
60
73
 
61
74
  /**
62
- * Delete message via MQTT
75
+ * Robust mqtt publish wrapper - handles both:
76
+ * - mqtt.publish({ topic, payload, qosLevel }) returning a Promise or using callback
77
+ * - mqtt.publish(topic, payload, { qos }, cb)
63
78
  */
64
- async deleteMessage(threadId, itemId) {
65
- this.enhancedDebug(`Deleting message ${itemId} from thread ${threadId}`);
66
-
79
+ async publishToMqtt(mqtt, publishObj) {
80
+ const topic = publishObj.topic;
81
+ const payload = publishObj.payload;
82
+ const qosLevel = typeof publishObj.qosLevel !== 'undefined' ? publishObj.qosLevel : 1;
83
+
84
+ // Try object-style publish first (some wrappers expect object)
67
85
  try {
68
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
69
- if (!mqtt || typeof mqtt.publish !== 'function') {
70
- throw new Error('MQTT client not available');
86
+ const maybePromise = mqtt.publish({
87
+ topic,
88
+ payload,
89
+ qosLevel,
90
+ });
91
+ if (maybePromise && typeof maybePromise.then === 'function') {
92
+ return await maybePromise;
71
93
  }
72
-
73
- const clientContext = (0, uuid_1.v4)();
74
- const command = {
75
- action: 'delete_item',
76
- thread_id: threadId,
77
- item_id: itemId,
78
- timestamp: Date.now(),
79
- client_context: clientContext,
80
- };
81
-
82
- const json = JSON.stringify(command);
83
- const { compressDeflate } = shared_1;
84
- const payload = await compressDeflate(json);
85
-
86
- this.enhancedDebug(`Publishing delete command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
87
- const result = await mqtt.publish({
88
- topic: constants_1.Topics.SEND_MESSAGE.id,
89
- qosLevel: 1,
90
- payload: payload,
94
+ // if it returned synchronously, maybe it still used callback style
95
+ return await new Promise((resolve, reject) => {
96
+ try {
97
+ mqtt.publish({ topic, payload, qosLevel }, (err, res) => {
98
+ if (err)
99
+ return reject(err);
100
+ return resolve(res);
101
+ });
102
+ } catch (err) {
103
+ reject(err);
104
+ }
91
105
  });
92
-
93
- this.enhancedDebug(`✅ Message deleted via MQTT!`);
94
- return result;
95
- } catch (err) {
96
- this.enhancedDebug(`Delete failed: ${err.message}`);
97
- throw err;
106
+ } catch (e) {
107
+ // fallthrough to positional try
108
+ }
109
+
110
+ // Try positional-style publish (topic, payload, options, callback)
111
+ try {
112
+ return await new Promise((resolve, reject) => {
113
+ try {
114
+ mqtt.publish(topic, payload, { qos: qosLevel }, (err, res) => {
115
+ if (err)
116
+ return reject(err);
117
+ return resolve(res);
118
+ });
119
+ } catch (err) {
120
+ reject(err);
121
+ }
122
+ });
123
+ } catch (e) {
124
+ // final fallback: some clients return synchronously or throw - try positional without callback
125
+ try {
126
+ const res = mqtt.publish(topic, payload, { qos: qosLevel });
127
+ if (res && typeof res.then === 'function') {
128
+ return await res;
129
+ }
130
+ // last attempt: resolve with returned value
131
+ return res;
132
+ } catch (err) {
133
+ // give clear error
134
+ throw new Error(`MQTT publish failed: no known publish signature worked. Errors: ${err && err.message ? err.message : String(err)}`);
135
+ }
98
136
  }
99
137
  }
100
138
 
101
139
  /**
102
- * Edit message via MQTT
140
+ * Send foreground state via MQTT with Thrift encoding (matching instagram_mqtt)
103
141
  */
104
- async editMessage(threadId, itemId, newText) {
105
- this.enhancedDebug(`Editing message ${itemId}: "${newText}"`);
106
-
142
+ async sendForegroundState(state) {
143
+ this.enhancedDebug(`Updated foreground state: ${JSON.stringify(state)}`);
144
+
107
145
  try {
108
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
109
- if (!mqtt || typeof mqtt.publish !== 'function') {
110
- throw new Error('MQTT client not available');
111
- }
112
-
113
- const clientContext = (0, uuid_1.v4)();
114
- const command = {
115
- action: 'edit_item',
116
- thread_id: threadId,
117
- item_id: itemId,
118
- text: newText,
119
- timestamp: Date.now(),
120
- client_context: clientContext,
121
- };
122
-
123
- const json = JSON.stringify(command);
124
- const { compressDeflate } = shared_1;
125
- const payload = await compressDeflate(json);
126
-
127
- this.enhancedDebug(`Publishing edit command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
128
- const result = await mqtt.publish({
129
- topic: constants_1.Topics.SEND_MESSAGE.id,
130
- qosLevel: 1,
146
+ const mqtt = this.getMqtt();
147
+
148
+ const thriftBuffer = (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig);
149
+ const concat = Buffer.concat([
150
+ Buffer.alloc(1, 0),
151
+ thriftBuffer
152
+ ]);
153
+
154
+ // ensure we pass Buffer to compressDeflate
155
+ const payload = await (0, shared_1.compressDeflate)(concat);
156
+
157
+ const result = await this.publishToMqtt(mqtt, {
158
+ topic: constants_1.Topics.FOREGROUND_STATE.id,
131
159
  payload: payload,
160
+ qosLevel: 1,
132
161
  });
133
-
134
- this.enhancedDebug(`✅ Message edited via MQTT!`);
162
+
163
+ // Update keepAlive if provided
164
+ if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
165
+ mqtt.keepAlive = state.keepAliveTimeout;
166
+ }
167
+
168
+ this.enhancedDebug(`✅ Foreground state updated via MQTT!`);
135
169
  return result;
136
170
  } catch (err) {
137
- this.enhancedDebug(`Edit failed: ${err.message}`);
171
+ this.enhancedDebug(`Foreground state failed: ${err && err.message ? err.message : String(err)}`);
138
172
  throw err;
139
173
  }
140
174
  }
141
175
 
142
176
  /**
143
- * Reply to message via MQTT (Quote Reply)
177
+ * Base command sender (matching instagram_mqtt format)
178
+ * It encodes the command as JSON, compresses, and publishes to SEND_MESSAGE topic.
144
179
  */
145
- async replyToMessage(threadId, messageId, replyText) {
146
- this.enhancedDebug(`Replying to ${messageId} in thread ${threadId}: "${replyText}"`);
147
-
180
+ async sendCommand({ action, data, threadId, clientContext }) {
148
181
  try {
149
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
150
- if (!mqtt || typeof mqtt.publish !== 'function') {
151
- throw new Error('MQTT client not available');
182
+ const mqtt = this.getMqtt();
183
+
184
+ if (clientContext) {
185
+ data.client_context = clientContext;
152
186
  }
153
-
154
- const clientContext = (0, uuid_1.v4)();
155
- const command = {
156
- action: 'send_item',
187
+
188
+ const json = JSON.stringify({
189
+ action,
157
190
  thread_id: threadId,
158
- item_type: 'text',
159
- text: replyText,
160
- replying_to_item_id: messageId,
161
- timestamp: Date.now(),
162
- client_context: clientContext,
163
- };
164
-
165
- const json = JSON.stringify(command);
166
- const { compressDeflate } = shared_1;
167
- const payload = await compressDeflate(json);
168
-
169
- this.enhancedDebug(`Publishing reply command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
170
- const result = await mqtt.publish({
191
+ ...data,
192
+ });
193
+
194
+ // ensure Buffer (some compress implementations expect Buffer)
195
+ const payload = await (0, shared_1.compressDeflate)(Buffer.from(json));
196
+
197
+ return this.publishToMqtt(mqtt, {
171
198
  topic: constants_1.Topics.SEND_MESSAGE.id,
172
199
  qosLevel: 1,
173
200
  payload: payload,
174
201
  });
175
-
176
- this.enhancedDebug(`✅ Reply sent via MQTT!`);
177
- return result;
178
202
  } catch (err) {
179
- this.enhancedDebug(`Reply failed: ${err.message}`);
203
+ this.enhancedDebug(`sendCommand failed: ${err && err.message ? err.message : String(err)}`);
180
204
  throw err;
181
205
  }
182
206
  }
183
207
 
184
208
  /**
185
- * Subscribe to follow notifications via MQTT
209
+ * Base item sender (matching instagram_mqtt format)
186
210
  */
187
- async subscribeToFollowNotifications() {
188
- this.enhancedDebug(`Subscribing to follow notifications via MQTT`);
189
-
190
- try {
191
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
192
- if (!mqtt || typeof mqtt.publish !== 'function') {
193
- throw new Error('MQTT client not available');
194
- }
195
-
196
- const clientContext = (0, uuid_1.v4)();
197
- const command = {
198
- action: 'subscribe',
199
- subscription_type: 'follow_notifications',
200
- timestamp: Date.now(),
201
- client_context: clientContext,
202
- };
203
-
204
- const json = JSON.stringify(command);
205
- const { compressDeflate } = shared_1;
206
- const payload = await compressDeflate(json);
207
-
208
- this.enhancedDebug(`Publishing follow subscription to MQTT`);
209
- const result = await mqtt.publish({
210
- topic: constants_1.Topics.SEND_MESSAGE.id,
211
- qosLevel: 1,
212
- payload: payload,
213
- });
214
-
215
- this.enhancedDebug(`✅ Follow notifications subscribed via MQTT!`);
216
- return result;
217
- } catch (err) {
218
- this.enhancedDebug(`Follow subscription failed: ${err.message}`);
219
- throw err;
220
- }
211
+ async sendItem({ threadId, itemType, data, clientContext }) {
212
+ return this.sendCommand({
213
+ action: 'send_item',
214
+ threadId,
215
+ clientContext: clientContext || (0, uuid_1.v4)(),
216
+ data: {
217
+ item_type: itemType,
218
+ ...data,
219
+ },
220
+ });
221
221
  }
222
222
 
223
223
  /**
224
- * Subscribe to mention notifications via MQTT
224
+ * Send text via MQTT
225
225
  */
226
- async subscribeToMentionNotifications() {
227
- this.enhancedDebug(`Subscribing to mention notifications via MQTT`);
228
-
229
- try {
230
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
231
- if (!mqtt || typeof mqtt.publish !== 'function') {
232
- throw new Error('MQTT client not available');
233
- }
234
-
235
- const clientContext = (0, uuid_1.v4)();
236
- const command = {
237
- action: 'subscribe',
238
- subscription_type: 'mention_notifications',
239
- timestamp: Date.now(),
240
- client_context: clientContext,
241
- };
242
-
243
- const json = JSON.stringify(command);
244
- const { compressDeflate } = shared_1;
245
- const payload = await compressDeflate(json);
246
-
247
- this.enhancedDebug(`Publishing mention subscription to MQTT`);
248
- const result = await mqtt.publish({
249
- topic: constants_1.Topics.SEND_MESSAGE.id,
250
- qosLevel: 1,
251
- payload: payload,
252
- });
253
-
254
- this.enhancedDebug(`✅ Mention notifications subscribed via MQTT!`);
255
- return result;
256
- } catch (err) {
257
- this.enhancedDebug(`Mention subscription failed: ${err.message}`);
258
- throw err;
259
- }
226
+ async sendText({ text, clientContext, threadId }) {
227
+ this.enhancedDebug(`Sending text to ${threadId}: "${text}"`);
228
+
229
+ const result = await this.sendItem({
230
+ itemType: 'text',
231
+ threadId,
232
+ clientContext,
233
+ data: {
234
+ text,
235
+ },
236
+ });
237
+
238
+ this.enhancedDebug(`✅ Text sent via MQTT!`);
239
+ return result;
260
240
  }
261
241
 
262
242
  /**
263
- * Subscribe to call notifications via MQTT
243
+ * Alias for sendText
264
244
  */
265
- async subscribeToCallNotifications() {
266
- this.enhancedDebug(`Subscribing to call notifications via MQTT`);
267
-
268
- try {
269
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
270
- if (!mqtt || typeof mqtt.publish !== 'function') {
271
- throw new Error('MQTT client not available');
272
- }
273
-
274
- const clientContext = (0, uuid_1.v4)();
275
- const command = {
276
- action: 'subscribe',
277
- subscription_type: 'call_notifications',
278
- timestamp: Date.now(),
279
- client_context: clientContext,
280
- };
281
-
282
- const json = JSON.stringify(command);
283
- const { compressDeflate } = shared_1;
284
- const payload = await compressDeflate(json);
285
-
286
- this.enhancedDebug(`Publishing call subscription to MQTT`);
287
- const result = await mqtt.publish({
288
- topic: constants_1.Topics.SEND_MESSAGE.id,
289
- qosLevel: 1,
290
- payload: payload,
291
- });
292
-
293
- this.enhancedDebug(`✅ Call notifications subscribed via MQTT!`);
294
- return result;
295
- } catch (err) {
296
- this.enhancedDebug(`Call subscription failed: ${err.message}`);
297
- throw err;
298
- }
245
+ async sendTextViaRealtime(threadId, text, clientContext) {
246
+ return this.sendText({
247
+ text,
248
+ threadId,
249
+ clientContext,
250
+ });
299
251
  }
300
252
 
301
253
  /**
302
- * Add member to thread via MQTT
254
+ * Send hashtag via MQTT
303
255
  */
304
- async addMemberToThread(threadId, userId) {
305
- this.enhancedDebug(`Adding user ${userId} to thread ${threadId} via MQTT`);
306
-
307
- try {
308
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
309
- if (!mqtt || typeof mqtt.publish !== 'function') {
310
- throw new Error('MQTT client not available');
256
+ async sendHashtag({ text, threadId, hashtag, clientContext }) {
257
+ this.enhancedDebug(`Sending hashtag #${hashtag} to ${threadId}`);
258
+
259
+ const result = await this.sendItem({
260
+ itemType: 'hashtag',
261
+ threadId,
262
+ clientContext,
263
+ data: {
264
+ text: text || '',
265
+ hashtag,
266
+ item_id: hashtag,
267
+ },
268
+ });
269
+
270
+ this.enhancedDebug(`✅ Hashtag sent via MQTT!`);
271
+ return result;
272
+ }
273
+
274
+ /**
275
+ * Send like via MQTT
276
+ */
277
+ async sendLike({ threadId, clientContext }) {
278
+ this.enhancedDebug(`Sending like in thread ${threadId}`);
279
+
280
+ const result = await this.sendItem({
281
+ itemType: 'like',
282
+ threadId,
283
+ clientContext,
284
+ data: {},
285
+ });
286
+
287
+ this.enhancedDebug(`✅ Like sent via MQTT!`);
288
+ return result;
289
+ }
290
+
291
+ /**
292
+ * Send location via MQTT (enhanced)
293
+ *
294
+ * Accepts:
295
+ * - threadId (string)
296
+ * - clientContext (optional)
297
+ * - venue (object) - should include at minimum:
298
+ * { id, name, address, lat, lng, facebook_places_id, external_source }
299
+ *
300
+ * The function tries to send a nested `location` object inside the item payload:
301
+ * {
302
+ * item_type: 'location',
303
+ * location: { lat, lng, name, address, external_source, facebook_places_id },
304
+ * text: '' // optional text
305
+ * }
306
+ *
307
+ * If the venue is missing required fields, it falls back to sending a link that points
308
+ * to the explore/locations/<id> page (which reliably appears in the chat).
309
+ *
310
+ * NOTE: Instagram expects the payload to be Thrift-encoded + compressed for MQTT.
311
+ */
312
+ async sendLocation({ threadId, clientContext, venue, text = '' }) {
313
+ this.enhancedDebug(`Attempting to send location to ${threadId}. Venue: ${venue ? JSON.stringify(venue) : 'none'}`);
314
+
315
+ // Basic validation - if we don't have lat/lng and id, fallback to link or error
316
+ const hasCoords = venue && typeof venue.lat === 'number' && typeof venue.lng === 'number';
317
+ const hasId = venue && (venue.facebook_places_id || venue.id);
318
+
319
+ // Build the "location" nested object expected semantically
320
+ const locationObj = hasCoords ? {
321
+ lat: Number(venue.lat),
322
+ lng: Number(venue.lng),
323
+ name: venue.name || '',
324
+ address: venue.address || '',
325
+ external_source: venue.external_source || 'facebook_places',
326
+ facebook_places_id: venue.facebook_places_id || String(venue.id || ''),
327
+ } : null;
328
+
329
+ // If we have a good location object, attempt to send it
330
+ if (locationObj && hasId) {
331
+ try {
332
+ const result = await this.sendItem({
333
+ itemType: 'location',
334
+ threadId,
335
+ clientContext: clientContext || (0, uuid_1.v4)(),
336
+ data: {
337
+ text: text || '',
338
+ // Put the nested `location` object in the payload as many reverse engineered
339
+ // clients do. We also add the older-style ids for compatibility.
340
+ location: locationObj,
341
+ venue_id: locationObj.facebook_places_id,
342
+ item_id: locationObj.facebook_places_id,
343
+ },
344
+ });
345
+
346
+ this.enhancedDebug(`✅ Location payload published via MQTT (may still be rejected server-side).`);
347
+ return result;
348
+ } catch (err) {
349
+ this.enhancedDebug(`Location publish failed: ${err && err.message ? err.message : String(err)} - falling back to link`);
350
+ // fallthrough to fallback below
351
+ }
352
+ }
353
+
354
+ // Fallback: send as a link to the location explore page (guaranteed to render in DM)
355
+ if (hasId) {
356
+ // prefer facebook_places_id if provided
357
+ const placeId = venue.facebook_places_id || venue.id;
358
+ const link = `https://www.instagram.com/explore/locations/${placeId}/`;
359
+ this.enhancedDebug(`Sending location fallback link: ${link}`);
360
+
361
+ try {
362
+ const fallback = await this.sendItem({
363
+ itemType: 'link',
364
+ threadId,
365
+ clientContext: clientContext || (0, uuid_1.v4)(),
366
+ data: {
367
+ link_text: text || (venue && venue.name) || 'Location',
368
+ link_urls: [link],
369
+ },
370
+ });
371
+ this.enhancedDebug(`✅ Location fallback link sent via MQTT!`);
372
+ return fallback;
373
+ } catch (err) {
374
+ this.enhancedDebug(`Fallback link send failed: ${err && err.message ? err.message : String(err)}`);
375
+ throw err;
311
376
  }
312
-
313
- const clientContext = (0, uuid_1.v4)();
314
- const command = {
315
- action: 'add_member',
316
- thread_id: threadId,
317
- user_id: userId,
318
- timestamp: Date.now(),
319
- client_context: clientContext,
320
- };
321
-
322
- const json = JSON.stringify(command);
323
- const { compressDeflate } = shared_1;
324
- const payload = await compressDeflate(json);
325
-
326
- this.enhancedDebug(`Publishing add member command to MQTT`);
327
- const result = await mqtt.publish({
328
- topic: constants_1.Topics.SEND_MESSAGE.id,
329
- qosLevel: 1,
330
- payload: payload,
331
- });
332
-
333
- this.enhancedDebug(`✅ Member added to thread via MQTT!`);
334
- return result;
335
- } catch (err) {
336
- this.enhancedDebug(`Add member failed: ${err.message}`);
337
- throw err;
338
377
  }
378
+
379
+ // If we don't have any usable info, throw an error
380
+ throw new Error('sendLocation requires a venue object with at least id (or facebook_places_id) and lat/lng to send a native location. Without that, nothing can be sent.');
339
381
  }
340
382
 
341
383
  /**
342
- * Remove member from thread via MQTT
384
+ * Helper: search places via the Instagram client (optional).
385
+ * If your realtimeClient has an .ig.request helper, this will call the appropriate
386
+ * endpoint to fetch place metadata, and then call sendLocation with the full venue.
387
+ *
388
+ * This is optional — you can call sendLocation yourself with the venue object you already have.
343
389
  */
344
- async removeMemberFromThread(threadId, userId) {
345
- this.enhancedDebug(`Removing user ${userId} from thread ${threadId} via MQTT`);
346
-
390
+ async searchAndSendLocation({ threadId, query, lat, lng, clientContext }) {
391
+ const ig = this.realtimeClient && this.realtimeClient.ig;
392
+ if (!ig || !ig.request) {
393
+ throw new Error('Instagram client (ig.request) not available on realtimeClient. Provide `venue` directly to sendLocation instead.');
394
+ }
395
+
396
+ this.enhancedDebug(`Searching location: ${query} at ${lat},${lng}`);
397
+
398
+ // Example endpoint - private API endpoints vary. If your client has a helper method,
399
+ // prefer that. This tries a common private endpoint pattern.
400
+ const url = '/fbsearch/places/';
401
+ const params = {
402
+ search_media_creation: false,
403
+ rank_token: (0, uuid_1.v4)(),
404
+ query: query,
405
+ latitude: lat,
406
+ longitude: lng,
407
+ };
408
+
347
409
  try {
348
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
349
- if (!mqtt || typeof mqtt.publish !== 'function') {
350
- throw new Error('MQTT client not available');
410
+ const res = await ig.request.send({
411
+ url: url,
412
+ method: 'GET',
413
+ qs: params,
414
+ });
415
+
416
+ // Parse response - different private API clients return different shapes.
417
+ // We try to find the first usable place with id/lat/lng/name.
418
+ const places = (res && (res.places || res.items || res.results)) || [];
419
+ const place = places.find(p => p && (p.pk || p.place || p.location || p.facebook_places_id)) || places[0];
420
+
421
+ if (!place) {
422
+ throw new Error('No places found from search.');
351
423
  }
352
-
353
- const clientContext = (0, uuid_1.v4)();
354
- const command = {
355
- action: 'remove_member',
356
- thread_id: threadId,
357
- user_id: userId,
358
- timestamp: Date.now(),
359
- client_context: clientContext,
424
+
425
+ // Normalize to `venue` shape our sendLocation expects
426
+ const venue = {
427
+ id: String(place.pk || (place.place && place.place.id) || place.id || place.facebook_places_id || ''),
428
+ name: place.name || (place.place && place.place.name) || '',
429
+ address: place.address || (place.place && place.place.address) || '',
430
+ lat: (place.location && (place.location.lat || place.location.latitude)) || place.lat || null,
431
+ lng: (place.location && (place.location.lng || place.location.longitude)) || place.lng || null,
432
+ facebook_places_id: place.facebook_places_id || (place.place && place.place.id) || String(place.pk || ''),
433
+ external_source: place.external_source || 'facebook_places',
360
434
  };
361
-
362
- const json = JSON.stringify(command);
363
- const { compressDeflate } = shared_1;
364
- const payload = await compressDeflate(json);
365
-
366
- this.enhancedDebug(`Publishing remove member command to MQTT`);
367
- const result = await mqtt.publish({
368
- topic: constants_1.Topics.SEND_MESSAGE.id,
369
- qosLevel: 1,
370
- payload: payload,
371
- });
372
-
373
- this.enhancedDebug(`✅ Member removed from thread via MQTT!`);
374
- return result;
435
+
436
+ return await this.sendLocation({ threadId, clientContext, venue });
375
437
  } catch (err) {
376
- this.enhancedDebug(`Remove member failed: ${err.message}`);
438
+ this.enhancedDebug(`place search/send failed: ${err && err.message ? err.message : String(err)}`);
377
439
  throw err;
378
440
  }
379
441
  }
380
442
 
381
443
  /**
382
- * Send reaction (emoji) via MQTT
444
+ * Send media via MQTT (media_share)
383
445
  */
384
- async sendReaction({ itemId, reactionType = 'like', clientContext, threadId, reactionStatus = 'created', emoji }) {
385
- this.enhancedDebug(`Sending ${reactionType} reaction to message ${itemId} via MQTT`);
386
-
387
- try {
388
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
389
- if (!mqtt || typeof mqtt.publish !== 'function') {
390
- throw new Error('MQTT client not available');
391
- }
392
-
393
- const ctx = clientContext || (0, uuid_1.v4)();
394
- const command = {
395
- action: 'send_reaction',
396
- thread_id: threadId,
446
+ async sendMedia({ text, mediaId, threadId, clientContext }) {
447
+ this.enhancedDebug(`Sending media ${mediaId} to ${threadId}`);
448
+
449
+ const result = await this.sendItem({
450
+ itemType: 'media_share',
451
+ threadId,
452
+ clientContext,
453
+ data: {
454
+ text: text || '',
455
+ media_id: mediaId,
456
+ },
457
+ });
458
+
459
+ this.enhancedDebug(`✅ Media sent via MQTT!`);
460
+ return result;
461
+ }
462
+
463
+ /**
464
+ * Send profile via MQTT
465
+ */
466
+ async sendProfile({ text, userId, threadId, clientContext }) {
467
+ this.enhancedDebug(`Sending profile ${userId} to ${threadId}`);
468
+
469
+ const result = await this.sendItem({
470
+ itemType: 'profile',
471
+ threadId,
472
+ clientContext,
473
+ data: {
474
+ text: text || '',
475
+ profile_user_id: userId,
476
+ item_id: userId,
477
+ },
478
+ });
479
+
480
+ this.enhancedDebug(`✅ Profile sent via MQTT!`);
481
+ return result;
482
+ }
483
+
484
+ /**
485
+ * Send reaction via MQTT
486
+ */
487
+ async sendReaction({ itemId, reactionType, clientContext, threadId, reactionStatus, targetItemType, emoji }) {
488
+ this.enhancedDebug(`Sending ${reactionType || 'like'} reaction to message ${itemId}`);
489
+
490
+ const result = await this.sendItem({
491
+ itemType: 'reaction',
492
+ threadId,
493
+ clientContext,
494
+ data: {
397
495
  item_id: itemId,
398
- reaction_type: reactionType,
399
- reaction_status: reactionStatus,
496
+ node_type: 'item',
497
+ reaction_type: reactionType || (emoji ? 'emoji' : 'like'),
498
+ reaction_status: reactionStatus || 'created',
499
+ target_item_type: targetItemType,
400
500
  emoji: emoji || '',
401
- timestamp: Date.now(),
402
- client_context: ctx,
403
- };
404
-
405
- const json = JSON.stringify(command);
406
- const { compressDeflate } = shared_1;
407
- const payload = await compressDeflate(json);
408
-
409
- this.enhancedDebug(`Publishing reaction to MQTT`);
410
- const result = await mqtt.publish({
411
- topic: constants_1.Topics.SEND_MESSAGE.id,
412
- qosLevel: 1,
413
- payload: payload,
414
- });
415
-
416
- this.enhancedDebug(`✅ Reaction sent via MQTT!`);
417
- return result;
418
- } catch (err) {
419
- this.enhancedDebug(`Reaction failed: ${err.message}`);
420
- throw err;
421
- }
501
+ },
502
+ });
503
+
504
+ this.enhancedDebug(`✅ Reaction sent via MQTT!`);
505
+ return result;
506
+ }
507
+
508
+ /**
509
+ * Send user story via MQTT (reel_share)
510
+ */
511
+ async sendUserStory({ text, storyId, threadId, clientContext }) {
512
+ this.enhancedDebug(`Sending story ${storyId} to ${threadId}`);
513
+
514
+ const result = await this.sendItem({
515
+ itemType: 'reel_share',
516
+ threadId,
517
+ clientContext,
518
+ data: {
519
+ text: text || '',
520
+ item_id: storyId,
521
+ media_id: storyId,
522
+ },
523
+ });
524
+
525
+ this.enhancedDebug(`✅ Story sent via MQTT!`);
526
+ return result;
422
527
  }
423
528
 
424
529
  /**
425
- * Mark message as seen via MQTT
530
+ * Mark as seen via MQTT (mark_seen action)
426
531
  */
427
532
  async markAsSeen({ threadId, itemId }) {
428
- this.enhancedDebug(`Marking message ${itemId} as seen in thread ${threadId} via MQTT`);
429
-
430
- try {
431
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
432
- if (!mqtt || typeof mqtt.publish !== 'function') {
433
- throw new Error('MQTT client not available');
434
- }
435
-
436
- const clientContext = (0, uuid_1.v4)();
437
- const command = {
438
- action: 'mark_as_seen',
439
- thread_id: threadId,
533
+ this.enhancedDebug(`Marking message ${itemId} as seen in thread ${threadId}`);
534
+
535
+ const result = await this.sendCommand({
536
+ action: 'mark_seen',
537
+ threadId,
538
+ data: {
440
539
  item_id: itemId,
441
- timestamp: Date.now(),
442
- client_context: clientContext,
443
- };
444
-
445
- const json = JSON.stringify(command);
446
- const { compressDeflate } = shared_1;
447
- const payload = await compressDeflate(json);
448
-
449
- this.enhancedDebug(`Publishing mark as seen to MQTT`);
450
- const result = await mqtt.publish({
451
- topic: constants_1.Topics.SEND_MESSAGE.id,
452
- qosLevel: 1,
453
- payload: payload,
454
- });
455
-
456
- this.enhancedDebug(`✅ Message marked as seen via MQTT!`);
457
- return result;
458
- } catch (err) {
459
- this.enhancedDebug(`Mark as seen failed: ${err.message}`);
460
- throw err;
461
- }
540
+ },
541
+ });
542
+
543
+ this.enhancedDebug(`✅ Message marked as seen via MQTT!`);
544
+ return result;
462
545
  }
463
546
 
464
547
  /**
465
- * Indicate activity (typing) via MQTT
548
+ * Indicate activity (typing) via MQTT (activity_status)
466
549
  */
467
- async indicateActivity({ threadId, isActive = true, clientContext }) {
468
- this.enhancedDebug(`Indicating ${isActive ? 'typing' : 'stopped'} in thread ${threadId} via MQTT`);
469
-
470
- try {
471
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
472
- if (!mqtt || typeof mqtt.publish !== 'function') {
473
- throw new Error('MQTT client not available');
474
- }
475
-
476
- const ctx = clientContext || (0, uuid_1.v4)();
477
- const command = {
478
- action: 'indicate_activity',
479
- thread_id: threadId,
480
- is_active: isActive,
481
- timestamp: Date.now(),
482
- client_context: ctx,
483
- };
484
-
485
- const json = JSON.stringify(command);
486
- const { compressDeflate } = shared_1;
487
- const payload = await compressDeflate(json);
488
-
489
- this.enhancedDebug(`Publishing activity indicator to MQTT`);
490
- const result = await mqtt.publish({
491
- topic: constants_1.Topics.SEND_MESSAGE.id,
492
- qosLevel: 1,
493
- payload: payload,
494
- });
495
-
496
- this.enhancedDebug(`✅ Activity indicator sent via MQTT!`);
497
- return result;
498
- } catch (err) {
499
- this.enhancedDebug(`Activity indicator failed: ${err.message}`);
500
- throw err;
501
- }
550
+ async indicateActivity({ threadId, isActive, clientContext }) {
551
+ const active = typeof isActive === 'undefined' ? true : isActive;
552
+ this.enhancedDebug(`Indicating ${active ? 'typing' : 'stopped'} in thread ${threadId}`);
553
+
554
+ const result = await this.sendCommand({
555
+ action: 'indicate_activity',
556
+ threadId,
557
+ clientContext: clientContext || (0, uuid_1.v4)(),
558
+ data: {
559
+ activity_status: active ? '1' : '0',
560
+ },
561
+ });
562
+
563
+ this.enhancedDebug(`✅ Activity indicator sent via MQTT!`);
564
+ return result;
502
565
  }
503
566
 
504
567
  /**
505
- * Send media (image/video) via MQTT
568
+ * Delete message via MQTT
506
569
  */
507
- async sendMedia({ text, mediaId, threadId, clientContext }) {
508
- this.enhancedDebug(`Sending media ${mediaId} to ${threadId} via MQTT`);
509
-
510
- try {
511
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
512
- if (!mqtt || typeof mqtt.publish !== 'function') {
513
- throw new Error('MQTT client not available');
514
- }
515
-
516
- const ctx = clientContext || (0, uuid_1.v4)();
517
- const command = {
518
- action: 'send_item',
519
- thread_id: threadId,
520
- item_type: 'media',
521
- media_id: mediaId,
522
- text: text || '',
523
- timestamp: Date.now(),
524
- client_context: ctx,
525
- };
526
-
527
- const json = JSON.stringify(command);
528
- const { compressDeflate } = shared_1;
529
- const payload = await compressDeflate(json);
530
-
531
- this.enhancedDebug(`Publishing media to MQTT`);
532
- const result = await mqtt.publish({
533
- topic: constants_1.Topics.SEND_MESSAGE.id,
534
- qosLevel: 1,
535
- payload: payload,
536
- });
537
-
538
- this.enhancedDebug(`✅ Media sent via MQTT!`);
539
- return result;
540
- } catch (err) {
541
- this.enhancedDebug(`Media send failed: ${err.message}`);
542
- throw err;
543
- }
570
+ async deleteMessage(threadId, itemId) {
571
+ this.enhancedDebug(`Deleting message ${itemId} from thread ${threadId}`);
572
+
573
+ const result = await this.sendCommand({
574
+ action: 'delete_item',
575
+ threadId,
576
+ clientContext: (0, uuid_1.v4)(),
577
+ data: {
578
+ item_id: itemId,
579
+ },
580
+ });
581
+
582
+ this.enhancedDebug(`✅ Message deleted via MQTT!`);
583
+ return result;
544
584
  }
545
585
 
546
586
  /**
547
- * Send location via MQTT
587
+ * Edit message via MQTT
548
588
  */
549
- async sendLocation({ text, locationId, threadId, clientContext }) {
550
- this.enhancedDebug(`Sending location ${locationId} to ${threadId} via MQTT`);
551
-
552
- try {
553
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
554
- if (!mqtt || typeof mqtt.publish !== 'function') {
555
- throw new Error('MQTT client not available');
556
- }
557
-
558
- const ctx = clientContext || (0, uuid_1.v4)();
559
- const command = {
560
- action: 'send_item',
561
- thread_id: threadId,
562
- item_type: 'location',
563
- location_id: locationId,
564
- text: text || '',
565
- timestamp: Date.now(),
566
- client_context: ctx,
567
- };
568
-
569
- const json = JSON.stringify(command);
570
- const { compressDeflate } = shared_1;
571
- const payload = await compressDeflate(json);
572
-
573
- this.enhancedDebug(`Publishing location to MQTT`);
574
- const result = await mqtt.publish({
575
- topic: constants_1.Topics.SEND_MESSAGE.id,
576
- qosLevel: 1,
577
- payload: payload,
578
- });
579
-
580
- this.enhancedDebug(`✅ Location sent via MQTT!`);
581
- return result;
582
- } catch (err) {
583
- this.enhancedDebug(`Location send failed: ${err.message}`);
584
- throw err;
585
- }
589
+ async editMessage(threadId, itemId, newText) {
590
+ this.enhancedDebug(`Editing message ${itemId}: "${newText}"`);
591
+
592
+ const result = await this.sendCommand({
593
+ action: 'edit_item',
594
+ threadId,
595
+ clientContext: (0, uuid_1.v4)(),
596
+ data: {
597
+ item_id: itemId,
598
+ text: newText,
599
+ },
600
+ });
601
+
602
+ this.enhancedDebug(`✅ Message edited via MQTT!`);
603
+ return result;
586
604
  }
587
605
 
588
606
  /**
589
- * Send profile via MQTT
607
+ * Reply to message via MQTT (Quote Reply)
590
608
  */
591
- async sendProfile({ text, userId, threadId, clientContext }) {
592
- this.enhancedDebug(`Sending profile ${userId} to ${threadId} via MQTT`);
593
-
594
- try {
595
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
596
- if (!mqtt || typeof mqtt.publish !== 'function') {
597
- throw new Error('MQTT client not available');
598
- }
599
-
600
- const ctx = clientContext || (0, uuid_1.v4)();
601
- const command = {
602
- action: 'send_item',
603
- thread_id: threadId,
604
- item_type: 'profile',
605
- user_id: userId,
606
- text: text || '',
607
- timestamp: Date.now(),
608
- client_context: ctx,
609
- };
610
-
611
- const json = JSON.stringify(command);
612
- const { compressDeflate } = shared_1;
613
- const payload = await compressDeflate(json);
614
-
615
- this.enhancedDebug(`Publishing profile to MQTT`);
616
- const result = await mqtt.publish({
617
- topic: constants_1.Topics.SEND_MESSAGE.id,
618
- qosLevel: 1,
619
- payload: payload,
620
- });
621
-
622
- this.enhancedDebug(`✅ Profile sent via MQTT!`);
623
- return result;
624
- } catch (err) {
625
- this.enhancedDebug(`Profile send failed: ${err.message}`);
626
- throw err;
627
- }
609
+ async replyToMessage(threadId, messageId, replyText) {
610
+ this.enhancedDebug(`Replying to ${messageId} in thread ${threadId}: "${replyText}"`);
611
+
612
+ const result = await this.sendItem({
613
+ itemType: 'text',
614
+ threadId,
615
+ clientContext: (0, uuid_1.v4)(),
616
+ data: {
617
+ text: replyText,
618
+ replied_to_item_id: messageId,
619
+ },
620
+ });
621
+
622
+ this.enhancedDebug(`✅ Reply sent via MQTT!`);
623
+ return result;
628
624
  }
629
625
 
630
626
  /**
631
- * Send hashtag via MQTT
627
+ * Add member to thread via MQTT
632
628
  */
633
- async sendHashtag({ text, hashtag, threadId, clientContext }) {
634
- this.enhancedDebug(`Sending hashtag ${hashtag} to ${threadId} via MQTT`);
635
-
636
- try {
637
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
638
- if (!mqtt || typeof mqtt.publish !== 'function') {
639
- throw new Error('MQTT client not available');
640
- }
641
-
642
- const ctx = clientContext || (0, uuid_1.v4)();
643
- const command = {
644
- action: 'send_item',
645
- thread_id: threadId,
646
- item_type: 'hashtag',
647
- hashtag: hashtag,
648
- text: text || '',
649
- timestamp: Date.now(),
650
- client_context: ctx,
651
- };
652
-
653
- const json = JSON.stringify(command);
654
- const { compressDeflate } = shared_1;
655
- const payload = await compressDeflate(json);
656
-
657
- this.enhancedDebug(`Publishing hashtag to MQTT`);
658
- const result = await mqtt.publish({
659
- topic: constants_1.Topics.SEND_MESSAGE.id,
660
- qosLevel: 1,
661
- payload: payload,
662
- });
663
-
664
- this.enhancedDebug(`✅ Hashtag sent via MQTT!`);
665
- return result;
666
- } catch (err) {
667
- this.enhancedDebug(`Hashtag send failed: ${err.message}`);
668
- throw err;
669
- }
629
+ async addMemberToThread(threadId, userId) {
630
+ this.enhancedDebug(`Adding user ${userId} to thread ${threadId}`);
631
+
632
+ const result = await this.sendCommand({
633
+ action: 'add_users',
634
+ threadId,
635
+ clientContext: (0, uuid_1.v4)(),
636
+ data: {
637
+ user_ids: Array.isArray(userId) ? userId : [userId],
638
+ },
639
+ });
640
+
641
+ this.enhancedDebug(`✅ Member added to thread via MQTT!`);
642
+ return result;
670
643
  }
671
644
 
672
645
  /**
673
- * Send like via MQTT
646
+ * Remove member from thread via MQTT
674
647
  */
675
- async sendLike({ threadId, clientContext }) {
676
- this.enhancedDebug(`Sending like in thread ${threadId} via MQTT`);
677
-
678
- try {
679
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
680
- if (!mqtt || typeof mqtt.publish !== 'function') {
681
- throw new Error('MQTT client not available');
682
- }
683
-
684
- const ctx = clientContext || (0, uuid_1.v4)();
685
- const command = {
686
- action: 'send_item',
687
- thread_id: threadId,
688
- item_type: 'like',
689
- timestamp: Date.now(),
690
- client_context: ctx,
691
- };
692
-
693
- const json = JSON.stringify(command);
694
- const { compressDeflate } = shared_1;
695
- const payload = await compressDeflate(json);
696
-
697
- this.enhancedDebug(`Publishing like to MQTT`);
698
- const result = await mqtt.publish({
699
- topic: constants_1.Topics.SEND_MESSAGE.id,
700
- qosLevel: 1,
701
- payload: payload,
702
- });
703
-
704
- this.enhancedDebug(`✅ Like sent via MQTT!`);
705
- return result;
706
- } catch (err) {
707
- this.enhancedDebug(`Like send failed: ${err.message}`);
708
- throw err;
709
- }
648
+ async removeMemberFromThread(threadId, userId) {
649
+ this.enhancedDebug(`Removing user ${userId} from thread ${threadId}`);
650
+
651
+ const result = await this.sendCommand({
652
+ action: 'remove_users',
653
+ threadId,
654
+ clientContext: (0, uuid_1.v4)(),
655
+ data: {
656
+ user_ids: Array.isArray(userId) ? userId : [userId],
657
+ },
658
+ });
659
+
660
+ this.enhancedDebug(`✅ Member removed from thread via MQTT!`);
661
+ return result;
710
662
  }
711
663
 
712
664
  /**
713
- * Send user story via MQTT
665
+ * Leave thread via MQTT
714
666
  */
715
- async sendUserStory({ text, storyId, threadId, clientContext }) {
716
- this.enhancedDebug(`Sending story ${storyId} to ${threadId} via MQTT`);
717
-
718
- try {
719
- const mqtt = this.realtimeClient.mqtt || this.realtimeClient._mqtt;
720
- if (!mqtt || typeof mqtt.publish !== 'function') {
721
- throw new Error('MQTT client not available');
722
- }
723
-
724
- const ctx = clientContext || (0, uuid_1.v4)();
725
- const command = {
726
- action: 'send_item',
727
- thread_id: threadId,
728
- item_type: 'story',
729
- story_id: storyId,
730
- text: text || '',
731
- timestamp: Date.now(),
732
- client_context: ctx,
733
- };
734
-
735
- const json = JSON.stringify(command);
736
- const { compressDeflate } = shared_1;
737
- const payload = await compressDeflate(json);
738
-
739
- this.enhancedDebug(`Publishing story to MQTT`);
740
- const result = await mqtt.publish({
741
- topic: constants_1.Topics.SEND_MESSAGE.id,
742
- qosLevel: 1,
743
- payload: payload,
744
- });
745
-
746
- this.enhancedDebug(`✅ Story sent via MQTT!`);
747
- return result;
748
- } catch (err) {
749
- this.enhancedDebug(`Story send failed: ${err.message}`);
750
- throw err;
751
- }
667
+ async leaveThread(threadId) {
668
+ this.enhancedDebug(`Leaving thread ${threadId}`);
669
+
670
+ const result = await this.sendCommand({
671
+ action: 'leave',
672
+ threadId,
673
+ clientContext: (0, uuid_1.v4)(),
674
+ data: {},
675
+ });
676
+
677
+ this.enhancedDebug(`✅ Left thread via MQTT!`);
678
+ return result;
679
+ }
680
+
681
+ /**
682
+ * Mute thread via MQTT
683
+ */
684
+ async muteThread(threadId, muteUntil = null) {
685
+ this.enhancedDebug(`Muting thread ${threadId}`);
686
+
687
+ const result = await this.sendCommand({
688
+ action: 'mute',
689
+ threadId,
690
+ clientContext: (0, uuid_1.v4)(),
691
+ data: {
692
+ mute_until: muteUntil,
693
+ },
694
+ });
695
+
696
+ this.enhancedDebug(`✅ Thread muted via MQTT!`);
697
+ return result;
698
+ }
699
+
700
+ /**
701
+ * Unmute thread via MQTT
702
+ */
703
+ async unmuteThread(threadId) {
704
+ this.enhancedDebug(`Unmuting thread ${threadId}`);
705
+
706
+ const result = await this.sendCommand({
707
+ action: 'unmute',
708
+ threadId,
709
+ clientContext: (0, uuid_1.v4)(),
710
+ data: {},
711
+ });
712
+
713
+ this.enhancedDebug(`✅ Thread unmuted via MQTT!`);
714
+ return result;
715
+ }
716
+
717
+ /**
718
+ * Update thread title via MQTT
719
+ */
720
+ async updateThreadTitle(threadId, title) {
721
+ this.enhancedDebug(`Updating thread ${threadId} title to: "${title}"`);
722
+
723
+ const result = await this.sendCommand({
724
+ action: 'update_title',
725
+ threadId,
726
+ clientContext: (0, uuid_1.v4)(),
727
+ data: {
728
+ title: title,
729
+ },
730
+ });
731
+
732
+ this.enhancedDebug(`✅ Thread title updated via MQTT!`);
733
+ return result;
734
+ }
735
+
736
+ /**
737
+ * Send link via MQTT
738
+ */
739
+ async sendLink({ link, text, threadId, clientContext }) {
740
+ this.enhancedDebug(`Sending link ${link} to ${threadId}`);
741
+
742
+ const result = await this.sendItem({
743
+ itemType: 'link',
744
+ threadId,
745
+ clientContext,
746
+ data: {
747
+ link_text: text || '',
748
+ // use array (not JSON string) to match instagram_mqtt expectations
749
+ link_urls: [link],
750
+ },
751
+ });
752
+
753
+ this.enhancedDebug(`✅ Link sent via MQTT!`);
754
+ return result;
755
+ }
756
+
757
+ /**
758
+ * Send animated media (GIF/sticker) via MQTT
759
+ */
760
+ async sendAnimatedMedia({ id, isSticker, threadId, clientContext }) {
761
+ this.enhancedDebug(`Sending animated media ${id} to ${threadId}`);
762
+
763
+ const result = await this.sendItem({
764
+ itemType: 'animated_media',
765
+ threadId,
766
+ clientContext,
767
+ data: {
768
+ id: id,
769
+ is_sticker: isSticker || false,
770
+ },
771
+ });
772
+
773
+ this.enhancedDebug(`✅ Animated media sent via MQTT!`);
774
+ return result;
775
+ }
776
+
777
+ /**
778
+ * Send voice message via MQTT (after upload)
779
+ */
780
+ async sendVoice({ uploadId, waveform, waveformSamplingFrequencyHz, threadId, clientContext }) {
781
+ this.enhancedDebug(`Sending voice ${uploadId} to ${threadId}`);
782
+
783
+ const result = await this.sendItem({
784
+ itemType: 'voice_media',
785
+ threadId,
786
+ clientContext,
787
+ data: {
788
+ upload_id: uploadId,
789
+ waveform: waveform,
790
+ waveform_sampling_frequency_hz: waveformSamplingFrequencyHz || 10,
791
+ },
792
+ });
793
+
794
+ this.enhancedDebug(`✅ Voice sent via MQTT!`);
795
+ return result;
752
796
  }
753
797
 
754
798
  /**
755
799
  * Send photo via Realtime (Upload + Broadcast)
756
- * This method uploads the photo first, then broadcasts it to the thread
757
- *
758
- * @param {Object} options - Photo sending options
759
- * @param {Buffer} options.photoBuffer - Image buffer (JPEG/PNG)
760
- * @param {string} options.threadId - Thread ID to send to
761
- * @param {string} [options.caption] - Optional caption
762
- * @param {string} [options.mimeType='image/jpeg'] - MIME type
763
- * @param {string} [options.clientContext] - Optional client context
800
+ * Note: depends on realtimeClient.ig.request for uploading
764
801
  */
765
802
  async sendPhotoViaRealtime({ photoBuffer, threadId, caption = '', mimeType = 'image/jpeg', clientContext }) {
766
803
  this.enhancedDebug(`Sending photo to thread ${threadId} via Realtime`);
767
-
804
+
768
805
  try {
769
- // Validate inputs
770
806
  if (!photoBuffer || !Buffer.isBuffer(photoBuffer) || photoBuffer.length === 0) {
771
807
  throw new Error('photoBuffer must be a non-empty Buffer');
772
808
  }
@@ -774,18 +810,16 @@ class EnhancedDirectCommands {
774
810
  throw new Error('threadId is required');
775
811
  }
776
812
 
777
- // Get the ig client from realtime client
778
813
  const ig = this.realtimeClient.ig;
779
814
  if (!ig || !ig.request) {
780
815
  throw new Error('Instagram client not available. Make sure you are logged in.');
781
816
  }
782
817
 
783
- // Step 1: Upload photo using rupload endpoint
784
818
  this.enhancedDebug(`Step 1: Uploading photo (${photoBuffer.length} bytes)...`);
785
-
819
+
786
820
  const uploadId = Date.now().toString();
787
821
  const objectName = `${(0, uuid_1.v4)()}.${mimeType === 'image/png' ? 'png' : 'jpg'}`;
788
-
822
+
789
823
  const isJpeg = mimeType === 'image/jpeg' || mimeType === 'image/jpg';
790
824
  const compression = isJpeg
791
825
  ? '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}'
@@ -824,13 +858,12 @@ class EnhancedDirectCommands {
824
858
  }
825
859
  this.enhancedDebug(`✅ Photo uploaded! upload_id: ${serverUploadId}`);
826
860
  } catch (uploadErr) {
827
- this.enhancedDebug(`Upload error: ${uploadErr.message}`);
828
- throw new Error(`Photo upload failed: ${uploadErr.message}`);
861
+ this.enhancedDebug(`Upload error: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
862
+ throw new Error(`Photo upload failed: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
829
863
  }
830
864
 
831
- // Step 2: Broadcast the uploaded photo to the thread
832
865
  this.enhancedDebug(`Step 2: Broadcasting photo to thread ${threadId}...`);
833
-
866
+
834
867
  const broadcastForm = {
835
868
  upload_id: serverUploadId,
836
869
  action: 'send_item',
@@ -851,18 +884,18 @@ class EnhancedDirectCommands {
851
884
  this.enhancedDebug(`✅ Photo sent successfully to thread ${threadId}!`);
852
885
  return broadcastResponse;
853
886
  } catch (broadcastErr) {
854
- this.enhancedDebug(`Broadcast error: ${broadcastErr.message}`);
855
- throw new Error(`Photo broadcast failed: ${broadcastErr.message}`);
887
+ this.enhancedDebug(`Broadcast error: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
888
+ throw new Error(`Photo broadcast failed: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
856
889
  }
857
890
 
858
891
  } catch (err) {
859
- this.enhancedDebug(`sendPhotoViaRealtime failed: ${err.message}`);
892
+ this.enhancedDebug(`sendPhotoViaRealtime failed: ${err && err.message ? err.message : String(err)}`);
860
893
  throw err;
861
894
  }
862
895
  }
863
896
 
864
897
  /**
865
- * Alias for sendPhotoViaRealtime (for compatibility)
898
+ * Alias for sendPhotoViaRealtime
866
899
  */
867
900
  async sendPhoto(options) {
868
901
  return this.sendPhotoViaRealtime(options);
@@ -870,21 +903,12 @@ class EnhancedDirectCommands {
870
903
 
871
904
  /**
872
905
  * Send video via Realtime (Upload + Broadcast)
873
- *
874
- * @param {Object} options - Video sending options
875
- * @param {Buffer} options.videoBuffer - Video buffer (MP4)
876
- * @param {string} options.threadId - Thread ID to send to
877
- * @param {string} [options.caption] - Optional caption
878
- * @param {number} [options.duration] - Video duration in seconds
879
- * @param {number} [options.width] - Video width
880
- * @param {number} [options.height] - Video height
881
- * @param {string} [options.clientContext] - Optional client context
906
+ * Note: depends on realtimeClient.ig.request for uploading
882
907
  */
883
908
  async sendVideoViaRealtime({ videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280, clientContext }) {
884
909
  this.enhancedDebug(`Sending video to thread ${threadId} via Realtime`);
885
-
910
+
886
911
  try {
887
- // Validate inputs
888
912
  if (!videoBuffer || !Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {
889
913
  throw new Error('videoBuffer must be a non-empty Buffer');
890
914
  }
@@ -892,21 +916,19 @@ class EnhancedDirectCommands {
892
916
  throw new Error('threadId is required');
893
917
  }
894
918
 
895
- // Get the ig client from realtime client
896
919
  const ig = this.realtimeClient.ig;
897
920
  if (!ig || !ig.request) {
898
921
  throw new Error('Instagram client not available. Make sure you are logged in.');
899
922
  }
900
923
 
901
- // Step 1: Upload video using rupload endpoint
902
924
  this.enhancedDebug(`Step 1: Uploading video (${videoBuffer.length} bytes)...`);
903
-
925
+
904
926
  const uploadId = Date.now().toString();
905
927
  const objectName = `${(0, uuid_1.v4)()}.mp4`;
906
-
928
+
907
929
  const ruploadParams = {
908
930
  upload_id: uploadId,
909
- media_type: 2, // 2 = video
931
+ media_type: 2,
910
932
  xsharing_user_ids: JSON.stringify([]),
911
933
  upload_media_duration_ms: Math.round(duration * 1000),
912
934
  upload_media_width: width,
@@ -939,13 +961,12 @@ class EnhancedDirectCommands {
939
961
  }
940
962
  this.enhancedDebug(`✅ Video uploaded! upload_id: ${serverUploadId}`);
941
963
  } catch (uploadErr) {
942
- this.enhancedDebug(`Video upload error: ${uploadErr.message}`);
943
- throw new Error(`Video upload failed: ${uploadErr.message}`);
964
+ this.enhancedDebug(`Video upload error: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
965
+ throw new Error(`Video upload failed: ${uploadErr && uploadErr.message ? uploadErr.message : String(uploadErr)}`);
944
966
  }
945
967
 
946
- // Step 2: Broadcast the uploaded video to the thread
947
968
  this.enhancedDebug(`Step 2: Broadcasting video to thread ${threadId}...`);
948
-
969
+
949
970
  const broadcastForm = {
950
971
  upload_id: serverUploadId,
951
972
  action: 'send_item',
@@ -967,21 +988,216 @@ class EnhancedDirectCommands {
967
988
  this.enhancedDebug(`✅ Video sent successfully to thread ${threadId}!`);
968
989
  return broadcastResponse;
969
990
  } catch (broadcastErr) {
970
- this.enhancedDebug(`Video broadcast error: ${broadcastErr.message}`);
971
- throw new Error(`Video broadcast failed: ${broadcastErr.message}`);
991
+ this.enhancedDebug(`Video broadcast error: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
992
+ throw new Error(`Video broadcast failed: ${broadcastErr && broadcastErr.message ? broadcastErr.message : String(broadcastErr)}`);
972
993
  }
973
994
 
974
995
  } catch (err) {
975
- this.enhancedDebug(`sendVideoViaRealtime failed: ${err.message}`);
996
+ this.enhancedDebug(`sendVideoViaRealtime failed: ${err && err.message ? err.message : String(err)}`);
976
997
  throw err;
977
998
  }
978
999
  }
979
1000
 
980
1001
  /**
981
- * Alias for sendVideoViaRealtime (for compatibility)
1002
+ * Alias for sendVideoViaRealtime
982
1003
  */
983
1004
  async sendVideo(options) {
984
1005
  return this.sendVideoViaRealtime(options);
985
1006
  }
1007
+
1008
+ /**
1009
+ * Approve pending thread via MQTT
1010
+ */
1011
+ async approveThread(threadId) {
1012
+ this.enhancedDebug(`Approving thread ${threadId}`);
1013
+
1014
+ const result = await this.sendCommand({
1015
+ action: 'approve',
1016
+ threadId,
1017
+ clientContext: (0, uuid_1.v4)(),
1018
+ data: {},
1019
+ });
1020
+
1021
+ this.enhancedDebug(`✅ Thread approved via MQTT!`);
1022
+ return result;
1023
+ }
1024
+
1025
+ /**
1026
+ * Decline pending thread via MQTT
1027
+ */
1028
+ async declineThread(threadId) {
1029
+ this.enhancedDebug(`Declining thread ${threadId}`);
1030
+
1031
+ const result = await this.sendCommand({
1032
+ action: 'decline',
1033
+ threadId,
1034
+ clientContext: (0, uuid_1.v4)(),
1035
+ data: {},
1036
+ });
1037
+
1038
+ this.enhancedDebug(`✅ Thread declined via MQTT!`);
1039
+ return result;
1040
+ }
1041
+
1042
+ /**
1043
+ * Block user in thread via MQTT
1044
+ */
1045
+ async blockUserInThread(threadId, userId) {
1046
+ this.enhancedDebug(`Blocking user ${userId} in thread ${threadId}`);
1047
+
1048
+ const result = await this.sendCommand({
1049
+ action: 'block',
1050
+ threadId,
1051
+ clientContext: (0, uuid_1.v4)(),
1052
+ data: {
1053
+ user_id: userId,
1054
+ },
1055
+ });
1056
+
1057
+ this.enhancedDebug(`✅ User blocked in thread via MQTT!`);
1058
+ return result;
1059
+ }
1060
+
1061
+ /**
1062
+ * Report thread via MQTT
1063
+ */
1064
+ async reportThread(threadId, reason) {
1065
+ this.enhancedDebug(`Reporting thread ${threadId}`);
1066
+
1067
+ const result = await this.sendCommand({
1068
+ action: 'report',
1069
+ threadId,
1070
+ clientContext: (0, uuid_1.v4)(),
1071
+ data: {
1072
+ reason: reason || 'spam',
1073
+ },
1074
+ });
1075
+
1076
+ this.enhancedDebug(`✅ Thread reported via MQTT!`);
1077
+ return result;
1078
+ }
1079
+
1080
+ /**
1081
+ * Remove reaction via MQTT
1082
+ */
1083
+ async removeReaction({ itemId, threadId, clientContext }) {
1084
+ this.enhancedDebug(`Removing reaction from message ${itemId}`);
1085
+
1086
+ const result = await this.sendItem({
1087
+ itemType: 'reaction',
1088
+ threadId,
1089
+ clientContext,
1090
+ data: {
1091
+ item_id: itemId,
1092
+ node_type: 'item',
1093
+ reaction_type: 'like',
1094
+ reaction_status: 'deleted',
1095
+ },
1096
+ });
1097
+
1098
+ this.enhancedDebug(`✅ Reaction removed via MQTT!`);
1099
+ return result;
1100
+ }
1101
+
1102
+ /**
1103
+ * Send disappearing photo via MQTT
1104
+ */
1105
+ async sendDisappearingPhoto({ uploadId, threadId, viewMode = 'once', clientContext }) {
1106
+ this.enhancedDebug(`Sending disappearing photo to ${threadId}`);
1107
+
1108
+ const result = await this.sendItem({
1109
+ itemType: 'expiring_media_message',
1110
+ threadId,
1111
+ clientContext,
1112
+ data: {
1113
+ upload_id: uploadId,
1114
+ view_mode: viewMode,
1115
+ allow_replay: viewMode === 'replayable',
1116
+ },
1117
+ });
1118
+
1119
+ this.enhancedDebug(`✅ Disappearing photo sent via MQTT!`);
1120
+ return result;
1121
+ }
1122
+
1123
+ /**
1124
+ * Send disappearing video via MQTT
1125
+ */
1126
+ async sendDisappearingVideo({ uploadId, threadId, viewMode = 'once', clientContext }) {
1127
+ this.enhancedDebug(`Sending disappearing video to ${threadId}`);
1128
+
1129
+ const result = await this.sendItem({
1130
+ itemType: 'expiring_media_message',
1131
+ threadId,
1132
+ clientContext,
1133
+ data: {
1134
+ upload_id: uploadId,
1135
+ view_mode: viewMode,
1136
+ allow_replay: viewMode === 'replayable',
1137
+ media_type: 2,
1138
+ },
1139
+ });
1140
+
1141
+ this.enhancedDebug(`✅ Disappearing video sent via MQTT!`);
1142
+ return result;
1143
+ }
1144
+
1145
+ /**
1146
+ * Mark visual message as seen via MQTT
1147
+ */
1148
+ async markVisualMessageSeen({ threadId, itemId, clientContext }) {
1149
+ this.enhancedDebug(`Marking visual message ${itemId} as seen`);
1150
+
1151
+ const result = await this.sendCommand({
1152
+ action: 'mark_visual_item_seen',
1153
+ threadId,
1154
+ clientContext: clientContext || (0, uuid_1.v4)(),
1155
+ data: {
1156
+ item_id: itemId,
1157
+ },
1158
+ });
1159
+
1160
+ this.enhancedDebug(`✅ Visual message marked as seen via MQTT!`);
1161
+ return result;
1162
+ }
1163
+
1164
+ /**
1165
+ * Screenshot notification via MQTT
1166
+ */
1167
+ async sendScreenshotNotification({ threadId, itemId, clientContext }) {
1168
+ this.enhancedDebug(`Sending screenshot notification for ${itemId}`);
1169
+
1170
+ const result = await this.sendCommand({
1171
+ action: 'screenshot_notification',
1172
+ threadId,
1173
+ clientContext: clientContext || (0, uuid_1.v4)(),
1174
+ data: {
1175
+ item_id: itemId,
1176
+ },
1177
+ });
1178
+
1179
+ this.enhancedDebug(`✅ Screenshot notification sent via MQTT!`);
1180
+ return result;
1181
+ }
1182
+
1183
+ /**
1184
+ * Replay notification via MQTT
1185
+ */
1186
+ async sendReplayNotification({ threadId, itemId, clientContext }) {
1187
+ this.enhancedDebug(`Sending replay notification for ${itemId}`);
1188
+
1189
+ const result = await this.sendCommand({
1190
+ action: 'replay_notification',
1191
+ threadId,
1192
+ clientContext: clientContext || (0, uuid_1.v4)(),
1193
+ data: {
1194
+ item_id: itemId,
1195
+ },
1196
+ });
1197
+
1198
+ this.enhancedDebug(`✅ Replay notification sent via MQTT!`);
1199
+ return result;
1200
+ }
986
1201
  }
1202
+
987
1203
  exports.EnhancedDirectCommands = EnhancedDirectCommands;