@lazyneoaz/metachat 1.0.9 → 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/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/getThreadInfo.js +1 -1
- package/src/apis/markAsSeen.js +7 -9
- package/src/apis/nickname.js +6 -3
- package/src/apis/sendEffect.js +4 -4
- 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/rateLimiter.js +1 -1
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function compactNamespace(ns) {
|
|
4
|
+
if (!ns || typeof ns !== "object") return ns;
|
|
5
|
+
const out = {};
|
|
6
|
+
for (const [k, v] of Object.entries(ns)) {
|
|
7
|
+
if (typeof v === "function") out[k] = v;
|
|
8
|
+
}
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function buildFallbackMessages(api) {
|
|
13
|
+
return {
|
|
14
|
+
send: api.sendMessage?.bind(api),
|
|
15
|
+
edit: api.editMessage?.bind(api),
|
|
16
|
+
unsend: api.unsendMessage?.bind(api),
|
|
17
|
+
delete: api.deleteMessage?.bind(api),
|
|
18
|
+
setReaction: api.setMessageReaction?.bind(api),
|
|
19
|
+
sendTyping: api.sendTypingIndicator?.bind(api),
|
|
20
|
+
markRead: api.markAsRead?.bind(api),
|
|
21
|
+
markDelivered: api.markAsDelivered?.bind(api),
|
|
22
|
+
markSeen: api.markAsSeen?.bind(api),
|
|
23
|
+
markReadAll: api.markAsReadAll?.bind(api),
|
|
24
|
+
upload: api.uploadAttachment?.bind(api),
|
|
25
|
+
forward: api.forwardAttachment?.bind(api),
|
|
26
|
+
forwardMessage: api.forwardMessage?.bind(api),
|
|
27
|
+
shareContact: api.shareContact?.bind(api),
|
|
28
|
+
changeColor: api.changeThreadColor?.bind(api),
|
|
29
|
+
changeEmoji: api.changeThreadEmoji?.bind(api),
|
|
30
|
+
getMessage: api.getMessage?.bind(api),
|
|
31
|
+
getEmojiUrl: api.getEmojiUrl?.bind(api),
|
|
32
|
+
resolvePhotoUrl: api.resolvePhotoUrl?.bind(api),
|
|
33
|
+
getThreadColors: api.getThreadColors?.bind(api),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildFallbackThreads(api) {
|
|
38
|
+
return {
|
|
39
|
+
getInfo: api.getThreadInfo?.bind(api),
|
|
40
|
+
getList: api.getThreadList?.bind(api),
|
|
41
|
+
getHistory: api.getThreadHistory?.bind(api),
|
|
42
|
+
getPictures: api.getThreadPictures?.bind(api),
|
|
43
|
+
getThemePictures: api.getThemePictures?.bind(api),
|
|
44
|
+
search: api.searchForThread?.bind(api),
|
|
45
|
+
createGroup: api.createNewGroup?.bind(api),
|
|
46
|
+
addUser: api.addUserToGroup?.bind(api),
|
|
47
|
+
removeUser: api.removeUserFromGroup?.bind(api),
|
|
48
|
+
changeAdmin: api.changeAdminStatus?.bind(api),
|
|
49
|
+
changeImage: api.changeGroupImage?.bind(api),
|
|
50
|
+
changeNickname: (api.setNickname || api.nickname || api.changeNickname)?.bind(api),
|
|
51
|
+
setTitle: api.setTitle?.bind(api),
|
|
52
|
+
createPoll: api.createPoll?.bind(api),
|
|
53
|
+
createThemeAI: api.createAITheme?.bind(api),
|
|
54
|
+
delete: api.deleteThread?.bind(api),
|
|
55
|
+
archive: api.changeArchivedStatus?.bind(api),
|
|
56
|
+
mute: api.muteThread?.bind(api),
|
|
57
|
+
handleRequest: api.handleMessageRequest?.bind(api),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildFallbackUsers(api) {
|
|
62
|
+
return {
|
|
63
|
+
getInfo: api.getUserInfo?.bind(api),
|
|
64
|
+
getInfoV2: api.getUserInfoV2?.bind(api),
|
|
65
|
+
getID: api.getUserID?.bind(api),
|
|
66
|
+
getFriendsList: api.getFriendsList?.bind(api),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildFallbackAccount(api) {
|
|
71
|
+
return {
|
|
72
|
+
getCurrentUserID: api.getCurrentUserID?.bind(api),
|
|
73
|
+
changeAvatar: api.changeAvatar?.bind(api),
|
|
74
|
+
changeBio: api.changeBio?.bind(api),
|
|
75
|
+
changeBlocked: api.changeBlockedStatus?.bind(api),
|
|
76
|
+
handleFriendReq: api.handleFriendRequest?.bind(api),
|
|
77
|
+
unfriend: api.unfriend?.bind(api),
|
|
78
|
+
setPostReaction: api.setPostReaction?.bind(api),
|
|
79
|
+
refreshDtsg: api.refreshFb_dtsg?.bind(api),
|
|
80
|
+
logout: api.logout?.bind(api),
|
|
81
|
+
addModule: api.addExternalModule?.bind(api),
|
|
82
|
+
enableAutoSave: api.enableAutoSaveAppState?.bind(api),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildFallbackRealtime(api) {
|
|
87
|
+
return {
|
|
88
|
+
listen: api.listenMqtt?.bind(api),
|
|
89
|
+
stopListening: api.stopListening?.bind(api),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildFallbackHttp(api) {
|
|
94
|
+
return {
|
|
95
|
+
get: api.httpGet?.bind(api),
|
|
96
|
+
post: api.httpPost?.bind(api),
|
|
97
|
+
postFormData: api.httpPostFormData?.bind(api),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildFallbackScheduler(api) {
|
|
102
|
+
return {
|
|
103
|
+
schedule: api.scheduler?.schedule?.bind(api.scheduler),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createFcaClient(api) {
|
|
108
|
+
const messages = compactNamespace(buildFallbackMessages(api));
|
|
109
|
+
const threads = compactNamespace(buildFallbackThreads(api));
|
|
110
|
+
const users = compactNamespace(buildFallbackUsers(api));
|
|
111
|
+
const account = compactNamespace(buildFallbackAccount(api));
|
|
112
|
+
const realtime = compactNamespace(buildFallbackRealtime(api));
|
|
113
|
+
const http = compactNamespace(buildFallbackHttp(api));
|
|
114
|
+
const scheduler = compactNamespace(buildFallbackScheduler(api));
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
raw: api,
|
|
118
|
+
messages,
|
|
119
|
+
threads,
|
|
120
|
+
users,
|
|
121
|
+
account,
|
|
122
|
+
realtime,
|
|
123
|
+
http,
|
|
124
|
+
scheduler,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function attachClientFacade(api) {
|
|
129
|
+
const client = createFcaClient(api);
|
|
130
|
+
api.client = client;
|
|
131
|
+
if (!api.messages) api.messages = client.messages;
|
|
132
|
+
if (!api.threads) api.threads = client.threads;
|
|
133
|
+
if (!api.users) api.users = client.users;
|
|
134
|
+
if (!api.account) api.account = client.account;
|
|
135
|
+
if (!api.realtime) api.realtime = client.realtime;
|
|
136
|
+
if (!api.http) api.http = client.http;
|
|
137
|
+
if (!api.scheduler) api.scheduler = client.scheduler;
|
|
138
|
+
return client;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function createMessagesDomain(api) {
|
|
142
|
+
return compactNamespace(buildFallbackMessages(api));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createThreadsDomain(api) {
|
|
146
|
+
return compactNamespace(buildFallbackThreads(api));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function createUsersDomain(api) {
|
|
150
|
+
return compactNamespace(buildFallbackUsers(api));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createAccountDomain(api) {
|
|
154
|
+
return compactNamespace(buildFallbackAccount(api));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function createRealtimeDomain(api) {
|
|
158
|
+
return compactNamespace(buildFallbackRealtime(api));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createHttpDomain(api) {
|
|
162
|
+
return compactNamespace(buildFallbackHttp(api));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function createSchedulerDomain(api) {
|
|
166
|
+
return compactNamespace(buildFallbackScheduler(api));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
createFcaClient,
|
|
171
|
+
attachClientFacade,
|
|
172
|
+
createMessagesDomain,
|
|
173
|
+
createThreadsDomain,
|
|
174
|
+
createUsersDomain,
|
|
175
|
+
createAccountDomain,
|
|
176
|
+
createRealtimeDomain,
|
|
177
|
+
createHttpDomain,
|
|
178
|
+
createSchedulerDomain,
|
|
179
|
+
};
|
package/src/app/state.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const EventEmitter = require("events");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a blank FcaContext object (API compat with @dongdev/fca-unofficial).
|
|
7
|
+
* @returns {object}
|
|
8
|
+
*/
|
|
9
|
+
function createDefaultContext() {
|
|
10
|
+
return {
|
|
11
|
+
userID: "",
|
|
12
|
+
fbid: "",
|
|
13
|
+
cookieString: "",
|
|
14
|
+
jar: null,
|
|
15
|
+
clientID: undefined,
|
|
16
|
+
appID: undefined,
|
|
17
|
+
globalOptions: {},
|
|
18
|
+
options: {},
|
|
19
|
+
loggedIn: false,
|
|
20
|
+
access_token: "NONE",
|
|
21
|
+
clientMutationId: 0,
|
|
22
|
+
mqttClient: undefined,
|
|
23
|
+
lastSeqId: null,
|
|
24
|
+
syncToken: undefined,
|
|
25
|
+
mqttEndpoint: undefined,
|
|
26
|
+
wsReqNumber: 0,
|
|
27
|
+
wsTaskNumber: 0,
|
|
28
|
+
reqCallbacks: {},
|
|
29
|
+
callback_Task: {},
|
|
30
|
+
region: "PRN",
|
|
31
|
+
firstListen: true,
|
|
32
|
+
_emitter: new EventEmitter(),
|
|
33
|
+
fb_dtsg: undefined,
|
|
34
|
+
jazoest: undefined,
|
|
35
|
+
lsd: undefined,
|
|
36
|
+
ttstamp: undefined,
|
|
37
|
+
api: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates an initialized FCA state object from an existing ctx.
|
|
43
|
+
* @param {object} [ctx] - Existing context to extend.
|
|
44
|
+
* @returns {object}
|
|
45
|
+
*/
|
|
46
|
+
function createFcaState(ctx = {}) {
|
|
47
|
+
return Object.assign(createDefaultContext(), ctx);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Builds a minimal api facade from an existing ctx. Allows attaching methods.
|
|
52
|
+
* @param {object} ctx - FcaContext object.
|
|
53
|
+
* @returns {object}
|
|
54
|
+
*/
|
|
55
|
+
function createApiFacade(ctx = {}) {
|
|
56
|
+
const facade = {
|
|
57
|
+
ctx,
|
|
58
|
+
getCurrentUserID() {
|
|
59
|
+
return ctx.userID || ctx.fbid || "";
|
|
60
|
+
},
|
|
61
|
+
getAppState() {
|
|
62
|
+
if (!ctx.jar) return [];
|
|
63
|
+
try {
|
|
64
|
+
const appState = [];
|
|
65
|
+
const cookies = ctx.jar.getCookiesSync("https://www.facebook.com");
|
|
66
|
+
for (const c of cookies) {
|
|
67
|
+
appState.push({
|
|
68
|
+
key: c.key,
|
|
69
|
+
value: c.value,
|
|
70
|
+
domain: c.domain,
|
|
71
|
+
path: c.path,
|
|
72
|
+
hostOnly: c.hostOnly,
|
|
73
|
+
creation: c.creation,
|
|
74
|
+
lastAccessed: c.lastAccessed,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return appState;
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
setOptions(opts) {
|
|
83
|
+
Object.assign(ctx.globalOptions || {}, opts);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
return facade;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a minimal request helper (for API compat). Real HTTP is done via axios.js.
|
|
91
|
+
* @param {object} ctx - FcaContext
|
|
92
|
+
* @returns {object}
|
|
93
|
+
*/
|
|
94
|
+
function createRequestHelper(ctx = {}) {
|
|
95
|
+
return {
|
|
96
|
+
get(url, qs, options) {
|
|
97
|
+
const network = require("../utils/axios");
|
|
98
|
+
return network.get(url, ctx.jar, qs, options || ctx.globalOptions, ctx);
|
|
99
|
+
},
|
|
100
|
+
post(url, form, options) {
|
|
101
|
+
const network = require("../utils/axios");
|
|
102
|
+
return network.post(url, ctx.jar, form, options || ctx.globalOptions, ctx);
|
|
103
|
+
},
|
|
104
|
+
postFormData(url, form, options) {
|
|
105
|
+
const network = require("../utils/axios");
|
|
106
|
+
return network.postFormData(url, ctx.jar, form, options || ctx.globalOptions, ctx);
|
|
107
|
+
},
|
|
108
|
+
ctx,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
createDefaultContext,
|
|
114
|
+
createFcaState,
|
|
115
|
+
createApiFacade,
|
|
116
|
+
createRequestHelper,
|
|
117
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const INVALIDATE_TYPES = new Set([
|
|
4
|
+
"log:thread-name",
|
|
5
|
+
"log:thread-image",
|
|
6
|
+
"log:user-nicknames",
|
|
7
|
+
"log:thread-color",
|
|
8
|
+
"log:thread-icon",
|
|
9
|
+
"log:thread-admins",
|
|
10
|
+
"log:thread-approval-mode",
|
|
11
|
+
"log:thread-lock-join-link",
|
|
12
|
+
"log:group-poll",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const PARTICIPANT_TYPES = new Set([
|
|
16
|
+
"log:subscribe",
|
|
17
|
+
"log:unsubscribe",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Hooks into MQTT events to keep the thread info cache in sync.
|
|
22
|
+
* When a thread event comes in, invalidates or partially updates the cache.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} ctx - FcaContext or ctx object with _emitter.
|
|
25
|
+
* @param {object} models - Sequelize models (optional). Expects models.Thread.
|
|
26
|
+
* @param {function} logger - Logger function (optional).
|
|
27
|
+
* @param {object} api - The API object (used for getUserInfo on participant changes).
|
|
28
|
+
*/
|
|
29
|
+
function attachThreadInfoRealtimeSync(ctx, models, logger, api) {
|
|
30
|
+
const log = (typeof logger === "function") ? logger : () => {};
|
|
31
|
+
const emitter = ctx && ctx._emitter;
|
|
32
|
+
|
|
33
|
+
if (!emitter || typeof emitter.on !== "function") {
|
|
34
|
+
log("attachThreadInfoRealtimeSync: no emitter on ctx, skipping", "warn");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Thread = models && models.Thread;
|
|
39
|
+
|
|
40
|
+
emitter.on("event", async (event) => {
|
|
41
|
+
try {
|
|
42
|
+
if (!event || !event.threadID || !event.logMessageType) return;
|
|
43
|
+
const tid = String(event.threadID);
|
|
44
|
+
const msgType = event.logMessageType;
|
|
45
|
+
|
|
46
|
+
if (!Thread) return;
|
|
47
|
+
|
|
48
|
+
if (INVALIDATE_TYPES.has(msgType)) {
|
|
49
|
+
try {
|
|
50
|
+
await Thread.update({ data: null }, { where: { threadID: tid } });
|
|
51
|
+
log(`threadSync: invalidated cache for thread ${tid} (${msgType})`, "info");
|
|
52
|
+
} catch (e) {
|
|
53
|
+
log(`threadSync: error invalidating thread ${tid}: ${e.message}`, "warn");
|
|
54
|
+
}
|
|
55
|
+
} else if (PARTICIPANT_TYPES.has(msgType)) {
|
|
56
|
+
try {
|
|
57
|
+
const row = await Thread.findOne({ where: { threadID: tid } });
|
|
58
|
+
if (!row || !row.data) {
|
|
59
|
+
await Thread.update({ data: null }, { where: { threadID: tid } });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let cached;
|
|
64
|
+
try { cached = JSON.parse(row.data); } catch { cached = null; }
|
|
65
|
+
if (!cached) return;
|
|
66
|
+
|
|
67
|
+
const addedIDs = event.logMessageData?.addedParticipants?.map(p => String(p.userFbId || p.id)) || [];
|
|
68
|
+
const removedIDs = event.logMessageData?.leftParticipantFbId
|
|
69
|
+
? [String(event.logMessageData.leftParticipantFbId)]
|
|
70
|
+
: [];
|
|
71
|
+
|
|
72
|
+
if (removedIDs.length > 0 && Array.isArray(cached.participantIDs)) {
|
|
73
|
+
cached.participantIDs = cached.participantIDs.filter(id => !removedIDs.includes(String(id)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (addedIDs.length > 0) {
|
|
77
|
+
if (!Array.isArray(cached.participantIDs)) cached.participantIDs = [];
|
|
78
|
+
for (const id of addedIDs) {
|
|
79
|
+
if (!cached.participantIDs.includes(id)) cached.participantIDs.push(id);
|
|
80
|
+
}
|
|
81
|
+
if (api && typeof api.getUserInfo === "function") {
|
|
82
|
+
try {
|
|
83
|
+
const userInfos = await new Promise((res, rej) => {
|
|
84
|
+
api.getUserInfo(addedIDs, (err, info) => err ? rej(err) : res(info));
|
|
85
|
+
});
|
|
86
|
+
if (!cached.userInfo) cached.userInfo = {};
|
|
87
|
+
Object.assign(cached.userInfo, userInfos);
|
|
88
|
+
} catch (_) {}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await Thread.update({ data: JSON.stringify(cached) }, { where: { threadID: tid } });
|
|
93
|
+
log(`threadSync: updated participants for thread ${tid} (${msgType})`, "info");
|
|
94
|
+
} catch (e) {
|
|
95
|
+
log(`threadSync: error updating participants for ${tid}: ${e.message}`, "warn");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
log(`threadSync: unhandled error: ${e.message}`, "warn");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
log("attachThreadInfoRealtimeSync: listening for MQTT events", "info");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { attachThreadInfoRealtimeSync };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const https = require("https");
|
|
4
|
+
const { execSync } = require("child_process");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checks npm registry for a newer version of the package.
|
|
8
|
+
* @param {string} packageName - The npm package name.
|
|
9
|
+
* @param {string} currentVersion - Current installed version.
|
|
10
|
+
* @param {object} [options] - Options.
|
|
11
|
+
* @param {string} [options.registryUrl] - npm registry URL.
|
|
12
|
+
* @param {number} [options.timeoutMs] - Request timeout in ms.
|
|
13
|
+
* @returns {Promise<{hasUpdate: boolean, latest: string, current: string}>}
|
|
14
|
+
*/
|
|
15
|
+
async function checkForPackageUpdate(packageName, currentVersion, options = {}) {
|
|
16
|
+
const registryUrl = options.registryUrl || "https://registry.npmjs.org";
|
|
17
|
+
const timeoutMs = options.timeoutMs || 10000;
|
|
18
|
+
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const url = `${registryUrl}/${encodeURIComponent(packageName)}/latest`;
|
|
21
|
+
const timer = setTimeout(() => resolve({ hasUpdate: false, latest: currentVersion, current: currentVersion }), timeoutMs);
|
|
22
|
+
|
|
23
|
+
https.get(url, { headers: { Accept: "application/json" } }, (res) => {
|
|
24
|
+
let data = "";
|
|
25
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
26
|
+
res.on("end", () => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
try {
|
|
29
|
+
const json = JSON.parse(data);
|
|
30
|
+
const latest = json.version || currentVersion;
|
|
31
|
+
const hasUpdate = latest !== currentVersion && compareVersions(latest, currentVersion) > 0;
|
|
32
|
+
resolve({ hasUpdate, latest, current: currentVersion });
|
|
33
|
+
} catch {
|
|
34
|
+
resolve({ hasUpdate: false, latest: currentVersion, current: currentVersion });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}).on("error", () => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
resolve({ hasUpdate: false, latest: currentVersion, current: currentVersion });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function compareVersions(a, b) {
|
|
45
|
+
const pa = a.split(".").map(Number);
|
|
46
|
+
const pb = b.split(".").map(Number);
|
|
47
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
48
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
49
|
+
if (diff !== 0) return diff;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Runs the update check based on fca-config settings.
|
|
56
|
+
* @param {object} config - Config object (from loadConfig).
|
|
57
|
+
* @param {function} [logger] - Logger function.
|
|
58
|
+
*/
|
|
59
|
+
async function runConfiguredUpdateCheck(config, logger) {
|
|
60
|
+
const log = typeof logger === "function" ? logger : () => {};
|
|
61
|
+
if (!config || !config.checkUpdate || !config.checkUpdate.enabled) return;
|
|
62
|
+
|
|
63
|
+
const pkg = config.checkUpdate.packageName || "@lazyneoaz/metachat";
|
|
64
|
+
let currentVersion = "unknown";
|
|
65
|
+
try {
|
|
66
|
+
currentVersion = require("../../package.json").version;
|
|
67
|
+
} catch (_) {}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const result = await checkForPackageUpdate(pkg, currentVersion, {
|
|
71
|
+
registryUrl: config.checkUpdate.registryUrl,
|
|
72
|
+
timeoutMs: config.checkUpdate.timeoutMs,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (result.hasUpdate) {
|
|
76
|
+
log(`UPDATE : New version ${result.latest} available (current: ${result.current}). Run: npm install ${pkg}@latest`, "warn");
|
|
77
|
+
if (config.checkUpdate.install) {
|
|
78
|
+
try {
|
|
79
|
+
execSync(`npm install ${pkg}@latest`, { stdio: "ignore" });
|
|
80
|
+
log(`UPDATE : Auto-installed ${pkg}@${result.latest}`, "info");
|
|
81
|
+
} catch (_) {}
|
|
82
|
+
}
|
|
83
|
+
} else if (config.checkUpdate.notifyIfCurrent) {
|
|
84
|
+
log(`UPDATE : Already on latest version ${result.current}`, "info");
|
|
85
|
+
}
|
|
86
|
+
} catch (_) {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
checkForPackageUpdate,
|
|
91
|
+
runConfiguredUpdateCheck,
|
|
92
|
+
};
|