nodejs-insta-private-api-mqtt 1.3.41 → 1.3.43
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
|
@@ -814,6 +814,140 @@ await ig.direct.sendText({ thread_ids: [threadId], text: 'Hello' });
|
|
|
814
814
|
| `presence` | User presence update |
|
|
815
815
|
| `error` | Connection error |
|
|
816
816
|
|
|
817
|
+
### Advanced Real-time Events (instagram_mqtt compatible)
|
|
818
|
+
|
|
819
|
+
These events provide deeper access to Instagram's MQTT protocol. They were added for full compatibility with the instagram_mqtt library.
|
|
820
|
+
|
|
821
|
+
| Event | Description |
|
|
822
|
+
|-------|-------------|
|
|
823
|
+
| `realtimeSub` | Raw realtime subscription data (all MQTT messages) |
|
|
824
|
+
| `direct` | Direct message events with parsed data |
|
|
825
|
+
| `subscription` | Legacy subscription event (backwards compatible) |
|
|
826
|
+
| `directTyping` | When someone is typing in a DM thread |
|
|
827
|
+
| `appPresence` | User online/offline status updates |
|
|
828
|
+
| `directStatus` | DM thread status changes |
|
|
829
|
+
| `liveWave` | Instagram Live wave notifications |
|
|
830
|
+
| `liveRealtimeComments` | Real-time comments on Instagram Live |
|
|
831
|
+
| `liveTypingIndicator` | Typing indicator in Live comments |
|
|
832
|
+
| `mediaFeedback` | Media engagement feedback |
|
|
833
|
+
| `clientConfigUpdate` | Client configuration updates |
|
|
834
|
+
|
|
835
|
+
#### Using the realtimeSub Event
|
|
836
|
+
|
|
837
|
+
The `realtimeSub` event gives you access to all raw MQTT messages. This is useful for debugging or implementing custom message handling:
|
|
838
|
+
|
|
839
|
+
```javascript
|
|
840
|
+
realtime.on('realtimeSub', ({ data, topic }) => {
|
|
841
|
+
console.log('Raw MQTT data received:', data);
|
|
842
|
+
console.log('Topic:', topic);
|
|
843
|
+
});
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
#### Using the direct Event
|
|
847
|
+
|
|
848
|
+
The `direct` event provides parsed direct message updates with automatic JSON parsing of nested values:
|
|
849
|
+
|
|
850
|
+
```javascript
|
|
851
|
+
realtime.on('direct', (data) => {
|
|
852
|
+
console.log('Direct update:', data);
|
|
853
|
+
|
|
854
|
+
// data.op contains the operation type (e.g., 'add', 'replace', 'remove')
|
|
855
|
+
// data.path contains the affected path
|
|
856
|
+
// data.value contains the parsed message data
|
|
857
|
+
|
|
858
|
+
if (data.op === 'add' && data.value) {
|
|
859
|
+
console.log('New message:', data.value.text);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
#### Using QueryID-based Events
|
|
865
|
+
|
|
866
|
+
These events are automatically emitted when Instagram sends specific subscription updates:
|
|
867
|
+
|
|
868
|
+
```javascript
|
|
869
|
+
// Listen for typing indicators
|
|
870
|
+
realtime.on('directTyping', (data) => {
|
|
871
|
+
console.log('User is typing:', data);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
// Listen for presence updates (online/offline status)
|
|
875
|
+
realtime.on('appPresence', (data) => {
|
|
876
|
+
console.log('Presence update:', data);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// Listen for DM status changes
|
|
880
|
+
realtime.on('directStatus', (data) => {
|
|
881
|
+
console.log('Direct status changed:', data);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// Listen for Instagram Live comments
|
|
885
|
+
realtime.on('liveRealtimeComments', (data) => {
|
|
886
|
+
console.log('Live comment:', data);
|
|
887
|
+
});
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
#### Complete Example: Multi-Event Listener
|
|
891
|
+
|
|
892
|
+
Here's a complete example showing how to listen to multiple events:
|
|
893
|
+
|
|
894
|
+
```javascript
|
|
895
|
+
const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqtt');
|
|
896
|
+
|
|
897
|
+
async function startAdvancedBot() {
|
|
898
|
+
const ig = new IgApiClient();
|
|
899
|
+
const auth = await useMultiFileAuthState('./auth_info_ig');
|
|
900
|
+
|
|
901
|
+
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
|
|
902
|
+
|
|
903
|
+
const realtime = new RealtimeClient(ig);
|
|
904
|
+
|
|
905
|
+
// Standard message handling
|
|
906
|
+
realtime.on('message_live', (msg) => {
|
|
907
|
+
console.log(`[${msg.username}]: ${msg.text}`);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// Advanced: Raw MQTT data (useful for debugging)
|
|
911
|
+
realtime.on('realtimeSub', ({ data }) => {
|
|
912
|
+
console.log('[Debug] Raw MQTT:', JSON.stringify(data).substring(0, 200));
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// Direct message updates with parsed data
|
|
916
|
+
realtime.on('direct', (data) => {
|
|
917
|
+
if (data.op === 'add') {
|
|
918
|
+
console.log('[Direct] New item added');
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// Typing indicators
|
|
923
|
+
realtime.on('directTyping', (data) => {
|
|
924
|
+
console.log('[Typing] Someone is typing...');
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
// User presence (online/offline)
|
|
928
|
+
realtime.on('appPresence', (data) => {
|
|
929
|
+
console.log('[Presence] User status changed');
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// Login and connect
|
|
933
|
+
if (!auth.hasSession()) {
|
|
934
|
+
await ig.login({
|
|
935
|
+
username: 'your_username',
|
|
936
|
+
password: 'your_password'
|
|
937
|
+
});
|
|
938
|
+
await auth.saveCreds(ig);
|
|
939
|
+
} else {
|
|
940
|
+
await auth.loadCreds(ig);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
await realtime.startRealTimeListener();
|
|
944
|
+
|
|
945
|
+
console.log('Advanced bot with full MQTT events is running!');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
startAdvancedBot().catch(console.error);
|
|
949
|
+
```
|
|
950
|
+
|
|
817
951
|
---
|
|
818
952
|
|
|
819
953
|
## Important Notes
|
|
@@ -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
|
-
* -
|
|
16
|
-
*
|
|
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
|
-
*
|
|
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 (
|
|
290
|
+
* Send location via MQTT (reworked)
|
|
293
291
|
*
|
|
294
|
-
*
|
|
295
|
-
* -
|
|
296
|
-
* -
|
|
297
|
-
* -
|
|
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
|
-
*
|
|
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 -
|
|
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
|
-
//
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
330
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
347
|
-
|
|
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(`
|
|
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)
|
|
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
|
/**
|
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RealtimeSubMixin - ENHANCED VERSION
|
|
4
|
+
*
|
|
5
|
+
* This file has been modified to include all MQTT features from instagram_mqtt library.
|
|
6
|
+
*
|
|
7
|
+
* CHANGES MADE (from instagram_mqtt compatibility):
|
|
8
|
+
* 1. Added import for subscriptions (QueryIDs) - was MISSING
|
|
9
|
+
* 2. Added client.mqtt.listen() approach for REALTIME_SUB topic - was MISSING
|
|
10
|
+
* 3. Added 'realtimeSub' event emission - was MISSING (only had 'subscription')
|
|
11
|
+
* 4. Added 'direct' topic processing - was MISSING
|
|
12
|
+
* 5. Added emitDirectEvent() method - was MISSING
|
|
13
|
+
* 6. Added QueryIDs-based event emission (directTyping, appPresence, etc.) - was MISSING
|
|
14
|
+
*
|
|
15
|
+
* PRESERVED:
|
|
16
|
+
* - Original 'subscription' event emission (for backwards compatibility)
|
|
17
|
+
* - Original on('message') approach with topicMap (extended, not replaced)
|
|
18
|
+
* - All existing retry logic and error handling
|
|
19
|
+
*/
|
|
2
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
21
|
exports.RealtimeSubMixin = void 0;
|
|
4
22
|
const mixin_1 = require("./mixin");
|
|
5
23
|
const constants_1 = require("../../constants");
|
|
6
24
|
const shared_1 = require("../../shared");
|
|
25
|
+
// ADDED: Import subscriptions for QueryIDs (was MISSING in nodejs-insta-private-api-mqtt)
|
|
26
|
+
const subscriptions_1 = require("../subscriptions");
|
|
7
27
|
const mqtts_1 = require("mqtts");
|
|
28
|
+
|
|
8
29
|
class RealtimeSubMixin extends mixin_1.Mixin {
|
|
9
30
|
apply(client) {
|
|
10
31
|
(0, mixin_1.hook)(client, 'connect', {
|
|
11
32
|
post: async () => {
|
|
12
|
-
// Wait for MQTT client to be ready
|
|
33
|
+
// Wait for MQTT client to be ready (PRESERVED from original)
|
|
13
34
|
let retries = 0;
|
|
14
35
|
while (!client.mqtt && retries < 50) {
|
|
15
36
|
await new Promise(r => setTimeout(r, 100));
|
|
@@ -18,6 +39,33 @@ class RealtimeSubMixin extends mixin_1.Mixin {
|
|
|
18
39
|
if (!client.mqtt) {
|
|
19
40
|
throw new mqtts_1.IllegalStateError('No mqtt client created after retries');
|
|
20
41
|
}
|
|
42
|
+
|
|
43
|
+
// ADDED: Use client.mqtt.listen() for REALTIME_SUB topic (from instagram_mqtt)
|
|
44
|
+
// This is the approach used by instagram_mqtt for handling realtime subscriptions
|
|
45
|
+
if (client.mqtt.listen) {
|
|
46
|
+
client.mqtt.listen({
|
|
47
|
+
topic: constants_1.Topics.REALTIME_SUB.id,
|
|
48
|
+
transformer: async ({ payload }) => {
|
|
49
|
+
try {
|
|
50
|
+
return constants_1.Topics.REALTIME_SUB.parser.parseMessage(
|
|
51
|
+
constants_1.Topics.REALTIME_SUB,
|
|
52
|
+
await (0, shared_1.tryUnzipAsync)(payload)
|
|
53
|
+
);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// If transformer fails, return null so handler can skip
|
|
56
|
+
console.warn('[RealtimeSubMixin] transformer parse failed:', err?.message || err);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
}, data => {
|
|
61
|
+
if (data) {
|
|
62
|
+
// ADDED: Call the instagram_mqtt compatible handler
|
|
63
|
+
this.handleRealtimeSubFromListen(client, data);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// PRESERVED: Original on('message') approach with topicMap (for backwards compatibility)
|
|
21
69
|
client.mqtt.on('message', async (msg) => {
|
|
22
70
|
const topicMap = client.mqtt?.topicMap;
|
|
23
71
|
const topic = topicMap?.get(msg.topic);
|
|
@@ -40,13 +88,91 @@ class RealtimeSubMixin extends mixin_1.Mixin {
|
|
|
40
88
|
},
|
|
41
89
|
});
|
|
42
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* ADDED: Handler compatible with instagram_mqtt's listen() approach
|
|
94
|
+
* This method handles data from client.mqtt.listen() and emits instagram_mqtt compatible events
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} client - The realtime client instance
|
|
97
|
+
* @param {Object} param1 - The parsed data object containing { data, topic }
|
|
98
|
+
*/
|
|
99
|
+
handleRealtimeSubFromListen(client, { data, topic: messageTopic }) {
|
|
100
|
+
const { message } = data;
|
|
101
|
+
|
|
102
|
+
// ADDED: Emit 'realtimeSub' event (was MISSING - this is what instagram_mqtt does)
|
|
103
|
+
client.emit('realtimeSub', { data, topic: messageTopic });
|
|
104
|
+
|
|
105
|
+
// ADDED: Process message based on type (was MISSING)
|
|
106
|
+
if (typeof message === 'string') {
|
|
107
|
+
// If message is a string, parse it and emit direct event
|
|
108
|
+
this.emitDirectEvent(client, JSON.parse(message));
|
|
109
|
+
}
|
|
110
|
+
else if (message) {
|
|
111
|
+
const { topic, payload, json } = message;
|
|
112
|
+
switch (topic) {
|
|
113
|
+
case 'direct': {
|
|
114
|
+
// ADDED: Handle 'direct' topic specifically (was MISSING)
|
|
115
|
+
this.emitDirectEvent(client, json);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
default: {
|
|
119
|
+
// ADDED: Emit QueryID-based events (was MISSING)
|
|
120
|
+
// This emits events like 'directTyping', 'appPresence', 'directStatus', etc.
|
|
121
|
+
const entries = Object.entries(subscriptions_1.QueryIDs);
|
|
122
|
+
const query = entries.find(e => e[1] === topic);
|
|
123
|
+
if (query) {
|
|
124
|
+
client.emit(query[0], json || payload);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* ADDED: Method to emit direct events (was MISSING from nodejs-insta-private-api-mqtt)
|
|
133
|
+
* This method parses string values in parsed.data and emits 'direct' event for each
|
|
134
|
+
*
|
|
135
|
+
* @param {Object} client - The realtime client instance
|
|
136
|
+
* @param {Object} parsed - The parsed message object containing data array
|
|
137
|
+
*/
|
|
138
|
+
emitDirectEvent(client, parsed) {
|
|
139
|
+
if (!parsed || !parsed.data || !Array.isArray(parsed.data)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
parsed.data = parsed.data.map((e) => {
|
|
144
|
+
if (typeof e.value === 'string') {
|
|
145
|
+
try {
|
|
146
|
+
e.value = JSON.parse(e.value);
|
|
147
|
+
} catch (parseErr) {
|
|
148
|
+
// If parsing fails, keep original value
|
|
149
|
+
console.warn('[RealtimeSubMixin] emitDirectEvent JSON parse failed:', parseErr?.message);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return e;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Emit 'direct' event for each data item
|
|
156
|
+
parsed.data.forEach((data) => client.emit('direct', data));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* PRESERVED: Original handler for on('message') approach (for backwards compatibility)
|
|
161
|
+
* This emits the 'subscription' event that existing code may depend on
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} client - The realtime client instance
|
|
164
|
+
* @param {Object} topic - The topic object from topicMap
|
|
165
|
+
* @param {Object} data - The parsed message data
|
|
166
|
+
*/
|
|
43
167
|
handleRealtimeSub(client, topic, data) {
|
|
168
|
+
// PRESERVED: Emit 'subscription' event (original behavior)
|
|
44
169
|
client.emit('subscription', {
|
|
45
170
|
query: topic.path,
|
|
46
171
|
data: data,
|
|
47
172
|
topic: topic,
|
|
48
173
|
});
|
|
49
174
|
}
|
|
175
|
+
|
|
50
176
|
get name() {
|
|
51
177
|
return 'Realtime Sub';
|
|
52
178
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-insta-private-api-mqtt",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.43",
|
|
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": {
|