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

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.
@@ -12,14 +12,12 @@ const thrift_1 = require("../../thrift");
12
12
  *
13
13
  * - Full, self-contained class that publishes correctly-formatted payloads to Instagram's
14
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).
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
17
19
  *
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.
20
+ * Note: server-side validation may still reject location stickers in some contexts.
23
21
  */
24
22
  class EnhancedDirectCommands {
25
23
  constructor(client) {
@@ -289,72 +287,97 @@ class EnhancedDirectCommands {
289
287
  }
290
288
 
291
289
  /**
292
- * Send location via MQTT (enhanced)
290
+ * Send location via MQTT (reworked)
293
291
  *
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 }
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}/
299
296
  *
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.
297
+ * venue shape expected:
298
+ * { id, name, address, lat, lng, facebook_places_id, external_source }
311
299
  */
312
300
  async sendLocation({ threadId, clientContext, venue, text = '' }) {
313
301
  this.enhancedDebug(`Attempting to send location to ${threadId}. Venue: ${venue ? JSON.stringify(venue) : 'none'}`);
314
302
 
315
- // Basic validation - if we don't have lat/lng and id, fallback to link or error
303
+ // Basic validation - need at least an id (or facebook_places_id)
316
304
  const hasCoords = venue && typeof venue.lat === 'number' && typeof venue.lng === 'number';
317
305
  const hasId = venue && (venue.facebook_places_id || venue.id);
318
306
 
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;
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);
328
312
 
329
- // If we have a good location object, attempt to send it
330
- if (locationObj && hasId) {
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') {
331
316
  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
- });
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
+ }
345
346
 
346
- this.enhancedDebug(`✅ Location payload published via MQTT (may still be rejected server-side).`);
347
- return result;
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
+ }
348
371
  } catch (err) {
349
- this.enhancedDebug(`Location publish failed: ${err && err.message ? err.message : String(err)} - falling back to link`);
350
- // fallthrough to fallback below
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
351
374
  }
375
+ } else {
376
+ this.enhancedDebug(`ig.publish.story not available on realtimeClient.ig — will use fallback link if possible.`);
352
377
  }
353
378
 
354
379
  // Fallback: send as a link to the location explore page (guaranteed to render in DM)
355
380
  if (hasId) {
356
- // prefer facebook_places_id if provided
357
- const placeId = venue.facebook_places_id || venue.id;
358
381
  const link = `https://www.instagram.com/explore/locations/${placeId}/`;
359
382
  this.enhancedDebug(`Sending location fallback link: ${link}`);
360
383
 
@@ -377,7 +400,60 @@ class EnhancedDirectCommands {
377
400
  }
378
401
 
379
402
  // 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.');
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;
381
457
  }
382
458
 
383
459
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqtt",
3
- "version": "1.3.41",
3
+ "version": "1.3.42",
4
4
  "description": "Complete Instagram MQTT protocol with FULL iOS + Android support. 33 device presets (21 iOS + 12 Android). iPhone 16/15/14/13/12, iPad Pro, Samsung, Pixel, Huawei. Real-time DM messaging, view-once media extraction, sub-500ms latency.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {