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