@lazyneoaz/metachat 1.0.9 → 1.0.11
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/index.js +55 -1
- package/package.json +1 -1
- package/src/apis/addUserToGroup.js +1 -1
- package/src/apis/changeAdminStatus.js +11 -9
- package/src/apis/changeGroupImage.js +1 -1
- package/src/apis/changeThreadColor.js +1 -1
- package/src/apis/createNewGroup.js +12 -4
- package/src/apis/createPoll.js +5 -2
- package/src/apis/editMessage.js +59 -48
- package/src/apis/emoji.js +2 -2
- package/src/apis/follow.js +1 -1
- package/src/apis/forwardAttachment.js +3 -2
- package/src/apis/gcmember.js +20 -12
- package/src/apis/gcname.js +2 -2
- package/src/apis/gcrule.js +16 -10
- package/src/apis/getAccess.js +3 -1
- package/src/apis/getBotInitialData.js +1 -1
- package/src/apis/getThreadInfo.js +1 -1
- package/src/apis/listenMqtt.js +72 -3
- package/src/apis/markAsSeen.js +7 -9
- package/src/apis/nickname.js +67 -50
- package/src/apis/pinMessage.js +1 -1
- package/src/apis/removeUserFromGroup.js +7 -0
- package/src/apis/sendEffect.js +7 -6
- package/src/apis/sendMessage.js +6 -2
- package/src/apis/setMessageReaction.js +59 -20
- package/src/apis/setMessageReactionMqtt.js +96 -56
- package/src/apis/setThreadThemeMqtt.js +4 -2
- package/src/apis/shareContact.js +37 -31
- package/src/apis/theme.js +1 -1
- package/src/app/MessengerBot.js +239 -0
- package/src/app/broadcast.js +48 -0
- package/src/app/config.js +97 -0
- package/src/app/createFcaClient.js +179 -0
- package/src/app/state.js +117 -0
- package/src/app/threadSync.js +106 -0
- package/src/app/updateCheck.js +92 -0
- package/src/engine/client.js +76 -28
- package/src/engine/models/loginHelper.js +5 -0
- package/src/utils/antiSuspension.js +10 -10
- package/src/utils/auth-helpers.js +17 -1
- package/src/utils/clients.js +21 -0
- package/src/utils/headers.js +1 -1
- package/src/utils/lsRequest.js +176 -0
- package/src/utils/rateLimiter.js +7 -3
- package/src/utils/tokenRefresh.js +14 -1
package/src/apis/listenMqtt.js
CHANGED
|
@@ -331,6 +331,7 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
331
331
|
}));
|
|
332
332
|
|
|
333
333
|
mqttClient.on('connect', guard("connect", async () => {
|
|
334
|
+
const wasReconnect = !ctx._mqttConnected && (ctx._reconnectAttempts || 0) > 0;
|
|
334
335
|
if (!ctx._mqttConnected) {
|
|
335
336
|
utils.log("MQTT connected successfully");
|
|
336
337
|
ctx._mqttConnected = true;
|
|
@@ -338,6 +339,17 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
338
339
|
ctx._cycling = false;
|
|
339
340
|
ctx._reconnectAttempts = 0;
|
|
340
341
|
ctx._mqttQuickCloseCount = 0;
|
|
342
|
+
|
|
343
|
+
// Update reconnect stats and emit lifecycle events
|
|
344
|
+
if (!ctx._reconnectStats) ctx._reconnectStats = { totalAttempts: 0, lastAttemptAt: null, nextAttemptAt: null, lastSuccessAt: null };
|
|
345
|
+
ctx._reconnectStats.nextAttemptAt = null;
|
|
346
|
+
ctx._reconnectStats.lastSuccessAt = Date.now();
|
|
347
|
+
try {
|
|
348
|
+
if (ctx._emitter) {
|
|
349
|
+
const eventName = wasReconnect ? 'reconnected' : 'connected';
|
|
350
|
+
ctx._emitter.emit(eventName, { timestamp: Date.now(), totalReconnects: ctx._reconnectStats.totalAttempts });
|
|
351
|
+
}
|
|
352
|
+
} catch (_) {}
|
|
341
353
|
if (ctx._reconnectTimer) {
|
|
342
354
|
clearTimeout(ctx._reconnectTimer);
|
|
343
355
|
ctx._reconnectTimer = null;
|
|
@@ -440,7 +452,7 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
440
452
|
ctx.syncToken = jsonMessage.syncToken;
|
|
441
453
|
}
|
|
442
454
|
if (jsonMessage.lastIssuedSeqId) {
|
|
443
|
-
ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
|
|
455
|
+
ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId, 10);
|
|
444
456
|
}
|
|
445
457
|
|
|
446
458
|
if (jsonMessage.deltas) {
|
|
@@ -583,7 +595,8 @@ const MQTT_DEFAULTS = {
|
|
|
583
595
|
autoReconnect: true,
|
|
584
596
|
watchdogIntervalMs: 60000,
|
|
585
597
|
staleMs: 300000,
|
|
586
|
-
reconnectAfterStop: false
|
|
598
|
+
reconnectAfterStop: false,
|
|
599
|
+
maxReconnectAttempts: 100
|
|
587
600
|
};
|
|
588
601
|
|
|
589
602
|
function mqttConf(ctx, overrides) {
|
|
@@ -812,11 +825,67 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
812
825
|
const msg = ((err && err.error) || (err && err.message) || String(err || "")) + detail;
|
|
813
826
|
|
|
814
827
|
if (/Not logged in/i.test(msg)) {
|
|
815
|
-
utils.error("MQTT", "Auth error in getSeqID: Not logged in");
|
|
828
|
+
utils.error("MQTT", "Auth error in getSeqID: Not logged in — attempting recovery...");
|
|
829
|
+
|
|
830
|
+
// Step 1: Try token refresh first (fastest, least invasive)
|
|
831
|
+
let tokenRefreshed = false;
|
|
832
|
+
try {
|
|
833
|
+
if (api.tokenRefreshManager && typeof api.tokenRefreshManager.refreshTokens === 'function') {
|
|
834
|
+
utils.log("MQTT", "getSeqID: refreshing tokens before giving up...");
|
|
835
|
+
await api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, 'https://www.facebook.com');
|
|
836
|
+
tokenRefreshed = true;
|
|
837
|
+
utils.log("MQTT", "getSeqID: token refresh succeeded, scheduling reconnect");
|
|
838
|
+
}
|
|
839
|
+
} catch (refreshErr) {
|
|
840
|
+
utils.warn("MQTT", `getSeqID: token refresh failed: ${refreshErr && refreshErr.message ? refreshErr.message : refreshErr}`);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (tokenRefreshed && ctx.globalOptions.autoReconnect) {
|
|
844
|
+
ctx._reconnectAttempts = Math.max(0, (ctx._reconnectAttempts || 0) - 1);
|
|
845
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 3000;
|
|
846
|
+
return scheduleReconnect(baseDelay);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Step 2: Try full auto re-login (email+password) as last resort
|
|
850
|
+
let reloginOk = false;
|
|
851
|
+
try {
|
|
852
|
+
const { globalAutoReLoginManager } = require('../utils/autoReLogin');
|
|
853
|
+
if (globalAutoReLoginManager && globalAutoReLoginManager.isEnabled && globalAutoReLoginManager.isEnabled()) {
|
|
854
|
+
utils.log("MQTT", "getSeqID: attempting auto re-login...");
|
|
855
|
+
reloginOk = await globalAutoReLoginManager.handleSessionExpiry(api, 'https://www.facebook.com', "MQTT getSeqID Not logged in");
|
|
856
|
+
if (reloginOk) {
|
|
857
|
+
utils.log("MQTT", "getSeqID: re-login succeeded, scheduling MQTT reconnect");
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
} catch (reloginErr) {
|
|
861
|
+
utils.warn("MQTT", `getSeqID: auto re-login failed: ${reloginErr && reloginErr.message ? reloginErr.message : reloginErr}`);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (reloginOk && ctx.globalOptions.autoReconnect) {
|
|
865
|
+
ctx._reconnectAttempts = 0;
|
|
866
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 5000;
|
|
867
|
+
return scheduleReconnect(baseDelay);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Both recovery paths exhausted — emit auth error to signal the user
|
|
816
871
|
return emitAuthError("not_logged_in", msg);
|
|
817
872
|
}
|
|
818
873
|
if (/blocked the login|checkpoint|security check|session.*expir|invalid.*session|authentication.*fail|auth.*fail|login.*block|account.*lock|verification.*requir/i.test(msg)) {
|
|
819
874
|
utils.error("MQTT", "Auth error in getSeqID: Session/Login blocked");
|
|
875
|
+
|
|
876
|
+
// Still try token refresh for session expiry before giving up
|
|
877
|
+
try {
|
|
878
|
+
if (api.tokenRefreshManager && typeof api.tokenRefreshManager.refreshTokens === 'function') {
|
|
879
|
+
utils.log("MQTT", "getSeqID: refreshing tokens on session expiry...");
|
|
880
|
+
await api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, 'https://www.facebook.com');
|
|
881
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
882
|
+
ctx._reconnectAttempts = 0;
|
|
883
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 5000;
|
|
884
|
+
return scheduleReconnect(baseDelay);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
} catch (_) {}
|
|
888
|
+
|
|
820
889
|
return emitAuthError("login_blocked", msg);
|
|
821
890
|
}
|
|
822
891
|
|
package/src/apis/markAsSeen.js
CHANGED
|
@@ -10,8 +10,8 @@ const utils = require('../utils');
|
|
|
10
10
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
11
11
|
/**
|
|
12
12
|
* Marks all messages as "seen" up to a specific timestamp.
|
|
13
|
-
* @param {number} [seen_timestamp=Date.now()] - The timestamp (in
|
|
14
|
-
* @param {Function} [callback] -
|
|
13
|
+
* @param {number} [seen_timestamp=Date.now()] - The timestamp (in ms) up to which messages should be marked as seen.
|
|
14
|
+
* @param {Function} [callback] - Optional callback function.
|
|
15
15
|
* @returns {Promise<void>} A Promise that resolves on success or rejects with an error.
|
|
16
16
|
*/
|
|
17
17
|
return async function markAsSeen(seen_timestamp, callback) {
|
|
@@ -30,11 +30,9 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (!callback) {
|
|
33
|
-
callback = function (err
|
|
34
|
-
if (err)
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
resolveFunc(friendList);
|
|
33
|
+
callback = function (err) {
|
|
34
|
+
if (err) return rejectFunc(err);
|
|
35
|
+
resolveFunc();
|
|
38
36
|
};
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -56,13 +54,13 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
56
54
|
throw resData;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
callback(null);
|
|
60
58
|
} catch (err) {
|
|
61
59
|
utils.error("markAsSeen", err);
|
|
62
60
|
if (utils.getType(err) == "Object" && err.error === "Not logged in.") {
|
|
63
61
|
ctx.loggedIn = false;
|
|
64
62
|
}
|
|
65
|
-
|
|
63
|
+
callback(err);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
return returnPromise;
|
package/src/apis/nickname.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const utils = require('../utils');
|
|
4
|
+
const { publishLsRequestWithAck, buildLsTask, generateEpochId } = require('../utils/lsRequest');
|
|
5
|
+
|
|
6
|
+
const NICKNAME_VERSION_ID = "25459622483894963";
|
|
4
7
|
|
|
5
8
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Sets a nickname for a participant in a Facebook thread via MQTT.
|
|
10
|
+
* Sets a nickname for a participant in a Facebook thread via MQTT with ACK.
|
|
11
|
+
* Uses publishLsRequestWithAck for reliable delivery confirmation.
|
|
10
12
|
*
|
|
11
|
-
* @param {string} nickname The new nickname to set.
|
|
13
|
+
* @param {string} nickname The new nickname to set (empty string to clear).
|
|
12
14
|
* @param {string} threadID The ID of the thread.
|
|
13
|
-
* @param {string} participantID The ID of the participant
|
|
14
|
-
* @param {Function} [callback] Optional callback
|
|
15
|
-
* @param {string} [initiatorID] The
|
|
16
|
-
* @returns {Promise<object>}
|
|
15
|
+
* @param {string} participantID The ID of the participant. Defaults to the bot's ID.
|
|
16
|
+
* @param {Function} [callback] Optional callback(err, result).
|
|
17
|
+
* @param {string} [initiatorID] The senderID who triggered the change (for event tracking).
|
|
18
|
+
* @returns {Promise<object>}
|
|
17
19
|
*/
|
|
18
20
|
return function setNickname(nickname, threadID, participantID, callback, initiatorID) {
|
|
19
21
|
let _callback;
|
|
@@ -37,8 +39,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
37
39
|
_callback = participantID;
|
|
38
40
|
participantID = ctx.userID;
|
|
39
41
|
_initiatorID = callback;
|
|
40
|
-
}
|
|
41
|
-
else if (utils.getType(callback) === "string") {
|
|
42
|
+
} else if (utils.getType(callback) === "string") {
|
|
42
43
|
_initiatorID = callback;
|
|
43
44
|
_callback = undefined;
|
|
44
45
|
} else {
|
|
@@ -65,66 +66,82 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
_initiatorID = _initiatorID || ctx.userID;
|
|
68
|
-
|
|
69
69
|
threadID = threadID || ctx.threadID;
|
|
70
70
|
participantID = participantID || ctx.userID;
|
|
71
71
|
|
|
72
72
|
if (!threadID) {
|
|
73
|
-
|
|
73
|
+
_callback(new Error("threadID is required to set a nickname."));
|
|
74
|
+
return returnPromise;
|
|
74
75
|
}
|
|
75
76
|
if (typeof nickname !== 'string') {
|
|
76
|
-
|
|
77
|
+
_callback(new Error("nickname must be a string."));
|
|
78
|
+
return returnPromise;
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return
|
|
80
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) {
|
|
81
|
+
_callback(new Error("Not connected to MQTT — call listenMqtt() first before using setNickname."));
|
|
82
|
+
return returnPromise;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
ctx.wsReqNumber
|
|
84
|
-
ctx.wsTaskNumber
|
|
85
|
+
if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
|
|
86
|
+
if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
|
|
85
87
|
|
|
86
|
-
const
|
|
88
|
+
const requestId = ++ctx.wsReqNumber;
|
|
89
|
+
const task = buildLsTask(ctx, "44", "thread_participant_nickname", {
|
|
87
90
|
thread_key: threadID.toString(),
|
|
88
91
|
contact_id: participantID.toString(),
|
|
89
92
|
nickname: nickname,
|
|
90
|
-
sync_group: 1
|
|
91
|
-
};
|
|
93
|
+
sync_group: 1
|
|
94
|
+
});
|
|
92
95
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
const envelope = {
|
|
97
|
+
app_id: String(ctx.appID || ctx.mqttAppID || "2220391788200892"),
|
|
98
|
+
payload: JSON.stringify({
|
|
99
|
+
epoch_id: generateEpochId(),
|
|
100
|
+
tasks: [task],
|
|
101
|
+
version_id: NICKNAME_VERSION_ID
|
|
102
|
+
}),
|
|
103
|
+
request_id: requestId,
|
|
104
|
+
type: 3
|
|
99
105
|
};
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
111
|
-
context.payload = JSON.stringify(context.payload);
|
|
112
|
-
|
|
113
|
-
ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
|
|
114
|
-
if (err) {
|
|
115
|
-
return _callback(new Error(`MQTT publish failed for setNickname: ${err.message || err}`));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const nicknameChangeEvent = {
|
|
119
|
-
type: "thread_nickname_update",
|
|
120
|
-
threadID: threadID,
|
|
121
|
-
participantID: participantID,
|
|
107
|
+
publishLsRequestWithAck({
|
|
108
|
+
client: ctx.mqttClient,
|
|
109
|
+
content: envelope,
|
|
110
|
+
requestId,
|
|
111
|
+
timeoutMs: 12000,
|
|
112
|
+
extract: (message) => ({
|
|
113
|
+
type: "thread_nickname_update",
|
|
114
|
+
threadID,
|
|
115
|
+
participantID,
|
|
122
116
|
newNickname: nickname,
|
|
123
117
|
senderID: _initiatorID,
|
|
124
118
|
BotID: ctx.userID,
|
|
125
119
|
timestamp: Date.now(),
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
success: true,
|
|
121
|
+
ack: message.payload
|
|
122
|
+
})
|
|
123
|
+
}).then((result) => {
|
|
124
|
+
_callback(null, result);
|
|
125
|
+
}).catch((err) => {
|
|
126
|
+
// ACK timeout is non-fatal — the nickname was likely applied anyway.
|
|
127
|
+
// Build a best-effort result so callers still get a useful object.
|
|
128
|
+
if (err && err.message && err.message.includes("Timeout waiting for LS ACK")) {
|
|
129
|
+
utils.warn("setNickname", "ACK timed out — nickname may still have been applied");
|
|
130
|
+
_callback(null, {
|
|
131
|
+
type: "thread_nickname_update",
|
|
132
|
+
threadID,
|
|
133
|
+
participantID,
|
|
134
|
+
newNickname: nickname,
|
|
135
|
+
senderID: _initiatorID,
|
|
136
|
+
BotID: ctx.userID,
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
success: true,
|
|
139
|
+
ackTimeout: true
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
utils.error("setNickname", err && err.message ? err.message : err);
|
|
143
|
+
_callback(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
|
|
144
|
+
}
|
|
128
145
|
});
|
|
129
146
|
|
|
130
147
|
return returnPromise;
|
package/src/apis/pinMessage.js
CHANGED
|
@@ -83,7 +83,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
if (!ctx.mqttClient) throw new Error("MQTT not connected.");
|
|
86
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) throw new Error("MQTT not connected.");
|
|
87
87
|
if (!threadID || !messageID) throw new Error(`"${action}" requires threadID and messageID.`);
|
|
88
88
|
|
|
89
89
|
const epoch_id = parseInt(utils.generateOfflineThreadingID());
|
|
@@ -16,6 +16,13 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
16
16
|
if (err) return rejectFunc(err);
|
|
17
17
|
resolveFunc(result);
|
|
18
18
|
};
|
|
19
|
+
} else {
|
|
20
|
+
const _userCb = callback;
|
|
21
|
+
callback = (err, result) => {
|
|
22
|
+
if (err) { _userCb(err); return rejectFunc(err); }
|
|
23
|
+
_userCb(null, result);
|
|
24
|
+
resolveFunc(result);
|
|
25
|
+
};
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
try {
|
package/src/apis/sendEffect.js
CHANGED
|
@@ -135,6 +135,7 @@ function buildMqttPayload(effectTag, msgObj, threadID, ctx) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
if (typeof ctx.wsReqNumber !== 'number') ctx.wsReqNumber = 0;
|
|
138
|
+
if (typeof ctx.wsTaskNumber !== 'number') ctx.wsTaskNumber = 0;
|
|
138
139
|
const request_id = ++ctx.wsReqNumber;
|
|
139
140
|
|
|
140
141
|
const content = {
|
|
@@ -145,7 +146,7 @@ function buildMqttPayload(effectTag, msgObj, threadID, ctx) {
|
|
|
145
146
|
label: '46',
|
|
146
147
|
payload: JSON.stringify(sendPayload),
|
|
147
148
|
queue_name: tid,
|
|
148
|
-
task_id:
|
|
149
|
+
task_id: ++ctx.wsTaskNumber,
|
|
149
150
|
failure_count: null,
|
|
150
151
|
},
|
|
151
152
|
{
|
|
@@ -156,7 +157,7 @@ function buildMqttPayload(effectTag, msgObj, threadID, ctx) {
|
|
|
156
157
|
sync_group: 1,
|
|
157
158
|
}),
|
|
158
159
|
queue_name: tid,
|
|
159
|
-
task_id:
|
|
160
|
+
task_id: ++ctx.wsTaskNumber,
|
|
160
161
|
failure_count: null,
|
|
161
162
|
},
|
|
162
163
|
],
|
|
@@ -213,9 +214,9 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
try {
|
|
216
|
-
if (!effectName)
|
|
217
|
-
if (message === undefined || message === null)
|
|
218
|
-
if (!threadID)
|
|
217
|
+
if (!effectName) { callback(new Error('effectName is required.')); return returnPromise; }
|
|
218
|
+
if (message === undefined || message === null) { callback(new Error('message is required.')); return returnPromise; }
|
|
219
|
+
if (!threadID) { callback(new Error('threadID is required.')); return returnPromise; }
|
|
219
220
|
|
|
220
221
|
const effectTag = resolveEffect(effectName);
|
|
221
222
|
utils.log('sendEffect', `Effect "${effectTag}" → thread ${threadID}`);
|
|
@@ -280,7 +281,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
280
281
|
timestamp: v.timestamp || p.timestamp,
|
|
281
282
|
}), { threadID, messageID: messageAndOTID, timestamp: httpTs });
|
|
282
283
|
|
|
283
|
-
|
|
284
|
+
callback(null, { ...msgInfo, effect: effectTag, method: 'http' });
|
|
284
285
|
|
|
285
286
|
} catch (err) {
|
|
286
287
|
utils.error('sendEffect', err.message || err);
|
package/src/apis/sendMessage.js
CHANGED
|
@@ -220,7 +220,11 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
220
220
|
throw new Error(JSON.stringify(resData));
|
|
221
221
|
}
|
|
222
222
|
const messageInfo = resData.payload.actions.reduce((p, v) => {
|
|
223
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
threadID: v.thread_fbid || (p && p.threadID),
|
|
225
|
+
messageID: v.message_id || (p && p.messageID),
|
|
226
|
+
timestamp: v.timestamp || (p && p.timestamp),
|
|
227
|
+
};
|
|
224
228
|
}, null);
|
|
225
229
|
return messageInfo;
|
|
226
230
|
}
|
|
@@ -259,7 +263,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
259
263
|
return callback(new Error("ThreadID should be of type number, string, or array and not " + threadIDType + "."));
|
|
260
264
|
}
|
|
261
265
|
if (replyToMessage && messageIDType !== 'String') {
|
|
262
|
-
return callback(new Error("MessageID should be of type string and not " +
|
|
266
|
+
return callback(new Error("MessageID should be of type string and not " + messageIDType + "."));
|
|
263
267
|
}
|
|
264
268
|
|
|
265
269
|
if (ctx.validator && !ctx.validator.isValidMessage(msg)) {
|
|
@@ -2,27 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
const utils = require('../utils');
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
7
|
-
return async (reaction, messageID)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
return async function setMessageReaction(reaction, messageID, callback) {
|
|
7
|
+
let resolveFunc = () => {};
|
|
8
|
+
let rejectFunc = () => {};
|
|
9
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
10
|
+
resolveFunc = resolve;
|
|
11
|
+
rejectFunc = reject;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!callback) {
|
|
15
|
+
callback = (err, data) => {
|
|
16
|
+
if (err) return rejectFunc(err);
|
|
17
|
+
resolveFunc(data);
|
|
18
|
+
};
|
|
19
|
+
} else {
|
|
20
|
+
const _userCb = callback;
|
|
21
|
+
callback = (err, data) => {
|
|
22
|
+
if (err) { _userCb(err); return rejectFunc(err); }
|
|
23
|
+
_userCb(null, data);
|
|
24
|
+
resolveFunc(data);
|
|
25
|
+
};
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
if (reaction === undefined || reaction === null) {
|
|
30
|
+
throw new Error("Please enter a valid emoji.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const action = reaction === "" ? "REMOVE_REACTION" : "ADD_REACTION";
|
|
34
|
+
|
|
35
|
+
const defData = await defaultFuncs.postFormData(
|
|
36
|
+
"https://www.facebook.com/webgraphql/mutation/",
|
|
37
|
+
ctx.jar,
|
|
38
|
+
{
|
|
39
|
+
doc_id: "3360163564070970",
|
|
40
|
+
variables: JSON.stringify({
|
|
41
|
+
data: {
|
|
42
|
+
client_mutation_id: ctx.clientMutationId++,
|
|
43
|
+
actor_id: ctx.userID,
|
|
44
|
+
action,
|
|
45
|
+
message_id: messageID,
|
|
46
|
+
reaction
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
dpr: 1
|
|
50
|
+
},
|
|
51
|
+
{}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const resData = await utils.parseAndCheckLogin(ctx, defaultFuncs)(defData);
|
|
55
|
+
if (!resData) {
|
|
56
|
+
throw new Error("setMessageReaction returned empty object.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
callback(null, { success: true, action, messageID });
|
|
60
|
+
} catch (err) {
|
|
61
|
+
utils.error("setMessageReaction", err);
|
|
62
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return returnPromise;
|
|
27
66
|
};
|
|
28
67
|
};
|
|
@@ -1,61 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
const { publishLsRequestWithAck } = require('../utils/lsRequest');
|
|
4
4
|
const utils = require('../utils');
|
|
5
5
|
|
|
6
|
-
function isCallable(func) {
|
|
7
|
-
try {
|
|
8
|
-
Reflect.apply(func, null, []);
|
|
9
|
-
return true;
|
|
10
|
-
} catch (error) {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
6
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Sets or clears a reaction on a Messenger message via MQTT with ACK.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} reaction Emoji string (e.g. "😍") or empty string to clear.
|
|
11
|
+
* @param {string} messageID The message ID.
|
|
12
|
+
* @param {string} threadID The thread ID.
|
|
13
|
+
* @param {Function} [callback] Optional callback(err, result).
|
|
14
|
+
* @returns {Promise}
|
|
15
|
+
*/
|
|
16
|
+
return function setMessageReactionMqtt(reaction, messageID, threadID, callback) {
|
|
17
|
+
let resolveFunc, rejectFunc;
|
|
18
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
19
|
+
resolveFunc = resolve;
|
|
20
|
+
rejectFunc = reject;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const cb = (typeof callback === "function")
|
|
24
|
+
? (err, data) => { callback(err, data); if (err) rejectFunc(err); else resolveFunc(data); }
|
|
25
|
+
: (err, data) => { if (err) rejectFunc(err); else resolveFunc(data); };
|
|
26
|
+
|
|
27
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) {
|
|
28
|
+
const err = new Error("Not connected to MQTT — call listenMqtt() first");
|
|
29
|
+
return cb(err), returnPromise;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
|
|
33
|
+
if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
|
|
34
|
+
|
|
35
|
+
const requestId = ++ctx.wsReqNumber;
|
|
36
|
+
const taskId = ++ctx.wsTaskNumber;
|
|
37
|
+
|
|
38
|
+
const taskPayload = {
|
|
39
|
+
thread_key: threadID,
|
|
40
|
+
timestamp_ms: Date.now(),
|
|
41
|
+
message_id: messageID,
|
|
42
|
+
reaction: reaction || "",
|
|
43
|
+
actor_id: ctx.userID,
|
|
44
|
+
reaction_style: null,
|
|
45
|
+
sync_group: 1,
|
|
46
|
+
send_attribution: Math.random() < 0.5 ? 65537 : 524289
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const task = {
|
|
50
|
+
failure_count: null,
|
|
51
|
+
label: "29",
|
|
52
|
+
payload: JSON.stringify(taskPayload),
|
|
53
|
+
queue_name: JSON.stringify(["reaction", messageID]),
|
|
54
|
+
task_id: taskId
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const content = {
|
|
58
|
+
app_id: "2220391788200892",
|
|
59
|
+
payload: JSON.stringify({
|
|
60
|
+
data_trace_id: null,
|
|
61
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
62
|
+
tasks: [task],
|
|
63
|
+
version_id: "7158486590867448"
|
|
64
|
+
}),
|
|
65
|
+
request_id: requestId,
|
|
66
|
+
type: 3
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
publishLsRequestWithAck({
|
|
70
|
+
client: ctx.mqttClient,
|
|
71
|
+
content,
|
|
72
|
+
requestId,
|
|
73
|
+
timeoutMs: 10000,
|
|
74
|
+
extract: (message) => ({
|
|
75
|
+
success: true,
|
|
76
|
+
messageID,
|
|
77
|
+
threadID,
|
|
78
|
+
reaction: reaction || "",
|
|
79
|
+
ack: message.payload
|
|
80
|
+
})
|
|
81
|
+
}).then((result) => {
|
|
82
|
+
cb(null, result);
|
|
83
|
+
}).catch((err) => {
|
|
84
|
+
if (err && err.message && err.message.includes("Timeout waiting for LS ACK")) {
|
|
85
|
+
utils.warn("setMessageReactionMqtt", "ACK timed out — reaction may still have been applied");
|
|
86
|
+
cb(null, {
|
|
87
|
+
success: true,
|
|
88
|
+
messageID,
|
|
89
|
+
threadID,
|
|
90
|
+
reaction: reaction || "",
|
|
91
|
+
ackTimeout: true
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
utils.error("setMessageReactionMqtt", err && err.message ? err.message : err);
|
|
95
|
+
cb(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return returnPromise;
|
|
100
|
+
};
|
|
61
101
|
};
|