stfca 1.0.26 → 1.1.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.
@@ -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;