@lazyneoaz/metachat 1.0.11 → 1.1.0

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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/apis/changeAdminStatus.js +39 -50
  3. package/src/apis/changeArchivedStatus.js +16 -8
  4. package/src/apis/changeAvatar.js +2 -2
  5. package/src/apis/changeBio.js +1 -1
  6. package/src/apis/changeBlockedStatus.js +1 -1
  7. package/src/apis/changeGroupImage.js +22 -14
  8. package/src/apis/changeThreadEmoji.js +14 -8
  9. package/src/apis/createNewGroup.js +1 -1
  10. package/src/apis/createPoll.js +9 -3
  11. package/src/apis/deleteMessage.js +11 -8
  12. package/src/apis/deleteThread.js +1 -1
  13. package/src/apis/follow.js +27 -31
  14. package/src/apis/forwardMessage.js +1 -1
  15. package/src/apis/gcname.js +1 -1
  16. package/src/apis/getFriendsList.js +2 -2
  17. package/src/apis/getThreadInfo.js +1 -1
  18. package/src/apis/getThreadPictures.js +1 -1
  19. package/src/apis/handleMessageRequest.js +1 -1
  20. package/src/apis/listenMqtt.js +88 -3
  21. package/src/apis/markAsRead.js +15 -9
  22. package/src/apis/muteThread.js +1 -1
  23. package/src/apis/nickname.js +1 -1
  24. package/src/apis/pinMessage.js +110 -130
  25. package/src/apis/removeUserFromGroup.js +2 -2
  26. package/src/apis/resolvePhotoUrl.js +1 -1
  27. package/src/apis/searchForThread.js +1 -1
  28. package/src/apis/sendMessage.js +7 -0
  29. package/src/apis/setMessageReaction.js +3 -3
  30. package/src/apis/setMessageReactionMqtt.js +2 -2
  31. package/src/apis/setThreadThemeMqtt.js +70 -77
  32. package/src/apis/unfriend.js +1 -1
  33. package/src/apis/unsendMessage.js +71 -9
  34. package/src/app/state.js +1 -1
  35. package/src/types/index.d.ts +40 -1
  36. package/src/utils/antiSuspension.js +4 -4
  37. package/src/utils/auth-helpers.js +1 -1
  38. package/src/utils/lsRequest.js +2 -3
  39. package/src/utils/tokenRefresh.js +10 -1
@@ -499,6 +499,9 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
499
499
  }
500
500
  // Save connected state BEFORE clearing it — used for quick-close detection.
501
501
  const wasConnected = ctx._mqttConnected;
502
+ try {
503
+ if (ctx._emitter) ctx._emitter.emit('disconnected', { timestamp: Date.now(), wasConnected });
504
+ } catch (_) {}
502
505
  ctx._mqttConnected = false;
503
506
  if (ctx._ending || ctx._cycling) return;
504
507
 
@@ -564,6 +567,15 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
564
567
  ctx._mqttWatchdog = null;
565
568
  }
566
569
  ctx._mqttConnected = false;
570
+
571
+ if (!ctx._ending && !ctx._cycling && ctx.globalOptions.autoReconnect) {
572
+ try { mqttClient.end(true); } catch (_) {}
573
+ ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
574
+ const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
575
+ const d = computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX);
576
+ utils.warn("MQTT", `Disconnect received — reconnecting in ${d}ms (attempt ${ctx._reconnectAttempts})`);
577
+ scheduleReconnect(d);
578
+ }
567
579
  }));
568
580
 
569
581
  mqttClient.on('offline', guard("offline", () => {
@@ -769,6 +781,22 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
769
781
  if (ctx._ending) return;
770
782
  if (ctx._reconnectTimer) return;
771
783
  utils.warn("MQTT", `Will reconnect in ${ms}ms`);
784
+
785
+ if (!ctx._reconnectStats) ctx._reconnectStats = { totalAttempts: 0, lastAttemptAt: null, nextAttemptAt: null, lastSuccessAt: null };
786
+ ctx._reconnectStats.totalAttempts = (ctx._reconnectStats.totalAttempts || 0) + 1;
787
+ ctx._reconnectStats.lastAttemptAt = Date.now();
788
+ ctx._reconnectStats.nextAttemptAt = Date.now() + ms;
789
+ try {
790
+ if (ctx._emitter) {
791
+ ctx._emitter.emit('reconnecting', {
792
+ attempt: ctx._reconnectAttempts || 0,
793
+ delayMs: ms,
794
+ nextAttemptAt: ctx._reconnectStats.nextAttemptAt,
795
+ timestamp: Date.now()
796
+ });
797
+ }
798
+ } catch (_) {}
799
+
772
800
  ctx._reconnectTimer = setTimeout(() => {
773
801
  ctx._reconnectTimer = null;
774
802
  getSeqIDWrapper();
@@ -1080,7 +1108,8 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
1080
1108
  try {
1081
1109
  const LIFECYCLE_EVENTS = [
1082
1110
  'sessionExpired', 'checkpoint', 'relogin', 'ready',
1083
- 'account_inactive', 'checkpoint_282', 'checkpoint_956', 'error'
1111
+ 'account_inactive', 'checkpoint_282', 'checkpoint_956', 'error',
1112
+ 'reconnecting', 'reconnected', 'connected', 'disconnected'
1084
1113
  ];
1085
1114
  for (const event of LIFECYCLE_EVENTS) {
1086
1115
  // Use listeners() NOT rawListeners() — rawListeners() returns the
@@ -1149,8 +1178,64 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
1149
1178
 
1150
1179
  ctx.firstListen = false;
1151
1180
 
1152
- api.stopListening = msgEmitter.stopListening;
1153
- api.stopListeningAsync = msgEmitter.stopListeningAsync;
1181
+ api.stopListening = msgEmitter.stopListening.bind(msgEmitter);
1182
+ api.stopListeningAsync = msgEmitter.stopListeningAsync.bind(msgEmitter);
1183
+
1184
+ api.reconnect = function(callback) {
1185
+ let resolveFunc, rejectFunc;
1186
+ const returnPromise = new Promise((resolve, reject) => {
1187
+ resolveFunc = resolve;
1188
+ rejectFunc = reject;
1189
+ });
1190
+ const cb = typeof callback === "function"
1191
+ ? (err) => { callback(err); if (err) rejectFunc(err); else resolveFunc(); }
1192
+ : (err) => { if (err) rejectFunc(err); else resolveFunc(); };
1193
+
1194
+ if (ctx._ending) {
1195
+ return cb(new Error("listenMqtt is not active — call listenMqtt() first")), returnPromise;
1196
+ }
1197
+ utils.log("MQTT", "api.reconnect() — force-reconnecting");
1198
+ try { if (ctx.mqttClient) ctx.mqttClient.end(true); } catch (_) {}
1199
+ ctx._mqttConnected = false;
1200
+ ctx._reconnectAttempts = 0;
1201
+ if (ctx._reconnectTimer) {
1202
+ clearTimeout(ctx._reconnectTimer);
1203
+ ctx._reconnectTimer = null;
1204
+ }
1205
+ try {
1206
+ if (ctx._emitter) ctx._emitter.emit('reconnecting', { forced: true, attempt: 0, delayMs: 0, timestamp: Date.now() });
1207
+ } catch (_) {}
1208
+ getSeqIDWrapper().then(() => cb(null)).catch(cb);
1209
+ return returnPromise;
1210
+ };
1211
+
1212
+ api.getReconnectStatus = function() {
1213
+ const stats = ctx._reconnectStats || {};
1214
+ return {
1215
+ connected: !!(ctx.mqttClient && ctx.mqttClient.connected),
1216
+ reconnectAttempts: ctx._reconnectAttempts || 0,
1217
+ totalReconnects: stats.totalAttempts || 0,
1218
+ lastAttemptAt: stats.lastAttemptAt || null,
1219
+ nextAttemptAt: ctx._reconnectTimer ? (stats.nextAttemptAt || null) : null,
1220
+ lastSuccessAt: stats.lastSuccessAt || null,
1221
+ reconnectPending: !!ctx._reconnectTimer,
1222
+ autoReconnect: !!ctx.globalOptions.autoReconnect,
1223
+ options: Object.assign({}, ctx._mqttOpt || {})
1224
+ };
1225
+ };
1226
+
1227
+ api.setReconnectOptions = function(newOpts) {
1228
+ if (!newOpts || typeof newOpts !== "object") return;
1229
+ ctx._mqttOpt = Object.assign({}, MQTT_DEFAULTS, ctx._mqttOpt || {}, newOpts);
1230
+ if (typeof ctx._mqttOpt.autoReconnect === "boolean") {
1231
+ ctx.globalOptions.autoReconnect = ctx._mqttOpt.autoReconnect;
1232
+ }
1233
+ if (typeof ctx._mqttOpt.cycleMs === "number") {
1234
+ conf = Object.assign({}, conf, { cycleMs: ctx._mqttOpt.cycleMs });
1235
+ }
1236
+ utils.log("MQTT", "setReconnectOptions applied:", JSON.stringify(ctx._mqttOpt));
1237
+ return ctx._mqttOpt;
1238
+ };
1154
1239
 
1155
1240
  api.useMiddleware = function(nameOrFn, fn) {
1156
1241
  const remove = ctx._middleware.use(nameOrFn, fn);
@@ -21,7 +21,18 @@ module.exports = function (defaultFuncs, api, ctx) {
21
21
 
22
22
  if (!callback) {
23
23
  callback = (err) => {
24
- if (err) return rejectFunc(err instanceof Error ? err : new Error(String(err.error || err.message || err)));
24
+ if (err) return rejectFunc(err instanceof Error ? err : new Error(String(err && (err.error || err.message) || err)));
25
+ resolveFunc(null);
26
+ };
27
+ } else {
28
+ const _userCb = callback;
29
+ callback = (err) => {
30
+ if (err) {
31
+ const e = err instanceof Error ? err : new Error(String(err && (err.error || err.message) || err));
32
+ _userCb(e);
33
+ return rejectFunc(e);
34
+ }
35
+ _userCb(null);
25
36
  resolveFunc(null);
26
37
  };
27
38
  }
@@ -43,9 +54,7 @@ module.exports = function (defaultFuncs, api, ctx) {
43
54
  .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
44
55
 
45
56
  if (resData && resData.error) {
46
- const err = new Error(String(resData.error.message || resData.error));
47
- callback(err);
48
- return returnPromise;
57
+ return callback(new Error(String(resData.error.message || resData.error)));
49
58
  }
50
59
 
51
60
  callback(null);
@@ -62,16 +71,13 @@ module.exports = function (defaultFuncs, api, ctx) {
62
71
  .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
63
72
 
64
73
  if (resData && resData.error) {
65
- const err = new Error(String(resData.error.message || resData.error));
66
- callback(err);
67
- return returnPromise;
74
+ return callback(new Error(String(resData.error.message || resData.error)));
68
75
  }
69
76
 
70
77
  callback(null);
71
78
  }
72
79
  } catch (e) {
73
- const err = e instanceof Error ? e : new Error(String(e.error || e.message || e));
74
- callback(err);
80
+ callback(e instanceof Error ? e : new Error(String(e && (e.error || e.message) || e)));
75
81
  }
76
82
 
77
83
  return returnPromise;
@@ -31,7 +31,7 @@ module.exports = (defaultFuncs, api, ctx) => {
31
31
  ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
32
32
 
33
33
  if (res && res.error) {
34
- throw res;
34
+ throw new Error(res.error_msg || res.errorSummary || String(res.error));
35
35
  }
36
36
 
37
37
  callback(null, { success: true });
@@ -3,7 +3,7 @@
3
3
  const utils = require('../utils');
4
4
  const { publishLsRequestWithAck, buildLsTask, generateEpochId } = require('../utils/lsRequest');
5
5
 
6
- const NICKNAME_VERSION_ID = "25459622483894963";
6
+ const NICKNAME_VERSION_ID = "8798795233522156";
7
7
 
8
8
  module.exports = function (defaultFuncs, api, ctx) {
9
9
  /**
@@ -1,152 +1,132 @@
1
1
  "use strict";
2
2
 
3
- const utils = require('../utils');
4
- const fs = require("fs");
5
- const path = require("path");
6
- const _ = require("lodash");
7
- const deepdash = require("deepdash");
8
- const { JSONPath } = require("jsonpath-plus");
3
+ const utils = require('../utils');
9
4
 
10
- deepdash(_);
11
-
12
- /**
13
- * Extract and save the full lightspeed_web_request object from thread data.
14
- * Also search for specific pinned message commands.
15
- * @param {Object} allJsonData - Raw JSON response from Facebook.
16
- * @param {Object} options - Options for debug output.
17
- * @returns {Object|null} The full lightspeed_web_request object or null.
18
- */
19
- function extractAndSearchLightspeedRequest(allJsonData, options = {}) {
20
- const { saveDebugFile = false, debugFilePath } = options;
21
-
22
- let outputFile = null;
23
- if (saveDebugFile && debugFilePath) {
24
- outputFile = debugFilePath;
25
- } else if (saveDebugFile) {
26
- // Only save to current working directory if explicitly requested
27
- outputFile = require('path').join(process.cwd(), "fca_neo_pro_lightspeed_debug.json");
28
- }
29
-
30
- const lightReq = _.get(allJsonData, "__bbox.result.data.viewer.lightspeed_web_request");
31
- if (!lightReq) {
32
- utils.warn("pin.js: lightspeed_web_request not found.");
33
- return null;
34
- }
35
-
36
-
37
- if (outputFile) {
38
- try {
39
- fs.writeFileSync(outputFile, JSON.stringify(lightReq, null, 2), "utf8");
40
- utils.log(`pin.js: Saved lightspeed_web_request to ${outputFile}`);
41
- } catch (err) {
42
- utils.error("pin.js: Failed to write lightspeed_web_request.json", err);
43
- }
44
- }
45
-
46
-
47
- try {
48
- const matches = JSONPath({
49
- path: `$..[?(@ === "setPinnedMessage" || @ === "deleteThenInsertMessage")]`,
50
- json: lightReq
51
- });
52
-
53
- utils.log(`pin.js: Found ${matches.length} matching command(s).`);
54
- matches.forEach((match, idx) => {
55
- utils.log(`📌 Match ${idx + 1}:`, match);
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ return async function pin(action, threadID, messageID, callback) {
7
+ let resolveFunc = () => {};
8
+ let rejectFunc = () => {};
9
+ const returnPromise = new Promise((resolve, reject) => {
10
+ resolveFunc = resolve;
11
+ rejectFunc = reject;
56
12
  });
57
- } catch (err) {
58
- utils.error("pin.js: JSONPath search failed.", err);
59
- }
60
13
 
61
- return lightReq;
62
- }
63
-
64
- module.exports = function (defaultFuncs, api, ctx) {
65
- return async function pin(action, threadID, messageID) {
66
- if (action === "list") {
67
- if (!threadID) throw new Error('Action "list" requires threadID.');
14
+ if (!callback) {
15
+ callback = (err, result) => {
16
+ if (err) return rejectFunc(err);
17
+ resolveFunc(result);
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
+ };
26
+ }
68
27
 
69
- try {
28
+ try {
29
+ if (action === "list") {
30
+ if (!threadID) throw new Error('Action "list" requires threadID.');
70
31
  const url = `https://www.facebook.com/messages/t/${threadID}/`;
71
32
  const allJsonData = await utils.json(url, ctx.jar, null, ctx.globalOptions, ctx);
72
-
73
-
74
- const lightReq = extractAndSearchLightspeedRequest(allJsonData, { saveDebugFile: false });
33
+ const lightReq = allJsonData && allJsonData.__bbox &&
34
+ allJsonData.__bbox.result &&
35
+ allJsonData.__bbox.result.data &&
36
+ allJsonData.__bbox.result.data.viewer &&
37
+ allJsonData.__bbox.result.data.viewer.lightspeed_web_request;
75
38
  if (!lightReq || !lightReq.pin_status) {
76
- utils.warn("pin.js: No pinned messages found or pin_status missing.");
77
- return [];
39
+ utils.warn("pinMessage: No pinned messages found or pin_status missing.");
40
+ return callback(null, []);
78
41
  }
79
- return lightReq;
80
- } catch (err) {
81
- utils.error(`pin.js: Failed to process "list" for thread ${threadID}`, err);
82
- throw err;
42
+ return callback(null, lightReq);
83
43
  }
84
- }
85
44
 
86
- if (!ctx.mqttClient || !ctx.mqttClient.connected) throw new Error("MQTT not connected.");
87
- if (!threadID || !messageID) throw new Error(`"${action}" requires threadID and messageID.`);
45
+ if (!ctx.mqttClient || !ctx.mqttClient.connected) {
46
+ throw new Error("MQTT not connected call listenMqtt() first.");
47
+ }
48
+ if (!threadID || !messageID) {
49
+ throw new Error(`"${action}" requires threadID and messageID.`);
50
+ }
88
51
 
89
- const epoch_id = parseInt(utils.generateOfflineThreadingID());
90
- const version_id = "9523201934447612";
91
- const app_id = "2220391788200892";
52
+ if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
53
+ if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
92
54
 
93
- const createMqttRequest = (tasks, increment = 0) => ({
94
- app_id,
95
- payload: JSON.stringify({ epoch_id: epoch_id + increment, tasks, version_id }),
96
- request_id: (ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1),
97
- type: 3
98
- });
55
+ const epoch_id = parseInt(utils.generateOfflineThreadingID());
56
+ const version_id = "9523201934447612";
57
+ const app_id = String(ctx.appID || ctx.mqttAppID || "2220391788200892");
99
58
 
100
- const publishMqtt = (content) =>
101
- new Promise((resolve, reject) => {
102
- ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, (err) => {
103
- if (err) reject(err);
104
- else resolve({ success: true, request_id: content.request_id });
105
- });
59
+ const createMqttRequest = (tasks, increment = 0) => ({
60
+ app_id,
61
+ payload: JSON.stringify({ epoch_id: epoch_id + increment, tasks, version_id }),
62
+ request_id: ++ctx.wsReqNumber,
63
+ type: 3
106
64
  });
107
65
 
108
- if (action === "pin") {
109
- const pinTask = {
110
- label: "430",
111
- payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
112
- queue_name: `pin_msg_v2_${threadID}`,
113
- task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
114
- };
115
- const setSearchTask = {
116
- label: "751",
117
- payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 1 }),
118
- queue_name: "set_pinned_message_search",
119
- task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
120
- };
121
- const req1 = createMqttRequest([pinTask], 0);
122
- const req2 = createMqttRequest([setSearchTask], 1);
123
- return Promise.all([publishMqtt(req1), publishMqtt(req2)]);
124
- }
66
+ const publishMqtt = (content) =>
67
+ new Promise((resolve, reject) => {
68
+ ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, (err) => {
69
+ if (err) reject(err instanceof Error ? err : new Error(String(err)));
70
+ else resolve({ success: true, request_id: content.request_id });
71
+ });
72
+ });
125
73
 
126
- if (action === "unpin") {
127
- const setSearchTask1 = {
128
- label: "751",
129
- payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
130
- queue_name: "set_pinned_message_search",
131
- task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
132
- };
133
- const unpinTask = {
134
- label: "431",
135
- payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
136
- queue_name: `unpin_msg_v2_${threadID}`,
137
- task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
138
- };
139
- const setSearchTask2 = {
140
- label: "751",
141
- payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
142
- queue_name: "set_pinned_message_search",
143
- task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
144
- };
145
- await publishMqtt(createMqttRequest([setSearchTask1], 0));
146
- await publishMqtt(createMqttRequest([unpinTask], 1));
147
- return publishMqtt(createMqttRequest([setSearchTask2], 2));
74
+ if (action === "pin") {
75
+ const pinTask = {
76
+ failure_count: null,
77
+ label: "430",
78
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
79
+ queue_name: `pin_msg_v2_${threadID}`,
80
+ task_id: ++ctx.wsTaskNumber
81
+ };
82
+ const setSearchTask = {
83
+ failure_count: null,
84
+ label: "751",
85
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 1 }),
86
+ queue_name: "set_pinned_message_search",
87
+ task_id: ++ctx.wsTaskNumber
88
+ };
89
+ const results = await Promise.all([
90
+ publishMqtt(createMqttRequest([pinTask], 0)),
91
+ publishMqtt(createMqttRequest([setSearchTask], 1))
92
+ ]);
93
+ return callback(null, { success: true, action: "pin", results });
94
+ }
95
+
96
+ if (action === "unpin") {
97
+ const setSearchTask1 = {
98
+ failure_count: null,
99
+ label: "751",
100
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
101
+ queue_name: "set_pinned_message_search",
102
+ task_id: ++ctx.wsTaskNumber
103
+ };
104
+ const unpinTask = {
105
+ failure_count: null,
106
+ label: "431",
107
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
108
+ queue_name: `unpin_msg_v2_${threadID}`,
109
+ task_id: ++ctx.wsTaskNumber
110
+ };
111
+ const setSearchTask2 = {
112
+ failure_count: null,
113
+ label: "751",
114
+ payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
115
+ queue_name: "set_pinned_message_search",
116
+ task_id: ++ctx.wsTaskNumber
117
+ };
118
+ await publishMqtt(createMqttRequest([setSearchTask1], 0));
119
+ await publishMqtt(createMqttRequest([unpinTask], 1));
120
+ const result = await publishMqtt(createMqttRequest([setSearchTask2], 2));
121
+ return callback(null, { success: true, action: "unpin", result });
122
+ }
123
+
124
+ throw new Error(`Invalid action: "${action}". Use "pin", "unpin", or "list".`);
125
+ } catch (err) {
126
+ utils.error("pinMessage", err);
127
+ callback(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
148
128
  }
149
129
 
150
- throw new Error(`Invalid action: "${action}". Use "pin", "unpin", or "list".`);
130
+ return returnPromise;
151
131
  };
152
132
  };
@@ -52,7 +52,7 @@ module.exports = (defaultFuncs, api, ctx) => {
52
52
  task_id: taskID
53
53
  }
54
54
  ],
55
- version_id: '8798795233522156'
55
+ version_id: '25002366262773827'
56
56
  };
57
57
 
58
58
  const form = JSON.stringify({
@@ -110,7 +110,7 @@ module.exports = (defaultFuncs, api, ctx) => {
110
110
  .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
111
111
 
112
112
  if (!res || res.error) {
113
- throw res || new Error("Remove from group failed");
113
+ throw new Error((res && (res.error_msg || res.errorSummary || String(res.error))) || "Remove from group failed");
114
114
  }
115
115
 
116
116
  callback(null, { success: true });
@@ -41,7 +41,7 @@ photo_id: photoID,
41
41
  .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
42
42
  .then((resData) => {
43
43
  if (resData.error) {
44
- throw resData;
44
+ throw new Error(resData.error_msg || resData.errorSummary || String(resData.error));
45
45
  }
46
46
 
47
47
  const photoUrl = resData.jsmods.require[0][3][0];
@@ -113,7 +113,7 @@ module.exports = (defaultFuncs, api, ctx) => {
113
113
  }
114
114
 
115
115
  if (res.error) {
116
- throw res;
116
+ throw new Error(res.error_msg || res.errorSummary || String(res.error));
117
117
  }
118
118
 
119
119
  // Support both legacy payload.threads (object map) and newer payload.mercury_payload.threads (array)
@@ -250,6 +250,13 @@ module.exports = (defaultFuncs, api, ctx) => {
250
250
  if (err) return rejectFunc(err);
251
251
  resolveFunc(data);
252
252
  };
253
+ } else {
254
+ const _userCb = callback;
255
+ callback = (err, data) => {
256
+ if (err) { _userCb(err); return rejectFunc(err); }
257
+ _userCb(null, data);
258
+ resolveFunc(data);
259
+ };
253
260
  }
254
261
 
255
262
  let msgType = utils.getType(msg);
@@ -35,8 +35,9 @@ module.exports = function (defaultFuncs, api, ctx) {
35
35
  const defData = await defaultFuncs.postFormData(
36
36
  "https://www.facebook.com/webgraphql/mutation/",
37
37
  ctx.jar,
38
+ {},
38
39
  {
39
- doc_id: "3360163564070970",
40
+ doc_id: "1491398900900362",
40
41
  variables: JSON.stringify({
41
42
  data: {
42
43
  client_mutation_id: ctx.clientMutationId++,
@@ -47,8 +48,7 @@ module.exports = function (defaultFuncs, api, ctx) {
47
48
  }
48
49
  }),
49
50
  dpr: 1
50
- },
51
- {}
51
+ }
52
52
  );
53
53
 
54
54
  const resData = await utils.parseAndCheckLogin(ctx, defaultFuncs)(defData);
@@ -50,7 +50,7 @@ module.exports = function (defaultFuncs, api, ctx) {
50
50
  failure_count: null,
51
51
  label: "29",
52
52
  payload: JSON.stringify(taskPayload),
53
- queue_name: JSON.stringify(["reaction", messageID]),
53
+ queue_name: `reaction:${messageID}`,
54
54
  task_id: taskId
55
55
  };
56
56
 
@@ -60,7 +60,7 @@ module.exports = function (defaultFuncs, api, ctx) {
60
60
  data_trace_id: null,
61
61
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
62
62
  tasks: [task],
63
- version_id: "7158486590867448"
63
+ version_id: "24585299697835063"
64
64
  }),
65
65
  request_id: requestId,
66
66
  type: 3