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.
- package/README.md +226 -2
- package/checkUpdate.js +53 -49
- package/index.js +151 -15
- package/package.json +13 -12
- package/src/OldMessage.js +2 -2
- package/src/editMessage.js +14 -1
- package/src/listenMqtt.js +407 -395
- package/src/sendMessage.js +390 -278
- package/src/sendMessageMqtt.js +0 -1
- package/src/sendTypingIndicator.js +54 -45
- package/src/setMessageReaction.js +25 -0
- package/src/unsendMessage.js +21 -0
- package/src/uploadAttachment.js +99 -77
- package/utils.js +75 -13
package/src/sendMessageMqtt.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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:
|
package/src/unsendMessage.js
CHANGED
|
@@ -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
|
};
|
package/src/uploadAttachment.js
CHANGED
|
@@ -1,93 +1,115 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
106
|
+
if (getType(attachments) !== "Array") attachments = [attachments];
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
"
|
|
39
|
-
"
|
|
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
|
-
|
|
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) {
|
|
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;
|