stfca 1.0.27 → 1.2.27

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.
@@ -209,7 +209,6 @@ module.exports = function (defaultFuncs, api, ctx) {
209
209
  task.payload = JSON.stringify(task.payload);
210
210
  });
211
211
  form.payload = JSON.stringify(form.payload);
212
- console.log(global.jsonStringifyColor(form, null, 2));
213
212
 
214
213
  return mqttClient.publish(
215
214
  "/ls_req",
@@ -1,45 +1,54 @@
1
- "use strict";
2
-
3
-
4
-
5
- var utils = require("../utils");
6
- // @NethWs3Dev
7
-
8
- module.exports = function (defaultFuncs, api, ctx) {
9
- return async function sendTypingIndicatorV2(sendTyping, threadID, callback) {
10
- const mqttClient = ctx.mqttClient || global.mqttClient;
11
- if (!mqttClient) {
12
- if (typeof callback === 'function') callback(new Error('No MQTT client available for typing indicator'));
13
- return;
14
- }
15
-
16
- let count_req = 0;
17
- var wsContent = {
18
- app_id: 2220391788200892,
19
- payload: JSON.stringify({
20
- label: 3,
21
- payload: JSON.stringify({
22
- thread_key: threadID.toString(),
23
- is_group_thread: +(threadID.toString().length >= 16),
24
- is_typing: +sendTyping,
25
- attribution: 0
26
- }),
27
- version: 5849951561777440
28
- }),
29
- request_id: ++count_req,
30
- type: 4
31
- };
32
-
33
- return new Promise((resolve, reject) => {
34
- mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => {
35
- if (err) {
36
- if (typeof callback === 'function') callback(err);
37
- reject(err);
38
- } else {
39
- if (typeof callback === 'function') callback(null, _packet);
40
- resolve(_packet);
41
- }
42
- });
43
- });
44
- };
45
- };
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ // @NethWs3Dev
5
+
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return async function sendTypingIndicatorV2(sendTyping, threadID, callback) {
8
+ // ── sendTypingE2EE: route E2EE typing indicator through the bridge ──────
9
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
10
+ var _e2eeMod = require('../e2ee');
11
+ if (_e2eeMod.isE2EEChatJid(String(threadID))) {
12
+ return _e2eeMod.createBridge(ctx).sendTyping(threadID, sendTyping !== false)
13
+ .then(function (r) { if (typeof callback === 'function') callback(null, r); return r; })
14
+ .catch(function (e) { if (typeof callback === 'function') callback(e); });
15
+ }
16
+ }
17
+ // ── end sendTypingE2EE ────────────────────────────────────────────────────
18
+
19
+ const mqttClient = ctx.mqttClient || global.mqttClient;
20
+ if (!mqttClient) {
21
+ if (typeof callback === 'function') callback(new Error('No MQTT client available for typing indicator'));
22
+ return;
23
+ }
24
+
25
+ let count_req = 0;
26
+ var wsContent = {
27
+ app_id: 2220391788200892,
28
+ payload: JSON.stringify({
29
+ label: 3,
30
+ payload: JSON.stringify({
31
+ thread_key: threadID.toString(),
32
+ is_group_thread: +(threadID.toString().length >= 16),
33
+ is_typing: +sendTyping,
34
+ attribution: 0
35
+ }),
36
+ version: 5849951561777440
37
+ }),
38
+ request_id: ++count_req,
39
+ type: 4
40
+ };
41
+
42
+ return new Promise((resolve, reject) => {
43
+ mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => {
44
+ if (err) {
45
+ if (typeof callback === 'function') callback(err);
46
+ reject(err);
47
+ } else {
48
+ if (typeof callback === 'function') callback(null, _packet);
49
+ resolve(_packet);
50
+ }
51
+ });
52
+ });
53
+ };
54
+ };
@@ -26,6 +26,31 @@ module.exports = function (defaultFuncs, api, ctx) {
26
26
  };
27
27
  }
28
28
 
29
+ // ── sendReactionE2EE: route E2EE reactions through the bridge ─────────────
30
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
31
+ var _e2eeMod = require('../e2ee');
32
+ var _jid = global._e2eeMessageMap && global._e2eeMessageMap.get(String(messageID));
33
+ if (_jid && _e2eeMod.isE2EEChatJid(_jid)) {
34
+ var _senderJid = (global._e2eeSenderJidMap && global._e2eeSenderJidMap.get(String(messageID))) || null;
35
+ // Always route through the bridge when the JID is confirmed E2EE —
36
+ // never fall through to the HTTP path which cannot reach E2EE threads.
37
+ // senderJid is required by the E2EE protocol; fall back to chatJid for DMs
38
+ // (in a DM the chatJid IS the other person's JID).
39
+ var _effectiveSender = _senderJid || _jid;
40
+ _e2eeMod.createBridge(ctx).sendReaction(_jid, messageID, _effectiveSender, reaction)
41
+ .then(function (r) { callback(null, r); resolveFunc(r); })
42
+ .catch(function (e) {
43
+ var log = require('../utils').log;
44
+ log.error("setMessageReaction", "E2EE sendReaction failed — jid:" + _jid +
45
+ " msgID:" + messageID + " sender:" + _effectiveSender + " emoji:" + reaction +
46
+ " err:" + (e && e.message ? e.message : String(e)));
47
+ callback(e); rejectFunc(e);
48
+ });
49
+ return returnPromise;
50
+ }
51
+ }
52
+ // ── end sendReactionE2EE ──────────────────────────────────────────────────
53
+
29
54
  switch (reaction) {
30
55
  case "\uD83D\uDE0D": //:heart_eyes:
31
56
  case "\uD83D\uDE06": //:laughing:
@@ -21,6 +21,27 @@ module.exports = function (defaultFuncs, api, ctx) {
21
21
  };
22
22
  }
23
23
 
24
+ // ── unsendMessageE2EE: route E2EE unsend through the bridge ─────────────
25
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
26
+ var _e2eeMod = require('../e2ee');
27
+ var _jid = global._e2eeMessageMap && global._e2eeMessageMap.get(String(messageID));
28
+ // Fallback: if messageID not in map but a pending JID was set externally, use it
29
+ if (!_jid && global._e2eePendingUnsendJid) {
30
+ var _pjid = global._e2eePendingUnsendJid[String(messageID)];
31
+ if (_pjid) {
32
+ _jid = _pjid;
33
+ delete global._e2eePendingUnsendJid[String(messageID)];
34
+ }
35
+ }
36
+ if (_jid && _e2eeMod.isE2EEChatJid(_jid)) {
37
+ _e2eeMod.createBridge(ctx).unsendMessage(_jid, messageID)
38
+ .then(function (r) { callback(null, r); resolveFunc(r); })
39
+ .catch(function (e) { callback(e); rejectFunc(e); });
40
+ return returnPromise;
41
+ }
42
+ }
43
+ // ── end unsendMessageE2EE ─────────────────────────────────────────────────
44
+
24
45
  const form = {
25
46
  message_id: messageID,
26
47
  };
@@ -1,93 +1,115 @@
1
- const utils = require("../utils");
1
+ "use strict";
2
2
 
3
- module.exports = function (defaultFuncs, api, ctx) {
4
- function upload(attachments, callback) {
5
- callback = callback || function () {};
6
- const uploads = [];
3
+ const log = require("npmlog");
4
+ const path = require("path");
5
+ const mime = require("mime");
6
+ const { parseAndCheckLogin, isReadableStream, getType } = require("../utils");
7
7
 
8
- // create an array of promises
9
- for (let i = 0; i < attachments.length; i++) {
10
- if (!utils.isReadableStream(attachments[i])) {
11
- throw {
12
- error:
13
- "Attachment should be a readable stream and not " +
14
- utils.getType(attachments[i]) +
15
- ".",
16
- };
17
- }
8
+ const UPLOAD_URL = "https://www.facebook.com/ajax/mercury/upload.php";
9
+
10
+ function _filenameFromStream(stream, fallback) {
11
+ try {
12
+ if (stream && typeof stream.path === "string" && stream.path.length) {
13
+ return path.basename(stream.path);
14
+ }
15
+ if (stream && stream.path && typeof stream.path.toString === "function") {
16
+ return path.basename(stream.path.toString());
17
+ }
18
+ } catch (_) { }
19
+ return fallback || "upload.bin";
20
+ }
18
21
 
19
- const form = {
20
- upload_1024: attachments[i],
21
- voice_clip: "true",
22
- };
22
+ function _contentTypeFor(filename, stream) {
23
+ try {
24
+ if (stream && typeof stream._contentType === "string" && stream._contentType.length) {
25
+ return stream._contentType;
26
+ }
27
+ if (stream && stream.headers && typeof stream.headers["content-type"] === "string") {
28
+ return stream.headers["content-type"];
29
+ }
30
+ } catch (_) { }
31
+ const t = mime.getType(filename);
32
+ return t || "application/octet-stream";
33
+ }
23
34
 
24
- uploads.push(
25
- defaultFuncs
26
- .postFormData(
27
- "https://upload.facebook.com/ajax/mercury/upload.php",
28
- ctx.jar,
29
- form,
30
- {},
31
- )
32
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
33
- .then(function (resData) {
34
- if (resData.error) {
35
- throw resData;
35
+ module.exports = function (defaultFuncs, api, ctx) {
36
+ function uploadOne(stream) {
37
+ if (!isReadableStream(stream)) {
38
+ return Promise.reject({
39
+ error: "Attachment should be a readable stream and not " + getType(stream) + "."
40
+ });
41
+ }
42
+
43
+ const filename = _filenameFromStream(stream);
44
+ const contentType = _contentTypeFor(filename, stream);
45
+
46
+ const form = {
47
+ farr: {
48
+ value: stream,
49
+ options: {
50
+ filename: filename,
51
+ contentType: contentType
52
+ }
36
53
  }
54
+ };
37
55
 
38
- // We have to return the data unformatted unless we want to change it
39
- // back in sendMessage.
40
- return resData.payload.metadata[0];
41
- }),
42
- );
56
+ return defaultFuncs
57
+ .postFormData(UPLOAD_URL, ctx.jar, form, {})
58
+ .then(parseAndCheckLogin(ctx, defaultFuncs))
59
+ .then(function (resData) {
60
+ if (resData.error) throw resData;
61
+ if (!resData.payload || !resData.payload.metadata) {
62
+ throw { error: "Mercury upload returned no metadata", res: resData };
63
+ }
64
+ const md = resData.payload.metadata[0] || resData.payload.metadata["0"];
65
+ if (!md) {
66
+ throw { error: "Mercury upload metadata[0] missing", res: resData };
67
+ }
68
+ return md;
69
+ });
43
70
  }
44
71
 
45
- // resolve all promises
46
- Promise.all(uploads)
47
- .then(function (resData) {
48
- callback(null, resData);
49
- })
50
- .catch(function (err) {
51
- console.error("uploadAttachment", err);
52
- return callback(err);
53
- });
54
- }
72
+ function upload(attachments, callback) {
73
+ callback = callback || function () { };
74
+ Promise.all(attachments.map(uploadOne))
75
+ .then(function (resData) { callback(null, resData); })
76
+ .catch(function (err) {
77
+ log.error("uploadAttachment", err);
78
+ return callback(err);
79
+ });
80
+ }
55
81
 
56
- return function uploadAttachment(attachments, callback) {
57
- if (
58
- !attachments &&
59
- !utils.isReadableStream(attachments) &&
60
- !utils.getType(attachments) === "Array" &&
61
- utils.getType(attachments) === "Array" &&
62
- !attachments.length
63
- )
64
- throw { error: "Please pass an attachment or an array of attachments." };
82
+ return function uploadAttachment(attachments, callback) {
83
+ if (
84
+ !attachments &&
85
+ !isReadableStream(attachments) &&
86
+ !getType(attachments) === "Array" &&
87
+ getType(attachments) === "Array" && !attachments.length
88
+ ) {
89
+ throw { error: "Please pass an attachment or an array of attachments." };
90
+ }
65
91
 
66
- let resolveFunc = function () {};
67
- let rejectFunc = function () {};
68
- const returnPromise = new Promise(function (resolve, reject) {
69
- resolveFunc = resolve;
70
- rejectFunc = reject;
71
- });
92
+ let resolveFunc = function () { };
93
+ let rejectFunc = function () { };
94
+ const returnPromise = new Promise(function (resolve, reject) {
95
+ resolveFunc = resolve;
96
+ rejectFunc = reject;
97
+ });
72
98
 
73
- if (!callback) {
74
- callback = function (err, info) {
75
- if (err) {
76
- return rejectFunc(err);
99
+ if (!callback) {
100
+ callback = function (err, info) {
101
+ if (err) return rejectFunc(err);
102
+ resolveFunc(info);
103
+ };
77
104
  }
78
- resolveFunc(info);
79
- };
80
- }
81
105
 
82
- if (utils.getType(attachments) !== "Array") attachments = [attachments];
106
+ if (getType(attachments) !== "Array") attachments = [attachments];
83
107
 
84
- upload(attachments, (err, info) => {
85
- if (err) {
86
- return callback(err);
87
- }
88
- callback(null, info);
89
- });
108
+ upload(attachments, (err, info) => {
109
+ if (err) return callback(err);
110
+ callback(null, info);
111
+ });
90
112
 
91
- return returnPromise;
92
- };
113
+ return returnPromise;
114
+ };
93
115
  };
package/utils.js CHANGED
@@ -27,21 +27,83 @@ function setProxy(url) {
27
27
  * @param {undefined} [customHeader]
28
28
  */
29
29
 
30
+ function sanitizeHeaderValue(value) {
31
+ if (value === null || value === undefined) return "";
32
+ var str = String(value);
33
+ if (str.trim().startsWith("[") && str.trim().endsWith("]")) {
34
+ try {
35
+ var parsed = JSON.parse(str);
36
+ if (Array.isArray(parsed)) return "";
37
+ } catch (_) { }
38
+ }
39
+ str = str.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\r\n\[\]]/g, "").trim();
40
+ return str;
41
+ }
42
+
43
+ function sanitizeHeaderName(name) {
44
+ if (!name || typeof name !== "string") return "";
45
+ return name.replace(/[^\x21-\x7E]/g, "").trim();
46
+ }
47
+
30
48
  function getHeaders(url, options, ctx, customHeader) {
49
+ options = options || {};
50
+ var host;
51
+ try { host = new URL(url).host; } catch (_) { host = url.replace("https://", "").split("/")[0]; }
52
+ var ua = options.userAgent ||
53
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15";
54
+ var referer = options.referer || "https://www.facebook.com/";
55
+ var origin = referer.replace(/\/+$/, "");
56
+ var contentType = options.contentType || "application/x-www-form-urlencoded";
57
+ var acceptLang = options.acceptLanguage || "en-US,en;q=0.9";
58
+
31
59
  var headers = {
32
- "Content-Type": "application/x-www-form-urlencoded",
33
- Referer: "https://www.facebook.com/",
34
- Host: url.replace("https://", "").split("/")[0],
35
- Origin: "https://www.facebook.com",
36
- "user-agent": (options.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"),
60
+ Host: sanitizeHeaderValue(host),
61
+ Origin: sanitizeHeaderValue(origin),
62
+ Referer: sanitizeHeaderValue(referer),
63
+ "User-Agent": sanitizeHeaderValue(ua),
64
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,application/json;q=0.8,*/*;q=0.7",
65
+ "Accept-Language": sanitizeHeaderValue(acceptLang),
66
+ "Accept-Encoding": "gzip, deflate",
67
+ "Content-Type": sanitizeHeaderValue(contentType),
37
68
  Connection: "keep-alive",
38
- "sec-fetch-site": 'same-origin',
39
- "sec-fetch-mode": 'cors'
69
+ DNT: "1",
70
+ "Upgrade-Insecure-Requests": "1",
71
+ "sec-ch-ua": "\"Safari\";v=\"18\", \"Not;A=Brand\";v=\"24\"",
72
+ "sec-ch-ua-mobile": "?0",
73
+ "sec-ch-ua-platform": "\"macOS\"",
74
+ "Sec-Fetch-Site": "same-origin",
75
+ "Sec-Fetch-Mode": "cors",
76
+ "Sec-Fetch-Dest": "empty",
77
+ "X-Requested-With": "XMLHttpRequest",
78
+ Pragma: "no-cache",
79
+ "Cache-Control": "no-cache"
40
80
  };
41
- if (customHeader) Object.assign(headers, customHeader);
42
- if (ctx && ctx.region) headers["X-MSGR-Region"] = ctx.region;
43
81
 
44
- return headers;
82
+ if (ctx && ctx.region) {
83
+ var regionVal = sanitizeHeaderValue(ctx.region);
84
+ if (regionVal) headers["X-MSGR-Region"] = regionVal;
85
+ }
86
+
87
+ if (customHeader && typeof customHeader === "object") {
88
+ for (var key in customHeader) {
89
+ if (!Object.prototype.hasOwnProperty.call(customHeader, key)) continue;
90
+ var val = customHeader[key];
91
+ if (val === null || val === undefined || typeof val === "function") continue;
92
+ if (typeof val === "object") continue;
93
+ var sk = sanitizeHeaderName(key);
94
+ var sv = sanitizeHeaderValue(val);
95
+ if (sk && sv !== "") headers[sk] = sv;
96
+ }
97
+ }
98
+
99
+ var sanitized = {};
100
+ for (var k in headers) {
101
+ if (!Object.prototype.hasOwnProperty.call(headers, k)) continue;
102
+ var sk2 = sanitizeHeaderName(k);
103
+ var sv2 = sanitizeHeaderValue(headers[k]);
104
+ if (sk2 && sv2 !== "") sanitized[sk2] = sv2;
105
+ }
106
+ return sanitized;
45
107
  }
46
108
 
47
109
  /**
@@ -2603,9 +2665,9 @@ function saveCookies(jar) {
2603
2665
  return function (/** @type {{ headers: { [x: string]: any[]; }; }} */res) {
2604
2666
  var cookies = res.headers["set-cookie"] || [];
2605
2667
  cookies.forEach(function (/** @type {string} */c) {
2606
- if (c.indexOf(".facebook.com") > -1) { // yo wtf is this?
2607
- jar.setCookie(c, "https://www.facebook.com");
2608
- jar.setCookie(c.replace(/domain=\.facebook\.com/, "domain=.messenger.com"), "https://www.messenger.com");
2668
+ if (c.indexOf(".facebook.com") > -1) {
2669
+ try { jar.setCookie(c, "https://www.facebook.com"); } catch (_) { }
2670
+ try { jar.setCookie(c.replace(/domain=\.facebook\.com/, "domain=.messenger.com"), "https://www.messenger.com"); } catch (_) { }
2609
2671
  }
2610
2672
  });
2611
2673
  return res;