@dongdev/fca-unofficial 3.0.22 → 3.0.25

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/CHANGELOG.md CHANGED
@@ -182,3 +182,9 @@ Too lazy to write changelog, sorry! (will write changelog in the next release, t
182
182
 
183
183
  ## v3.0.21 - 2025-12-31
184
184
  - Hotfix / auto bump
185
+
186
+ ## v3.0.22 - 2026-01-07
187
+ - Hotfix / auto bump
188
+
189
+ ## v3.0.23 - 2026-01-30
190
+ - Hotfix / auto bump
package/DOCS.md CHANGED
@@ -1568,7 +1568,7 @@ Add reaction (like, love, haha, wow, sad, angry) to message.
1568
1568
 
1569
1569
  #### Syntax:
1570
1570
  ```javascript
1571
- api.setMessageReaction(reaction, messageID, callback);
1571
+ api.setMessageReaction(reaction, messageID, threadID, callback, forceCustomReaction);
1572
1572
  ```
1573
1573
 
1574
1574
  #### Example:
@@ -1587,7 +1587,7 @@ api.listenMqtt((err, event) => {
1587
1587
  if (err) return console.error(err);
1588
1588
 
1589
1589
  if (event.type === "message" && event.body === "React me") {
1590
- api.setMessageReaction("❤️", event.messageID, (err) => {
1590
+ api.setMessageReaction("❤️", event.messageID, event.threadID, (err) => {
1591
1591
  if (err) {
1592
1592
  console.error("React error:", err);
1593
1593
  return;
@@ -1598,7 +1598,7 @@ api.listenMqtt((err, event) => {
1598
1598
  });
1599
1599
 
1600
1600
  // Remove reaction
1601
- api.setMessageReaction("", "mid.xxx", (err) => {
1601
+ api.setMessageReaction("", "mid.xxx", "1234567890", (err) => {
1602
1602
  if (err) return console.error(err);
1603
1603
  console.log("Reaction removed!");
1604
1604
  });
package/README.md CHANGED
@@ -343,7 +343,7 @@ api.deleteMessage(messageID, callback);
343
343
  api.unsendMessage(messageID, callback);
344
344
 
345
345
  // Set message reaction
346
- api.setMessageReaction(reaction, messageID, callback);
346
+ api.setMessageReaction(reaction, messageID, threadID, callback, forceCustomReaction);
347
347
 
348
348
  // Forward attachment
349
349
  api.forwardAttachment(attachmentID, threadID, callback);
package/index.d.ts CHANGED
@@ -143,7 +143,7 @@
143
143
  pinMessage: (pinMode: boolean, messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>;
144
144
 
145
145
  // Reactions & Interactions
146
- setMessageReaction: (reaction: string, messageID: string, callback?: (err?: Error) => void, forceCustomReaction?: boolean) => Promise<void>;
146
+ setMessageReaction: (reaction: string, messageID: string, threadID: string, callback?: (err?: Error) => void, forceCustomReaction?: boolean) => Promise<void>;
147
147
  setMessageReactionMqtt: (reaction: string, messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>;
148
148
  sendTypingIndicator: (threadID: string, callback?: (err?: Error) => void) => Promise<void>;
149
149
  sendTypingIndicatorMqtt: (isTyping: boolean, threadID: string, callback?: (err?: Error) => void) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "3.0.22",
3
+ "version": "3.0.25",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -3,15 +3,17 @@
3
3
  const { generateOfflineThreadingID, getType } = require("../../utils/format");
4
4
 
5
5
  module.exports = function (defaultFuncs, api, ctx) {
6
- function changeAdminStatusNoMqtt(threadID, adminID, adminStatus) {
7
- if (getType(threadID) !== "String") throw { error: "changeAdminStatus: threadID must be a string" };
8
- if (getType(adminID) !== "String" && getType(adminID) !== "Array") throw { error: "changeAdminStatus: adminID must be a string or an array" };
9
- if (getType(adminStatus) !== "Boolean") throw { error: "changeAdminStatus: adminStatus must be true or false" };
10
- let wsContent = {
11
- request_id: 1,
12
- type: 3,
13
- payload: {
14
- version_id: '3816854585040595',
6
+ function changeAdminStatusNoMqtt(threadID, adminID, adminStatus) {
7
+ if (getType(threadID) !== "String") throw { error: "changeAdminStatus: threadID must be a string" };
8
+ if (getType(adminID) !== "String" && getType(adminID) !== "Array") throw { error: "changeAdminStatus: adminID must be a string or an array" };
9
+ if (getType(adminStatus) !== "Boolean") throw { error: "changeAdminStatus: adminStatus must be true or false" };
10
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
11
+ const reqID = ++ctx.wsReqNumber;
12
+ let wsContent = {
13
+ request_id: reqID,
14
+ type: 3,
15
+ payload: {
16
+ version_id: '3816854585040595',
15
17
  tasks: [],
16
18
  epoch_id: generateOfflineThreadingID(),
17
19
  data_trace_id: null
@@ -37,12 +39,20 @@ module.exports = function (defaultFuncs, api, ctx) {
37
39
  failure_count: null
38
40
  });
39
41
  }
40
- wsContent.payload = JSON.stringify(wsContent.payload);
41
- return new Promise((resolve, reject) => ctx.mqttClient && ctx.mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => err ? reject(err) : resolve()));
42
- };
43
- function changeAdminStatusMqtt(threadID, adminID, adminStatus) {
44
- if (!ctx.mqttClient) {
45
- throw new Error("Not connected to MQTT");
42
+ wsContent.payload = JSON.stringify(wsContent.payload);
43
+ return new Promise((resolve, reject) => {
44
+ if (!ctx.mqttClient) {
45
+ return reject(new Error("Not connected to MQTT"));
46
+ }
47
+ ctx.mqttClient.publish("/ls_req", JSON.stringify(wsContent), {}, (err) => {
48
+ if (err) return reject(err);
49
+ resolve();
50
+ });
51
+ });
52
+ };
53
+ function changeAdminStatusMqtt(threadID, adminID, adminStatus) {
54
+ if (!ctx.mqttClient) {
55
+ throw new Error("Not connected to MQTT");
46
56
  }
47
57
  if (getType(threadID) !== "String") {
48
58
  throw { error: "changeAdminStatus: threadID must be a string" };
@@ -83,40 +93,34 @@ module.exports = function (defaultFuncs, api, ctx) {
83
93
  task_id: 1
84
94
  });
85
95
  }
86
- let count_req = 0
87
- const form = JSON.stringify({
88
- app_id: "2220391788200892",
89
- payload: JSON.stringify({
90
- epoch_id: epochID,
91
- tasks: tasks,
92
- version_id: "8798795233522156"
93
- }),
94
- request_id: ++count_req,
95
- type: 3
96
- });
97
- return new Promise((resolve, reject) => {
98
- if (ctx.mqttClient) {
99
- ctx.mqttClient.publish("/ls_req", form, {}, (err, _packet) => {
100
- if (err) {
101
- reject(err);
102
- } else {
103
- resolve();
104
- }
105
- });
106
- } else {
107
- reject(new Error("MQTT client is not available"));
108
- }
109
- });
110
- };
111
- return function changeAdminStatus(threadID, adminID, adminStatus) {
112
- if (ctx.mqttClient) {
113
- try {
114
- changeAdminStatusMqtt(threadID, adminID, adminStatus);
115
- } catch (e) {
116
- changeAdminStatusNoMqtt(threadID, adminID, adminStatus);
117
- }
118
- } else {
119
- changeAdminStatusNoMqtt(threadID, adminID, adminStatus);
120
- }
121
- };
122
- };
96
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
97
+ const reqID = ++ctx.wsReqNumber;
98
+ const form = JSON.stringify({
99
+ app_id: "2220391788200892",
100
+ payload: JSON.stringify({
101
+ epoch_id: epochID,
102
+ tasks: tasks,
103
+ version_id: "8798795233522156"
104
+ }),
105
+ request_id: reqID,
106
+ type: 3
107
+ });
108
+ return new Promise((resolve, reject) => {
109
+ ctx.mqttClient.publish("/ls_req", form, {}, (err) => {
110
+ if (err) return reject(err);
111
+ resolve();
112
+ });
113
+ });
114
+ };
115
+ return function changeAdminStatus(threadID, adminID, adminStatus) {
116
+ if (ctx.mqttClient) {
117
+ try {
118
+ return changeAdminStatusMqtt(threadID, adminID, adminStatus);
119
+ } catch (e) {
120
+ return changeAdminStatusNoMqtt(threadID, adminID, adminStatus);
121
+ }
122
+ } else {
123
+ return changeAdminStatusNoMqtt(threadID, adminID, adminStatus);
124
+ }
125
+ };
126
+ };
@@ -48,28 +48,30 @@ module.exports = function (defaultFuncs, api, ctx) {
48
48
  });
49
49
  return returnPromise;
50
50
  };
51
- function changeThreadEmojiMqtt(emoji, threadID, callback) {
52
- if (!ctx.mqttClient) {
53
- throw new Error("Not connected to MQTT");
54
- }
55
- var resolveFunc = function () { };
56
- var rejectFunc = function () { };
57
- var returnPromise = new Promise(function (resolve, reject) {
58
- resolveFunc = resolve;
59
- rejectFunc = reject;
60
- });
61
- if (!callback) {
62
- callback = function (err, data) {
63
- if (err) return rejectFunc(err);
64
- resolveFunc(data);
65
- data
66
- };
67
- }
68
- let count_req = 0
69
- var form = JSON.stringify({
70
- "app_id": "2220391788200892",
71
- "payload": JSON.stringify({
72
- epoch_id: generateOfflineThreadingID(),
51
+ function changeThreadEmojiMqtt(emoji, threadID, callback) {
52
+ if (!ctx.mqttClient) {
53
+ throw new Error("Not connected to MQTT");
54
+ }
55
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
56
+ const reqID = ++ctx.wsReqNumber;
57
+ var resolveFunc = function () { };
58
+ var rejectFunc = function () { };
59
+ var returnPromise = new Promise(function (resolve, reject) {
60
+ resolveFunc = resolve;
61
+ rejectFunc = reject;
62
+ });
63
+ const done = (err, data) => {
64
+ if (err) {
65
+ if (callback) callback(err);
66
+ return rejectFunc(err);
67
+ }
68
+ if (callback) callback(null, data);
69
+ resolveFunc(data);
70
+ };
71
+ var form = JSON.stringify({
72
+ "app_id": "2220391788200892",
73
+ "payload": JSON.stringify({
74
+ epoch_id: generateOfflineThreadingID(),
73
75
  tasks: [
74
76
  {
75
77
  failure_count: null,
@@ -84,23 +86,26 @@ module.exports = function (defaultFuncs, api, ctx) {
84
86
  task_id: Math.random() * 1001 << 0
85
87
  }
86
88
  ],
87
- version_id: '8798795233522156'
88
- }),
89
- "request_id": ++count_req,
90
- "type": 3
91
- });
92
- mqttClient.publish('/ls_req', form);
93
- return returnPromise;
94
- };
95
- return function changeThreadEmoji(emoji, threadID, callback) {
96
- if (ctx.mqttClient) {
97
- try {
98
- changeThreadEmojiMqtt(emoji, threadID, callback);
99
- } catch (e) {
100
- changeThreadEmojiNoMqtt(emoji, threadID, callback);
101
- }
102
- } else {
103
- changeThreadEmojiNoMqtt(emoji, threadID, callback);
104
- }
105
- };
106
- };
89
+ version_id: '8798795233522156'
90
+ }),
91
+ "request_id": reqID,
92
+ "type": 3
93
+ });
94
+ ctx.mqttClient.publish("/ls_req", form, { qos: 1, retain: false }, (err) => {
95
+ if (err) return done(err);
96
+ done(null, { success: true });
97
+ });
98
+ return returnPromise;
99
+ };
100
+ return function changeThreadEmoji(emoji, threadID, callback) {
101
+ if (ctx.mqttClient) {
102
+ try {
103
+ return changeThreadEmojiMqtt(emoji, threadID, callback);
104
+ } catch (e) {
105
+ return changeThreadEmojiNoMqtt(emoji, threadID, callback);
106
+ }
107
+ } else {
108
+ return changeThreadEmojiNoMqtt(emoji, threadID, callback);
109
+ }
110
+ };
111
+ };
@@ -2,13 +2,16 @@
2
2
 
3
3
  const { generateOfflineThreadingID } = require("../../utils/format");
4
4
 
5
- module.exports = function (defaultFuncs, api, ctx) {
6
- return async function createPoll(threadID, questionText, options) {
7
- let count_req = 0
8
- return new Promise((resolve, reject) => {
9
- const payload = {
10
- epoch_id: generateOfflineThreadingID(),
11
- tasks: [
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ return async function createPoll(threadID, questionText, options) {
7
+ let count_req = 0
8
+ return new Promise((resolve, reject) => {
9
+ if (!ctx.mqttClient) {
10
+ return reject(new Error("Not connected to MQTT"));
11
+ }
12
+ const payload = {
13
+ epoch_id: generateOfflineThreadingID(),
14
+ tasks: [
12
15
  {
13
16
  failure_count: null,
14
17
  label: "163",
@@ -30,14 +33,14 @@ module.exports = function (defaultFuncs, api, ctx) {
30
33
  payload: JSON.stringify(payload),
31
34
  request_id: ++count_req,
32
35
  type: 3
33
- });
34
-
35
- try {
36
- mqttClient.publish("/ls_req", form);
37
- resolve();
38
- } catch (err) {
39
- reject(err);
40
- }
41
- });
36
+ });
37
+
38
+ try {
39
+ ctx.mqttClient.publish("/ls_req", form);
40
+ resolve();
41
+ } catch (err) {
42
+ reject(err);
43
+ }
44
+ });
42
45
  };
43
46
  };
@@ -2,23 +2,29 @@
2
2
 
3
3
  const { generateOfflineThreadingID } = require("../../utils/format");
4
4
 
5
- module.exports = function (defaultFuncs, api, ctx) {
6
- return async function forwardMessage(threadID, forwardedMsgID, callback) {
7
- let resolveFunc, rejectFunc;
8
- const returnPromise = new Promise((resolve, reject) => {
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ return async function forwardMessage(threadID, forwardedMsgID, callback) {
7
+ let resolveFunc, rejectFunc;
8
+ const returnPromise = new Promise((resolve, reject) => {
9
9
  resolveFunc = resolve;
10
10
  rejectFunc = reject;
11
11
  });
12
- if (!callback) {
13
- callback = (err, data) => {
14
- if (err) return rejectFunc(err);
15
- resolveFunc(data);
16
- };
17
- }
18
- let count_req = 0
19
- const payload = {
20
- epoch_id: generateOfflineThreadingID(),
21
- tasks: [
12
+ if (!callback) {
13
+ callback = (err, data) => {
14
+ if (err) return rejectFunc(err);
15
+ resolveFunc(data);
16
+ };
17
+ }
18
+ if (!ctx.mqttClient) {
19
+ const err = new Error("Not connected to MQTT");
20
+ callback?.(err);
21
+ rejectFunc(err);
22
+ return returnPromise;
23
+ }
24
+ let count_req = 0
25
+ const payload = {
26
+ epoch_id: generateOfflineThreadingID(),
27
+ tasks: [
22
28
  {
23
29
  failure_count: null,
24
30
  label: "46",
@@ -41,11 +47,11 @@ module.exports = function (defaultFuncs, api, ctx) {
41
47
  };
42
48
  const form = JSON.stringify({
43
49
  app_id: "772021112871879",
44
- payload: JSON.stringify(payload),
45
- "request_id": ++count_req,
46
- "type": 3
47
- });
48
- mqttClient.publish("/ls_req", form);
49
- return returnPromise;
50
- };
51
- };
50
+ payload: JSON.stringify(payload),
51
+ "request_id": ++count_req,
52
+ "type": 3
53
+ });
54
+ ctx.mqttClient.publish("/ls_req", form);
55
+ return returnPromise;
56
+ };
57
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { getType } = require("../../utils/format");
3
+ const { getType, generateOfflineThreadingID } = require("../../utils/format");
4
4
  const { parseAndCheckLogin } = require("../../utils/client");
5
5
  const log = require("npmlog");
6
6
 
@@ -39,13 +39,15 @@ module.exports = function (defaultFuncs, api, ctx) {
39
39
  });
40
40
  return returnPromise;
41
41
  };
42
- function removeUserFromGroupMqtt(userID, threadID, callback) {
43
- if (!ctx.mqttClient) {
44
- throw new Error("Not connected to MQTT");
45
- }
46
- if (!callback && (getType(threadID) === "Function" || getType(threadID) === "AsyncFunction")) throw { error: "please pass a threadID as a second argument." };
47
- if (getType(threadID) !== "Number" && getType(threadID) !== "String") throw { error: "threadID should be of type Number or String and not " + getType(threadID) + "." };
48
- if (getType(userID) !== "Number" && getType(userID) !== "String") throw { error: "userID should be of type Number or String and not " + getType(userID) + "." };
42
+ function removeUserFromGroupMqtt(userID, threadID, callback) {
43
+ if (!ctx.mqttClient) {
44
+ throw new Error("Not connected to MQTT");
45
+ }
46
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
47
+ const reqID = ++ctx.wsReqNumber;
48
+ if (!callback && (getType(threadID) === "Function" || getType(threadID) === "AsyncFunction")) throw { error: "please pass a threadID as a second argument." };
49
+ if (getType(threadID) !== "Number" && getType(threadID) !== "String") throw { error: "threadID should be of type Number or String and not " + getType(threadID) + "." };
50
+ if (getType(userID) !== "Number" && getType(userID) !== "String") throw { error: "userID should be of type Number or String and not " + getType(userID) + "." };
49
51
  var resolveFunc = function () { };
50
52
  var rejectFunc = function () { };
51
53
  var returnPromise = new Promise(function (resolve, reject) {
@@ -58,11 +60,10 @@ module.exports = function (defaultFuncs, api, ctx) {
58
60
  resolveFunc(data);
59
61
  };
60
62
  }
61
- let count_req = 0;
62
- var form = JSON.stringify({
63
- "app_id": "2220391788200892",
64
- "payload": JSON.stringify({
65
- epoch_id: generateOfflineThreadingID(),
63
+ var form = JSON.stringify({
64
+ "app_id": "2220391788200892",
65
+ "payload": JSON.stringify({
66
+ epoch_id: generateOfflineThreadingID(),
66
67
  tasks: [
67
68
  {
68
69
  failure_count: null,
@@ -76,31 +77,31 @@ module.exports = function (defaultFuncs, api, ctx) {
76
77
  task_id: Math.random() * 1001 << 0
77
78
  }
78
79
  ],
79
- version_id: '8798795233522156'
80
- }),
81
- "request_id": ++count_req,
82
- "type": 3
83
- });
84
- mqttClient.publish('/ls_req', form, (err, data) => {
85
- if (err) {
86
- callback(err, null);
87
- rejectFunc(false);
88
- } else {
89
- callback(null, true);
90
- resolveFunc(true);
91
- }
92
- });
80
+ version_id: '8798795233522156'
81
+ }),
82
+ "request_id": reqID,
83
+ "type": 3
84
+ });
85
+ ctx.mqttClient.publish('/ls_req', form, (err, data) => {
86
+ if (err) {
87
+ callback(err, null);
88
+ rejectFunc(err);
89
+ } else {
90
+ callback(null, true);
91
+ resolveFunc(true);
92
+ }
93
+ });
93
94
  return returnPromise;
94
95
  };
95
- return function removeUserFromGroup(userID, threadID, callback) {
96
- if (ctx.mqttClient) {
97
- try {
98
- removeUserFromGroupMqtt(userID, threadID, callback);
99
- } catch (e) {
100
- removeUserFromGroupNoMqtt(userID, threadID, callback);
101
- }
102
- } else {
103
- removeUserFromGroupNoMqtt(userID, threadID, callback);
104
- }
105
- };
106
- };
96
+ return function removeUserFromGroup(userID, threadID, callback) {
97
+ if (ctx.mqttClient) {
98
+ try {
99
+ return removeUserFromGroupMqtt(userID, threadID, callback);
100
+ } catch (e) {
101
+ return removeUserFromGroupNoMqtt(userID, threadID, callback);
102
+ }
103
+ } else {
104
+ return removeUserFromGroupNoMqtt(userID, threadID, callback);
105
+ }
106
+ };
107
+ };
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  const { getType } = require("../../utils/format.js");
3
3
  module.exports = function (defaultFuncs, api, ctx) {
4
- return function sendTyping(threadID, isTyping, options, callback) {
5
- var resolveFunc = function () { };
6
- var rejectFunc = function () { };
7
- var returnPromise = new Promise(function (resolve, reject) {
8
- resolveFunc = resolve;
9
- rejectFunc = reject;
10
- });
4
+ return function sendTyping(threadID, isTyping, options, callback) {
5
+ var resolveFunc = function () { };
6
+ var rejectFunc = function () { };
7
+ var returnPromise = new Promise(function (resolve, reject) {
8
+ resolveFunc = resolve;
9
+ rejectFunc = reject;
10
+ });
11
11
  if (getType(options) == "Function" || getType(options) == "AsyncFunction") {
12
12
  callback = options;
13
13
  options = {};
@@ -19,11 +19,18 @@ module.exports = function (defaultFuncs, api, ctx) {
19
19
  resolveFunc(data);
20
20
  };
21
21
  }
22
- if (!threadID) {
23
- return callback(new Error("threadID is required"));
24
- }
25
- const threadIDs = Array.isArray(threadID) ? threadID : [threadID];
26
- threadIDs.forEach(tid => {
22
+ if (!threadID) {
23
+ return callback(new Error("threadID is required"));
24
+ }
25
+ if (!ctx.mqttClient) {
26
+ const err = new Error("Not connected to MQTT");
27
+ callback(err);
28
+ rejectFunc(err);
29
+ return returnPromise;
30
+ }
31
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
32
+ const threadIDs = Array.isArray(threadID) ? threadID : [threadID];
33
+ threadIDs.forEach(tid => {
27
34
  var isGroupThread = getType(tid) === "Array" ? 0 : 1;
28
35
  var threadType = isGroupThread ? 2 : 1;
29
36
  var duration = options.duration || 10000;
@@ -45,10 +52,10 @@ module.exports = function (defaultFuncs, api, ctx) {
45
52
  }),
46
53
  version: "8965252033599983"
47
54
  }),
48
- request_id: ++ctx.req_ID,
49
- type: 4
50
- }),
51
- {
55
+ request_id: ++ctx.wsReqNumber,
56
+ type: 4
57
+ }),
58
+ {
52
59
  qos: 1,
53
60
  retain: false,
54
61
  }
@@ -3,33 +3,51 @@
3
3
  const logger = require("../../../func/logger");
4
4
  const { generateOfflineThreadingID, getCurrentTimestamp } = require("../../utils/format");
5
5
 
6
- module.exports = function (defaultFuncs, api, ctx) {
7
- return function setMessageReaction(reaction, messageID, threadID, callback) {
8
- return new Promise((resolve, reject) => {
9
- if (!ctx.mqttClient) {
10
- const err = new Error("MQTT client not connected");
11
- if (typeof callback === 'function') callback(err);
12
- return reject(err);
13
- }
14
- if (!reaction || !messageID || !threadID) {
15
- const err = new Error("Missing required parameters");
16
- if (typeof callback === 'function') callback(err);
17
- return reject(err);
18
- }
19
- const reqID = ++ctx.wsReqNumber;
20
- const taskID = ++ctx.wsTaskNumber;
21
- const taskPayload = {
22
- thread_key: threadID,
23
- timestamp_ms: getCurrentTimestamp(),
24
- message_id: messageID,
25
- reaction: reaction,
26
- actor_id: ctx.userID,
27
- reaction_style: null,
28
- sync_group: 1,
29
- send_attribution: 65537,
30
- dataclass_params: null,
31
- attachment_fbid: null
32
- };
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return function setMessageReaction(reaction, messageID, threadID, callback, forceCustomReaction) {
8
+ // Signature support:
9
+ // 1) (reaction, messageID, threadID, callback?, forceCustomReaction?)
10
+ // 2) (reaction, messageID, callback?, forceCustomReaction?) -> will error because threadID is required for MQTT
11
+ if (typeof threadID === "function") {
12
+ forceCustomReaction = callback;
13
+ callback = threadID;
14
+ threadID = undefined;
15
+ } else if (typeof threadID === "boolean") {
16
+ forceCustomReaction = threadID;
17
+ threadID = undefined;
18
+ } else if (typeof callback === "boolean") {
19
+ forceCustomReaction = callback;
20
+ callback = undefined;
21
+ }
22
+ const cb = typeof callback === "function" ? callback : undefined;
23
+
24
+ return new Promise((resolve, reject) => {
25
+ if (!ctx.mqttClient) {
26
+ const err = new Error("MQTT client not connected");
27
+ if (cb) cb(err);
28
+ return reject(err);
29
+ }
30
+ if (reaction === undefined || reaction === null || !messageID || !threadID) {
31
+ const err = new Error("Missing required parameters (reaction, messageID, threadID)");
32
+ if (cb) cb(err);
33
+ return reject(err);
34
+ }
35
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
36
+ if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
37
+ const reqID = ++ctx.wsReqNumber;
38
+ const taskID = ++ctx.wsTaskNumber;
39
+ const taskPayload = {
40
+ thread_key: threadID,
41
+ timestamp_ms: getCurrentTimestamp(),
42
+ message_id: messageID,
43
+ reaction: reaction,
44
+ actor_id: ctx.userID,
45
+ reaction_style: forceCustomReaction ? 1 : null,
46
+ sync_group: 1,
47
+ send_attribution: 65537,
48
+ dataclass_params: null,
49
+ attachment_fbid: null
50
+ };
33
51
  const task = {
34
52
  failure_count: null,
35
53
  label: "29",
@@ -59,18 +77,18 @@ module.exports = function (defaultFuncs, api, ctx) {
59
77
  }
60
78
  if (json.request_id !== reqID) return;
61
79
  ctx.mqttClient.removeListener("message", handleResponse);
62
- if (typeof callback === 'function') callback(null, { success: true });
63
- return resolve({ success: true });
64
- };
65
- ctx.mqttClient.on("message", handleResponse);
66
- ctx.mqttClient.publish("/ls_req", JSON.stringify(mqttForm), { qos: 1, retain: false }, (err) => {
67
- if (err) {
68
- ctx.mqttClient.removeListener("message", handleResponse);
69
- logger("setMessageReaction" + err, "error");
70
- if (typeof callback === 'function') callback(err);
71
- return reject(err);
72
- }
73
- });
74
- });
75
- };
76
- };
80
+ if (cb) cb(null, { success: true });
81
+ return resolve({ success: true });
82
+ };
83
+ ctx.mqttClient.on("message", handleResponse);
84
+ ctx.mqttClient.publish("/ls_req", JSON.stringify(mqttForm), { qos: 1, retain: false }, (err) => {
85
+ if (err) {
86
+ ctx.mqttClient.removeListener("message", handleResponse);
87
+ logger("setMessageReaction" + err, "error");
88
+ if (cb) cb(err);
89
+ return reject(err);
90
+ }
91
+ });
92
+ });
93
+ };
94
+ };
@@ -62,28 +62,30 @@ module.exports = function (defaultFuncs, api, ctx) {
62
62
  });
63
63
  return returnPromise;
64
64
  };
65
- function setTitleMqtt(newTitle, threadID, callback) {
66
- if (!ctx.mqttClient) {
67
- throw new Error("Not connected to MQTT");
68
- }
69
- var resolveFunc = function () { };
70
- var rejectFunc = function () { };
71
- var returnPromise = new Promise(function (resolve, reject) {
72
- resolveFunc = resolve;
73
- rejectFunc = reject;
74
- });
75
- if (!callback) {
76
- callback = function (err, data) {
77
- if (err) return rejectFunc(err);
78
- resolveFunc(data);
79
- data
80
- };
81
- }
82
- let count_req = 0
83
- var form = JSON.stringify({
84
- "app_id": "2220391788200892",
85
- "payload": JSON.stringify({
86
- epoch_id: generateOfflineThreadingID(),
65
+ function setTitleMqtt(newTitle, threadID, callback) {
66
+ if (!ctx.mqttClient) {
67
+ throw new Error("Not connected to MQTT");
68
+ }
69
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
70
+ const reqID = ++ctx.wsReqNumber;
71
+ var resolveFunc = function () { };
72
+ var rejectFunc = function () { };
73
+ var returnPromise = new Promise(function (resolve, reject) {
74
+ resolveFunc = resolve;
75
+ rejectFunc = reject;
76
+ });
77
+ const done = (err, data) => {
78
+ if (err) {
79
+ if (callback) callback(err);
80
+ return rejectFunc(err);
81
+ }
82
+ if (callback) callback(null, data);
83
+ resolveFunc(data);
84
+ };
85
+ var form = JSON.stringify({
86
+ "app_id": "2220391788200892",
87
+ "payload": JSON.stringify({
88
+ epoch_id: generateOfflineThreadingID(),
87
89
  tasks: [
88
90
  {
89
91
  failure_count: null,
@@ -97,23 +99,26 @@ module.exports = function (defaultFuncs, api, ctx) {
97
99
  task_id: Math.random() * 1001 << 0
98
100
  }
99
101
  ],
100
- version_id: '8798795233522156'
101
- }),
102
- "request_id": ++count_req,
103
- "type": 3
104
- });
105
- mqttClient.publish('/ls_req', form);
106
- return returnPromise;
107
- };
108
- return function setTitle(newTitle, threadID, callback) {
109
- if (ctx.mqttClient) {
110
- try {
111
- setTitleMqtt(newTitle, threadID, callback);
112
- } catch (e) {
113
- setTitleNoMqtt(newTitle, threadID, callback);
114
- }
115
- } else {
116
- setTitleNoMqtt(newTitle, threadID, callback);
117
- }
118
- };
119
- };
102
+ version_id: '8798795233522156'
103
+ }),
104
+ "request_id": reqID,
105
+ "type": 3
106
+ });
107
+ ctx.mqttClient.publish("/ls_req", form, { qos: 1, retain: false }, (err) => {
108
+ if (err) return done(err);
109
+ done(null, { success: true });
110
+ });
111
+ return returnPromise;
112
+ };
113
+ return function setTitle(newTitle, threadID, callback) {
114
+ if (ctx.mqttClient) {
115
+ try {
116
+ return setTitleMqtt(newTitle, threadID, callback);
117
+ } catch (e) {
118
+ return setTitleNoMqtt(newTitle, threadID, callback);
119
+ }
120
+ } else {
121
+ return setTitleNoMqtt(newTitle, threadID, callback);
122
+ }
123
+ };
124
+ };
@@ -43,7 +43,7 @@ module.exports = function(defaultFuncs, api, ctx) {
43
43
  request_id: ++count_req,
44
44
  type: 3
45
45
  });
46
- mqttClient.publish("/ls_req", form);
46
+ ctx.mqttClient.publish("/ls_req", form);
47
47
  return returnPromise;
48
48
  };
49
49
  };
@@ -85,11 +85,6 @@ module.exports = function createListenMqtt(deps) {
85
85
  options
86
86
  );
87
87
  const mqttClient = ctx.mqttClient;
88
- // Remove global reference to prevent memory leak
89
- // Only set if needed for debugging, but clear on cleanup
90
- if (process.env.DEBUG_MQTT) {
91
- global.mqttClient = mqttClient;
92
- }
93
88
 
94
89
  mqttClient.on("error", function (err) {
95
90
  const msg = String(err && err.message ? err.message : err || "");
@@ -80,14 +80,7 @@ module.exports = function createEmitAuth({ logger }) {
80
80
  }
81
81
  } catch (_) { }
82
82
 
83
- // Clear global mqttClient reference if set
84
- try {
85
- if (global.mqttClient) {
86
- delete global.mqttClient;
87
- }
88
- } catch (_) { }
89
-
90
- const msg = detail || reason;
83
+ const msg = detail || reason;
91
84
  logger(`auth change -> ${reason}: ${msg}`, "error");
92
85
 
93
86
  if (typeof globalCallback === "function") {
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const { formatDeltaEvent, formatMessage, _formatAttachment, formatDeltaMessage, formatDeltaReadReceipt, formatID, getType, decodeClientPayload } = require("../../../utils/format");
2
+ const { formatDeltaEvent, formatMessage, _formatAttachment, formatDeltaMessage, formatDeltaReadReceipt, formatID, getType, decodeClientPayload, getMentionsFromDeltaMessage } = require("../../../utils/format");
3
3
  const logger = require("../../../../func/logger");
4
4
  module.exports = function createParseDelta(deps) {
5
5
  const { markDelivery, parseAndCheckLogin } = deps;
@@ -67,14 +67,7 @@ module.exports = function createParseDelta(deps) {
67
67
  logger("parseDelta: deltaMessageReply.message or messageMetadata is missing", "warn");
68
68
  return;
69
69
  }
70
- const mdata = msg === undefined ? [] : msg.data === undefined ? [] : msg.data.prng === undefined ? [] : JSON.parse(msg.data.prng);
71
- const m_id = mdata.map(u => u.i);
72
- const m_offset = mdata.map(u => u.o);
73
- const m_length = mdata.map(u => u.l);
74
- const mentions = {};
75
- for (let i = 0; i < m_id.length; i++) {
76
- mentions[m_id[i]] = (msg.body || "").substring(m_offset[i], m_offset[i] + m_length[i]);
77
- }
70
+ const mentions = getMentionsFromDeltaMessage(msg);
78
71
  const msgMetadata = msg.messageMetadata;
79
72
  const threadKey = msgMetadata.threadKey || {};
80
73
  callbackToReturn = {
@@ -111,14 +104,7 @@ module.exports = function createParseDelta(deps) {
111
104
  if (d.deltaMessageReply.repliedToMessage) {
112
105
  try {
113
106
  const repliedTo = d.deltaMessageReply.repliedToMessage;
114
- const mdata2 = repliedTo === undefined ? [] : repliedTo.data === undefined ? [] : repliedTo.data.prng === undefined ? [] : JSON.parse(repliedTo.data.prng);
115
- const m_id2 = mdata2.map(u => u.i);
116
- const m_offset2 = mdata2.map(u => u.o);
117
- const m_length2 = mdata2.map(u => u.l);
118
- const rmentions = {};
119
- for (let i = 0; i < m_id2.length; i++) {
120
- rmentions[m_id2[i]] = (repliedTo.body || "").substring(m_offset2[i], m_offset2[i] + m_length2[i]);
121
- }
107
+ const rmentions = getMentionsFromDeltaMessage(repliedTo);
122
108
  const msgMetadata = repliedTo.messageMetadata;
123
109
  if (msgMetadata && msgMetadata.threadKey) {
124
110
  callbackToReturn.messageReply = {
@@ -199,10 +199,6 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
199
199
  } catch (_) { }
200
200
  ctx._scheduler = undefined;
201
201
  }
202
- // Clear global mqttClient reference if set
203
- if (global.mqttClient) {
204
- delete global.mqttClient;
205
- }
206
202
  next && next();
207
203
  };
208
204
  try {
@@ -540,25 +540,56 @@ function formatAttachment(attachments, attachmentIds, attachmentMap, shareMap) {
540
540
  : [];
541
541
  }
542
542
 
543
+ /**
544
+ * Extract mentions from message - supports legacy (data.prng) and new format (messageMetadata.data.data.Gb.asMap.data).
545
+ * @param {Object} m - Raw message object (NewMessage delta)
546
+ * @returns {Object} mentions - Map of userID -> mention text
547
+ */
548
+ function getMentionsFromDeltaMessage(m) {
549
+ var body = m.body || "";
550
+ var mentions = {};
551
+ var mdata = [];
552
+ if (m.data && m.data.prng) {
553
+ try {
554
+ mdata = JSON.parse(m.data.prng);
555
+ } catch (e) {
556
+ mdata = [];
557
+ }
558
+ }
559
+ if (mdata.length > 0) {
560
+ for (var i = 0; i < mdata.length; i++) {
561
+ var id = mdata[i].i;
562
+ var o = parseInt(mdata[i].o, 10) || 0;
563
+ var l = parseInt(mdata[i].l, 10) || 0;
564
+ mentions[String(id)] = body.substring(o, o + l);
565
+ }
566
+ return mentions;
567
+ }
568
+ var md = m.messageMetadata;
569
+ if (md && md.data && md.data.data && md.data.data.Gb && md.data.data.Gb.asMap && md.data.data.Gb.asMap.data) {
570
+ var gbData = md.data.data.Gb.asMap.data;
571
+ for (var key in gbData) {
572
+ if (!Object.prototype.hasOwnProperty.call(gbData, key)) continue;
573
+ var entry = gbData[key];
574
+ if (entry && entry.asMap && entry.asMap.data) {
575
+ var d = entry.asMap.data;
576
+ var id = d.id && d.id.asLong ? String(d.id.asLong) : null;
577
+ var offset = parseInt((d.offset && d.offset.asLong) ? d.offset.asLong : 0, 10);
578
+ var len = parseInt((d.length && d.length.asLong) ? d.length.asLong : 0, 10);
579
+ if (id != null) {
580
+ mentions[id] = body.substring(offset, offset + len);
581
+ }
582
+ }
583
+ }
584
+ }
585
+ return mentions;
586
+ }
587
+
543
588
  function formatDeltaMessage(m) {
544
589
  var md = m.messageMetadata;
545
- var mdata =
546
- m.data === undefined
547
- ? []
548
- : m.data.prng === undefined
549
- ? []
550
- : JSON.parse(m.data.prng);
551
- var m_id = mdata.map((/** @type {{ i: any; }} */ u) => u.i);
552
- var m_offset = mdata.map((/** @type {{ o: any; }} */ u) => u.o);
553
- var m_length = mdata.map((/** @type {{ l: any; }} */ u) => u.l);
554
- var mentions = {};
555
590
  var body = m.body || "";
556
- var args = body == "" ? [] : body.trim().split(/\s+/);
557
- for (var i = 0; i < m_id.length; i++)
558
- mentions[m_id[i]] = m.body.substring(
559
- m_offset[i],
560
- m_offset[i] + m_length[i]
561
- );
591
+ var mentions = getMentionsFromDeltaMessage(m);
592
+ var args = body === "" ? [] : body.trim().split(/\s+/);
562
593
  return {
563
594
  type: "message",
564
595
  senderID: formatID(md.actorFbId.toString()),
@@ -1091,6 +1122,7 @@ module.exports = {
1091
1122
  _formatAttachment,
1092
1123
  formatDeltaEvent,
1093
1124
  formatDeltaMessage,
1125
+ getMentionsFromDeltaMessage,
1094
1126
  formatDeltaReadReceipt,
1095
1127
  getType,
1096
1128
  formatID,