@dongdev/fca-unofficial 3.0.30 → 4.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/LICENSE +191 -0
- package/README.md +224 -406
- package/dist/index.d.mts +1241 -0
- package/dist/index.d.ts +1241 -0
- package/dist/index.js +27749 -0
- package/dist/index.mjs +27713 -0
- package/docs/ARCHITECTURE.md +467 -0
- package/docs/DOCS.md +686 -0
- package/fca-config.example.json +33 -0
- package/package.json +33 -22
- package/test/fca.test.cjs +533 -0
- package/CHANGELOG.md +0 -293
- package/DOCS.md +0 -2712
- package/func/checkUpdate.js +0 -222
- package/func/logAdapter.js +0 -33
- package/func/logger.js +0 -48
- package/index.d.ts +0 -751
- package/index.js +0 -8
- package/module/config.js +0 -40
- package/module/login.js +0 -133
- package/module/loginHelper.js +0 -1296
- package/module/options.js +0 -44
- package/src/api/action/addExternalModule.js +0 -25
- package/src/api/action/changeAvatar.js +0 -137
- package/src/api/action/changeBio.js +0 -75
- package/src/api/action/enableAutoSaveAppState.js +0 -73
- package/src/api/action/getCurrentUserID.js +0 -7
- package/src/api/action/handleFriendRequest.js +0 -57
- package/src/api/action/logout.js +0 -76
- package/src/api/action/refreshFb_dtsg.js +0 -48
- package/src/api/action/setPostReaction.js +0 -106
- package/src/api/action/unfriend.js +0 -54
- package/src/api/http/httpGet.js +0 -46
- package/src/api/http/httpPost.js +0 -52
- package/src/api/http/postFormData.js +0 -47
- package/src/api/messaging/addUserToGroup.js +0 -68
- package/src/api/messaging/changeAdminStatus.js +0 -126
- package/src/api/messaging/changeArchivedStatus.js +0 -55
- package/src/api/messaging/changeBlockedStatus.js +0 -48
- package/src/api/messaging/changeGroupImage.js +0 -91
- package/src/api/messaging/changeNickname.js +0 -70
- package/src/api/messaging/changeThreadColor.js +0 -79
- package/src/api/messaging/changeThreadEmoji.js +0 -111
- package/src/api/messaging/createNewGroup.js +0 -88
- package/src/api/messaging/createPoll.js +0 -46
- package/src/api/messaging/createThemeAI.js +0 -98
- package/src/api/messaging/deleteMessage.js +0 -136
- package/src/api/messaging/deleteThread.js +0 -56
- package/src/api/messaging/editMessage.js +0 -68
- package/src/api/messaging/forwardAttachment.js +0 -57
- package/src/api/messaging/getEmojiUrl.js +0 -29
- package/src/api/messaging/getFriendsList.js +0 -82
- package/src/api/messaging/getMessage.js +0 -829
- package/src/api/messaging/getThemePictures.js +0 -62
- package/src/api/messaging/handleMessageRequest.js +0 -65
- package/src/api/messaging/markAsDelivered.js +0 -57
- package/src/api/messaging/markAsRead.js +0 -88
- package/src/api/messaging/markAsReadAll.js +0 -49
- package/src/api/messaging/markAsSeen.js +0 -61
- package/src/api/messaging/muteThread.js +0 -50
- package/src/api/messaging/removeUserFromGroup.js +0 -62
- package/src/api/messaging/resolvePhotoUrl.js +0 -43
- package/src/api/messaging/scheduler.js +0 -264
- package/src/api/messaging/searchForThread.js +0 -52
- package/src/api/messaging/sendMessage.js +0 -270
- package/src/api/messaging/sendTypingIndicator.js +0 -74
- package/src/api/messaging/setMessageReaction.js +0 -91
- package/src/api/messaging/setTitle.js +0 -124
- package/src/api/messaging/shareContact.js +0 -49
- package/src/api/messaging/threadColors.js +0 -128
- package/src/api/messaging/unsendMessage.js +0 -81
- package/src/api/messaging/uploadAttachment.js +0 -492
- package/src/api/socket/core/connectMqtt.js +0 -258
- package/src/api/socket/core/emitAuth.js +0 -103
- package/src/api/socket/core/getSeqID.js +0 -320
- package/src/api/socket/core/getTaskResponseData.js +0 -25
- package/src/api/socket/core/parseDelta.js +0 -377
- package/src/api/socket/detail/buildStream.js +0 -215
- package/src/api/socket/detail/constants.js +0 -28
- package/src/api/socket/listenMqtt.js +0 -377
- package/src/api/socket/middleware/index.js +0 -216
- package/src/api/threads/getThreadHistory.js +0 -664
- package/src/api/threads/getThreadInfo.js +0 -295
- package/src/api/threads/getThreadList.js +0 -293
- package/src/api/threads/getThreadPictures.js +0 -78
- package/src/api/users/getUserID.js +0 -65
- package/src/api/users/getUserInfo.js +0 -399
- package/src/api/users/getUserInfoV2.js +0 -134
- package/src/core/sendReqMqtt.js +0 -96
- package/src/database/models/index.js +0 -87
- package/src/database/models/thread.js +0 -50
- package/src/database/models/user.js +0 -46
- package/src/database/threadData.js +0 -98
- package/src/database/userData.js +0 -89
- package/src/remote/remoteClient.js +0 -123
- package/src/utils/broadcast.js +0 -51
- package/src/utils/client.js +0 -10
- package/src/utils/constants.js +0 -23
- package/src/utils/cookies.js +0 -68
- package/src/utils/format.js +0 -1174
- package/src/utils/headers.js +0 -115
- package/src/utils/loginParser.js +0 -365
- package/src/utils/messageFormat.js +0 -1173
- package/src/utils/request.js +0 -332
|
@@ -1,295 +0,0 @@
|
|
|
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');
|
|
7
|
-
|
|
8
|
-
function formatEventReminders(reminder) {
|
|
9
|
-
return {
|
|
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
|
-
})),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function formatThreadGraphQLResponse(data) {
|
|
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
|
-
|
|
55
|
-
return {
|
|
56
|
-
threadID,
|
|
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,
|
|
73
|
-
})),
|
|
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,
|
|
113
|
-
snippet: snippetText,
|
|
114
|
-
snippetSender: snippetID,
|
|
115
|
-
snippetAttachments: [],
|
|
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",
|
|
120
|
-
recipientsLoadable: true,
|
|
121
|
-
hasEmailParticipant: false,
|
|
122
|
-
readOnly: false,
|
|
123
|
-
canReply: messageThread.cannot_reply_reason == null,
|
|
124
|
-
lastMessageTimestamp:
|
|
125
|
-
messageThread.last_message?.timestamp_precise || null,
|
|
126
|
-
lastMessageType: "message",
|
|
127
|
-
lastReadTimestamp,
|
|
128
|
-
threadType: messageThread.thread_type === "GROUP" ? 2 : 1,
|
|
129
|
-
inviteLink: {
|
|
130
|
-
enable: messageThread.joinable_mode?.mode === 1,
|
|
131
|
-
link: messageThread.joinable_mode?.link || null,
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
module.exports = function (defaultFuncs, api, ctx) {
|
|
137
|
-
const dbFiles = fs.readdirSync(path.join(__dirname, "../../database"))
|
|
138
|
-
.filter(f => path.extname(f) === ".js")
|
|
139
|
-
.reduce((acc, file) => {
|
|
140
|
-
acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
|
|
141
|
-
return acc;
|
|
142
|
-
}, {});
|
|
143
|
-
|
|
144
|
-
const { threadData } = dbFiles;
|
|
145
|
-
const { create, get, update } = threadData || {};
|
|
146
|
-
const FRESH_MS = 10 * 60 * 1000;
|
|
147
|
-
return function getThreadInfo(threadID, callback) {
|
|
148
|
-
let resolveFunc;
|
|
149
|
-
let rejectFunc;
|
|
150
|
-
|
|
151
|
-
const returnPromise = new Promise((resolve, reject) => {
|
|
152
|
-
resolveFunc = resolve;
|
|
153
|
-
rejectFunc = reject;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
if (typeof callback !== "function") {
|
|
157
|
-
callback = (err, data) => {
|
|
158
|
-
if (err) {
|
|
159
|
-
return rejectFunc(err);
|
|
160
|
-
}
|
|
161
|
-
return resolveFunc(data);
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const threadIDs = Array.isArray(threadID) ? threadID.map(String) : [String(threadID)];
|
|
166
|
-
|
|
167
|
-
const now = Date.now();
|
|
168
|
-
|
|
169
|
-
const loadFromDb = async ids => {
|
|
170
|
-
if (!threadData || typeof get !== "function") return { fresh: {}, stale: ids };
|
|
171
|
-
const fresh = {};
|
|
172
|
-
const stale = [];
|
|
173
|
-
const rows = await Promise.all(ids.map(id => get(id).catch(() => null)));
|
|
174
|
-
for (let i = 0; i < ids.length; i++) {
|
|
175
|
-
const id = ids[i];
|
|
176
|
-
const row = rows[i];
|
|
177
|
-
if (row && row.data) {
|
|
178
|
-
const updatedAt = row.updatedAt ? new Date(row.updatedAt).getTime() : 0;
|
|
179
|
-
if (updatedAt && now - updatedAt <= FRESH_MS) {
|
|
180
|
-
fresh[id] = row.data;
|
|
181
|
-
} else {
|
|
182
|
-
stale.push(id);
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
stale.push(id);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return { fresh, stale };
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const fetchFromGraphQL = async ids => {
|
|
192
|
-
if (!ids.length) return {};
|
|
193
|
-
const queries = {};
|
|
194
|
-
ids.forEach((t, i) => {
|
|
195
|
-
queries["o" + i] = {
|
|
196
|
-
doc_id: "3449967031715030",
|
|
197
|
-
query_params: {
|
|
198
|
-
id: t,
|
|
199
|
-
message_limit: 0,
|
|
200
|
-
load_messages: false,
|
|
201
|
-
load_read_receipts: false,
|
|
202
|
-
before: null
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
const form = {
|
|
208
|
-
queries: JSON.stringify(queries),
|
|
209
|
-
batch_name: "MessengerGraphQLThreadFetcher"
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const resData = await defaultFuncs
|
|
213
|
-
.post(
|
|
214
|
-
"https://www.facebook.com/api/graphqlbatch/",
|
|
215
|
-
ctx.jar,
|
|
216
|
-
form
|
|
217
|
-
)
|
|
218
|
-
.then(parseAndCheckLogin(ctx, defaultFuncs));
|
|
219
|
-
|
|
220
|
-
if (resData.error) {
|
|
221
|
-
throw resData;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const out = {};
|
|
225
|
-
for (let i = resData.length - 2; i >= 0; i--) {
|
|
226
|
-
const res = resData[i];
|
|
227
|
-
const oKey = Object.keys(res)[0];
|
|
228
|
-
const responseData = res[oKey];
|
|
229
|
-
try {
|
|
230
|
-
const info = formatThreadGraphQLResponse(responseData.data);
|
|
231
|
-
if (info && info.threadID) {
|
|
232
|
-
out[info.threadID] = info;
|
|
233
|
-
}
|
|
234
|
-
} catch (e) {
|
|
235
|
-
// Skip malformed entries but continue processing others
|
|
236
|
-
log.error("getThreadInfoGraphQL", e && e.message ? e.message : String(e));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return out;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
(async () => {
|
|
243
|
-
try {
|
|
244
|
-
const { fresh, stale } = await loadFromDb(threadIDs);
|
|
245
|
-
let fetched = {};
|
|
246
|
-
|
|
247
|
-
if (stale.length) {
|
|
248
|
-
fetched = await fetchFromGraphQL(stale);
|
|
249
|
-
|
|
250
|
-
// Persist fetched data back to DB
|
|
251
|
-
if (threadData && (typeof create === "function" || typeof update === "function")) {
|
|
252
|
-
const tasks = [];
|
|
253
|
-
for (const id of stale) {
|
|
254
|
-
const info = fetched[id];
|
|
255
|
-
if (!info) continue;
|
|
256
|
-
const payload = { data: info };
|
|
257
|
-
if (typeof update === "function") {
|
|
258
|
-
tasks.push(update(id, payload).catch(() => null));
|
|
259
|
-
} else if (typeof create === "function") {
|
|
260
|
-
tasks.push(create(id, payload).catch(() => null));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (tasks.length) {
|
|
264
|
-
try {
|
|
265
|
-
await Promise.all(tasks);
|
|
266
|
-
} catch {
|
|
267
|
-
// Swallow DB errors – not critical for API behavior
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const resultMap = {};
|
|
274
|
-
for (const id of threadIDs) {
|
|
275
|
-
resultMap[id] = fresh[id] || fetched[id] || null;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const result = Array.isArray(threadID)
|
|
279
|
-
? resultMap
|
|
280
|
-
: resultMap[threadIDs[0]] || null;
|
|
281
|
-
|
|
282
|
-
return callback(null, result);
|
|
283
|
-
} catch (err) {
|
|
284
|
-
// Horizon-style anti-get-info message to hint possible spam/limit
|
|
285
|
-
log.error(
|
|
286
|
-
"getThreadInfoGraphQL",
|
|
287
|
-
"Lỗi: getThreadInfoGraphQL Có Thể Do Bạn Spam Quá Nhiều, Hãy Thử Lại !"
|
|
288
|
-
);
|
|
289
|
-
return callback(err);
|
|
290
|
-
}
|
|
291
|
-
})();
|
|
292
|
-
|
|
293
|
-
return returnPromise;
|
|
294
|
-
};
|
|
295
|
-
};
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const log = require("../../../func/logAdapter");
|
|
4
|
-
const { parseAndCheckLogin } = require("../../utils/client");
|
|
5
|
-
const { formatID, getType } = require("../../utils/format");
|
|
6
|
-
function createProfileUrl(url, username, id) {
|
|
7
|
-
if (url) return url;
|
|
8
|
-
return (
|
|
9
|
-
"https://www.facebook.com/" + (username || formatID(id.toString()))
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function formatParticipants(participants) {
|
|
14
|
-
return participants.edges.map(p => {
|
|
15
|
-
p = p.node.messaging_actor;
|
|
16
|
-
switch (p["__typename"]) {
|
|
17
|
-
case "User":
|
|
18
|
-
return {
|
|
19
|
-
accountType: p["__typename"],
|
|
20
|
-
userID: formatID(p.id.toString()), // do we need .toString()? when it is not a string?
|
|
21
|
-
name: p.name,
|
|
22
|
-
shortName: p.short_name,
|
|
23
|
-
gender: p.gender,
|
|
24
|
-
url: p.url, // how about making it profileURL
|
|
25
|
-
profilePicture: p.big_image_src.uri,
|
|
26
|
-
username: p.username || null,
|
|
27
|
-
// TODO: maybe better names for these?
|
|
28
|
-
isViewerFriend: p.is_viewer_friend, // true/false
|
|
29
|
-
isMessengerUser: p.is_messenger_user, // true/false
|
|
30
|
-
isVerified: p.is_verified, // true/false
|
|
31
|
-
isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
|
|
32
|
-
isViewerCoworker: p.is_viewer_coworker, // true/false
|
|
33
|
-
isEmployee: p.is_employee // null? when it is something other? can someone check?
|
|
34
|
-
};
|
|
35
|
-
case "Page":
|
|
36
|
-
return {
|
|
37
|
-
accountType: p["__typename"],
|
|
38
|
-
userID: formatID(p.id.toString()), // or maybe... pageID?
|
|
39
|
-
name: p.name,
|
|
40
|
-
url: p.url,
|
|
41
|
-
profilePicture: p.big_image_src.uri,
|
|
42
|
-
username: p.username || null,
|
|
43
|
-
// uhm... better names maybe?
|
|
44
|
-
acceptsMessengerUserFeedback: p.accepts_messenger_user_feedback, // true/false
|
|
45
|
-
isMessengerUser: p.is_messenger_user, // true/false
|
|
46
|
-
isVerified: p.is_verified, // true/false
|
|
47
|
-
isMessengerPlatformBot: p.is_messenger_platform_bot, // true/false
|
|
48
|
-
isMessageBlockedByViewer: p.is_message_blocked_by_viewer // true/false
|
|
49
|
-
};
|
|
50
|
-
case "ReducedMessagingActor":
|
|
51
|
-
case "UnavailableMessagingActor":
|
|
52
|
-
return {
|
|
53
|
-
accountType: p["__typename"],
|
|
54
|
-
userID: formatID(p.id.toString()),
|
|
55
|
-
name: p.name,
|
|
56
|
-
url: createProfileUrl(p.url, p.username, p.id), // in this case p.url is null all the time
|
|
57
|
-
profilePicture: p.big_image_src.uri, // in this case it is default facebook photo, we could determine gender using it
|
|
58
|
-
username: p.username || null, // maybe we could use it to generate profile URL?
|
|
59
|
-
isMessageBlockedByViewer: p.is_message_blocked_by_viewer // true/false
|
|
60
|
-
};
|
|
61
|
-
default:
|
|
62
|
-
log.warn(
|
|
63
|
-
"getThreadList",
|
|
64
|
-
"Found participant with unsupported typename. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues\n" +
|
|
65
|
-
JSON.stringify(p, null, 2)
|
|
66
|
-
);
|
|
67
|
-
return {
|
|
68
|
-
accountType: p["__typename"],
|
|
69
|
-
userID: formatID(p.id.toString()),
|
|
70
|
-
name: p.name || `[unknown ${p["__typename"]}]` // probably it will always be something... but fallback to [unknown], just in case
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// "FF8C0077" -> "8C0077"
|
|
77
|
-
function formatColor(color) {
|
|
78
|
-
if (color && color.match(/^(?:[0-9a-fA-F]{8})$/g)) return color.slice(2);
|
|
79
|
-
return color;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function getThreadName(t) {
|
|
83
|
-
if (t.name || t.thread_key.thread_fbid) return t.name;
|
|
84
|
-
|
|
85
|
-
for (let po of t.all_participants.edges) {
|
|
86
|
-
let p = po.node;
|
|
87
|
-
if (p.messaging_actor.id === t.thread_key.other_user_id)
|
|
88
|
-
return p.messaging_actor.name;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function mapNicknames(customizationInfo) {
|
|
93
|
-
return customizationInfo && customizationInfo.participant_customizations
|
|
94
|
-
? customizationInfo.participant_customizations.map(u => {
|
|
95
|
-
return {
|
|
96
|
-
userID: u.participant_id,
|
|
97
|
-
nickname: u.nickname
|
|
98
|
-
};
|
|
99
|
-
})
|
|
100
|
-
: [];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function formatThreadList(data) {
|
|
104
|
-
return data.map(t => {
|
|
105
|
-
let lastMessageNode =
|
|
106
|
-
t.last_message && t.last_message.nodes && t.last_message.nodes.length > 0
|
|
107
|
-
? t.last_message.nodes[0]
|
|
108
|
-
: null;
|
|
109
|
-
return {
|
|
110
|
-
threadID: t.thread_key
|
|
111
|
-
? formatID(t.thread_key.thread_fbid || t.thread_key.other_user_id)
|
|
112
|
-
: null, // shall never be null
|
|
113
|
-
name: getThreadName(t),
|
|
114
|
-
unreadCount: t.unread_count,
|
|
115
|
-
messageCount: t.messages_count,
|
|
116
|
-
imageSrc: t.image ? t.image.uri : null,
|
|
117
|
-
emoji: t.customization_info ? t.customization_info.emoji : null,
|
|
118
|
-
color: formatColor(
|
|
119
|
-
t.customization_info ? t.customization_info.outgoing_bubble_color : null
|
|
120
|
-
),
|
|
121
|
-
threadTheme: t.thread_theme,
|
|
122
|
-
nicknames: mapNicknames(t.customization_info),
|
|
123
|
-
muteUntil: t.mute_until,
|
|
124
|
-
participants: formatParticipants(t.all_participants),
|
|
125
|
-
adminIDs: t.thread_admins.map(a => a.id),
|
|
126
|
-
folder: t.folder,
|
|
127
|
-
isGroup: t.thread_type === "GROUP",
|
|
128
|
-
customizationEnabled: t.customization_enabled, // false for ONE_TO_ONE with Page or ReducedMessagingActor
|
|
129
|
-
participantAddMode: t.participant_add_mode_as_string, // "ADD" if "GROUP" and null if "ONE_TO_ONE"
|
|
130
|
-
montageThread: t.montage_thread
|
|
131
|
-
? Buffer.from(t.montage_thread.id, "base64").toString()
|
|
132
|
-
: null, // base64 encoded string "message_thread:0000000000000000"
|
|
133
|
-
reactionsMuteMode: t.reactions_mute_mode,
|
|
134
|
-
mentionsMuteMode: t.mentions_mute_mode,
|
|
135
|
-
isArchived: t.has_viewer_archived,
|
|
136
|
-
isSubscribed: t.is_viewer_subscribed,
|
|
137
|
-
timestamp: t.updated_time_precise, // in miliseconds
|
|
138
|
-
snippet: lastMessageNode ? lastMessageNode.snippet : null,
|
|
139
|
-
snippetAttachments: lastMessageNode
|
|
140
|
-
? lastMessageNode.extensible_attachment
|
|
141
|
-
: null, // TODO: not sure if it works
|
|
142
|
-
snippetSender: lastMessageNode
|
|
143
|
-
? formatID(
|
|
144
|
-
(lastMessageNode.message_sender.messaging_actor.id || "").toString()
|
|
145
|
-
)
|
|
146
|
-
: null,
|
|
147
|
-
lastMessageTimestamp: lastMessageNode
|
|
148
|
-
? lastMessageNode.timestamp_precise
|
|
149
|
-
: null, // timestamp in miliseconds
|
|
150
|
-
lastReadTimestamp:
|
|
151
|
-
t.last_read_receipt && t.last_read_receipt.nodes.length > 0
|
|
152
|
-
? t.last_read_receipt.nodes[0]
|
|
153
|
-
? t.last_read_receipt.nodes[0].timestamp_precise
|
|
154
|
-
: null
|
|
155
|
-
: null,
|
|
156
|
-
cannotReplyReason: t.cannot_reply_reason,
|
|
157
|
-
approvalMode: Boolean(t.approval_mode),
|
|
158
|
-
participantIDs: formatParticipants(t.all_participants).map(
|
|
159
|
-
participant => participant.userID
|
|
160
|
-
),
|
|
161
|
-
threadType: t.thread_type === "GROUP" ? 2 : 1, // "GROUP" or "ONE_TO_ONE"
|
|
162
|
-
inviteLink: {
|
|
163
|
-
enable: t.joinable_mode ? t.joinable_mode.mode == 1 : false,
|
|
164
|
-
link: t.joinable_mode ? t.joinable_mode.link : null
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = function(defaultFuncs, api, ctx) {
|
|
171
|
-
return function getThreadList(limit, timestamp, tags, callback) {
|
|
172
|
-
if (
|
|
173
|
-
!callback &&
|
|
174
|
-
(getType(tags) === "Function" ||
|
|
175
|
-
getType(tags) === "AsyncFunction")
|
|
176
|
-
) {
|
|
177
|
-
callback = tags;
|
|
178
|
-
tags = [""];
|
|
179
|
-
}
|
|
180
|
-
if (
|
|
181
|
-
getType(limit) !== "Number" ||
|
|
182
|
-
!Number.isInteger(limit) ||
|
|
183
|
-
limit <= 0
|
|
184
|
-
)
|
|
185
|
-
throw { error: "getThreadList: limit must be a positive integer" };
|
|
186
|
-
if (
|
|
187
|
-
getType(timestamp) !== "Null" &&
|
|
188
|
-
(getType(timestamp) !== "Number" || !Number.isInteger(timestamp))
|
|
189
|
-
)
|
|
190
|
-
throw { error: "getThreadList: timestamp must be an integer or null" };
|
|
191
|
-
if (getType(tags) === "String") tags = [tags];
|
|
192
|
-
if (getType(tags) !== "Array")
|
|
193
|
-
throw { error: "getThreadList: tags must be an array" };
|
|
194
|
-
var resolveFunc = function() {};
|
|
195
|
-
var rejectFunc = function() {};
|
|
196
|
-
var returnPromise = new Promise(function(resolve, reject) {
|
|
197
|
-
resolveFunc = resolve;
|
|
198
|
-
rejectFunc = reject;
|
|
199
|
-
});
|
|
200
|
-
if (
|
|
201
|
-
getType(callback) !== "Function" &&
|
|
202
|
-
getType(callback) !== "AsyncFunction"
|
|
203
|
-
) {
|
|
204
|
-
callback = function(err, data) {
|
|
205
|
-
if (err) return rejectFunc(err);
|
|
206
|
-
resolveFunc(data);
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const form = {
|
|
210
|
-
av: ctx.userID,
|
|
211
|
-
queries: JSON.stringify({
|
|
212
|
-
o0: {
|
|
213
|
-
doc_id: "3336396659757871",
|
|
214
|
-
query_params: {
|
|
215
|
-
limit: limit + (timestamp ? 1 : 0),
|
|
216
|
-
before: timestamp,
|
|
217
|
-
tags: tags,
|
|
218
|
-
includeDeliveryReceipts: true,
|
|
219
|
-
includeSeqID: false
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}),
|
|
223
|
-
batch_name: "MessengerGraphQLThreadlistFetcher"
|
|
224
|
-
};
|
|
225
|
-
defaultFuncs
|
|
226
|
-
.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
|
|
227
|
-
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
228
|
-
.then(resData => {
|
|
229
|
-
// Validate resData is an array and has elements
|
|
230
|
-
if (!resData || !Array.isArray(resData) || resData.length === 0) {
|
|
231
|
-
throw {
|
|
232
|
-
error: "getThreadList: Invalid response data - resData is not a valid array",
|
|
233
|
-
res: resData
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Validate last element exists and has required properties
|
|
238
|
-
const lastElement = resData[resData.length - 1];
|
|
239
|
-
if (!lastElement || typeof lastElement !== "object") {
|
|
240
|
-
throw {
|
|
241
|
-
error: "getThreadList: Invalid response data - last element is missing or invalid",
|
|
242
|
-
res: resData
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (lastElement.error_results > 0) {
|
|
247
|
-
// Check if first element and o0 exist before accessing errors
|
|
248
|
-
if (resData[0] && resData[0].o0 && resData[0].o0.errors) {
|
|
249
|
-
throw resData[0].o0.errors;
|
|
250
|
-
} else {
|
|
251
|
-
throw {
|
|
252
|
-
error: "getThreadList: Error results > 0 but error details not available",
|
|
253
|
-
res: resData
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (lastElement.successful_results === 0) {
|
|
259
|
-
throw {
|
|
260
|
-
error: "getThreadList: there was no successful_results",
|
|
261
|
-
res: resData
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Validate first element and nested data structure
|
|
266
|
-
if (!resData[0] || !resData[0].o0 || !resData[0].o0.data ||
|
|
267
|
-
!resData[0].o0.data.viewer || !resData[0].o0.data.viewer.message_threads ||
|
|
268
|
-
!Array.isArray(resData[0].o0.data.viewer.message_threads.nodes)) {
|
|
269
|
-
throw {
|
|
270
|
-
error: "getThreadList: Invalid response data structure - missing required fields",
|
|
271
|
-
res: resData
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (timestamp) {
|
|
276
|
-
const nodes = resData[0].o0.data.viewer.message_threads.nodes;
|
|
277
|
-
if (Array.isArray(nodes) && nodes.length > 0) {
|
|
278
|
-
nodes.shift();
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
callback(
|
|
283
|
-
null,
|
|
284
|
-
formatThreadList(resData[0].o0.data.viewer.message_threads.nodes)
|
|
285
|
-
);
|
|
286
|
-
})
|
|
287
|
-
.catch(err => {
|
|
288
|
-
log.error("getThreadList", err);
|
|
289
|
-
return callback(err);
|
|
290
|
-
});
|
|
291
|
-
return returnPromise;
|
|
292
|
-
};
|
|
293
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const log = require("../../../func/logAdapter");
|
|
4
|
-
const { parseAndCheckLogin } = require("../../utils/client");
|
|
5
|
-
module.exports = function(defaultFuncs, api, ctx) {
|
|
6
|
-
return function getThreadPictures(threadID, offset, limit, callback) {
|
|
7
|
-
let resolveFunc = function() {};
|
|
8
|
-
let rejectFunc = function() {};
|
|
9
|
-
const returnPromise = new Promise(function(resolve, reject) {
|
|
10
|
-
resolveFunc = resolve;
|
|
11
|
-
rejectFunc = reject;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
if (!callback) {
|
|
15
|
-
callback = function(err, friendList) {
|
|
16
|
-
if (err) {
|
|
17
|
-
return rejectFunc(err);
|
|
18
|
-
}
|
|
19
|
-
resolveFunc(friendList);
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let form = {
|
|
24
|
-
thread_id: threadID,
|
|
25
|
-
offset: offset,
|
|
26
|
-
limit: limit
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
defaultFuncs
|
|
30
|
-
.post(
|
|
31
|
-
"https://www.facebook.com/ajax/messaging/attachments/sharedphotos.php",
|
|
32
|
-
ctx.jar,
|
|
33
|
-
form
|
|
34
|
-
)
|
|
35
|
-
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
36
|
-
.then(function(resData) {
|
|
37
|
-
if (resData.error) {
|
|
38
|
-
throw resData;
|
|
39
|
-
}
|
|
40
|
-
return Promise.all(
|
|
41
|
-
resData.payload.imagesData.map(function(image) {
|
|
42
|
-
form = {
|
|
43
|
-
thread_id: threadID,
|
|
44
|
-
image_id: image.fbid
|
|
45
|
-
};
|
|
46
|
-
return defaultFuncs
|
|
47
|
-
.post(
|
|
48
|
-
"https://www.facebook.com/ajax/messaging/attachments/sharedphotos.php",
|
|
49
|
-
ctx.jar,
|
|
50
|
-
form
|
|
51
|
-
)
|
|
52
|
-
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
53
|
-
.then(function(resData) {
|
|
54
|
-
if (resData.error) {
|
|
55
|
-
throw resData;
|
|
56
|
-
}
|
|
57
|
-
// the response is pretty messy
|
|
58
|
-
const queryThreadID =
|
|
59
|
-
resData.jsmods.require[0][3][1].query_metadata.query_path[0]
|
|
60
|
-
.message_thread;
|
|
61
|
-
const imageData =
|
|
62
|
-
resData.jsmods.require[0][3][1].query_results[queryThreadID]
|
|
63
|
-
.message_images.edges[0].node.image2;
|
|
64
|
-
return imageData;
|
|
65
|
-
});
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
})
|
|
69
|
-
.then(function(resData) {
|
|
70
|
-
callback(null, resData);
|
|
71
|
-
})
|
|
72
|
-
.catch(function(err) {
|
|
73
|
-
log.error("Error in getThreadPictures", err);
|
|
74
|
-
callback(err);
|
|
75
|
-
});
|
|
76
|
-
return returnPromise;
|
|
77
|
-
};
|
|
78
|
-
};
|