nodejs-insta-private-api-mqtt 1.3.40 → 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.
package/README.md CHANGED
@@ -870,3 +870,164 @@ MIT
870
870
  For issues, bugs, or feature requests: https://github.com/Kunboruto20/nodejs-insta-private-api/issues
871
871
 
872
872
  Documentation: https://github.com/Kunboruto20/nodejs-insta-private-api
873
+
874
+
875
+ ---
876
+ ## Enhanced Location Usage — Practical Examples (English)
877
+
878
+ The `EnhancedDirectCommands` class (see `enhanced.direct.commands.js`) implements robust location sending for Instagram Direct by trying **story-with-location-sticker → share story to thread → fallback to link**. Below are ready-to-copy examples showing how to use the location-related methods and payloads exposed by the class.
879
+
880
+ > These examples assume:
881
+ > - `realtime` is an instance of `RealtimeClient`.
882
+ > - `realtime.directCommands` is an instance of `EnhancedDirectCommands`.
883
+ > - You have a valid `threadId` (target DM thread).
884
+ > - `realtime.ig` exists when examples require publishing a story via the private IG client.
885
+
886
+ ### 1) Send a location when you already have a `venue` object (recommended)
887
+
888
+ ```javascript
889
+ // venue shape expected by sendLocation:
890
+ // { id, name, address, lat, lng, facebook_places_id, external_source }
891
+ const venue = {
892
+ id: "213385402",
893
+ name: "McDonald's Unirii",
894
+ address: "Piața Unirii, Bucharest",
895
+ lat: 44.4268,
896
+ lng: 26.1025,
897
+ facebook_places_id: "213385402",
898
+ external_source: "facebook_places"
899
+ };
900
+
901
+ await realtime.directCommands.sendLocation({
902
+ threadId: "340282366841710300949128114477782749726",
903
+ venue,
904
+ text: "Meet me here at 18:00"
905
+ });
906
+ ```
907
+
908
+ **What happens:**
909
+ 1. The method attempts to publish a Story with a location sticker using `realtime.ig.publish.story`.
910
+ 2. If the Story publish succeeds and a `storyId` is returned, `sendUserStory` (reel_share) is used to share the story to the thread.
911
+ 3. If either step fails, the method falls back to sending a link to `https://www.instagram.com/explore/locations/{placeId}/` via `itemType: 'link'`.
912
+
913
+ ---
914
+
915
+ ### 2) Search for a place (instagram private search) and send it
916
+
917
+ Use `searchAndSendLocation()` when you only have a search query or coordinates:
918
+
919
+ ```javascript
920
+ await realtime.directCommands.searchAndSendLocation({
921
+ threadId: "340282366841710300949128114477782749726",
922
+ query: "Starbucks Piata Unirii",
923
+ lat: 44.4268,
924
+ lng: 26.1025
925
+ });
926
+ ```
927
+
928
+ This helper calls Instagram's `/fbsearch/places/` private endpoint (via `realtime.ig.request`) and then normalizes the first result into the `venue` shape before calling `sendLocation()`.
929
+
930
+ ---
931
+
932
+ ### 3) Build a location sticker manually & publish story (advanced)
933
+
934
+ If you want direct control over the sticker object or to publish your own image, you can use `createLocationStickerFromVenue()` and `realtime.ig.publish.story()` directly:
935
+
936
+ ```javascript
937
+ const venue = {
938
+ id: "213385402",
939
+ name: "McDonald's Unirii",
940
+ address: "Piața Unirii, Bucharest",
941
+ lat: 44.4268,
942
+ lng: 26.1025,
943
+ facebook_places_id: "213385402"
944
+ };
945
+
946
+ // create sticker compatible with publish.story helpers
947
+ const sticker = realtime.directCommands.createLocationStickerFromVenue(venue);
948
+
949
+ // create a tiny placeholder image (1x1 PNG) or your real photo buffer
950
+ const SINGLE_PIXEL_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=";
951
+ const photoBuffer = Buffer.from(SINGLE_PIXEL_PNG_BASE64, 'base64');
952
+
953
+ // publish story with the sticker (if realtime.ig.publish.story exists)
954
+ const publishResult = await realtime.ig.publish.story({
955
+ file: photoBuffer,
956
+ stickers: [sticker]
957
+ });
958
+
959
+ // try to resolve returned story id and then share it
960
+ const storyId = publishResult?.media?.pk || publishResult?.item_id || publishResult?.upload_id;
961
+ if (storyId) {
962
+ await realtime.directCommands.sendUserStory({
963
+ threadId: "340282366841710300949128114477782749726",
964
+ storyId,
965
+ text: "Location for tonight"
966
+ });
967
+ } else {
968
+ // fallback: send explore link manually
969
+ await realtime.directCommands.sendLink({
970
+ threadId: "340282366841710300949128114477782749726",
971
+ link: `https://www.instagram.com/explore/locations/${venue.facebook_places_id || venue.id}/`,
972
+ text: venue.name
973
+ });
974
+ }
975
+ ```
976
+
977
+ ---
978
+
979
+ ### 4) Force sending the explore-location link (explicit fallback)
980
+
981
+ If you don't want to publish a story and only need the location link in DM:
982
+
983
+ ```javascript
984
+ const placeId = "213385402";
985
+ await realtime.directCommands.sendLink({
986
+ threadId: "340282366841710300949128114477782749726",
987
+ link: `https://www.instagram.com/explore/locations/${placeId}/`,
988
+ text: "Meet here"
989
+ });
990
+ ```
991
+
992
+ ---
993
+
994
+ ### 5) Error handling & debug tips
995
+
996
+ ```javascript
997
+ try {
998
+ await realtime.directCommands.sendLocation({ threadId, venue, text: "See you" });
999
+ console.log("Location sent!");
1000
+ } catch (err) {
1001
+ console.error("Failed to send location:", err);
1002
+ // fallback to explicit link if needed
1003
+ await realtime.directCommands.sendLink({
1004
+ threadId,
1005
+ link: `https://www.instagram.com/explore/locations/${venue.facebook_places_id || venue.id}/`,
1006
+ text: venue.name || "Location"
1007
+ });
1008
+ }
1009
+ ```
1010
+
1011
+ If you need verbose logs, enable debug for the realtime/enhanced module:
1012
+
1013
+ ```bash
1014
+ # in your environment (example)
1015
+ DEBUG="realtime:enhanced-commands" node your_bot.js
1016
+ # or to see broader realtime logs
1017
+ DEBUG="realtime:*" node your_bot.js
1018
+ ```
1019
+
1020
+ ---
1021
+
1022
+ ### 6) Quick checklist (what the library needs to make story-with-sticker work)
1023
+
1024
+ - `realtime.ig` must exist and expose `publish.story(...)` (a private client publish helper).
1025
+ - The `venue` must include either `facebook_places_id` or `id`.
1026
+ - If `realtime.ig.publish.story` is unavailable or Instagram rejects the sticker, the library **automatically falls back** to sending a DM link to the Explore locations page.
1027
+
1028
+ ---
1029
+
1030
+ ## End of Location Examples
1031
+
1032
+ These examples are appended to the original README in order to keep the whole original file intact while adding clear, English examples for location flows.
1033
+
@@ -1,20 +1,29 @@
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");
7
8
  const thrift_1 = require("../../thrift");
8
9
 
9
10
  /**
10
- * Enhanced Direct Commands - sends MQTT directly with proper payload formatting
11
- * Fixed to match instagram_mqtt library format for proper functionality
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.
12
21
  */
13
22
  class EnhancedDirectCommands {
14
23
  constructor(client) {
15
24
  this.realtimeClient = client;
16
25
  this.enhancedDebug = (0, shared_1.debugChannel)('realtime', 'enhanced-commands');
17
-
26
+
18
27
  // Foreground state config for Thrift encoding (matching instagram_mqtt)
19
28
  this.foregroundStateConfig = [
20
29
  thrift_1.ThriftDescriptors.boolean('inForegroundApp', 1),
@@ -29,10 +38,10 @@ class EnhancedDirectCommands {
29
38
  }
30
39
 
31
40
  /**
32
- * Get MQTT client from realtime client
41
+ * Attempt to locate the MQTT client object on the realtime client.
42
+ * Many wrappers expose mqtt under different property names.
33
43
  */
34
44
  getMqtt() {
35
- // Try several known property names for mqtt client (some libs/wrappers expose differently)
36
45
  const candidates = [
37
46
  'mqtt',
38
47
  '_mqtt',
@@ -43,7 +52,6 @@ class EnhancedDirectCommands {
43
52
  ];
44
53
  let mqtt = null;
45
54
  for (const key of candidates) {
46
- // eslint-disable-next-line no-prototype-builtins
47
55
  if (this.realtimeClient && Object.prototype.hasOwnProperty.call(this.realtimeClient, key) && this.realtimeClient[key]) {
48
56
  mqtt = this.realtimeClient[key];
49
57
  break;
@@ -67,7 +75,6 @@ class EnhancedDirectCommands {
67
75
  * - mqtt.publish(topic, payload, { qos }, cb)
68
76
  */
69
77
  async publishToMqtt(mqtt, publishObj) {
70
- // publishObj: { topic, payload, qosLevel }
71
78
  const topic = publishObj.topic;
72
79
  const payload = publishObj.payload;
73
80
  const qosLevel = typeof publishObj.qosLevel !== 'undefined' ? publishObj.qosLevel : 1;
@@ -132,10 +139,10 @@ class EnhancedDirectCommands {
132
139
  */
133
140
  async sendForegroundState(state) {
134
141
  this.enhancedDebug(`Updated foreground state: ${JSON.stringify(state)}`);
135
-
142
+
136
143
  try {
137
144
  const mqtt = this.getMqtt();
138
-
145
+
139
146
  const thriftBuffer = (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig);
140
147
  const concat = Buffer.concat([
141
148
  Buffer.alloc(1, 0),
@@ -144,18 +151,18 @@ class EnhancedDirectCommands {
144
151
 
145
152
  // ensure we pass Buffer to compressDeflate
146
153
  const payload = await (0, shared_1.compressDeflate)(concat);
147
-
154
+
148
155
  const result = await this.publishToMqtt(mqtt, {
149
156
  topic: constants_1.Topics.FOREGROUND_STATE.id,
150
157
  payload: payload,
151
158
  qosLevel: 1,
152
159
  });
153
-
160
+
154
161
  // Update keepAlive if provided
155
162
  if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
156
163
  mqtt.keepAlive = state.keepAliveTimeout;
157
164
  }
158
-
165
+
159
166
  this.enhancedDebug(`✅ Foreground state updated via MQTT!`);
160
167
  return result;
161
168
  } catch (err) {
@@ -166,15 +173,16 @@ class EnhancedDirectCommands {
166
173
 
167
174
  /**
168
175
  * Base command sender (matching instagram_mqtt format)
176
+ * It encodes the command as JSON, compresses, and publishes to SEND_MESSAGE topic.
169
177
  */
170
178
  async sendCommand({ action, data, threadId, clientContext }) {
171
179
  try {
172
180
  const mqtt = this.getMqtt();
173
-
181
+
174
182
  if (clientContext) {
175
183
  data.client_context = clientContext;
176
184
  }
177
-
185
+
178
186
  const json = JSON.stringify({
179
187
  action,
180
188
  thread_id: threadId,
@@ -183,7 +191,7 @@ class EnhancedDirectCommands {
183
191
 
184
192
  // ensure Buffer (some compress implementations expect Buffer)
185
193
  const payload = await (0, shared_1.compressDeflate)(Buffer.from(json));
186
-
194
+
187
195
  return this.publishToMqtt(mqtt, {
188
196
  topic: constants_1.Topics.SEND_MESSAGE.id,
189
197
  qosLevel: 1,
@@ -211,11 +219,11 @@ class EnhancedDirectCommands {
211
219
  }
212
220
 
213
221
  /**
214
- * Send text via MQTT (matching instagram_mqtt format)
222
+ * Send text via MQTT
215
223
  */
216
224
  async sendText({ text, clientContext, threadId }) {
217
225
  this.enhancedDebug(`Sending text to ${threadId}: "${text}"`);
218
-
226
+
219
227
  const result = await this.sendItem({
220
228
  itemType: 'text',
221
229
  threadId,
@@ -224,7 +232,7 @@ class EnhancedDirectCommands {
224
232
  text,
225
233
  },
226
234
  });
227
-
235
+
228
236
  this.enhancedDebug(`✅ Text sent via MQTT!`);
229
237
  return result;
230
238
  }
@@ -241,11 +249,11 @@ class EnhancedDirectCommands {
241
249
  }
242
250
 
243
251
  /**
244
- * Send hashtag via MQTT (matching instagram_mqtt format)
252
+ * Send hashtag via MQTT
245
253
  */
246
254
  async sendHashtag({ text, threadId, hashtag, clientContext }) {
247
255
  this.enhancedDebug(`Sending hashtag #${hashtag} to ${threadId}`);
248
-
256
+
249
257
  const result = await this.sendItem({
250
258
  itemType: 'hashtag',
251
259
  threadId,
@@ -256,55 +264,264 @@ class EnhancedDirectCommands {
256
264
  item_id: hashtag,
257
265
  },
258
266
  });
259
-
267
+
260
268
  this.enhancedDebug(`✅ Hashtag sent via MQTT!`);
261
269
  return result;
262
270
  }
263
271
 
264
272
  /**
265
- * Send like via MQTT (matching instagram_mqtt format)
273
+ * Send like via MQTT
266
274
  */
267
275
  async sendLike({ threadId, clientContext }) {
268
276
  this.enhancedDebug(`Sending like in thread ${threadId}`);
269
-
277
+
270
278
  const result = await this.sendItem({
271
279
  itemType: 'like',
272
280
  threadId,
273
281
  clientContext,
274
282
  data: {},
275
283
  });
276
-
284
+
277
285
  this.enhancedDebug(`✅ Like sent via MQTT!`);
278
286
  return result;
279
287
  }
280
288
 
281
289
  /**
282
- * Send location via MQTT (matching instagram_mqtt format)
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 }
283
299
  */
284
- async sendLocation({ text, locationId, threadId, clientContext }) {
285
- this.enhancedDebug(`Sending location ${locationId} to ${threadId}`);
286
-
287
- const result = await this.sendItem({
288
- itemType: 'location',
289
- threadId,
290
- clientContext,
291
- data: {
292
- text: text || '',
293
- venue_id: locationId,
294
- item_id: locationId,
295
- },
296
- });
297
-
298
- this.enhancedDebug(`✅ Location sent via MQTT!`);
299
- return result;
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).');
300
404
  }
301
405
 
302
406
  /**
303
- * Send media via MQTT (matching instagram_mqtt format - media_share)
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)
304
521
  */
305
522
  async sendMedia({ text, mediaId, threadId, clientContext }) {
306
523
  this.enhancedDebug(`Sending media ${mediaId} to ${threadId}`);
307
-
524
+
308
525
  const result = await this.sendItem({
309
526
  itemType: 'media_share',
310
527
  threadId,
@@ -314,17 +531,17 @@ class EnhancedDirectCommands {
314
531
  media_id: mediaId,
315
532
  },
316
533
  });
317
-
534
+
318
535
  this.enhancedDebug(`✅ Media sent via MQTT!`);
319
536
  return result;
320
537
  }
321
538
 
322
539
  /**
323
- * Send profile via MQTT (matching instagram_mqtt format)
540
+ * Send profile via MQTT
324
541
  */
325
542
  async sendProfile({ text, userId, threadId, clientContext }) {
326
543
  this.enhancedDebug(`Sending profile ${userId} to ${threadId}`);
327
-
544
+
328
545
  const result = await this.sendItem({
329
546
  itemType: 'profile',
330
547
  threadId,
@@ -335,17 +552,17 @@ class EnhancedDirectCommands {
335
552
  item_id: userId,
336
553
  },
337
554
  });
338
-
555
+
339
556
  this.enhancedDebug(`✅ Profile sent via MQTT!`);
340
557
  return result;
341
558
  }
342
559
 
343
560
  /**
344
- * Send reaction via MQTT (matching instagram_mqtt format)
561
+ * Send reaction via MQTT
345
562
  */
346
563
  async sendReaction({ itemId, reactionType, clientContext, threadId, reactionStatus, targetItemType, emoji }) {
347
564
  this.enhancedDebug(`Sending ${reactionType || 'like'} reaction to message ${itemId}`);
348
-
565
+
349
566
  const result = await this.sendItem({
350
567
  itemType: 'reaction',
351
568
  threadId,
@@ -359,17 +576,17 @@ class EnhancedDirectCommands {
359
576
  emoji: emoji || '',
360
577
  },
361
578
  });
362
-
579
+
363
580
  this.enhancedDebug(`✅ Reaction sent via MQTT!`);
364
581
  return result;
365
582
  }
366
583
 
367
584
  /**
368
- * Send user story via MQTT (matching instagram_mqtt format - reel_share)
585
+ * Send user story via MQTT (reel_share)
369
586
  */
370
587
  async sendUserStory({ text, storyId, threadId, clientContext }) {
371
588
  this.enhancedDebug(`Sending story ${storyId} to ${threadId}`);
372
-
589
+
373
590
  const result = await this.sendItem({
374
591
  itemType: 'reel_share',
375
592
  threadId,
@@ -380,17 +597,17 @@ class EnhancedDirectCommands {
380
597
  media_id: storyId,
381
598
  },
382
599
  });
383
-
600
+
384
601
  this.enhancedDebug(`✅ Story sent via MQTT!`);
385
602
  return result;
386
603
  }
387
604
 
388
605
  /**
389
- * Mark as seen via MQTT (matching instagram_mqtt format - mark_seen action)
606
+ * Mark as seen via MQTT (mark_seen action)
390
607
  */
391
608
  async markAsSeen({ threadId, itemId }) {
392
609
  this.enhancedDebug(`Marking message ${itemId} as seen in thread ${threadId}`);
393
-
610
+
394
611
  const result = await this.sendCommand({
395
612
  action: 'mark_seen',
396
613
  threadId,
@@ -398,18 +615,18 @@ class EnhancedDirectCommands {
398
615
  item_id: itemId,
399
616
  },
400
617
  });
401
-
618
+
402
619
  this.enhancedDebug(`✅ Message marked as seen via MQTT!`);
403
620
  return result;
404
621
  }
405
622
 
406
623
  /**
407
- * Indicate activity (typing) via MQTT (matching instagram_mqtt format - activity_status)
624
+ * Indicate activity (typing) via MQTT (activity_status)
408
625
  */
409
626
  async indicateActivity({ threadId, isActive, clientContext }) {
410
627
  const active = typeof isActive === 'undefined' ? true : isActive;
411
628
  this.enhancedDebug(`Indicating ${active ? 'typing' : 'stopped'} in thread ${threadId}`);
412
-
629
+
413
630
  const result = await this.sendCommand({
414
631
  action: 'indicate_activity',
415
632
  threadId,
@@ -418,7 +635,7 @@ class EnhancedDirectCommands {
418
635
  activity_status: active ? '1' : '0',
419
636
  },
420
637
  });
421
-
638
+
422
639
  this.enhancedDebug(`✅ Activity indicator sent via MQTT!`);
423
640
  return result;
424
641
  }
@@ -428,7 +645,7 @@ class EnhancedDirectCommands {
428
645
  */
429
646
  async deleteMessage(threadId, itemId) {
430
647
  this.enhancedDebug(`Deleting message ${itemId} from thread ${threadId}`);
431
-
648
+
432
649
  const result = await this.sendCommand({
433
650
  action: 'delete_item',
434
651
  threadId,
@@ -437,7 +654,7 @@ class EnhancedDirectCommands {
437
654
  item_id: itemId,
438
655
  },
439
656
  });
440
-
657
+
441
658
  this.enhancedDebug(`✅ Message deleted via MQTT!`);
442
659
  return result;
443
660
  }
@@ -447,7 +664,7 @@ class EnhancedDirectCommands {
447
664
  */
448
665
  async editMessage(threadId, itemId, newText) {
449
666
  this.enhancedDebug(`Editing message ${itemId}: "${newText}"`);
450
-
667
+
451
668
  const result = await this.sendCommand({
452
669
  action: 'edit_item',
453
670
  threadId,
@@ -457,7 +674,7 @@ class EnhancedDirectCommands {
457
674
  text: newText,
458
675
  },
459
676
  });
460
-
677
+
461
678
  this.enhancedDebug(`✅ Message edited via MQTT!`);
462
679
  return result;
463
680
  }
@@ -467,7 +684,7 @@ class EnhancedDirectCommands {
467
684
  */
468
685
  async replyToMessage(threadId, messageId, replyText) {
469
686
  this.enhancedDebug(`Replying to ${messageId} in thread ${threadId}: "${replyText}"`);
470
-
687
+
471
688
  const result = await this.sendItem({
472
689
  itemType: 'text',
473
690
  threadId,
@@ -477,7 +694,7 @@ class EnhancedDirectCommands {
477
694
  replied_to_item_id: messageId,
478
695
  },
479
696
  });
480
-
697
+
481
698
  this.enhancedDebug(`✅ Reply sent via MQTT!`);
482
699
  return result;
483
700
  }
@@ -487,7 +704,7 @@ class EnhancedDirectCommands {
487
704
  */
488
705
  async addMemberToThread(threadId, userId) {
489
706
  this.enhancedDebug(`Adding user ${userId} to thread ${threadId}`);
490
-
707
+
491
708
  const result = await this.sendCommand({
492
709
  action: 'add_users',
493
710
  threadId,
@@ -496,7 +713,7 @@ class EnhancedDirectCommands {
496
713
  user_ids: Array.isArray(userId) ? userId : [userId],
497
714
  },
498
715
  });
499
-
716
+
500
717
  this.enhancedDebug(`✅ Member added to thread via MQTT!`);
501
718
  return result;
502
719
  }
@@ -506,7 +723,7 @@ class EnhancedDirectCommands {
506
723
  */
507
724
  async removeMemberFromThread(threadId, userId) {
508
725
  this.enhancedDebug(`Removing user ${userId} from thread ${threadId}`);
509
-
726
+
510
727
  const result = await this.sendCommand({
511
728
  action: 'remove_users',
512
729
  threadId,
@@ -515,7 +732,7 @@ class EnhancedDirectCommands {
515
732
  user_ids: Array.isArray(userId) ? userId : [userId],
516
733
  },
517
734
  });
518
-
735
+
519
736
  this.enhancedDebug(`✅ Member removed from thread via MQTT!`);
520
737
  return result;
521
738
  }
@@ -525,14 +742,14 @@ class EnhancedDirectCommands {
525
742
  */
526
743
  async leaveThread(threadId) {
527
744
  this.enhancedDebug(`Leaving thread ${threadId}`);
528
-
745
+
529
746
  const result = await this.sendCommand({
530
747
  action: 'leave',
531
748
  threadId,
532
749
  clientContext: (0, uuid_1.v4)(),
533
750
  data: {},
534
751
  });
535
-
752
+
536
753
  this.enhancedDebug(`✅ Left thread via MQTT!`);
537
754
  return result;
538
755
  }
@@ -542,7 +759,7 @@ class EnhancedDirectCommands {
542
759
  */
543
760
  async muteThread(threadId, muteUntil = null) {
544
761
  this.enhancedDebug(`Muting thread ${threadId}`);
545
-
762
+
546
763
  const result = await this.sendCommand({
547
764
  action: 'mute',
548
765
  threadId,
@@ -551,7 +768,7 @@ class EnhancedDirectCommands {
551
768
  mute_until: muteUntil,
552
769
  },
553
770
  });
554
-
771
+
555
772
  this.enhancedDebug(`✅ Thread muted via MQTT!`);
556
773
  return result;
557
774
  }
@@ -561,14 +778,14 @@ class EnhancedDirectCommands {
561
778
  */
562
779
  async unmuteThread(threadId) {
563
780
  this.enhancedDebug(`Unmuting thread ${threadId}`);
564
-
781
+
565
782
  const result = await this.sendCommand({
566
783
  action: 'unmute',
567
784
  threadId,
568
785
  clientContext: (0, uuid_1.v4)(),
569
786
  data: {},
570
787
  });
571
-
788
+
572
789
  this.enhancedDebug(`✅ Thread unmuted via MQTT!`);
573
790
  return result;
574
791
  }
@@ -578,7 +795,7 @@ class EnhancedDirectCommands {
578
795
  */
579
796
  async updateThreadTitle(threadId, title) {
580
797
  this.enhancedDebug(`Updating thread ${threadId} title to: "${title}"`);
581
-
798
+
582
799
  const result = await this.sendCommand({
583
800
  action: 'update_title',
584
801
  threadId,
@@ -587,7 +804,7 @@ class EnhancedDirectCommands {
587
804
  title: title,
588
805
  },
589
806
  });
590
-
807
+
591
808
  this.enhancedDebug(`✅ Thread title updated via MQTT!`);
592
809
  return result;
593
810
  }
@@ -597,7 +814,7 @@ class EnhancedDirectCommands {
597
814
  */
598
815
  async sendLink({ link, text, threadId, clientContext }) {
599
816
  this.enhancedDebug(`Sending link ${link} to ${threadId}`);
600
-
817
+
601
818
  const result = await this.sendItem({
602
819
  itemType: 'link',
603
820
  threadId,
@@ -608,7 +825,7 @@ class EnhancedDirectCommands {
608
825
  link_urls: [link],
609
826
  },
610
827
  });
611
-
828
+
612
829
  this.enhancedDebug(`✅ Link sent via MQTT!`);
613
830
  return result;
614
831
  }
@@ -618,7 +835,7 @@ class EnhancedDirectCommands {
618
835
  */
619
836
  async sendAnimatedMedia({ id, isSticker, threadId, clientContext }) {
620
837
  this.enhancedDebug(`Sending animated media ${id} to ${threadId}`);
621
-
838
+
622
839
  const result = await this.sendItem({
623
840
  itemType: 'animated_media',
624
841
  threadId,
@@ -628,7 +845,7 @@ class EnhancedDirectCommands {
628
845
  is_sticker: isSticker || false,
629
846
  },
630
847
  });
631
-
848
+
632
849
  this.enhancedDebug(`✅ Animated media sent via MQTT!`);
633
850
  return result;
634
851
  }
@@ -638,7 +855,7 @@ class EnhancedDirectCommands {
638
855
  */
639
856
  async sendVoice({ uploadId, waveform, waveformSamplingFrequencyHz, threadId, clientContext }) {
640
857
  this.enhancedDebug(`Sending voice ${uploadId} to ${threadId}`);
641
-
858
+
642
859
  const result = await this.sendItem({
643
860
  itemType: 'voice_media',
644
861
  threadId,
@@ -649,17 +866,18 @@ class EnhancedDirectCommands {
649
866
  waveform_sampling_frequency_hz: waveformSamplingFrequencyHz || 10,
650
867
  },
651
868
  });
652
-
869
+
653
870
  this.enhancedDebug(`✅ Voice sent via MQTT!`);
654
871
  return result;
655
872
  }
656
873
 
657
874
  /**
658
875
  * Send photo via Realtime (Upload + Broadcast)
876
+ * Note: depends on realtimeClient.ig.request for uploading
659
877
  */
660
878
  async sendPhotoViaRealtime({ photoBuffer, threadId, caption = '', mimeType = 'image/jpeg', clientContext }) {
661
879
  this.enhancedDebug(`Sending photo to thread ${threadId} via Realtime`);
662
-
880
+
663
881
  try {
664
882
  if (!photoBuffer || !Buffer.isBuffer(photoBuffer) || photoBuffer.length === 0) {
665
883
  throw new Error('photoBuffer must be a non-empty Buffer');
@@ -674,10 +892,10 @@ class EnhancedDirectCommands {
674
892
  }
675
893
 
676
894
  this.enhancedDebug(`Step 1: Uploading photo (${photoBuffer.length} bytes)...`);
677
-
895
+
678
896
  const uploadId = Date.now().toString();
679
897
  const objectName = `${(0, uuid_1.v4)()}.${mimeType === 'image/png' ? 'png' : 'jpg'}`;
680
-
898
+
681
899
  const isJpeg = mimeType === 'image/jpeg' || mimeType === 'image/jpg';
682
900
  const compression = isJpeg
683
901
  ? '{"lib_name":"moz","lib_version":"3.1.m","quality":"80"}'
@@ -721,7 +939,7 @@ class EnhancedDirectCommands {
721
939
  }
722
940
 
723
941
  this.enhancedDebug(`Step 2: Broadcasting photo to thread ${threadId}...`);
724
-
942
+
725
943
  const broadcastForm = {
726
944
  upload_id: serverUploadId,
727
945
  action: 'send_item',
@@ -761,10 +979,11 @@ class EnhancedDirectCommands {
761
979
 
762
980
  /**
763
981
  * Send video via Realtime (Upload + Broadcast)
982
+ * Note: depends on realtimeClient.ig.request for uploading
764
983
  */
765
984
  async sendVideoViaRealtime({ videoBuffer, threadId, caption = '', duration = 0, width = 720, height = 1280, clientContext }) {
766
985
  this.enhancedDebug(`Sending video to thread ${threadId} via Realtime`);
767
-
986
+
768
987
  try {
769
988
  if (!videoBuffer || !Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {
770
989
  throw new Error('videoBuffer must be a non-empty Buffer');
@@ -779,10 +998,10 @@ class EnhancedDirectCommands {
779
998
  }
780
999
 
781
1000
  this.enhancedDebug(`Step 1: Uploading video (${videoBuffer.length} bytes)...`);
782
-
1001
+
783
1002
  const uploadId = Date.now().toString();
784
1003
  const objectName = `${(0, uuid_1.v4)()}.mp4`;
785
-
1004
+
786
1005
  const ruploadParams = {
787
1006
  upload_id: uploadId,
788
1007
  media_type: 2,
@@ -823,7 +1042,7 @@ class EnhancedDirectCommands {
823
1042
  }
824
1043
 
825
1044
  this.enhancedDebug(`Step 2: Broadcasting video to thread ${threadId}...`);
826
-
1045
+
827
1046
  const broadcastForm = {
828
1047
  upload_id: serverUploadId,
829
1048
  action: 'send_item',
@@ -867,14 +1086,14 @@ class EnhancedDirectCommands {
867
1086
  */
868
1087
  async approveThread(threadId) {
869
1088
  this.enhancedDebug(`Approving thread ${threadId}`);
870
-
1089
+
871
1090
  const result = await this.sendCommand({
872
1091
  action: 'approve',
873
1092
  threadId,
874
1093
  clientContext: (0, uuid_1.v4)(),
875
1094
  data: {},
876
1095
  });
877
-
1096
+
878
1097
  this.enhancedDebug(`✅ Thread approved via MQTT!`);
879
1098
  return result;
880
1099
  }
@@ -884,14 +1103,14 @@ class EnhancedDirectCommands {
884
1103
  */
885
1104
  async declineThread(threadId) {
886
1105
  this.enhancedDebug(`Declining thread ${threadId}`);
887
-
1106
+
888
1107
  const result = await this.sendCommand({
889
1108
  action: 'decline',
890
1109
  threadId,
891
1110
  clientContext: (0, uuid_1.v4)(),
892
1111
  data: {},
893
1112
  });
894
-
1113
+
895
1114
  this.enhancedDebug(`✅ Thread declined via MQTT!`);
896
1115
  return result;
897
1116
  }
@@ -901,7 +1120,7 @@ class EnhancedDirectCommands {
901
1120
  */
902
1121
  async blockUserInThread(threadId, userId) {
903
1122
  this.enhancedDebug(`Blocking user ${userId} in thread ${threadId}`);
904
-
1123
+
905
1124
  const result = await this.sendCommand({
906
1125
  action: 'block',
907
1126
  threadId,
@@ -910,7 +1129,7 @@ class EnhancedDirectCommands {
910
1129
  user_id: userId,
911
1130
  },
912
1131
  });
913
-
1132
+
914
1133
  this.enhancedDebug(`✅ User blocked in thread via MQTT!`);
915
1134
  return result;
916
1135
  }
@@ -920,7 +1139,7 @@ class EnhancedDirectCommands {
920
1139
  */
921
1140
  async reportThread(threadId, reason) {
922
1141
  this.enhancedDebug(`Reporting thread ${threadId}`);
923
-
1142
+
924
1143
  const result = await this.sendCommand({
925
1144
  action: 'report',
926
1145
  threadId,
@@ -929,7 +1148,7 @@ class EnhancedDirectCommands {
929
1148
  reason: reason || 'spam',
930
1149
  },
931
1150
  });
932
-
1151
+
933
1152
  this.enhancedDebug(`✅ Thread reported via MQTT!`);
934
1153
  return result;
935
1154
  }
@@ -939,7 +1158,7 @@ class EnhancedDirectCommands {
939
1158
  */
940
1159
  async removeReaction({ itemId, threadId, clientContext }) {
941
1160
  this.enhancedDebug(`Removing reaction from message ${itemId}`);
942
-
1161
+
943
1162
  const result = await this.sendItem({
944
1163
  itemType: 'reaction',
945
1164
  threadId,
@@ -951,7 +1170,7 @@ class EnhancedDirectCommands {
951
1170
  reaction_status: 'deleted',
952
1171
  },
953
1172
  });
954
-
1173
+
955
1174
  this.enhancedDebug(`✅ Reaction removed via MQTT!`);
956
1175
  return result;
957
1176
  }
@@ -961,7 +1180,7 @@ class EnhancedDirectCommands {
961
1180
  */
962
1181
  async sendDisappearingPhoto({ uploadId, threadId, viewMode = 'once', clientContext }) {
963
1182
  this.enhancedDebug(`Sending disappearing photo to ${threadId}`);
964
-
1183
+
965
1184
  const result = await this.sendItem({
966
1185
  itemType: 'expiring_media_message',
967
1186
  threadId,
@@ -972,7 +1191,7 @@ class EnhancedDirectCommands {
972
1191
  allow_replay: viewMode === 'replayable',
973
1192
  },
974
1193
  });
975
-
1194
+
976
1195
  this.enhancedDebug(`✅ Disappearing photo sent via MQTT!`);
977
1196
  return result;
978
1197
  }
@@ -982,7 +1201,7 @@ class EnhancedDirectCommands {
982
1201
  */
983
1202
  async sendDisappearingVideo({ uploadId, threadId, viewMode = 'once', clientContext }) {
984
1203
  this.enhancedDebug(`Sending disappearing video to ${threadId}`);
985
-
1204
+
986
1205
  const result = await this.sendItem({
987
1206
  itemType: 'expiring_media_message',
988
1207
  threadId,
@@ -994,7 +1213,7 @@ class EnhancedDirectCommands {
994
1213
  media_type: 2,
995
1214
  },
996
1215
  });
997
-
1216
+
998
1217
  this.enhancedDebug(`✅ Disappearing video sent via MQTT!`);
999
1218
  return result;
1000
1219
  }
@@ -1004,7 +1223,7 @@ class EnhancedDirectCommands {
1004
1223
  */
1005
1224
  async markVisualMessageSeen({ threadId, itemId, clientContext }) {
1006
1225
  this.enhancedDebug(`Marking visual message ${itemId} as seen`);
1007
-
1226
+
1008
1227
  const result = await this.sendCommand({
1009
1228
  action: 'mark_visual_item_seen',
1010
1229
  threadId,
@@ -1013,7 +1232,7 @@ class EnhancedDirectCommands {
1013
1232
  item_id: itemId,
1014
1233
  },
1015
1234
  });
1016
-
1235
+
1017
1236
  this.enhancedDebug(`✅ Visual message marked as seen via MQTT!`);
1018
1237
  return result;
1019
1238
  }
@@ -1023,7 +1242,7 @@ class EnhancedDirectCommands {
1023
1242
  */
1024
1243
  async sendScreenshotNotification({ threadId, itemId, clientContext }) {
1025
1244
  this.enhancedDebug(`Sending screenshot notification for ${itemId}`);
1026
-
1245
+
1027
1246
  const result = await this.sendCommand({
1028
1247
  action: 'screenshot_notification',
1029
1248
  threadId,
@@ -1032,7 +1251,7 @@ class EnhancedDirectCommands {
1032
1251
  item_id: itemId,
1033
1252
  },
1034
1253
  });
1035
-
1254
+
1036
1255
  this.enhancedDebug(`✅ Screenshot notification sent via MQTT!`);
1037
1256
  return result;
1038
1257
  }
@@ -1042,7 +1261,7 @@ class EnhancedDirectCommands {
1042
1261
  */
1043
1262
  async sendReplayNotification({ threadId, itemId, clientContext }) {
1044
1263
  this.enhancedDebug(`Sending replay notification for ${itemId}`);
1045
-
1264
+
1046
1265
  const result = await this.sendCommand({
1047
1266
  action: 'replay_notification',
1048
1267
  threadId,
@@ -1051,9 +1270,10 @@ class EnhancedDirectCommands {
1051
1270
  item_id: itemId,
1052
1271
  },
1053
1272
  });
1054
-
1273
+
1055
1274
  this.enhancedDebug(`✅ Replay notification sent via MQTT!`);
1056
1275
  return result;
1057
1276
  }
1058
1277
  }
1059
- exports.EnhancedDirectCommands = EnhancedDirectCommands;
1278
+
1279
+ exports.EnhancedDirectCommands = EnhancedDirectCommands;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqtt",
3
- "version": "1.3.40",
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": {