@lazyneoaz/metachat 1.0.8 → 1.0.10
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/index.js +55 -1
- package/package.json +1 -1
- package/src/apis/changeAdminStatus.js +2 -6
- package/src/apis/createPoll.js +1 -1
- package/src/apis/gcmember.js +17 -10
- package/src/apis/gcrule.js +14 -9
- package/src/apis/getAccess.js +3 -1
- package/src/apis/getBotInitialData.js +1 -1
- package/src/apis/getMessage.js +83 -29
- package/src/apis/getThemeInfo.js +6 -2
- package/src/apis/getThreadHistory.js +1 -1
- package/src/apis/getThreadInfo.js +16 -6
- package/src/apis/getThreadList.js +11 -5
- package/src/apis/listenMqtt.js +19 -40
- package/src/apis/markAsRead.js +12 -15
- package/src/apis/markAsSeen.js +7 -9
- package/src/apis/mqttDeltaValue.js +2 -2
- package/src/apis/nickname.js +7 -4
- package/src/apis/pinMessage.js +7 -5
- package/src/apis/removeUserFromGroup.js +1 -0
- package/src/apis/sendEffect.js +4 -4
- package/src/apis/setMessageReaction.js +12 -11
- package/src/apis/setThreadTheme.js +11 -11
- package/src/apis/unsendMessage.js +1 -1
- package/src/app/MessengerBot.js +239 -0
- package/src/app/broadcast.js +48 -0
- package/src/app/config.js +97 -0
- package/src/app/createFcaClient.js +179 -0
- package/src/app/state.js +117 -0
- package/src/app/threadSync.js +106 -0
- package/src/app/updateCheck.js +92 -0
- package/src/engine/client.js +76 -28
- package/src/utils/antiSuspension.js +9 -9
- package/src/utils/auth-helpers.js +17 -1
- package/src/utils/axios.js +30 -16
- package/src/utils/clients.js +112 -112
- package/src/utils/rateLimiter.js +1 -1
package/src/apis/markAsSeen.js
CHANGED
|
@@ -10,8 +10,8 @@ const utils = require('../utils');
|
|
|
10
10
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
11
11
|
/**
|
|
12
12
|
* Marks all messages as "seen" up to a specific timestamp.
|
|
13
|
-
* @param {number} [seen_timestamp=Date.now()] - The timestamp (in
|
|
14
|
-
* @param {Function} [callback] -
|
|
13
|
+
* @param {number} [seen_timestamp=Date.now()] - The timestamp (in ms) up to which messages should be marked as seen.
|
|
14
|
+
* @param {Function} [callback] - Optional callback function.
|
|
15
15
|
* @returns {Promise<void>} A Promise that resolves on success or rejects with an error.
|
|
16
16
|
*/
|
|
17
17
|
return async function markAsSeen(seen_timestamp, callback) {
|
|
@@ -30,11 +30,9 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (!callback) {
|
|
33
|
-
callback = function (err
|
|
34
|
-
if (err)
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
resolveFunc(friendList);
|
|
33
|
+
callback = function (err) {
|
|
34
|
+
if (err) return rejectFunc(err);
|
|
35
|
+
resolveFunc();
|
|
38
36
|
};
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -56,13 +54,13 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
56
54
|
throw resData;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
callback(null);
|
|
60
58
|
} catch (err) {
|
|
61
59
|
utils.error("markAsSeen", err);
|
|
62
60
|
if (utils.getType(err) == "Object" && err.error === "Not logged in.") {
|
|
63
61
|
ctx.loggedIn = false;
|
|
64
62
|
}
|
|
65
|
-
|
|
63
|
+
callback(err);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
return returnPromise;
|
|
@@ -183,7 +183,7 @@ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
|
|
|
183
183
|
} catch (err) {
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
|
-
if (!ctx.globalOptions.
|
|
186
|
+
if (!ctx.globalOptions.selfListenEvent && fmtEvent2 && fmtEvent2.author && fmtEvent2.author.toString() === ctx.userID) return;
|
|
187
187
|
if (!ctx.loggedIn) return;
|
|
188
188
|
if (fmtEvent2) globalCallback(null, fmtEvent2);
|
|
189
189
|
break;
|
|
@@ -193,7 +193,7 @@ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
|
|
|
193
193
|
var tid = v.delta.threadKey.threadFbId;
|
|
194
194
|
if (mid && tid) {
|
|
195
195
|
var form = {
|
|
196
|
-
av: ctx.globalOptions.pageID,
|
|
196
|
+
av: ctx.globalOptions.pageID || ctx.userID,
|
|
197
197
|
queries: JSON.stringify({
|
|
198
198
|
o0: {
|
|
199
199
|
doc_id: "2848441488556444",
|
package/src/apis/nickname.js
CHANGED
|
@@ -70,14 +70,17 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
70
70
|
participantID = participantID || ctx.userID;
|
|
71
71
|
|
|
72
72
|
if (!threadID) {
|
|
73
|
-
|
|
73
|
+
_callback(new Error("threadID is required to set a nickname."));
|
|
74
|
+
return returnPromise;
|
|
74
75
|
}
|
|
75
76
|
if (typeof nickname !== 'string') {
|
|
76
|
-
|
|
77
|
+
_callback(new Error("nickname must be a string."));
|
|
78
|
+
return returnPromise;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
if (!ctx.mqttClient) {
|
|
80
|
-
|
|
82
|
+
_callback(new Error("Not connected to MQTT"));
|
|
83
|
+
return returnPromise;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
ctx.wsReqNumber += 1;
|
|
@@ -99,7 +102,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
99
102
|
};
|
|
100
103
|
|
|
101
104
|
const context = {
|
|
102
|
-
app_id: ctx.appID,
|
|
105
|
+
app_id: ctx.appID || "2220391788200892",
|
|
103
106
|
payload: {
|
|
104
107
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
105
108
|
tasks: [query],
|
package/src/apis/pinMessage.js
CHANGED
|
@@ -34,11 +34,13 @@ function extractAndSearchLightspeedRequest(allJsonData, options = {}) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
|
|
@@ -67,6 +67,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
67
67
|
}
|
|
68
68
|
if (jsonMsg.request_id !== reqID) return;
|
|
69
69
|
responseHandled = true;
|
|
70
|
+
clearTimeout(timeout);
|
|
70
71
|
ctx.mqttClient.removeListener("message", handleRes);
|
|
71
72
|
callback(null, { success: true });
|
|
72
73
|
resolveFunc({ success: true });
|
package/src/apis/sendEffect.js
CHANGED
|
@@ -213,9 +213,9 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
try {
|
|
216
|
-
if (!effectName)
|
|
217
|
-
if (message === undefined || message === null)
|
|
218
|
-
if (!threadID)
|
|
216
|
+
if (!effectName) { callback(new Error('effectName is required.')); return returnPromise; }
|
|
217
|
+
if (message === undefined || message === null) { callback(new Error('message is required.')); return returnPromise; }
|
|
218
|
+
if (!threadID) { callback(new Error('threadID is required.')); return returnPromise; }
|
|
219
219
|
|
|
220
220
|
const effectTag = resolveEffect(effectName);
|
|
221
221
|
utils.log('sendEffect', `Effect "${effectTag}" → thread ${threadID}`);
|
|
@@ -280,7 +280,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
280
280
|
timestamp: v.timestamp || p.timestamp,
|
|
281
281
|
}), { threadID, messageID: messageAndOTID, timestamp: httpTs });
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
callback(null, { ...msgInfo, effect: effectTag, method: 'http' });
|
|
284
284
|
|
|
285
285
|
} catch (err) {
|
|
286
286
|
utils.error('sendEffect', err.message || err);
|
|
@@ -5,20 +5,21 @@ const utils = require('../utils');
|
|
|
5
5
|
|
|
6
6
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
7
7
|
return async (reaction, messageID) => {
|
|
8
|
-
if (
|
|
9
|
-
const
|
|
8
|
+
if (reaction === undefined || reaction === null) throw new Error("Please enter a valid emoji.");
|
|
9
|
+
const action = reaction === "" ? "REMOVE_REACTION" : "ADD_REACTION";
|
|
10
|
+
const defData = await defaultFuncs.postFormData("https://www.facebook.com/webgraphql/mutation/", ctx.jar, {
|
|
10
11
|
doc_id: "1491398900900362",
|
|
11
12
|
variables: JSON.stringify({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
data: {
|
|
14
|
+
client_mutation_id: ctx.clientMutationId++,
|
|
15
|
+
actor_id: ctx.userID,
|
|
16
|
+
action,
|
|
17
|
+
message_id: messageID,
|
|
18
|
+
reaction
|
|
19
|
+
}
|
|
20
|
+
}),
|
|
20
21
|
dpr: 1
|
|
21
|
-
});
|
|
22
|
+
}, {});
|
|
22
23
|
const resData = await utils.parseAndCheckLogin(ctx, defaultFuncs)(defData);
|
|
23
24
|
if (!resData) {
|
|
24
25
|
throw new Error("setMessageReactionLegacy returned empty object.");
|
|
@@ -35,17 +35,17 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
35
35
|
|
|
36
36
|
// ── Hardcoded colour aliases (fast path — no API round trip needed) ──────
|
|
37
37
|
const PALETTE = {
|
|
38
|
-
blue: "196241301102133",
|
|
39
|
-
purple: "
|
|
40
|
-
green: "
|
|
41
|
-
pink: "
|
|
42
|
-
orange: "175615189761153",
|
|
43
|
-
red: "
|
|
44
|
-
yellow: "
|
|
45
|
-
teal: "
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
default: "196241301102133",
|
|
38
|
+
blue: "196241301102133", // DefaultBlue / MessengerBlue
|
|
39
|
+
purple: "234137870477637", // BrightPurple / MediumSlateBlue
|
|
40
|
+
green: "2136751179887052", // FreeSpeechGreen / Green
|
|
41
|
+
pink: "169463077092846", // HotPink / BrilliantRose
|
|
42
|
+
orange: "175615189761153", // Pumpkin / Orange
|
|
43
|
+
red: "2129984390566328", // RadicalRed / Red
|
|
44
|
+
yellow: "174636906462322", // GoldenPoppy / Yellow
|
|
45
|
+
teal: "1928399724138152", // TealBlue / Viking
|
|
46
|
+
aqua: "417639218648241", // Aqua
|
|
47
|
+
black: "271607034185782", // Shadow (darkest solid theme)
|
|
48
|
+
default: "196241301102133", // DefaultBlue
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
(async function worker() {
|
|
@@ -10,7 +10,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
10
10
|
})
|
|
11
11
|
const resData = await utils.parseAndCheckLogin(ctx, defaultFuncs)(defData);
|
|
12
12
|
if (resData.error) {
|
|
13
|
-
throw new Error(resData);
|
|
13
|
+
throw new Error(JSON.stringify(resData));
|
|
14
14
|
}
|
|
15
15
|
return resData;
|
|
16
16
|
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const EventEmitter = require("events");
|
|
4
|
+
|
|
5
|
+
class MessengerContext {
|
|
6
|
+
constructor(bot, event) {
|
|
7
|
+
this.bot = bot;
|
|
8
|
+
this.event = event;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get threadID() { return this.event.threadID; }
|
|
12
|
+
get senderID() { return this.event.senderID; }
|
|
13
|
+
get messageID() { return this.event.messageID; }
|
|
14
|
+
get text() { return (this.event.body || "").trim(); }
|
|
15
|
+
get body() { return this.event.body; }
|
|
16
|
+
get message() { return this.event; }
|
|
17
|
+
|
|
18
|
+
reply(payload, callback) {
|
|
19
|
+
const tid = this.event.threadID;
|
|
20
|
+
if (tid == null) throw new Error("MessengerContext.reply: threadID is missing");
|
|
21
|
+
return this.bot.api.sendMessage(payload, tid, callback);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async replyAsync(payload) {
|
|
25
|
+
const r = this.reply(payload);
|
|
26
|
+
if (r && typeof r.then === "function") return r;
|
|
27
|
+
return Promise.resolve(r);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function escapeRegex(s) {
|
|
32
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function emitIf(bot, channel, payload) {
|
|
36
|
+
if (bot.listenerCount(channel) > 0) bot.emit(channel, payload);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function emitGatewayEvents(bot, event) {
|
|
40
|
+
emitIf(bot, "update", event);
|
|
41
|
+
emitIf(bot, "raw", event);
|
|
42
|
+
const t = event.type;
|
|
43
|
+
if (!t) return;
|
|
44
|
+
if (t === "message" || t === "message_reply") {
|
|
45
|
+
emitIf(bot, "message", event);
|
|
46
|
+
emitIf(bot, "messageCreate", event);
|
|
47
|
+
}
|
|
48
|
+
if (t === "message_reply") {
|
|
49
|
+
emitIf(bot, "message_reply", event);
|
|
50
|
+
} else if (t !== "message") {
|
|
51
|
+
emitIf(bot, t, event);
|
|
52
|
+
}
|
|
53
|
+
switch (t) {
|
|
54
|
+
case "message_reaction":
|
|
55
|
+
emitIf(bot, "messageReactionAdd", event);
|
|
56
|
+
break;
|
|
57
|
+
case "message_unsend":
|
|
58
|
+
emitIf(bot, "messageDelete", event);
|
|
59
|
+
break;
|
|
60
|
+
case "typ":
|
|
61
|
+
emitIf(bot, event.isTyping ? "typingStart" : "typingStop", event);
|
|
62
|
+
break;
|
|
63
|
+
case "event":
|
|
64
|
+
emitIf(bot, "threadUpdate", event);
|
|
65
|
+
break;
|
|
66
|
+
case "ready":
|
|
67
|
+
emitIf(bot, "ready", event);
|
|
68
|
+
emitIf(bot, "shardReady", event);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class MessengerBot extends EventEmitter {
|
|
74
|
+
constructor(api, options = {}) {
|
|
75
|
+
super();
|
|
76
|
+
this.api = api;
|
|
77
|
+
this.options = Object.assign({
|
|
78
|
+
commandPrefix: "/",
|
|
79
|
+
enableComposer: true,
|
|
80
|
+
autoListen: true,
|
|
81
|
+
stopOnSignals: false,
|
|
82
|
+
maxEventListeners: 64,
|
|
83
|
+
}, options);
|
|
84
|
+
|
|
85
|
+
if (this.options.maxEventListeners > 0) {
|
|
86
|
+
this.setMaxListeners(this.options.maxEventListeners);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this._middleware = [];
|
|
90
|
+
this._commands = new Map();
|
|
91
|
+
this._hears = [];
|
|
92
|
+
this._catchHandler = null;
|
|
93
|
+
this._mqttHandle = null;
|
|
94
|
+
this._signalHandlers = null;
|
|
95
|
+
this._client = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get client() {
|
|
99
|
+
if (!this._client) {
|
|
100
|
+
const { createFcaClient } = require("./createFcaClient");
|
|
101
|
+
this._client = createFcaClient(this.api);
|
|
102
|
+
}
|
|
103
|
+
return this._client;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
use(fn) {
|
|
107
|
+
this._middleware.push(fn);
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
command(name, fn) {
|
|
112
|
+
this._commands.set(String(name).toLowerCase(), fn);
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
hears(trigger, fn) {
|
|
117
|
+
this._hears.push({ trigger, fn });
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
catch(fn) {
|
|
122
|
+
this._catchHandler = fn;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async _runComposer(event) {
|
|
127
|
+
if (!this.options.enableComposer) return;
|
|
128
|
+
if (event.type !== "message" && event.type !== "message_reply") return;
|
|
129
|
+
|
|
130
|
+
const ctx = new MessengerContext(this, event);
|
|
131
|
+
const prefix = this.options.commandPrefix || "/";
|
|
132
|
+
|
|
133
|
+
const body = event.body || "";
|
|
134
|
+
const trimmed = body.trim();
|
|
135
|
+
|
|
136
|
+
const pipeline = [...this._middleware];
|
|
137
|
+
|
|
138
|
+
if (trimmed.startsWith(prefix)) {
|
|
139
|
+
const withoutPrefix = trimmed.slice(prefix.length);
|
|
140
|
+
const cmdName = withoutPrefix.split(/\s+/)[0].toLowerCase();
|
|
141
|
+
if (this._commands.has(cmdName)) {
|
|
142
|
+
pipeline.push(this._commands.get(cmdName));
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
for (const { trigger, fn } of this._hears) {
|
|
146
|
+
if (typeof trigger === "string") {
|
|
147
|
+
if (trimmed.toLowerCase().includes(trigger.toLowerCase())) {
|
|
148
|
+
pipeline.push(fn);
|
|
149
|
+
}
|
|
150
|
+
} else if (trigger instanceof RegExp) {
|
|
151
|
+
if (trigger.test(trimmed)) {
|
|
152
|
+
pipeline.push(fn);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let idx = 0;
|
|
159
|
+
const next = async () => {
|
|
160
|
+
if (idx >= pipeline.length) return;
|
|
161
|
+
const fn = pipeline[idx++];
|
|
162
|
+
try {
|
|
163
|
+
await fn(ctx, next);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (this._catchHandler) {
|
|
166
|
+
this._catchHandler(err, ctx);
|
|
167
|
+
} else {
|
|
168
|
+
this.emit("error", err);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
await next();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
startListening() {
|
|
177
|
+
if (this._mqttHandle) return;
|
|
178
|
+
this._mqttHandle = this.api.listenMqtt((err, event) => {
|
|
179
|
+
if (err) {
|
|
180
|
+
this.emit("error", err);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
emitGatewayEvents(this, event);
|
|
184
|
+
this._runComposer(event).catch((e) => this.emit("error", e));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
stopListening() {
|
|
189
|
+
if (this._mqttHandle && typeof this._mqttHandle.stopListening === "function") {
|
|
190
|
+
this._mqttHandle.stopListening();
|
|
191
|
+
} else if (this.api && typeof this.api.stopListening === "function") {
|
|
192
|
+
this.api.stopListening();
|
|
193
|
+
}
|
|
194
|
+
this._mqttHandle = null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async stop() {
|
|
198
|
+
if (this._signalHandlers) {
|
|
199
|
+
for (const [sig, handler] of Object.entries(this._signalHandlers)) {
|
|
200
|
+
process.removeListener(sig, handler);
|
|
201
|
+
}
|
|
202
|
+
this._signalHandlers = null;
|
|
203
|
+
}
|
|
204
|
+
this.stopListening();
|
|
205
|
+
this.removeAllListeners();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async launch(launchOptions = {}) {
|
|
209
|
+
const opts = Object.assign({}, this.options, launchOptions);
|
|
210
|
+
if (opts.stopOnSignals) {
|
|
211
|
+
const handler = async () => {
|
|
212
|
+
await this.stop();
|
|
213
|
+
process.exit(0);
|
|
214
|
+
};
|
|
215
|
+
this._signalHandlers = { SIGINT: handler, SIGTERM: handler };
|
|
216
|
+
process.once("SIGINT", handler);
|
|
217
|
+
process.once("SIGTERM", handler);
|
|
218
|
+
}
|
|
219
|
+
if (opts.autoListen !== false) {
|
|
220
|
+
this.startListening();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function createMessengerBot(credentials, options = {}) {
|
|
226
|
+
const { login } = require("../engine/client");
|
|
227
|
+
const api = await login(credentials, options);
|
|
228
|
+
const bot = new MessengerBot(api, options);
|
|
229
|
+
if (options.autoListen !== false) {
|
|
230
|
+
bot.startListening();
|
|
231
|
+
}
|
|
232
|
+
return bot;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
MessengerBot,
|
|
237
|
+
MessengerContext,
|
|
238
|
+
createMessengerBot,
|
|
239
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Broadcast a message to multiple threads.
|
|
5
|
+
* @param {object} api - The FCA api object.
|
|
6
|
+
* @param {string|object} message - Message body or message object.
|
|
7
|
+
* @param {string[]} threadIDs - Array of thread IDs to send to.
|
|
8
|
+
* @param {object} [options] - Options.
|
|
9
|
+
* @param {number} [options.delayMs=200] - Delay between sends in ms.
|
|
10
|
+
* @returns {Promise<{sent: string[], failed: string[]}>}
|
|
11
|
+
*/
|
|
12
|
+
async function broadcast(api, message, threadIDs, options = {}) {
|
|
13
|
+
if (!api || typeof api.sendMessage !== "function") {
|
|
14
|
+
throw new Error("broadcast: api must be a valid FCA api object");
|
|
15
|
+
}
|
|
16
|
+
if (!Array.isArray(threadIDs) || threadIDs.length === 0) {
|
|
17
|
+
throw new Error("broadcast: threadIDs must be a non-empty array");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const delayMs = typeof options.delayMs === "number" ? options.delayMs : 200;
|
|
21
|
+
const sent = [];
|
|
22
|
+
const failed = [];
|
|
23
|
+
|
|
24
|
+
for (const tid of threadIDs) {
|
|
25
|
+
try {
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
const result = api.sendMessage(message, tid, (err) => {
|
|
28
|
+
if (err) reject(err);
|
|
29
|
+
else resolve();
|
|
30
|
+
});
|
|
31
|
+
if (result && typeof result.then === "function") {
|
|
32
|
+
result.then(resolve).catch(reject);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
sent.push(tid);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
failed.push(tid);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (delayMs > 0 && threadIDs.indexOf(tid) < threadIDs.length - 1) {
|
|
41
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { sent, failed };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { broadcast };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const CONFIG_FILE = "fca-config.json";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
checkUpdate: {
|
|
10
|
+
enabled: false,
|
|
11
|
+
install: false,
|
|
12
|
+
notifyIfCurrent: false,
|
|
13
|
+
packageName: "@lazyneoaz/metachat",
|
|
14
|
+
registryUrl: "https://registry.npmjs.org",
|
|
15
|
+
timeoutMs: 10000,
|
|
16
|
+
},
|
|
17
|
+
mqtt: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
reconnectInterval: 3600,
|
|
20
|
+
},
|
|
21
|
+
credentials: {
|
|
22
|
+
email: "",
|
|
23
|
+
password: "",
|
|
24
|
+
twofactor: "",
|
|
25
|
+
},
|
|
26
|
+
antiGetInfo: {
|
|
27
|
+
AntiGetThreadInfo: false,
|
|
28
|
+
AntiGetUserInfo: false,
|
|
29
|
+
},
|
|
30
|
+
remoteControl: {
|
|
31
|
+
enabled: false,
|
|
32
|
+
url: "",
|
|
33
|
+
token: "",
|
|
34
|
+
autoReconnect: true,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function defaultConfig() {
|
|
39
|
+
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadConfig() {
|
|
43
|
+
const configPath = path.resolve(process.cwd(), CONFIG_FILE);
|
|
44
|
+
let fileConfig = {};
|
|
45
|
+
let created = false;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(configPath)) {
|
|
49
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
50
|
+
fileConfig = JSON.parse(raw);
|
|
51
|
+
} else {
|
|
52
|
+
try {
|
|
53
|
+
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf8");
|
|
54
|
+
created = true;
|
|
55
|
+
} catch (_) {}
|
|
56
|
+
}
|
|
57
|
+
} catch (_) {}
|
|
58
|
+
|
|
59
|
+
const config = resolveConfig(fileConfig);
|
|
60
|
+
return { config, created };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveConfig(partial = {}) {
|
|
64
|
+
const base = defaultConfig();
|
|
65
|
+
return deepMerge(base, partial);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function deepMerge(target, source) {
|
|
69
|
+
const out = Object.assign({}, target);
|
|
70
|
+
for (const key of Object.keys(source || {})) {
|
|
71
|
+
if (
|
|
72
|
+
source[key] !== null &&
|
|
73
|
+
typeof source[key] === "object" &&
|
|
74
|
+
!Array.isArray(source[key]) &&
|
|
75
|
+
typeof target[key] === "object" &&
|
|
76
|
+
target[key] !== null
|
|
77
|
+
) {
|
|
78
|
+
out[key] = deepMerge(target[key], source[key]);
|
|
79
|
+
} else {
|
|
80
|
+
out[key] = source[key];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function writeConfigTemplate(destPath) {
|
|
87
|
+
const target = destPath || path.resolve(process.cwd(), "fca-config.example.json");
|
|
88
|
+
fs.writeFileSync(target, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf8");
|
|
89
|
+
return target;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
defaultConfig,
|
|
94
|
+
loadConfig,
|
|
95
|
+
resolveConfig,
|
|
96
|
+
writeConfigTemplate,
|
|
97
|
+
};
|