@dongdev/fca-unofficial 3.0.29 → 3.0.31
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 +232 -132
- package/DOCS.md +82 -3
- package/README.md +524 -632
- package/func/logAdapter.js +33 -0
- package/index.d.ts +6 -0
- package/module/config.js +11 -1
- package/module/loginHelper.js +63 -4
- package/package.json +89 -81
- package/src/api/action/changeAvatar.js +1 -1
- package/src/api/action/changeBio.js +1 -1
- package/src/api/action/handleFriendRequest.js +1 -1
- package/src/api/action/logout.js +1 -1
- package/src/api/action/refreshFb_dtsg.js +1 -1
- package/src/api/action/setPostReaction.js +1 -1
- package/src/api/action/unfriend.js +1 -1
- package/src/api/http/httpGet.js +2 -2
- package/src/api/http/postFormData.js +1 -1
- package/src/api/messaging/addUserToGroup.js +1 -1
- package/src/api/messaging/changeArchivedStatus.js +1 -1
- package/src/api/messaging/changeBlockedStatus.js +1 -1
- package/src/api/messaging/changeGroupImage.js +2 -2
- package/src/api/messaging/changeNickname.js +2 -2
- package/src/api/messaging/changeThreadColor.js +1 -1
- package/src/api/messaging/changeThreadEmoji.js +1 -1
- package/src/api/messaging/createNewGroup.js +1 -1
- package/src/api/messaging/createThemeAI.js +1 -1
- package/src/api/messaging/deleteMessage.js +1 -1
- package/src/api/messaging/deleteThread.js +1 -1
- package/src/api/messaging/editMessage.js +1 -1
- package/src/api/messaging/getFriendsList.js +1 -1
- package/src/api/messaging/getMessage.js +1 -1
- package/src/api/messaging/getThemePictures.js +1 -1
- package/src/api/messaging/handleMessageRequest.js +1 -1
- package/src/api/messaging/markAsDelivered.js +1 -1
- package/src/api/messaging/markAsRead.js +1 -1
- package/src/api/messaging/markAsReadAll.js +1 -1
- package/src/api/messaging/markAsSeen.js +1 -1
- package/src/api/messaging/muteThread.js +1 -1
- package/src/api/messaging/resolvePhotoUrl.js +1 -1
- package/src/api/messaging/searchForThread.js +2 -1
- package/src/api/messaging/sendMessage.js +1 -1
- package/src/api/messaging/sendTypingIndicator.js +1 -1
- package/src/api/messaging/setMessageReaction.js +3 -4
- package/src/api/messaging/setTitle.js +1 -1
- package/src/api/messaging/unsendMessage.js +2 -2
- package/src/api/messaging/uploadAttachment.js +1 -1
- package/src/api/socket/core/connectMqtt.js +16 -8
- package/src/api/socket/core/emitAuth.js +4 -0
- package/src/api/socket/core/getSeqID.js +6 -8
- package/src/api/socket/core/getTaskResponseData.js +3 -0
- package/src/api/socket/core/parseDelta.js +9 -0
- package/src/api/socket/detail/buildStream.js +11 -4
- package/src/api/socket/detail/constants.js +4 -0
- package/src/api/socket/listenMqtt.js +11 -5
- package/src/api/threads/getThreadHistory.js +1 -1
- package/src/api/threads/getThreadInfo.js +246 -388
- package/src/api/threads/getThreadList.js +1 -1
- package/src/api/threads/getThreadPictures.js +1 -1
- package/src/api/users/getUserID.js +1 -1
- package/src/api/users/getUserInfo.js +87 -12
- package/src/database/helpers.js +53 -0
- package/src/database/models/index.js +2 -1
- package/src/database/models/thread.js +5 -0
- package/src/database/threadData.js +49 -53
- package/src/database/userData.js +46 -37
- package/src/remote/remoteClient.js +123 -0
- package/src/utils/broadcast.js +51 -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/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/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/.gitattributes +0 -2
- package/Fca_Database/database.sqlite +0 -0
- package/LICENSE-MIT +0 -21
- package/src/utils/format.js +0 -1174
- package/src/utils/loginParser.js +0 -347
- package/src/utils/messageFormat.js +0 -1173
- package/src/utils/request.js +0 -305
|
@@ -1,438 +1,296 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { parseAndCheckLogin } = require('../../utils/client');
|
|
6
|
+
const log = require('../../../func/logAdapter');
|
|
6
7
|
|
|
7
8
|
function formatEventReminders(reminder) {
|
|
8
9
|
return {
|
|
9
|
-
reminderID: reminder
|
|
10
|
-
eventCreatorID: reminder
|
|
11
|
-
time: reminder
|
|
12
|
-
eventType:
|
|
13
|
-
locationName: reminder
|
|
14
|
-
locationCoordinates: reminder
|
|
15
|
-
locationPage: reminder
|
|
16
|
-
eventStatus:
|
|
17
|
-
note: reminder
|
|
18
|
-
repeatMode:
|
|
19
|
-
eventTitle: reminder
|
|
20
|
-
triggerMessage: reminder
|
|
21
|
-
secondsToNotifyBefore: reminder
|
|
22
|
-
allowsRsvp: reminder
|
|
23
|
-
relatedEvent: reminder
|
|
24
|
-
members:
|
|
25
|
-
memberID:
|
|
26
|
-
state:
|
|
27
|
-
}))
|
|
10
|
+
reminderID: reminder.id,
|
|
11
|
+
eventCreatorID: reminder.lightweight_event_creator.id,
|
|
12
|
+
time: reminder.time,
|
|
13
|
+
eventType: reminder.lightweight_event_type.toLowerCase(),
|
|
14
|
+
locationName: reminder.location_name,
|
|
15
|
+
locationCoordinates: reminder.location_coordinates,
|
|
16
|
+
locationPage: reminder.location_page,
|
|
17
|
+
eventStatus: reminder.lightweight_event_status.toLowerCase(),
|
|
18
|
+
note: reminder.note,
|
|
19
|
+
repeatMode: reminder.repeat_mode.toLowerCase(),
|
|
20
|
+
eventTitle: reminder.event_title,
|
|
21
|
+
triggerMessage: reminder.trigger_message,
|
|
22
|
+
secondsToNotifyBefore: reminder.seconds_to_notify_before,
|
|
23
|
+
allowsRsvp: reminder.allows_rsvp,
|
|
24
|
+
relatedEvent: reminder.related_event,
|
|
25
|
+
members: reminder.event_reminder_members.edges.map((member) => ({
|
|
26
|
+
memberID: member.node.id,
|
|
27
|
+
state: member.guest_list_state.toLowerCase(),
|
|
28
|
+
})),
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function formatThreadGraphQLResponse(data) {
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
if (data.errors) {
|
|
34
|
+
const details = data.errors.map(e => e.message || e).join(", ");
|
|
35
|
+
const error = new Error(`GraphQL error in getThreadInfo: ${details}`);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const messageThread = data.message_thread;
|
|
40
|
+
if (!messageThread) {
|
|
41
|
+
throw new Error("No message_thread in GraphQL response");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const threadID =
|
|
45
|
+
messageThread.thread_key.thread_fbid ||
|
|
46
|
+
messageThread.thread_key.other_user_id;
|
|
47
|
+
|
|
48
|
+
const lastM = messageThread.last_message;
|
|
49
|
+
const snippetID =
|
|
50
|
+
lastM?.nodes?.[0]?.message_sender?.messaging_actor?.id || null;
|
|
51
|
+
const snippetText = lastM?.nodes?.[0]?.snippet || null;
|
|
52
|
+
const lastReadTimestamp =
|
|
53
|
+
messageThread.last_read_receipt?.nodes?.[0]?.timestamp_precise || null;
|
|
54
|
+
|
|
52
55
|
return {
|
|
53
56
|
threadID,
|
|
54
|
-
threadName:
|
|
55
|
-
participantIDs:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
messageCount: t?.messages_count ?? 0,
|
|
71
|
-
timestamp: t?.updated_time_precise || null,
|
|
72
|
-
muteUntil: t?.mute_until || null,
|
|
73
|
-
isGroup: t?.thread_type === "GROUP",
|
|
74
|
-
isSubscribed: !!t?.is_viewer_subscribed,
|
|
75
|
-
isArchived: !!t?.has_viewer_archived,
|
|
76
|
-
folder: t?.folder || null,
|
|
77
|
-
cannotReplyReason: t?.cannot_reply_reason || null,
|
|
78
|
-
eventReminders: Array.isArray(t?.event_reminders?.nodes) ? t.event_reminders.nodes.map(formatEventReminders) : [],
|
|
79
|
-
emoji: customInfo?.emoji || null,
|
|
80
|
-
color: bubble ? String(bubble).slice(2) : null,
|
|
81
|
-
threadTheme: t?.thread_theme || null,
|
|
82
|
-
nicknames,
|
|
83
|
-
adminIDs: Array.isArray(t?.thread_admins) ? t.thread_admins : [],
|
|
84
|
-
approvalMode: !!t?.approval_mode,
|
|
85
|
-
approvalQueue: approvals.map(a => ({
|
|
86
|
-
inviterID: a?.inviter?.id || null,
|
|
87
|
-
requesterID: a?.requester?.id || null,
|
|
88
|
-
timestamp: a?.request_timestamp || null,
|
|
89
|
-
request_source: a?.request_source || null,
|
|
57
|
+
threadName: messageThread.name,
|
|
58
|
+
participantIDs: messageThread.all_participants.edges.map(
|
|
59
|
+
d => d.node.messaging_actor.id
|
|
60
|
+
),
|
|
61
|
+
userInfo: messageThread.all_participants.edges.map(d => ({
|
|
62
|
+
id: d.node.messaging_actor.id,
|
|
63
|
+
name: d.node.messaging_actor.name,
|
|
64
|
+
firstName: d.node.messaging_actor.short_name,
|
|
65
|
+
vanity: d.node.messaging_actor.username,
|
|
66
|
+
url: d.node.messaging_actor.url,
|
|
67
|
+
thumbSrc: d.node.messaging_actor.big_image_src.uri,
|
|
68
|
+
profileUrl: d.node.messaging_actor.big_image_src.uri,
|
|
69
|
+
gender: d.node.messaging_actor.gender,
|
|
70
|
+
type: d.node.messaging_actor.__typename,
|
|
71
|
+
isFriend: d.node.messaging_actor.is_viewer_friend,
|
|
72
|
+
isBirthday: !!d.node.messaging_actor.is_birthday,
|
|
90
73
|
})),
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
unreadCount: messageThread.unread_count,
|
|
75
|
+
messageCount: messageThread.messages_count,
|
|
76
|
+
timestamp: messageThread.updated_time_precise,
|
|
77
|
+
muteUntil: messageThread.mute_until,
|
|
78
|
+
isGroup: messageThread.thread_type === "GROUP",
|
|
79
|
+
isSubscribed: messageThread.is_viewer_subscribed,
|
|
80
|
+
isArchived: messageThread.has_viewer_archived,
|
|
81
|
+
folder: messageThread.folder,
|
|
82
|
+
cannotReplyReason: messageThread.cannot_reply_reason,
|
|
83
|
+
eventReminders: messageThread.event_reminders
|
|
84
|
+
? messageThread.event_reminders.nodes.map(formatEventReminders)
|
|
85
|
+
: null,
|
|
86
|
+
emoji: messageThread.customization_info?.emoji || null,
|
|
87
|
+
color: messageThread.customization_info?.outgoing_bubble_color
|
|
88
|
+
? messageThread.customization_info.outgoing_bubble_color.slice(2)
|
|
89
|
+
: null,
|
|
90
|
+
threadTheme: messageThread.thread_theme,
|
|
91
|
+
nicknames:
|
|
92
|
+
messageThread.customization_info?.participant_customizations?.reduce(
|
|
93
|
+
(res, val) => {
|
|
94
|
+
if (val.nickname) res[val.participant_id] = val.nickname;
|
|
95
|
+
return res;
|
|
96
|
+
},
|
|
97
|
+
{}
|
|
98
|
+
) || {},
|
|
99
|
+
adminIDs: messageThread.thread_admins,
|
|
100
|
+
approvalMode: Boolean(messageThread.approval_mode),
|
|
101
|
+
approvalQueue:
|
|
102
|
+
messageThread.group_approval_queue?.nodes?.map(a => ({
|
|
103
|
+
inviterID: a.inviter.id,
|
|
104
|
+
requesterID: a.requester.id,
|
|
105
|
+
timestamp: a.request_timestamp,
|
|
106
|
+
request_source: a.request_source,
|
|
107
|
+
})) || [],
|
|
108
|
+
reactionsMuteMode: messageThread.reactions_mute_mode?.toLowerCase(),
|
|
109
|
+
mentionsMuteMode: messageThread.mentions_mute_mode?.toLowerCase(),
|
|
110
|
+
isPinProtected: messageThread.is_pin_protected,
|
|
111
|
+
relatedPageThread: messageThread.related_page_thread,
|
|
112
|
+
name: messageThread.name,
|
|
96
113
|
snippet: snippetText,
|
|
97
114
|
snippetSender: snippetID,
|
|
98
115
|
snippetAttachments: [],
|
|
99
|
-
serverTimestamp:
|
|
100
|
-
imageSrc:
|
|
101
|
-
isCanonicalUser:
|
|
102
|
-
isCanonical:
|
|
116
|
+
serverTimestamp: messageThread.updated_time_precise,
|
|
117
|
+
imageSrc: messageThread.image?.uri || null,
|
|
118
|
+
isCanonicalUser: messageThread.is_canonical_neo_user,
|
|
119
|
+
isCanonical: messageThread.thread_type !== "GROUP",
|
|
103
120
|
recipientsLoadable: true,
|
|
104
121
|
hasEmailParticipant: false,
|
|
105
122
|
readOnly: false,
|
|
106
|
-
canReply:
|
|
107
|
-
lastMessageTimestamp:
|
|
123
|
+
canReply: messageThread.cannot_reply_reason == null,
|
|
124
|
+
lastMessageTimestamp:
|
|
125
|
+
messageThread.last_message?.timestamp_precise || null,
|
|
108
126
|
lastMessageType: "message",
|
|
109
127
|
lastReadTimestamp,
|
|
110
|
-
threadType:
|
|
128
|
+
threadType: messageThread.thread_type === "GROUP" ? 2 : 1,
|
|
111
129
|
inviteLink: {
|
|
112
|
-
enable:
|
|
113
|
-
link:
|
|
130
|
+
enable: messageThread.joinable_mode?.mode === 1,
|
|
131
|
+
link: messageThread.joinable_mode?.link || null,
|
|
114
132
|
},
|
|
115
133
|
};
|
|
116
134
|
}
|
|
117
135
|
|
|
118
|
-
const queue = [];
|
|
119
|
-
let isProcessingQueue = false;
|
|
120
|
-
const processingThreads = new Set();
|
|
121
|
-
const queuedThreads = new Set();
|
|
122
|
-
const cooldown = new Map();
|
|
123
|
-
const inflight = new Map();
|
|
124
|
-
let loopStarted = false;
|
|
125
|
-
|
|
126
136
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
const dbFiles = fs.readdirSync(path.join(__dirname, "../../database"))
|
|
138
|
+
.filter(f => path.extname(f) === ".js")
|
|
139
|
+
.reduce((acc, file) => {
|
|
140
|
+
const mod = require(path.join(__dirname, "../../database", file));
|
|
141
|
+
acc[path.basename(file, ".js")] = typeof mod === "function" ? mod(api) : mod;
|
|
142
|
+
return acc;
|
|
143
|
+
}, {});
|
|
144
|
+
|
|
145
|
+
const { threadData } = dbFiles;
|
|
146
|
+
const { create, get, update } = threadData || {};
|
|
147
|
+
const FRESH_MS = 10 * 60 * 1000;
|
|
148
|
+
return function getThreadInfo(threadID, callback) {
|
|
149
|
+
let resolveFunc;
|
|
150
|
+
let rejectFunc;
|
|
151
|
+
|
|
152
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
153
|
+
resolveFunc = resolve;
|
|
154
|
+
rejectFunc = reject;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (typeof callback !== "function") {
|
|
158
|
+
callback = (err, data) => {
|
|
159
|
+
if (err) {
|
|
160
|
+
return rejectFunc(err);
|
|
161
|
+
}
|
|
162
|
+
return resolveFunc(data);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const threadIDs = Array.isArray(threadID) ? threadID.map(String) : [String(threadID)];
|
|
167
|
+
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
|
|
170
|
+
const loadFromDb = async ids => {
|
|
171
|
+
if (!threadData || typeof get !== "function") return { fresh: {}, stale: ids };
|
|
172
|
+
const fresh = {};
|
|
173
|
+
const stale = [];
|
|
174
|
+
const rows = await Promise.all(ids.map(id => get(id).catch(() => null)));
|
|
175
|
+
for (let i = 0; i < ids.length; i++) {
|
|
176
|
+
const id = ids[i];
|
|
177
|
+
const row = rows[i];
|
|
178
|
+
if (row && row.data) {
|
|
179
|
+
const updatedAt = row.updatedAt ? new Date(row.updatedAt).getTime() : 0;
|
|
180
|
+
if (updatedAt && now - updatedAt <= FRESH_MS) {
|
|
181
|
+
fresh[id] = row.data;
|
|
182
|
+
} else {
|
|
183
|
+
stale.push(id);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
stale.push(id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { fresh, stale };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const fetchFromGraphQL = async ids => {
|
|
193
|
+
if (!ids.length) return {};
|
|
194
|
+
const queries = {};
|
|
195
|
+
ids.forEach((t, i) => {
|
|
196
|
+
queries["o" + i] = {
|
|
132
197
|
doc_id: "3449967031715030",
|
|
133
198
|
query_params: {
|
|
134
|
-
id:
|
|
199
|
+
id: t,
|
|
135
200
|
message_limit: 0,
|
|
136
201
|
load_messages: false,
|
|
137
202
|
load_read_receipts: false,
|
|
138
|
-
before: null
|
|
139
|
-
}
|
|
203
|
+
before: null
|
|
204
|
+
}
|
|
140
205
|
};
|
|
141
206
|
});
|
|
142
|
-
return {
|
|
143
|
-
queries: JSON.stringify(form),
|
|
144
|
-
batch_name: "MessengerGraphQLThreadFetcher",
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
const maxAttempts = 3;
|
|
148
|
-
let lastErr = null;
|
|
149
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
150
|
-
try {
|
|
151
|
-
const Submit = buildQueries();
|
|
152
|
-
const resData = await defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, Submit).then(parseAndCheckLogin(ctx, defaultFuncs));
|
|
153
|
-
if (!Array.isArray(resData) || resData.length === 0) throw new Error("EmptyGraphBatch");
|
|
154
|
-
const tail = resData[resData.length - 1];
|
|
155
|
-
if (tail?.error_results && tail.error_results !== 0) throw new Error("GraphErrorResults");
|
|
156
|
-
const body = resData.slice(0, -1).sort((a, b) => Object.keys(a)[0].localeCompare(Object.keys(b)[0]));
|
|
157
|
-
const out = [];
|
|
158
|
-
body.forEach((x, y) => out.push(formatThreadGraphQLResponse(x["o" + y]?.data)));
|
|
159
|
-
const valid = out.some(d => !!d && !!d.threadID);
|
|
160
|
-
if (!valid) throw new Error("GraphNoData");
|
|
161
|
-
return { Success: true, Data: out };
|
|
162
|
-
} catch (e) {
|
|
163
|
-
lastErr = e;
|
|
164
|
-
if (attempt < maxAttempts) await new Promise(r => setTimeout(r, 300 * attempt));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return { Success: false, Data: null, Error: lastErr ? String(lastErr.message || lastErr) : "Unknown" };
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const dbFiles = fs.readdirSync(path.join(__dirname, "../../database")).filter(f => path.extname(f) === ".js").reduce((acc, file) => {
|
|
171
|
-
acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
|
|
172
|
-
return acc;
|
|
173
|
-
}, {});
|
|
174
|
-
const { threadData, userData } = dbFiles;
|
|
175
|
-
const { create, get, update, getAll } = threadData;
|
|
176
|
-
const { update: updateUser } = userData;
|
|
177
207
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
async function upsertUsersFromThreadInfo(info) {
|
|
183
|
-
try {
|
|
184
|
-
if (!info || !Array.isArray(info.userInfo) || info.userInfo.length === 0) return;
|
|
185
|
-
const tasks = info.userInfo.filter(u => u && u.id).map(u => {
|
|
186
|
-
const data = {
|
|
187
|
-
id: u.id,
|
|
188
|
-
name: u.name || null,
|
|
189
|
-
firstName: u.firstName || null,
|
|
190
|
-
vanity: u.vanity || null,
|
|
191
|
-
url: u.url || null,
|
|
192
|
-
thumbSrc: u.thumbSrc || null,
|
|
193
|
-
profileUrl: u.profileUrl || null,
|
|
194
|
-
gender: u.gender || null,
|
|
195
|
-
type: u.type || null,
|
|
196
|
-
isFriend: !!u.isFriend,
|
|
197
|
-
isBirthday: !!u.isBirthday,
|
|
198
|
-
};
|
|
199
|
-
return updateUser(u.id, { data });
|
|
200
|
-
});
|
|
201
|
-
await Promise.allSettled(tasks);
|
|
202
|
-
} catch (e) {
|
|
203
|
-
logger(`upsertUsers error: ${e?.message || e}`, "warn");
|
|
204
|
-
}
|
|
205
|
-
}
|
|
208
|
+
const form = {
|
|
209
|
+
queries: JSON.stringify(queries),
|
|
210
|
+
batch_name: "MessengerGraphQLThreadFetcher"
|
|
211
|
+
};
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return "create";
|
|
215
|
-
}
|
|
216
|
-
}
|
|
213
|
+
const resData = await defaultFuncs
|
|
214
|
+
.post(
|
|
215
|
+
"https://www.facebook.com/api/graphqlbatch/",
|
|
216
|
+
ctx.jar,
|
|
217
|
+
form
|
|
218
|
+
)
|
|
219
|
+
.then(parseAndCheckLogin(ctx, defaultFuncs));
|
|
217
220
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const response = await getMultiInfo([tID]);
|
|
221
|
-
if (!response.Success || !response.Data || !isValidThread(response.Data[0])) {
|
|
222
|
-
cooldown.set(tID, Date.now() + 5 * 60 * 1000);
|
|
223
|
-
logger(`GraphQL empty for ${tID}, cooldown applied`, "warn");
|
|
224
|
-
return;
|
|
221
|
+
if (resData.error) {
|
|
222
|
+
throw resData;
|
|
225
223
|
}
|
|
226
|
-
const threadInfo = response.Data[0];
|
|
227
|
-
await upsertUsersFromThreadInfo(threadInfo);
|
|
228
|
-
const op = await createOrUpdateThread(tID, threadInfo);
|
|
229
|
-
logger(`${op === "create" ? "Success create data thread" : "Success update data thread"}: ${tID}`, "info");
|
|
230
|
-
} catch (err) {
|
|
231
|
-
cooldown.set(tID, Date.now() + 5 * 60 * 1000);
|
|
232
|
-
logger(`fetchThreadInfo error ${tID}: ${err?.message || err}`, "error");
|
|
233
|
-
} finally {
|
|
234
|
-
queuedThreads.delete(tID);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
224
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
queue.push(() => fetchThreadInfo(t, false));
|
|
225
|
+
const out = {};
|
|
226
|
+
for (let i = resData.length - 2; i >= 0; i--) {
|
|
227
|
+
const res = resData[i];
|
|
228
|
+
const oKey = Object.keys(res)[0];
|
|
229
|
+
const responseData = res[oKey];
|
|
230
|
+
try {
|
|
231
|
+
const info = formatThreadGraphQLResponse(responseData.data);
|
|
232
|
+
if (info && info.threadID) {
|
|
233
|
+
out[info.threadID] = info;
|
|
234
|
+
}
|
|
235
|
+
} catch (e) {
|
|
236
|
+
// Skip malformed entries but continue processing others
|
|
237
|
+
log.error("getThreadInfoGraphQL", e && e.message ? e.message : String(e));
|
|
252
238
|
}
|
|
253
239
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
}
|
|
240
|
+
return out;
|
|
241
|
+
};
|
|
258
242
|
|
|
259
|
-
|
|
260
|
-
if (isProcessingQueue) return;
|
|
261
|
-
isProcessingQueue = true;
|
|
262
|
-
while (queue.length > 0) {
|
|
263
|
-
const task = queue.shift();
|
|
243
|
+
(async () => {
|
|
264
244
|
try {
|
|
265
|
-
await
|
|
266
|
-
|
|
267
|
-
logger(`Queue processing error: ${err?.message || err}`, "error");
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
isProcessingQueue = false;
|
|
271
|
-
}
|
|
245
|
+
const { fresh, stale } = await loadFromDb(threadIDs);
|
|
246
|
+
let fetched = {};
|
|
272
247
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
setInterval(() => {
|
|
276
|
-
checkAndUpdateThreads();
|
|
277
|
-
processQueue();
|
|
278
|
-
}, 10000);
|
|
279
|
-
}
|
|
248
|
+
if (stale.length) {
|
|
249
|
+
fetched = await fetchFromGraphQL(stale);
|
|
280
250
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
callback(null, cachedCd.data);
|
|
303
|
-
return returnPromise;
|
|
251
|
+
// Persist fetched data back to DB
|
|
252
|
+
if (threadData && (typeof create === "function" || typeof update === "function")) {
|
|
253
|
+
const tasks = [];
|
|
254
|
+
for (const id of stale) {
|
|
255
|
+
const info = fetched[id];
|
|
256
|
+
if (!info) continue;
|
|
257
|
+
const payload = { data: info };
|
|
258
|
+
if (typeof update === "function") {
|
|
259
|
+
tasks.push(update(id, payload).catch(() => null));
|
|
260
|
+
} else if (typeof create === "function") {
|
|
261
|
+
tasks.push(create(id, payload).catch(() => null));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (tasks.length) {
|
|
265
|
+
try {
|
|
266
|
+
await Promise.all(tasks);
|
|
267
|
+
} catch {
|
|
268
|
+
// Swallow DB errors – not critical for API behavior
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
304
272
|
}
|
|
305
|
-
const stub = {
|
|
306
|
-
threadID: tid,
|
|
307
|
-
threadName: null,
|
|
308
|
-
participantIDs: [],
|
|
309
|
-
userInfo: [],
|
|
310
|
-
unreadCount: 0,
|
|
311
|
-
messageCount: 0,
|
|
312
|
-
timestamp: null,
|
|
313
|
-
muteUntil: null,
|
|
314
|
-
isGroup: false,
|
|
315
|
-
isSubscribed: false,
|
|
316
|
-
isArchived: false,
|
|
317
|
-
folder: null,
|
|
318
|
-
cannotReplyReason: null,
|
|
319
|
-
eventReminders: [],
|
|
320
|
-
emoji: null,
|
|
321
|
-
color: null,
|
|
322
|
-
threadTheme: null,
|
|
323
|
-
nicknames: {},
|
|
324
|
-
adminIDs: [],
|
|
325
|
-
approvalMode: false,
|
|
326
|
-
approvalQueue: [],
|
|
327
|
-
reactionsMuteMode: "",
|
|
328
|
-
mentionsMuteMode: "",
|
|
329
|
-
isPinProtected: false,
|
|
330
|
-
relatedPageThread: null,
|
|
331
|
-
name: null,
|
|
332
|
-
snippet: null,
|
|
333
|
-
snippetSender: null,
|
|
334
|
-
snippetAttachments: [],
|
|
335
|
-
serverTimestamp: null,
|
|
336
|
-
imageSrc: null,
|
|
337
|
-
isCanonicalUser: false,
|
|
338
|
-
isCanonical: true,
|
|
339
|
-
recipientsLoadable: false,
|
|
340
|
-
hasEmailParticipant: false,
|
|
341
|
-
readOnly: false,
|
|
342
|
-
canReply: false,
|
|
343
|
-
lastMessageTimestamp: null,
|
|
344
|
-
lastMessageType: "message",
|
|
345
|
-
lastReadTimestamp: null,
|
|
346
|
-
threadType: 1,
|
|
347
|
-
inviteLink: { enable: false, link: null },
|
|
348
|
-
__status: "cooldown",
|
|
349
|
-
};
|
|
350
|
-
await createOrUpdateThread(tid, stub);
|
|
351
|
-
callback(null, stub);
|
|
352
|
-
return returnPromise;
|
|
353
|
-
}
|
|
354
273
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return returnPromise;
|
|
360
|
-
}
|
|
274
|
+
const resultMap = {};
|
|
275
|
+
for (const id of threadIDs) {
|
|
276
|
+
resultMap[id] = fresh[id] || fetched[id] || null;
|
|
277
|
+
}
|
|
361
278
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
279
|
+
const result = Array.isArray(threadID)
|
|
280
|
+
? resultMap
|
|
281
|
+
: resultMap[threadIDs[0]] || null;
|
|
366
282
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
threadID: tid,
|
|
378
|
-
threadName: null,
|
|
379
|
-
participantIDs: [],
|
|
380
|
-
userInfo: [],
|
|
381
|
-
unreadCount: 0,
|
|
382
|
-
messageCount: 0,
|
|
383
|
-
timestamp: null,
|
|
384
|
-
muteUntil: null,
|
|
385
|
-
isGroup: false,
|
|
386
|
-
isSubscribed: false,
|
|
387
|
-
isArchived: false,
|
|
388
|
-
folder: null,
|
|
389
|
-
cannotReplyReason: null,
|
|
390
|
-
eventReminders: [],
|
|
391
|
-
emoji: null,
|
|
392
|
-
color: null,
|
|
393
|
-
threadTheme: null,
|
|
394
|
-
nicknames: {},
|
|
395
|
-
adminIDs: [],
|
|
396
|
-
approvalMode: false,
|
|
397
|
-
approvalQueue: [],
|
|
398
|
-
reactionsMuteMode: "",
|
|
399
|
-
mentionsMuteMode: "",
|
|
400
|
-
isPinProtected: false,
|
|
401
|
-
relatedPageThread: null,
|
|
402
|
-
name: null,
|
|
403
|
-
snippet: null,
|
|
404
|
-
snippetSender: null,
|
|
405
|
-
snippetAttachments: [],
|
|
406
|
-
serverTimestamp: null,
|
|
407
|
-
imageSrc: null,
|
|
408
|
-
isCanonicalUser: false,
|
|
409
|
-
isCanonical: true,
|
|
410
|
-
recipientsLoadable: false,
|
|
411
|
-
hasEmailParticipant: false,
|
|
412
|
-
readOnly: false,
|
|
413
|
-
canReply: false,
|
|
414
|
-
lastMessageTimestamp: null,
|
|
415
|
-
lastMessageType: "message",
|
|
416
|
-
lastReadTimestamp: null,
|
|
417
|
-
threadType: 1,
|
|
418
|
-
inviteLink: { enable: false, link: null },
|
|
419
|
-
__status: "unavailable",
|
|
420
|
-
};
|
|
421
|
-
cooldown.set(tid, Date.now() + 5 * 60 * 1000);
|
|
422
|
-
await createOrUpdateThread(tid, stub);
|
|
423
|
-
return stub;
|
|
424
|
-
}
|
|
425
|
-
})()
|
|
426
|
-
.finally(() => {
|
|
427
|
-
processingThreads.delete(tid);
|
|
428
|
-
inflight.delete(tid);
|
|
429
|
-
});
|
|
283
|
+
return callback(null, result);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
// Horizon-style anti-get-info message to hint possible spam/limit
|
|
286
|
+
log.error(
|
|
287
|
+
"getThreadInfoGraphQL",
|
|
288
|
+
"Lỗi: getThreadInfoGraphQL Có Thể Do Bạn Spam Quá Nhiều, Hãy Thử Lại !"
|
|
289
|
+
);
|
|
290
|
+
return callback(err);
|
|
291
|
+
}
|
|
292
|
+
})();
|
|
430
293
|
|
|
431
|
-
inflight.set(tid, p);
|
|
432
|
-
p.then(data => callback(null, data)).catch(err => callback(err));
|
|
433
|
-
} catch (err) {
|
|
434
|
-
callback(err);
|
|
435
|
-
}
|
|
436
294
|
return returnPromise;
|
|
437
295
|
};
|
|
438
296
|
};
|