@eryxenx/fca 1.0.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.
- package/CHANGELOG.md +325 -0
- package/DOCS.md +2712 -0
- package/README.md +455 -0
- package/func/checkUpdate.js +7 -0
- package/func/logAdapter.js +33 -0
- package/func/logger.js +48 -0
- package/index.d.ts +751 -0
- package/index.js +22 -0
- package/module/config.js +40 -0
- package/module/login.js +133 -0
- package/module/loginHelper.js +1365 -0
- package/module/options.js +44 -0
- package/package.json +98 -0
- package/src/api/action/addExternalModule.js +25 -0
- package/src/api/action/changeAvatar.js +137 -0
- package/src/api/action/changeBio.js +75 -0
- package/src/api/action/enableAutoSaveAppState.js +73 -0
- package/src/api/action/getCurrentUserID.js +7 -0
- package/src/api/action/handleFriendRequest.js +57 -0
- package/src/api/action/logout.js +76 -0
- package/src/api/action/refreshFb_dtsg.js +48 -0
- package/src/api/action/setPostReaction.js +106 -0
- package/src/api/action/unfriend.js +54 -0
- package/src/api/http/httpGet.js +46 -0
- package/src/api/http/httpPost.js +52 -0
- package/src/api/http/postFormData.js +47 -0
- package/src/api/messaging/addUserToGroup.js +68 -0
- package/src/api/messaging/changeAdminStatus.js +126 -0
- package/src/api/messaging/changeArchivedStatus.js +55 -0
- package/src/api/messaging/changeBlockedStatus.js +48 -0
- package/src/api/messaging/changeGroupImage.js +91 -0
- package/src/api/messaging/changeNickname.js +70 -0
- package/src/api/messaging/changeThreadColor.js +79 -0
- package/src/api/messaging/changeThreadEmoji.js +111 -0
- package/src/api/messaging/createNewGroup.js +88 -0
- package/src/api/messaging/createPoll.js +46 -0
- package/src/api/messaging/createThemeAI.js +98 -0
- package/src/api/messaging/deleteMessage.js +136 -0
- package/src/api/messaging/deleteThread.js +56 -0
- package/src/api/messaging/editMessage.js +105 -0
- package/src/api/messaging/forwardAttachment.js +57 -0
- package/src/api/messaging/forwardMessage.js +134 -0
- package/src/api/messaging/getEmojiUrl.js +29 -0
- package/src/api/messaging/getFriendsList.js +82 -0
- package/src/api/messaging/getMessage.js +829 -0
- package/src/api/messaging/getThemePictures.js +62 -0
- package/src/api/messaging/handleMessageRequest.js +65 -0
- package/src/api/messaging/markAsDelivered.js +57 -0
- package/src/api/messaging/markAsRead.js +88 -0
- package/src/api/messaging/markAsReadAll.js +56 -0
- package/src/api/messaging/markAsSeen.js +68 -0
- package/src/api/messaging/muteThread.js +50 -0
- package/src/api/messaging/pinMessage.js +115 -0
- package/src/api/messaging/removeUserFromGroup.js +62 -0
- package/src/api/messaging/resolvePhotoUrl.js +43 -0
- package/src/api/messaging/scheduler.js +264 -0
- package/src/api/messaging/searchForThread.js +53 -0
- package/src/api/messaging/sendBroadcast.js +93 -0
- package/src/api/messaging/sendMessage.js +269 -0
- package/src/api/messaging/sendTypingIndicator.js +90 -0
- package/src/api/messaging/sessionGuard.js +130 -0
- package/src/api/messaging/setMessageReaction.js +109 -0
- package/src/api/messaging/setTitle.js +124 -0
- package/src/api/messaging/shareContact.js +98 -0
- package/src/api/messaging/threadColors.js +128 -0
- package/src/api/messaging/unsendMessage.js +105 -0
- package/src/api/messaging/uploadAttachment.js +492 -0
- package/src/api/socket/OldMessage.js +186 -0
- package/src/api/socket/core/connectMqtt.js +269 -0
- package/src/api/socket/core/emitAuth.js +103 -0
- package/src/api/socket/core/getSeqID.js +321 -0
- package/src/api/socket/core/getTaskResponseData.js +25 -0
- package/src/api/socket/core/parseDelta.js +387 -0
- package/src/api/socket/detail/buildStream.js +215 -0
- package/src/api/socket/detail/constants.js +28 -0
- package/src/api/socket/e2ee/crypto.js +173 -0
- package/src/api/socket/e2ee/index.js +925 -0
- package/src/api/socket/e2ee/localMediaServer.js +59 -0
- package/src/api/socket/e2ee/mediaDecode.js +155 -0
- package/src/api/socket/e2ee/native/NOTICE.md +29 -0
- package/src/api/socket/e2ee/native/build/messagix.dll +0 -0
- package/src/api/socket/e2ee/native/build/messagix.so +0 -0
- package/src/api/socket/e2ee/native/lib/index.mjs +1426 -0
- package/src/api/socket/e2ee/native/nativeMediaBridge.js +233 -0
- package/src/api/socket/e2ee/proto/ArmadilloApplication.proto +281 -0
- package/src/api/socket/e2ee/proto/ArmadilloICDC.proto +14 -0
- package/src/api/socket/e2ee/proto/ConsumerApplication.proto +232 -0
- package/src/api/socket/e2ee/proto/MessageApplication.proto +82 -0
- package/src/api/socket/e2ee/proto/MessageTransport.proto +77 -0
- package/src/api/socket/e2ee/proto/WACommon.proto +66 -0
- package/src/api/socket/e2ee/proto/WAMediaTransport.proto +176 -0
- package/src/api/socket/e2ee/proto/proto-writer.ts +76 -0
- package/src/api/socket/e2ee/protocol.js +196 -0
- package/src/api/socket/e2ee/ratchet.js +219 -0
- package/src/api/socket/e2ee/store.js +182 -0
- package/src/api/socket/e2ee/vendor/fme/dist/index.cjs +6477 -0
- package/src/api/socket/e2ee/vendor/fme/proto/ArmadilloApplication.proto +281 -0
- package/src/api/socket/e2ee/vendor/fme/proto/ArmadilloICDC.proto +14 -0
- package/src/api/socket/e2ee/vendor/fme/proto/ConsumerApplication.proto +232 -0
- package/src/api/socket/e2ee/vendor/fme/proto/MessageApplication.proto +82 -0
- package/src/api/socket/e2ee/vendor/fme/proto/MessageTransport.proto +77 -0
- package/src/api/socket/e2ee/vendor/fme/proto/WACommon.proto +66 -0
- package/src/api/socket/e2ee/vendor/fme/proto/WAMediaTransport.proto +176 -0
- package/src/api/socket/listenE2EE.js +75 -0
- package/src/api/socket/listenMqtt.js +436 -0
- package/src/api/socket/middleware/index.js +216 -0
- package/src/api/socket/sendMessage.js +314 -0
- package/src/api/socket/sendMessageMqtt.js +69 -0
- package/src/api/threads/getThreadHistory.js +664 -0
- package/src/api/threads/getThreadInfo.js +329 -0
- package/src/api/threads/getThreadList.js +293 -0
- package/src/api/threads/getThreadPictures.js +78 -0
- package/src/api/users/getUserID.js +65 -0
- package/src/api/users/getUserInfo.js +402 -0
- package/src/api/users/getUserInfoV2.js +134 -0
- package/src/app/MessengerBot.js +209 -0
- package/src/app/MessengerContext.js +32 -0
- package/src/app/createFcaClient.js +136 -0
- package/src/app/threadInfoRealtimeSync.js +284 -0
- package/src/core/sendReqMqtt.js +96 -0
- package/src/database/helpers.js +53 -0
- package/src/database/models/index.js +88 -0
- package/src/database/models/thread.js +50 -0
- package/src/database/models/user.js +46 -0
- package/src/database/threadData.js +94 -0
- package/src/database/userData.js +98 -0
- package/src/remote/remoteClient.js +123 -0
- package/src/utils/broadcast.js +51 -0
- package/src/utils/client.js +10 -0
- package/src/utils/constants.js +23 -0
- package/src/utils/cookies.js +68 -0
- package/src/utils/format/attachment.js +357 -0
- package/src/utils/format/cookie.js +9 -0
- package/src/utils/format/date.js +50 -0
- package/src/utils/format/decode.js +44 -0
- package/src/utils/format/delta.js +194 -0
- package/src/utils/format/ids.js +64 -0
- package/src/utils/format/index.js +64 -0
- package/src/utils/format/message.js +88 -0
- package/src/utils/format/presence.js +132 -0
- package/src/utils/format/readTyp.js +44 -0
- package/src/utils/format/thread.js +42 -0
- package/src/utils/format/utils.js +141 -0
- package/src/utils/headers.js +128 -0
- package/src/utils/loginParser/autoLogin.js +125 -0
- package/src/utils/loginParser/helpers.js +43 -0
- package/src/utils/loginParser/index.js +10 -0
- package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
- package/src/utils/loginParser/textUtils.js +28 -0
- package/src/utils/nexca-logger.js +144 -0
- package/src/utils/nexca-utils.js +686 -0
- package/src/utils/request/client.js +26 -0
- package/src/utils/request/config.js +23 -0
- package/src/utils/request/defaults.js +46 -0
- package/src/utils/request/helpers.js +46 -0
- package/src/utils/request/index.js +17 -0
- package/src/utils/request/methods.js +163 -0
- package/src/utils/request/proxy.js +21 -0
- package/src/utils/request/retry.js +77 -0
- package/src/utils/request/sanitize.js +49 -0
- package/src/utils/versionCheck.js +47 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const EventEmitter = require("events");
|
|
4
|
+
const { MessengerContext } = require("./MessengerContext");
|
|
5
|
+
const { createFcaClient } = require("./createFcaClient");
|
|
6
|
+
|
|
7
|
+
function escapeRegex(s) {
|
|
8
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function emitIf(bot, channel, payload) {
|
|
12
|
+
if (bot.listenerCount(channel) > 0) bot.emit(channel, payload);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function emitGatewayEvents(bot, event) {
|
|
16
|
+
emitIf(bot, "update", event);
|
|
17
|
+
emitIf(bot, "raw", event);
|
|
18
|
+
const t = event.type;
|
|
19
|
+
if (!t) return;
|
|
20
|
+
if (t === "message" || t === "message_reply") {
|
|
21
|
+
emitIf(bot, "message", event);
|
|
22
|
+
emitIf(bot, "messageCreate", event);
|
|
23
|
+
}
|
|
24
|
+
if (t === "message_reply") {
|
|
25
|
+
emitIf(bot, "message_reply", event);
|
|
26
|
+
} else if (t !== "message") {
|
|
27
|
+
emitIf(bot, t, event);
|
|
28
|
+
}
|
|
29
|
+
switch (t) {
|
|
30
|
+
case "message_reaction": emitIf(bot, "messageReactionAdd", event); break;
|
|
31
|
+
case "message_unsend": emitIf(bot, "messageDelete", event); break;
|
|
32
|
+
case "typ": emitIf(bot, event.isTyping ? "typingStart" : "typingStop", event); break;
|
|
33
|
+
case "event": emitIf(bot, "threadUpdate", event); break;
|
|
34
|
+
case "ready":
|
|
35
|
+
emitIf(bot, "ready", event);
|
|
36
|
+
emitIf(bot, "shardReady", event);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class MessengerBot extends EventEmitter {
|
|
42
|
+
constructor(api, options = {}) {
|
|
43
|
+
super();
|
|
44
|
+
const cap = options.maxEventListeners ?? 64;
|
|
45
|
+
this.setMaxListeners(cap === 0 ? 0 : cap);
|
|
46
|
+
this.api = api;
|
|
47
|
+
this._commandPrefix = options.commandPrefix || "/";
|
|
48
|
+
this._enableComposer = options.enableComposer !== false;
|
|
49
|
+
this._stopOnSignals = options.stopOnSignals || false;
|
|
50
|
+
this._middlewares = [];
|
|
51
|
+
this._catchHandler = null;
|
|
52
|
+
this._facade = null;
|
|
53
|
+
this._mqtt = null;
|
|
54
|
+
this._listening = false;
|
|
55
|
+
this._signalsBound = false;
|
|
56
|
+
this._onStopSignal = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get commandPrefix() { return this._commandPrefix; }
|
|
60
|
+
set commandPrefix(value) { this._commandPrefix = value || "/"; }
|
|
61
|
+
|
|
62
|
+
get client() {
|
|
63
|
+
if (!this._facade) this._facade = createFcaClient(this.api);
|
|
64
|
+
return this._facade;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
use(middleware) {
|
|
68
|
+
this._middlewares.push(middleware);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
command(name, handler) {
|
|
73
|
+
const n = name.toLowerCase();
|
|
74
|
+
this.use(async (ctx, next) => {
|
|
75
|
+
const text = ctx.text;
|
|
76
|
+
if (!text) { await next(); return; }
|
|
77
|
+
const prefix = escapeRegex(this._commandPrefix);
|
|
78
|
+
const re = new RegExp(`^${prefix}${escapeRegex(n)}(?:\\s|$)`, "i");
|
|
79
|
+
if (re.test(text)) { await handler(ctx); return; }
|
|
80
|
+
await next();
|
|
81
|
+
});
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hears(trigger, handler) {
|
|
86
|
+
const match = typeof trigger === "string"
|
|
87
|
+
? (text) => text.toLowerCase().includes(trigger.toLowerCase())
|
|
88
|
+
: (text) => trigger.test(text);
|
|
89
|
+
this.use(async (ctx, next) => {
|
|
90
|
+
const text = ctx.text;
|
|
91
|
+
if (!text) { await next(); return; }
|
|
92
|
+
if (match(text)) { await handler(ctx); return; }
|
|
93
|
+
await next();
|
|
94
|
+
});
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
catch(handler) {
|
|
99
|
+
this._catchHandler = handler;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
startListening() {
|
|
104
|
+
if (this._listening) return this;
|
|
105
|
+
if (typeof this.api.listenMqtt !== "function") {
|
|
106
|
+
throw new Error("listenMqtt is not available on API");
|
|
107
|
+
}
|
|
108
|
+
const mqtt = this.api.listenMqtt();
|
|
109
|
+
this._mqtt = mqtt;
|
|
110
|
+
this._listening = true;
|
|
111
|
+
mqtt.on("message", (event) => {
|
|
112
|
+
emitGatewayEvents(this, event);
|
|
113
|
+
this._enqueueComposerIfNeeded(event);
|
|
114
|
+
});
|
|
115
|
+
mqtt.on("error", (err) => this.emit("error", err));
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async launch(opts = {}) {
|
|
120
|
+
this.startListening();
|
|
121
|
+
const bind = opts.stopOnSignals ?? this._stopOnSignals;
|
|
122
|
+
if (bind) this._attachStopSignals();
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_attachStopSignals() {
|
|
127
|
+
if (this._signalsBound) return;
|
|
128
|
+
this._signalsBound = true;
|
|
129
|
+
this._onStopSignal = () => {
|
|
130
|
+
this.stop().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
131
|
+
};
|
|
132
|
+
process.once("SIGINT", this._onStopSignal);
|
|
133
|
+
process.once("SIGTERM", this._onStopSignal);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_detachStopSignals() {
|
|
137
|
+
if (!this._signalsBound || !this._onStopSignal) return;
|
|
138
|
+
process.off("SIGINT", this._onStopSignal);
|
|
139
|
+
process.off("SIGTERM", this._onStopSignal);
|
|
140
|
+
this._signalsBound = false;
|
|
141
|
+
this._onStopSignal = null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async stop() {
|
|
145
|
+
this._detachStopSignals();
|
|
146
|
+
if (!this._mqtt) return;
|
|
147
|
+
const mqtt = this._mqtt;
|
|
148
|
+
if (typeof mqtt.stopListeningAsync === "function") {
|
|
149
|
+
await mqtt.stopListeningAsync();
|
|
150
|
+
} else if (typeof mqtt.stopListening === "function") {
|
|
151
|
+
mqtt.stopListening();
|
|
152
|
+
}
|
|
153
|
+
if (typeof mqtt.removeAllListeners === "function") mqtt.removeAllListeners();
|
|
154
|
+
this._mqtt = null;
|
|
155
|
+
this._listening = false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_enqueueComposerIfNeeded(event) {
|
|
159
|
+
if (!this._enableComposer || this._middlewares.length === 0) return;
|
|
160
|
+
if (event.type !== "message" && event.type !== "message_reply") return;
|
|
161
|
+
const ctx = new MessengerContext(this, event);
|
|
162
|
+
queueMicrotask(() => { this._runComposer(ctx); });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async _runComposer(ctx) {
|
|
166
|
+
const dispatch = async (index) => {
|
|
167
|
+
if (index >= this._middlewares.length) return;
|
|
168
|
+
const mw = this._middlewares[index];
|
|
169
|
+
await mw(ctx, () => dispatch(index + 1));
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
await dispatch(0);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (this._catchHandler) this._catchHandler(err, ctx);
|
|
175
|
+
else this.emit("error", err);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static async connect(credentials, options = {}) {
|
|
180
|
+
const login = require("../../module/login");
|
|
181
|
+
const {
|
|
182
|
+
autoListen = true,
|
|
183
|
+
enableComposer = true,
|
|
184
|
+
commandPrefix = "/",
|
|
185
|
+
stopOnSignals = false,
|
|
186
|
+
maxEventListeners = 64,
|
|
187
|
+
...fcaOptions
|
|
188
|
+
} = options;
|
|
189
|
+
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
login(credentials, fcaOptions, (err, api) => {
|
|
192
|
+
if (err) return reject(err);
|
|
193
|
+
const bot = new MessengerBot(api, { enableComposer, commandPrefix, stopOnSignals, maxEventListeners });
|
|
194
|
+
if (autoListen) {
|
|
195
|
+
bot.launch({ stopOnSignals }).then(() => resolve(bot)).catch(reject);
|
|
196
|
+
} else {
|
|
197
|
+
if (stopOnSignals) bot._attachStopSignals();
|
|
198
|
+
resolve(bot);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createMessengerBot(credentials, options) {
|
|
206
|
+
return MessengerBot.connect(credentials, options);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { MessengerBot, createMessengerBot };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
class MessengerContext {
|
|
4
|
+
constructor(bot, event) {
|
|
5
|
+
this.bot = bot;
|
|
6
|
+
this.event = event;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get threadID() { return this.event.threadID; }
|
|
10
|
+
get senderID() { return this.event.senderID; }
|
|
11
|
+
get messageID() { return this.event.messageID; }
|
|
12
|
+
get body() { return this.event.body; }
|
|
13
|
+
get message() { return this.event; }
|
|
14
|
+
|
|
15
|
+
get text() {
|
|
16
|
+
return (this.event.body ?? "").trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
reply(payload, callback) {
|
|
20
|
+
const tid = this.event.threadID;
|
|
21
|
+
if (tid == null) throw new Error("MessengerContext.reply: threadID is missing");
|
|
22
|
+
return this.bot.api.sendMessage(payload, tid, callback);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async replyAsync(payload) {
|
|
26
|
+
const r = this.reply(payload);
|
|
27
|
+
if (r && typeof r.then === "function") return r;
|
|
28
|
+
return Promise.resolve(r);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { MessengerContext };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function bindOptionalMethod(api, key) {
|
|
4
|
+
const candidate = api[key];
|
|
5
|
+
return typeof candidate === "function" ? candidate.bind(api) : undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function readOptionalMember(api, key) {
|
|
9
|
+
return typeof api[key] === "undefined" ? undefined : api[key];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function bindLiveMethod(api, key) {
|
|
13
|
+
return (...args) => {
|
|
14
|
+
const candidate = api[key];
|
|
15
|
+
if (typeof candidate !== "function") {
|
|
16
|
+
throw new Error(`API method "${key}" is not available`);
|
|
17
|
+
}
|
|
18
|
+
return candidate.apply(api, args);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function compactNamespace(namespace) {
|
|
23
|
+
return Object.fromEntries(
|
|
24
|
+
Object.entries(namespace).filter(([, value]) => value !== undefined)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readNamespace(api, key) {
|
|
29
|
+
const value = api[key];
|
|
30
|
+
return value && typeof value === "object" ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createFallbackNamespaces(api) {
|
|
34
|
+
return {
|
|
35
|
+
messages: compactNamespace({
|
|
36
|
+
send: bindOptionalMethod(api, "sendMessage"),
|
|
37
|
+
edit: bindOptionalMethod(api, "editMessage"),
|
|
38
|
+
delete: bindOptionalMethod(api, "deleteMessage"),
|
|
39
|
+
unsend: bindOptionalMethod(api, "unsendMessage"),
|
|
40
|
+
get: bindOptionalMethod(api, "getMessage"),
|
|
41
|
+
markRead: bindOptionalMethod(api, "markAsRead"),
|
|
42
|
+
markReadAll: bindOptionalMethod(api, "markAsReadAll"),
|
|
43
|
+
markSeen: bindOptionalMethod(api, "markAsSeen"),
|
|
44
|
+
markDelivered: bindOptionalMethod(api, "markAsDelivered"),
|
|
45
|
+
typing: bindOptionalMethod(api, "sendTypingIndicator"),
|
|
46
|
+
react: bindOptionalMethod(api, "setMessageReaction"),
|
|
47
|
+
shareContact: bindOptionalMethod(api, "shareContact"),
|
|
48
|
+
getEmojiUrl: bindOptionalMethod(api, "getEmojiUrl"),
|
|
49
|
+
resolvePhotoUrl: bindOptionalMethod(api, "resolvePhotoUrl"),
|
|
50
|
+
uploadAttachment: bindOptionalMethod(api, "uploadAttachment"),
|
|
51
|
+
forwardAttachment: bindOptionalMethod(api, "forwardAttachment")
|
|
52
|
+
}),
|
|
53
|
+
threads: compactNamespace({
|
|
54
|
+
createGroup: bindOptionalMethod(api, "createNewGroup"),
|
|
55
|
+
getInfo: bindOptionalMethod(api, "getThreadInfo"),
|
|
56
|
+
getList: bindOptionalMethod(api, "getThreadList"),
|
|
57
|
+
getHistory: bindOptionalMethod(api, "getThreadHistory"),
|
|
58
|
+
getPictures: bindOptionalMethod(api, "getThreadPictures"),
|
|
59
|
+
addUsers: bindOptionalMethod(api, "addUserToGroup"),
|
|
60
|
+
archive: bindOptionalMethod(api, "changeArchivedStatus"),
|
|
61
|
+
removeUser: bindOptionalMethod(api, "removeUserFromGroup"),
|
|
62
|
+
setAdmin: bindOptionalMethod(api, "changeAdminStatus"),
|
|
63
|
+
setImage: bindOptionalMethod(api, "changeGroupImage"),
|
|
64
|
+
setColor: bindOptionalMethod(api, "changeThreadColor"),
|
|
65
|
+
setEmoji: bindOptionalMethod(api, "changeThreadEmoji"),
|
|
66
|
+
setNickname: bindOptionalMethod(api, "changeNickname"),
|
|
67
|
+
createPoll: bindOptionalMethod(api, "createPoll"),
|
|
68
|
+
createThemeAI: bindOptionalMethod(api, "createThemeAI"),
|
|
69
|
+
getThemePictures: bindOptionalMethod(api, "getThemePictures"),
|
|
70
|
+
delete: bindOptionalMethod(api, "deleteThread"),
|
|
71
|
+
colors: readOptionalMember(api, "threadColors"),
|
|
72
|
+
handleMessageRequest: bindOptionalMethod(api, "handleMessageRequest"),
|
|
73
|
+
mute: bindOptionalMethod(api, "muteThread"),
|
|
74
|
+
setTitle: bindOptionalMethod(api, "setTitle"),
|
|
75
|
+
search: bindOptionalMethod(api, "searchForThread")
|
|
76
|
+
}),
|
|
77
|
+
users: compactNamespace({
|
|
78
|
+
getID: bindOptionalMethod(api, "getUserID"),
|
|
79
|
+
getInfo: bindOptionalMethod(api, "getUserInfo"),
|
|
80
|
+
getInfoV2: bindOptionalMethod(api, "getUserInfoV2"),
|
|
81
|
+
getFriends: bindOptionalMethod(api, "getFriendsList")
|
|
82
|
+
}),
|
|
83
|
+
account: compactNamespace({
|
|
84
|
+
addExternalModule: bindOptionalMethod(api, "addExternalModule"),
|
|
85
|
+
changeAvatar: bindOptionalMethod(api, "changeAvatar"),
|
|
86
|
+
changeBio: bindOptionalMethod(api, "changeBio"),
|
|
87
|
+
enableAutoSaveAppState: bindOptionalMethod(api, "enableAutoSaveAppState"),
|
|
88
|
+
getCurrentUserID: bindOptionalMethod(api, "getCurrentUserID"),
|
|
89
|
+
handleFriendRequest: bindOptionalMethod(api, "handleFriendRequest"),
|
|
90
|
+
logout: bindOptionalMethod(api, "logout"),
|
|
91
|
+
refreshDtsg: bindOptionalMethod(api, "refreshFb_dtsg"),
|
|
92
|
+
changeBlockedStatus: bindOptionalMethod(api, "changeBlockedStatus"),
|
|
93
|
+
setOptions: bindOptionalMethod(api, "setOptions"),
|
|
94
|
+
setPostReaction: bindOptionalMethod(api, "setPostReaction"),
|
|
95
|
+
unfriend: bindOptionalMethod(api, "unfriend"),
|
|
96
|
+
getAppState: bindOptionalMethod(api, "getAppState"),
|
|
97
|
+
getCookies: bindOptionalMethod(api, "getCookies")
|
|
98
|
+
}),
|
|
99
|
+
realtime: compactNamespace({
|
|
100
|
+
listen: bindLiveMethod(api, "listenMqtt"),
|
|
101
|
+
stop: bindLiveMethod(api, "stopListening"),
|
|
102
|
+
stopAsync: bindLiveMethod(api, "stopListeningAsync"),
|
|
103
|
+
useMiddleware: bindLiveMethod(api, "useMiddleware"),
|
|
104
|
+
removeMiddleware: bindLiveMethod(api, "removeMiddleware"),
|
|
105
|
+
clearMiddleware: bindLiveMethod(api, "clearMiddleware"),
|
|
106
|
+
listMiddleware: bindLiveMethod(api, "listMiddleware"),
|
|
107
|
+
setMiddlewareEnabled: bindLiveMethod(api, "setMiddlewareEnabled")
|
|
108
|
+
}),
|
|
109
|
+
http: compactNamespace({
|
|
110
|
+
get: bindOptionalMethod(api, "httpGet"),
|
|
111
|
+
post: bindOptionalMethod(api, "httpPost"),
|
|
112
|
+
postFormData: bindOptionalMethod(api, "postFormData")
|
|
113
|
+
}),
|
|
114
|
+
scheduler: compactNamespace(readOptionalMember(api, "scheduler") || {})
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function mergeNamespace(fallback, existing) {
|
|
119
|
+
return compactNamespace({ ...fallback, ...(existing || {}) });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function createFcaClient(api) {
|
|
123
|
+
const fallback = createFallbackNamespaces(api);
|
|
124
|
+
return {
|
|
125
|
+
raw: api,
|
|
126
|
+
messages: mergeNamespace(fallback.messages, readNamespace(api, "messages")),
|
|
127
|
+
threads: mergeNamespace(fallback.threads, readNamespace(api, "threads")),
|
|
128
|
+
users: mergeNamespace(fallback.users, readNamespace(api, "users")),
|
|
129
|
+
account: mergeNamespace(fallback.account, readNamespace(api, "account")),
|
|
130
|
+
realtime: mergeNamespace(fallback.realtime, readNamespace(api, "realtime")),
|
|
131
|
+
http: mergeNamespace(fallback.http, readNamespace(api, "http")),
|
|
132
|
+
scheduler: mergeNamespace(fallback.scheduler, readNamespace(api, "scheduler"))
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { createFcaClient };
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function parseRowData(raw) {
|
|
4
|
+
if (raw == null) return null;
|
|
5
|
+
if (typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
6
|
+
if (typeof raw === "string") {
|
|
7
|
+
try {
|
|
8
|
+
const o = JSON.parse(raw);
|
|
9
|
+
return typeof o === "object" && o !== null ? o : null;
|
|
10
|
+
} catch { return null; }
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeParticipantId(v) {
|
|
16
|
+
if (v == null) return "";
|
|
17
|
+
if (typeof v === "object" && v && "id" in v) return String(v.id);
|
|
18
|
+
return String(v);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeAddedParticipants(raw) {
|
|
22
|
+
if (!raw || !Array.isArray(raw)) return [];
|
|
23
|
+
return raw.map(normalizeParticipantId).filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mergedEventData(ev) {
|
|
27
|
+
const a = ev.logMessageData || {};
|
|
28
|
+
const b = ev.eventData || {};
|
|
29
|
+
return { ...b, ...a };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function invalidateThreadCacheRow(Thread, threadID) {
|
|
33
|
+
await Thread.update({ data: null }, { where: { threadID } });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toThreadParticipant(entry, idFallback) {
|
|
37
|
+
const id = String(entry?.id ?? idFallback ?? "");
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
name: entry?.name ?? null,
|
|
41
|
+
firstName: entry?.firstName ?? null,
|
|
42
|
+
vanity: entry?.vanity ?? null,
|
|
43
|
+
url: entry?.profileUrl ?? entry?.url ?? null,
|
|
44
|
+
thumbSrc: entry?.thumbSrc ?? null,
|
|
45
|
+
profileUrl: entry?.profileUrl ?? null,
|
|
46
|
+
gender: entry?.gender ?? null,
|
|
47
|
+
type: entry?.type ?? null,
|
|
48
|
+
isFriend: Boolean(entry?.isFriend),
|
|
49
|
+
isBirthday: Boolean(entry?.isBirthday)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function minimalParticipant(id) {
|
|
54
|
+
return { id, name: null, firstName: null, vanity: null, url: null, thumbSrc: null, profileUrl: null, gender: null, type: null, isFriend: false, isBirthday: false };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function fetchAndMergeParticipants(api, info, idsToFetch, log) {
|
|
58
|
+
const uniq = [...new Set(idsToFetch.map(String).filter(Boolean))];
|
|
59
|
+
if (!uniq.length || !api || typeof api.getUserInfo !== "function") return false;
|
|
60
|
+
try {
|
|
61
|
+
const map = await api.getUserInfo(uniq);
|
|
62
|
+
if (!map || typeof map !== "object") return false;
|
|
63
|
+
const prev = new Map();
|
|
64
|
+
for (const u of Array.isArray(info.userInfo) ? info.userInfo : []) {
|
|
65
|
+
if (u?.id != null) prev.set(String(u.id), u);
|
|
66
|
+
}
|
|
67
|
+
for (const id of uniq) {
|
|
68
|
+
const entry = map[id];
|
|
69
|
+
if (entry && typeof entry === "object") prev.set(id, toThreadParticipant(entry, id));
|
|
70
|
+
}
|
|
71
|
+
const pids = (info.participantIDs || []).map(String);
|
|
72
|
+
info.userInfo = pids.map(pid => {
|
|
73
|
+
const fresh = map[pid];
|
|
74
|
+
if (fresh && typeof fresh === "object") return toThreadParticipant(fresh, pid);
|
|
75
|
+
const old = prev.get(pid);
|
|
76
|
+
if (old) return old;
|
|
77
|
+
return minimalParticipant(pid);
|
|
78
|
+
});
|
|
79
|
+
return true;
|
|
80
|
+
} catch (e) {
|
|
81
|
+
log?.(`thread-info-realtime-sync getUserInfo: ${e?.message || e}`, "warn");
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function syncParticipantListFromEvent(ev, info) {
|
|
87
|
+
const p = ev.participantIDs;
|
|
88
|
+
if (!Array.isArray(p) || !p.length) return false;
|
|
89
|
+
const next = p.map(String).filter(Boolean);
|
|
90
|
+
if (!next.length) return false;
|
|
91
|
+
const prev = JSON.stringify((info.participantIDs || []).map(String).sort());
|
|
92
|
+
if (prev === JSON.stringify([...next].sort())) return false;
|
|
93
|
+
info.participantIDs = next;
|
|
94
|
+
if (Array.isArray(info.userInfo)) {
|
|
95
|
+
const set = new Set(next);
|
|
96
|
+
info.userInfo = info.userInfo.filter(u => set.has(String(u?.id)));
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function tryPatchThemeFromUntyped(data, info) {
|
|
102
|
+
if (!data || typeof data !== "object") return false;
|
|
103
|
+
let mutated = false;
|
|
104
|
+
const stack = [{ obj: data, depth: 0 }];
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
while (stack.length) {
|
|
107
|
+
const { obj, depth } = stack.pop();
|
|
108
|
+
if (!obj || seen.has(obj)) continue;
|
|
109
|
+
seen.add(obj);
|
|
110
|
+
if (depth > 10) continue;
|
|
111
|
+
for (const k of Object.keys(obj)) {
|
|
112
|
+
const v = obj[k];
|
|
113
|
+
const kl = k.toLowerCase();
|
|
114
|
+
if (typeof v === "string") {
|
|
115
|
+
if (kl.includes("emoji") && !kl.includes("unicode")) { info.emoji = v; mutated = true; }
|
|
116
|
+
if (kl.includes("bubble_color") || kl.includes("theme_color") || kl.includes("outgoing_bubble") || kl === "gradient_color" || kl === "accessory_color") {
|
|
117
|
+
const hex = v.replace(/^#/, "").trim();
|
|
118
|
+
if (/^[0-9a-f]{6,12}$/i.test(hex)) { info.color = hex.length >= 8 ? hex.slice(0, 8) : hex; mutated = true; }
|
|
119
|
+
}
|
|
120
|
+
} else if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
121
|
+
stack.push({ obj: v, depth: depth + 1 });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return mutated;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function tryPatchAdminsFromUntyped(data, info) {
|
|
129
|
+
if (!data || typeof data !== "object") return false;
|
|
130
|
+
const tryArray = (arr) => {
|
|
131
|
+
if (!Array.isArray(arr) || !arr.length) return false;
|
|
132
|
+
const ids = arr.map(normalizeParticipantId).filter(Boolean);
|
|
133
|
+
if (!ids.length) return false;
|
|
134
|
+
info.adminIDs = ids;
|
|
135
|
+
return true;
|
|
136
|
+
};
|
|
137
|
+
const o = data;
|
|
138
|
+
for (const key of Object.keys(o)) {
|
|
139
|
+
if (key.toLowerCase().includes("admin") && Array.isArray(o[key])) {
|
|
140
|
+
if (tryArray(o[key])) return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let mutated = false;
|
|
144
|
+
const walk = (node, depth) => {
|
|
145
|
+
if (!node || typeof node !== "object" || depth > 8) return;
|
|
146
|
+
if (Array.isArray(node)) {
|
|
147
|
+
if (node.length && node.every(x => typeof x === "string" || typeof x === "number")) {
|
|
148
|
+
if (tryArray(node)) mutated = true;
|
|
149
|
+
}
|
|
150
|
+
node.forEach(x => walk(x, depth + 1));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (const k of Object.keys(node)) {
|
|
154
|
+
const kl = k.toLowerCase();
|
|
155
|
+
const v = node[k];
|
|
156
|
+
if (kl.includes("thread_admin") || kl === "admin_ids" || kl === "admins") {
|
|
157
|
+
if (tryArray(v)) mutated = true;
|
|
158
|
+
}
|
|
159
|
+
if (v && typeof v === "object") walk(v, depth + 1);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
walk(data, 0);
|
|
163
|
+
return mutated;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function tryPatchNickname(data, info) {
|
|
167
|
+
if (!data || typeof data !== "object") return false;
|
|
168
|
+
const o = data;
|
|
169
|
+
const pid = o.participant_id ?? o.participantId ?? o.user_id ?? o.actor_id ?? o.target_id;
|
|
170
|
+
const nn = o.nickname ?? o.new_nickname ?? o.name;
|
|
171
|
+
if (pid == null || nn == null || typeof nn !== "string" || !nn.trim()) return false;
|
|
172
|
+
if (!info.nicknames || typeof info.nicknames !== "object") info.nicknames = {};
|
|
173
|
+
info.nicknames[String(pid)] = nn.trim();
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function tryPatchApprovalMode(data, info) {
|
|
178
|
+
if (!data || typeof data !== "object") return false;
|
|
179
|
+
for (const k of ["approval_mode", "approvalMode", "is_approval_mode_enabled"]) {
|
|
180
|
+
if (typeof data[k] === "boolean") { info.approvalMode = data[k]; return true; }
|
|
181
|
+
if (data[k] === "1" || data[k] === "0" || data[k] === 1 || data[k] === 0) { info.approvalMode = Boolean(Number(data[k])); return true; }
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function tryPatchInviteLink(data, info) {
|
|
187
|
+
if (!data || typeof data !== "object") return false;
|
|
188
|
+
const jm = data.joinable_mode ?? data.joinableMode;
|
|
189
|
+
if (!jm || typeof jm !== "object") return false;
|
|
190
|
+
if (!info.inviteLink || typeof info.inviteLink !== "object") info.inviteLink = {};
|
|
191
|
+
if (jm.mode !== undefined) info.inviteLink.enable = jm.mode === 1 || jm.mode === true;
|
|
192
|
+
if (typeof jm.link === "string") info.inviteLink.link = jm.link;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function applyThreadInfoRealtimeEvent(Thread, threadID, ev, log, api) {
|
|
197
|
+
if (!Thread || !threadID || !ev || ev.type !== "event") return;
|
|
198
|
+
const logType = ev.logMessageType != null ? String(ev.logMessageType) : "";
|
|
199
|
+
const data = mergedEventData(ev);
|
|
200
|
+
try {
|
|
201
|
+
const row = await Thread.findOne({ where: { threadID } });
|
|
202
|
+
const rawData = row && typeof row.get === "function" ? row.get("data") : row?.data;
|
|
203
|
+
let info = parseRowData(rawData);
|
|
204
|
+
if (!row || !info) { await invalidateThreadCacheRow(Thread, threadID); return; }
|
|
205
|
+
let mutated = false;
|
|
206
|
+
if (syncParticipantListFromEvent(ev, info)) mutated = true;
|
|
207
|
+
switch (logType) {
|
|
208
|
+
case "log:thread-name": {
|
|
209
|
+
const name = data.name != null ? String(data.name) : "";
|
|
210
|
+
if (name) { info.threadName = name; info.name = name; mutated = true; }
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "log:unsubscribe": {
|
|
214
|
+
const left = normalizeParticipantId(data.leftParticipantFbId);
|
|
215
|
+
if (left) {
|
|
216
|
+
if (Array.isArray(info.participantIDs)) { info.participantIDs = info.participantIDs.map(String).filter(id => id !== left); mutated = true; }
|
|
217
|
+
if (Array.isArray(info.userInfo)) { info.userInfo = info.userInfo.filter(u => String(u?.id) !== left); mutated = true; }
|
|
218
|
+
}
|
|
219
|
+
if (mutated && Array.isArray(info.participantIDs) && info.participantIDs.length) {
|
|
220
|
+
if (await fetchAndMergeParticipants(api, info, info.participantIDs.map(String), log)) mutated = true;
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "log:subscribe": {
|
|
225
|
+
const added = normalizeAddedParticipants(data.addedParticipants);
|
|
226
|
+
if (added.length) {
|
|
227
|
+
if (!Array.isArray(info.participantIDs)) info.participantIDs = [];
|
|
228
|
+
const set = new Set(info.participantIDs.map(String));
|
|
229
|
+
for (const id of added) { if (!set.has(id)) { info.participantIDs.push(id); set.add(id); mutated = true; } }
|
|
230
|
+
if (await fetchAndMergeParticipants(api, info, added, log)) mutated = true;
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "log:thread-icon":
|
|
235
|
+
case "log:thread-image": {
|
|
236
|
+
const img = data.image;
|
|
237
|
+
const url = img && (img.url || img.uri);
|
|
238
|
+
if (url) { info.imageSrc = String(url); mutated = true; }
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "log:thread-color": if (tryPatchThemeFromUntyped(data, info)) mutated = true; break;
|
|
242
|
+
case "log:thread-admins": if (tryPatchAdminsFromUntyped(data, info)) mutated = true; break;
|
|
243
|
+
case "log:user-nickname": if (tryPatchNickname(data, info)) mutated = true; break;
|
|
244
|
+
case "log:thread-approval-mode": if (tryPatchApprovalMode(data, info)) mutated = true; break;
|
|
245
|
+
case "log:link-status": if (tryPatchInviteLink(data, info)) mutated = true; break;
|
|
246
|
+
case "log:approval-queue": {
|
|
247
|
+
const q = data.approvalQueue;
|
|
248
|
+
if (q && typeof q === "object") { info.approvalQueue = [...(Array.isArray(info.approvalQueue) ? info.approvalQueue : []), q]; mutated = true; }
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "log:magic-words":
|
|
252
|
+
case "log:thread-poll":
|
|
253
|
+
case "log:thread-pinned":
|
|
254
|
+
case "log:unpin-message":
|
|
255
|
+
case "log:thread-call":
|
|
256
|
+
case "log:user-location":
|
|
257
|
+
break;
|
|
258
|
+
default:
|
|
259
|
+
if (tryPatchThemeFromUntyped(data, info)) mutated = true;
|
|
260
|
+
if (tryPatchAdminsFromUntyped(data, info)) mutated = true;
|
|
261
|
+
if (tryPatchNickname(data, info)) mutated = true;
|
|
262
|
+
if (tryPatchApprovalMode(data, info)) mutated = true;
|
|
263
|
+
if (tryPatchInviteLink(data, info)) mutated = true;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
if (mutated && typeof row.update === "function") await row.update({ data: info });
|
|
267
|
+
else await invalidateThreadCacheRow(Thread, threadID);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
log?.(`thread-info-realtime-sync: ${e?.message || e}`, "warn");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function attachThreadInfoRealtimeSync(ctx, models, logger, api) {
|
|
274
|
+
const Thread = models?.Thread;
|
|
275
|
+
if (!Thread || typeof Thread.findOne !== "function") return false;
|
|
276
|
+
const resolveApi = () => api || ctx.api;
|
|
277
|
+
ctx._syncThreadInfoFromEvent = (ev) => {
|
|
278
|
+
if (!ev || ev.type !== "event" || ev.threadID == null) return;
|
|
279
|
+
applyThreadInfoRealtimeEvent(Thread, String(ev.threadID), ev, logger, resolveApi());
|
|
280
|
+
};
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
module.exports = { attachThreadInfoRealtimeSync, applyThreadInfoRealtimeEvent };
|