fca-luxury 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. package/DOCS.md +1686 -0
  2. package/README.md +222 -0
  3. package/index.js +544 -0
  4. package/package.json +73 -0
  5. package/src/Screenshot.js +83 -0
  6. package/src/addExternalModule.js +15 -0
  7. package/src/addUserToGroup.js +77 -0
  8. package/src/changeAdminStatus.js +47 -0
  9. package/src/changeArchivedStatus.js +41 -0
  10. package/src/changeAvt.js +85 -0
  11. package/src/changeBio.js +65 -0
  12. package/src/changeBlockedStatus.js +36 -0
  13. package/src/changeGroupImage.js +106 -0
  14. package/src/changeNickname.js +45 -0
  15. package/src/changeThreadColor.js +61 -0
  16. package/src/changeThreadEmoji.js +41 -0
  17. package/src/createNewGroup.js +70 -0
  18. package/src/createPoll.js +59 -0
  19. package/src/deleteMessage.js +44 -0
  20. package/src/deleteThread.js +42 -0
  21. package/src/editMessage.js +55 -0
  22. package/src/forwardAttachment.js +47 -0
  23. package/src/getCurrentUserID.js +7 -0
  24. package/src/getEmojiUrl.js +27 -0
  25. package/src/getFriendsList.js +73 -0
  26. package/src/getThreadHistory.js +537 -0
  27. package/src/getThreadHistoryDeprecated.js +71 -0
  28. package/src/getThreadInfo.js +171 -0
  29. package/src/getThreadInfoDeprecated.js +56 -0
  30. package/src/getThreadList.js +213 -0
  31. package/src/getThreadListDeprecated.js +46 -0
  32. package/src/getThreadPictures.js +59 -0
  33. package/src/getUserID.js +61 -0
  34. package/src/getUserInfo.js +66 -0
  35. package/src/handleFriendRequest.js +46 -0
  36. package/src/handleMessageRequest.js +47 -0
  37. package/src/httpGet.js +49 -0
  38. package/src/httpPost.js +48 -0
  39. package/src/listenMqtt.js +701 -0
  40. package/src/logout.js +68 -0
  41. package/src/markAsDelivered.js +47 -0
  42. package/src/markAsRead.js +70 -0
  43. package/src/markAsReadAll.js +40 -0
  44. package/src/markAsSeen.js +48 -0
  45. package/src/muteThread.js +45 -0
  46. package/src/removeUserFromGroup.js +45 -0
  47. package/src/resolvePhotoUrl.js +36 -0
  48. package/src/searchForThread.js +42 -0
  49. package/src/sendMessage.js +461 -0
  50. package/src/sendTypingIndicator.js +70 -0
  51. package/src/setMessageReaction.js +109 -0
  52. package/src/setPostReaction.js +102 -0
  53. package/src/setTitle.js +70 -0
  54. package/src/shareContact.js +46 -0
  55. package/src/shareLink.js +55 -0
  56. package/src/threadColors.js +41 -0
  57. package/src/unfriend.js +42 -0
  58. package/src/unsendMessage.js +39 -0
  59. package/test/data/shareAttach.js +146 -0
  60. package/test/data/something.mov +0 -0
  61. package/test/data/test.png +0 -0
  62. package/test/data/test.txt +7 -0
  63. package/test/example-config.json +18 -0
  64. package/test/test-page.js +140 -0
  65. package/test/test.js +385 -0
  66. package/utils.js +1196 -0
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+
6
+ function formatEventReminders(reminder) {
7
+ return {
8
+ reminderID: reminder.id,
9
+ eventCreatorID: reminder.lightweight_event_creator.id,
10
+ time: reminder.time,
11
+ eventType: reminder.lightweight_event_type.toLowerCase(),
12
+ locationName: reminder.location_name,
13
+ // @TODO verify this
14
+ locationCoordinates: reminder.location_coordinates,
15
+ locationPage: reminder.location_page,
16
+ eventStatus: reminder.lightweight_event_status.toLowerCase(),
17
+ note: reminder.note,
18
+ repeatMode: reminder.repeat_mode.toLowerCase(),
19
+ eventTitle: reminder.event_title,
20
+ triggerMessage: reminder.trigger_message,
21
+ secondsToNotifyBefore: reminder.seconds_to_notify_before,
22
+ allowsRsvp: reminder.allows_rsvp,
23
+ relatedEvent: reminder.related_event,
24
+ members: reminder.event_reminder_members.edges.map(function (member) {
25
+ return {
26
+ memberID: member.node.id,
27
+ state: member.guest_list_state.toLowerCase()
28
+ };
29
+ })
30
+ };
31
+ }
32
+
33
+ function formatThreadGraphQLResponse(data) {
34
+ var messageThread = data.o0.data.message_thread;
35
+ var threadID = messageThread.thread_key.thread_fbid ? messageThread.thread_key.thread_fbid : messageThread.thread_key.other_user_id;
36
+
37
+ // Remove me
38
+ var lastM = messageThread.last_message;
39
+ var snippetID = lastM && lastM.nodes && lastM.nodes[0] && lastM.nodes[0].message_sender && lastM.nodes[0].message_sender.messaging_actor ? lastM.nodes[0].message_sender.messaging_actor.id : null;
40
+ var snippetText = lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
41
+ var lastR = messageThread.last_read_receipt;
42
+ var lastReadTimestamp = lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise ? lastR.nodes[0].timestamp_precise : null;
43
+
44
+ return {
45
+ threadID: threadID,
46
+ threadName: messageThread.name,
47
+ participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id),
48
+ userInfo: messageThread.all_participants.edges.map(d => ({
49
+ id: d.node.messaging_actor.id,
50
+ name: d.node.messaging_actor.name,
51
+ firstName: d.node.messaging_actor.short_name,
52
+ vanity: d.node.messaging_actor.username,
53
+ thumbSrc: d.node.messaging_actor.big_image_src.uri,
54
+ profileUrl: d.node.messaging_actor.big_image_src.uri,
55
+ gender: d.node.messaging_actor.gender,
56
+ type: d.node.messaging_actor.__typename,
57
+ isFriend: d.node.messaging_actor.is_viewer_friend,
58
+ isBirthday: !!d.node.messaging_actor.is_birthday //not sure?
59
+ })),
60
+ unreadCount: messageThread.unread_count,
61
+ messageCount: messageThread.messages_count,
62
+ timestamp: messageThread.updated_time_precise,
63
+ muteUntil: messageThread.mute_until,
64
+ isGroup: messageThread.thread_type == "GROUP",
65
+ isSubscribed: messageThread.is_viewer_subscribed,
66
+ isArchived: messageThread.has_viewer_archived,
67
+ folder: messageThread.folder,
68
+ cannotReplyReason: messageThread.cannot_reply_reason,
69
+ eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders) : null,
70
+ emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
71
+ color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
72
+ nicknames:
73
+ messageThread.customization_info &&
74
+ messageThread.customization_info.participant_customizations
75
+ ? messageThread.customization_info.participant_customizations.reduce(function (res, val) {
76
+ if (val.nickname) res[val.participant_id] = val.nickname;
77
+ return res;
78
+ }, {})
79
+ : {},
80
+ adminIDs: messageThread.thread_admins,
81
+ approvalMode: Boolean(messageThread.approval_mode),
82
+ approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({
83
+ inviterID: a.inviter.id,
84
+ requesterID: a.requester.id,
85
+ timestamp: a.request_timestamp,
86
+ request_source: a.request_source // @Undocumented
87
+ })),
88
+
89
+ // @Undocumented
90
+ reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
91
+ mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
92
+ isPinProtected: messageThread.is_pin_protected,
93
+ relatedPageThread: messageThread.related_page_thread,
94
+
95
+ // @Legacy
96
+ name: messageThread.name,
97
+ snippet: snippetText,
98
+ snippetSender: snippetID,
99
+ snippetAttachments: [],
100
+ serverTimestamp: messageThread.updated_time_precise,
101
+ imageSrc: messageThread.image ? messageThread.image.uri : null,
102
+ isCanonicalUser: messageThread.is_canonical_neo_user,
103
+ isCanonical: messageThread.thread_type != "GROUP",
104
+ recipientsLoadable: true,
105
+ hasEmailParticipant: false,
106
+ readOnly: false,
107
+ canReply: messageThread.cannot_reply_reason == null,
108
+ lastMessageTimestamp: messageThread.last_message ? messageThread.last_message.timestamp_precise : null,
109
+ lastMessageType: "message",
110
+ lastReadTimestamp: lastReadTimestamp,
111
+ threadType: messageThread.thread_type == "GROUP" ? 2 : 1
112
+ };
113
+ }
114
+
115
+ module.exports = function (defaultFuncs, api, ctx) {
116
+ return function getThreadInfoGraphQL(threadID, callback) {
117
+ var resolveFunc = function () { };
118
+ var rejectFunc = function () { };
119
+ var returnPromise = new Promise(function (resolve, reject) {
120
+ resolveFunc = resolve;
121
+ rejectFunc = reject;
122
+ });
123
+
124
+ if (utils.getType(callback) != "Function" && utils.getType(callback) != "AsyncFunction") {
125
+ callback = function (err, data) {
126
+ if (err) return rejectFunc(err);
127
+ resolveFunc(data);
128
+ };
129
+ }
130
+
131
+ // `queries` has to be a string. I couldn't tell from the dev console. This
132
+ // took me a really long time to figure out. I deserve a cookie for this.
133
+ var form = {
134
+ queries: JSON.stringify({
135
+ o0: {
136
+ // This doc_id is valid as of July 20th, 2020
137
+ doc_id: "3449967031715030",
138
+ query_params: {
139
+ id: threadID,
140
+ message_limit: 0,
141
+ load_messages: false,
142
+ load_read_receipts: false,
143
+ before: null
144
+ }
145
+ }
146
+ }),
147
+ batch_name: "MessengerGraphQLThreadFetcher"
148
+ };
149
+
150
+ defaultFuncs
151
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
152
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
153
+ .then(function (resData) {
154
+ if (resData.error) throw resData;
155
+ // This returns us an array of things. The last one is the success /
156
+ // failure one.
157
+ // @TODO What do we do in this case?
158
+ if (resData[resData.length - 1].error_results !== 0) {
159
+ console.log(resData); //Log more info
160
+ throw new Error("well darn there was an error_result");
161
+ }
162
+ callback(null, formatThreadGraphQLResponse(resData[0]));
163
+ })
164
+ .catch(function (err) {
165
+ log.error("getThreadInfoGraphQL", err);
166
+ return callback(err);
167
+ });
168
+
169
+ return returnPromise;
170
+ };
171
+ };
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return function getThreadInfo(threadID, callback) {
8
+ var resolveFunc = function () { };
9
+ var rejectFunc = function () { };
10
+ var returnPromise = new Promise(function (resolve, reject) {
11
+ resolveFunc = resolve;
12
+ rejectFunc = reject;
13
+ });
14
+
15
+ if (!callback) {
16
+ callback = function (err, data) {
17
+ if (err) return rejectFunc(err);
18
+ resolveFunc(data);
19
+ };
20
+ }
21
+
22
+ var form = {
23
+ client: "mercury"
24
+ };
25
+
26
+ api.getUserInfo(threadID, function (err, userRes) {
27
+ if (err) return callback(err);
28
+ var key = Object.keys(userRes).length > 0 ? "user_ids" : "thread_fbids";
29
+ form["threads[" + key + "][0]"] = threadID;
30
+
31
+ if (ctx.globalOptions.pageId) form.request_user_id = ctx.globalOptions.pageId;
32
+
33
+ defaultFuncs
34
+ .post("https://www.facebook.com/ajax/mercury/thread_info.php", ctx.jar, form)
35
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
36
+ .then(function (resData) {
37
+ if (resData.error) throw resData;
38
+ else if (!resData.payload) throw { error: "Could not retrieve thread Info." };
39
+
40
+ var threadData = resData.payload.threads[0];
41
+ var userData = userRes[threadID];
42
+
43
+ if (threadData == null) throw { error: "ThreadData is null" };
44
+
45
+ threadData.name = userData != null && userData.name != null ? userData.name : threadData.name;
46
+ threadData.image_src = userData != null && userData.thumbSrc != null ? userData.thumbSrc : threadData.image_src;
47
+ callback(null, utils.formatThread(threadData));
48
+ })
49
+ .catch(function (err) {
50
+ log.error("getThreadInfo", err);
51
+ return callback(err);
52
+ });
53
+ });
54
+ return returnPromise;
55
+ };
56
+ };
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+
3
+ const utils = require("../utils");
4
+ const log = require("npmlog");
5
+
6
+ function createProfileUrl(url, username, id) {
7
+ if (url) return url;
8
+ return "https://www.facebook.com/" + (username || utils.formatID(id.toString()));
9
+ }
10
+
11
+ function formatParticipants(participants) {
12
+ return participants.edges.map((p) => {
13
+ p = p.node.messaging_actor;
14
+ switch (p["__typename"]) {
15
+ case "User":
16
+ return {
17
+ accountType: p["__typename"],
18
+ userID: utils.formatID(p.id.toString()), // do we need .toString()? when it is not a string?
19
+ name: p.name,
20
+ shortName: p.short_name,
21
+ gender: p.gender,
22
+ url: p.url, // how about making it profileURL
23
+ profilePicture: p.big_image_src.uri,
24
+ username: (p.username || null),
25
+ // TODO: maybe better names for these?
26
+ isViewerFriend: p.is_viewer_friend, // true/false
27
+ isMessengerUser: p.is_messenger_user, // true/false
28
+ isVerified: p.is_verified, // true/false
29
+ isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
30
+ isViewerCoworker: p.is_viewer_coworker, // true/false
31
+ isEmployee: p.is_employee // null? when it is something other? can someone check?
32
+ };
33
+ case "Page":
34
+ return {
35
+ accountType: p["__typename"],
36
+ userID: utils.formatID(p.id.toString()), // or maybe... pageID?
37
+ name: p.name,
38
+ url: p.url,
39
+ profilePicture: p.big_image_src.uri,
40
+ username: (p.username || null),
41
+ // uhm... better names maybe?
42
+ acceptsMessengerUserFeedback: p.accepts_messenger_user_feedback, // true/false
43
+ isMessengerUser: p.is_messenger_user, // true/false
44
+ isVerified: p.is_verified, // true/false
45
+ isMessengerPlatformBot: p.is_messenger_platform_bot, // true/false
46
+ isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
47
+ };
48
+ case "ReducedMessagingActor":
49
+ case "UnavailableMessagingActor":
50
+ return {
51
+ accountType: p["__typename"],
52
+ userID: utils.formatID(p.id.toString()),
53
+ name: p.name,
54
+ url: createProfileUrl(p.url, p.username, p.id), // in this case p.url is null all the time
55
+ profilePicture: p.big_image_src.uri, // in this case it is default facebook photo, we could determine gender using it
56
+ username: (p.username || null), // maybe we could use it to generate profile URL?
57
+ isMessageBlockedByViewer: p.is_message_blocked_by_viewer, // true/false
58
+ };
59
+ default:
60
+ log.warn("getThreadList", "Found participant with unsupported typename. Please open an issue at https://github.com/Schmavery/facebook-chat-api/issues\n" + JSON.stringify(p, null, 2));
61
+ return {
62
+ accountType: p["__typename"],
63
+ userID: utils.formatID(p.id.toString()),
64
+ name: p.name || `[unknown ${p["__typename"]}]`, // probably it will always be something... but fallback to [unknown], just in case
65
+ };
66
+ }
67
+ });
68
+ }
69
+
70
+ // "FF8C0077" -> "8C0077"
71
+ function formatColor(color) {
72
+ if (color && color.match(/^(?:[0-9a-fA-F]{8})$/g)) return color.slice(2);
73
+ return color;
74
+ }
75
+
76
+ function getThreadName(t) {
77
+ if (t.name || t.thread_key.thread_fbid) return t.name;
78
+
79
+ for (let po of t.all_participants.edges) {
80
+ let p = po.node;
81
+ if (p.messaging_actor.id === t.thread_key.other_user_id) return p.messaging_actor.name;
82
+ }
83
+ }
84
+
85
+ function mapNicknames(customizationInfo) {
86
+ return (customizationInfo && customizationInfo.participant_customizations) ? customizationInfo.participant_customizations.map(u => {
87
+ return {
88
+ "userID": u.participant_id,
89
+ "nickname": u.nickname
90
+ };
91
+ }) : [];
92
+ }
93
+
94
+ function formatThreadList(data) {
95
+ return data.map(t => {
96
+ let lastMessageNode = (t.last_message && t.last_message.nodes && t.last_message.nodes.length > 0) ? t.last_message.nodes[0] : null;
97
+ return {
98
+ threadID: t.thread_key ? utils.formatID(t.thread_key.thread_fbid || t.thread_key.other_user_id) : null, // shall never be null
99
+ name: getThreadName(t),
100
+ unreadCount: t.unread_count,
101
+ messageCount: t.messages_count,
102
+ imageSrc: t.image ? t.image.uri : null,
103
+ emoji: t.customization_info ? t.customization_info.emoji : null,
104
+ color: formatColor(t.customization_info ? t.customization_info.outgoing_bubble_color : null),
105
+ nicknames: mapNicknames(t.customization_info),
106
+ muteUntil: t.mute_until,
107
+ participants: formatParticipants(t.all_participants),
108
+ adminIDs: t.thread_admins.map(a => a.id),
109
+ folder: t.folder,
110
+ isGroup: t.thread_type === "GROUP",
111
+ // rtc_call_data: t.rtc_call_data, // TODO: format and document this
112
+ // isPinProtected: t.is_pin_protected, // feature from future? always false (2018-04-04)
113
+ customizationEnabled: t.customization_enabled, // false for ONE_TO_ONE with Page or ReducedMessagingActor
114
+ participantAddMode: t.participant_add_mode_as_string, // "ADD" if "GROUP" and null if "ONE_TO_ONE"
115
+ montageThread: t.montage_thread ? Buffer.from(t.montage_thread.id, "base64").toString() : null, // base64 encoded string "message_thread:0000000000000000"
116
+ // it is not userID nor any other ID known to me...
117
+ // can somebody inspect it? where is it used?
118
+ // probably Messenger Day uses it
119
+ reactionsMuteMode: t.reactions_mute_mode,
120
+ mentionsMuteMode: t.mentions_mute_mode,
121
+ isArchived: t.has_viewer_archived,
122
+ isSubscribed: t.is_viewer_subscribed,
123
+ timestamp: t.updated_time_precise, // in miliseconds
124
+ // isCanonicalUser: t.is_canonical_neo_user, // is it always false?
125
+ // TODO: how about putting snippet in another object? current implementation does not handle every possibile message type etc.
126
+ snippet: lastMessageNode ? lastMessageNode.snippet : null,
127
+ snippetAttachments: lastMessageNode ? lastMessageNode.extensible_attachment : null, // TODO: not sure if it works
128
+ snippetSender: lastMessageNode ? utils.formatID((lastMessageNode.message_sender.messaging_actor.id || "").toString()) : null,
129
+ lastMessageTimestamp: lastMessageNode ? lastMessageNode.timestamp_precise : null, // timestamp in miliseconds
130
+ lastReadTimestamp: (t.last_read_receipt && t.last_read_receipt.nodes.length > 0)
131
+ ? (t.last_read_receipt.nodes[0] ? t.last_read_receipt.nodes[0].timestamp_precise : null)
132
+ : null, // timestamp in miliseconds
133
+ cannotReplyReason: t.cannot_reply_reason, // TODO: inspect possible values
134
+ approvalMode: Boolean(t.approval_mode),
135
+
136
+ // @Legacy
137
+ participantIDs: formatParticipants(t.all_participants).map(participant => participant.userID),
138
+ threadType: t.thread_type === "GROUP" ? 2 : 1 // "GROUP" or "ONE_TO_ONE"
139
+ };
140
+ });
141
+ }
142
+
143
+ module.exports = function (defaultFuncs, api, ctx) {
144
+ return function getThreadList(limit, timestamp, tags, callback) {
145
+ if (!callback && (utils.getType(tags) === "Function" || utils.getType(tags) === "AsyncFunction")) {
146
+ callback = tags;
147
+ tags = [""];
148
+ }
149
+ if (utils.getType(limit) !== "Number" || !Number.isInteger(limit) || limit <= 0) throw { error: "getThreadList: limit must be a positive integer" };
150
+
151
+ if (utils.getType(timestamp) !== "Null" && (utils.getType(timestamp) !== "Number" || !Number.isInteger(timestamp))) throw { error: "getThreadList: timestamp must be an integer or null" };
152
+
153
+ if (utils.getType(tags) === "String") tags = [tags];
154
+ if (utils.getType(tags) !== "Array") throw { error: "getThreadList: tags must be an array" };
155
+
156
+ var resolveFunc = function () { };
157
+ var rejectFunc = function () { };
158
+ var returnPromise = new Promise(function (resolve, reject) {
159
+ resolveFunc = resolve;
160
+ rejectFunc = reject;
161
+ });
162
+
163
+ if (utils.getType(callback) !== "Function" && utils.getType(callback) !== "AsyncFunction") {
164
+ callback = function (err, data) {
165
+ if (err) return rejectFunc(err);
166
+ resolveFunc(data);
167
+ };
168
+ }
169
+
170
+ const form = {
171
+ "av": ctx.globalOptions.pageID,
172
+ "queries": JSON.stringify({
173
+ "o0": {
174
+ // This doc_id was valid on 2020-07-20
175
+ "doc_id": "3336396659757871",
176
+ "query_params": {
177
+ "limit": limit + (timestamp ? 1 : 0),
178
+ "before": timestamp,
179
+ "tags": tags,
180
+ "includeDeliveryReceipts": true,
181
+ "includeSeqID": false
182
+ }
183
+ }
184
+ }),
185
+ "batch_name": "MessengerGraphQLThreadlistFetcher"
186
+ };
187
+
188
+ defaultFuncs
189
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
190
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
191
+ .then((resData) => {
192
+ if (resData[resData.length - 1].error_results > 0) throw resData[0].o0.errors;
193
+
194
+ if (resData[resData.length - 1].successful_results === 0) throw { error: "getThreadList: there was no successful_results", res: resData };
195
+
196
+ // When we ask for threads using timestamp from the previous request,
197
+ // we are getting the last thread repeated as the first thread in this response.
198
+ // .shift() gets rid of it
199
+ // It is also the reason for increasing limit by 1 when timestamp is set
200
+ // this way user asks for 10 threads, we are asking for 11,
201
+ // but after removing the duplicated one, it is again 10
202
+ if (timestamp) resData[0].o0.data.viewer.message_threads.nodes.shift();
203
+
204
+ callback(null, formatThreadList(resData[0].o0.data.viewer.message_threads.nodes));
205
+ })
206
+ .catch((err) => {
207
+ log.error("getThreadList", err);
208
+ return callback(err);
209
+ });
210
+
211
+ return returnPromise;
212
+ };
213
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return function getThreadList(start, end, type, callback) {
8
+ if (utils.getType(callback) === "Undefined") {
9
+ if (utils.getType(end) !== "Number") throw { error: "Please pass a number as a second argument." };
10
+ else if (utils.getType(type) === "Function" || utils.getType(type) === "AsyncFunction") {
11
+ callback = type;
12
+ type = "inbox"; //default to inbox
13
+ }
14
+ else if (utils.getType(type) !== "String") throw { error: "Please pass a String as a third argument. Your options are: inbox, pending, and archived" };
15
+ else throw { error: "getThreadList: need callback" };
16
+ }
17
+
18
+ if (type === "archived") type = "action:archived";
19
+ else if (type !== "inbox" && type !== "pending" && type !== "other") throw { error: "type can only be one of the following: inbox, pending, archived, other" };
20
+
21
+
22
+ if (end <= start) end = start + 20;
23
+
24
+ var form = {
25
+ client: "mercury"
26
+ };
27
+
28
+ form[type + "[offset]"] = start;
29
+ form[type + "[limit]"] = end - start;
30
+
31
+ if (ctx.globalOptions.pageID) form.request_user_id = ctx.globalOptions.pageID;
32
+
33
+ defaultFuncs
34
+ .post("https://www.facebook.com/ajax/mercury/threadlist_info.php", ctx.jar, form)
35
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
36
+ .then(function (resData) {
37
+ if (resData.error) throw resData;
38
+ log.verbose("getThreadList", JSON.stringify(resData.payload.threads));
39
+ return callback(null, (resData.payload.threads || []).map(utils.formatThread));
40
+ })
41
+ .catch(function (err) {
42
+ log.error("getThreadList", err);
43
+ return callback(err);
44
+ });
45
+ };
46
+ };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return function getThreadPictures(threadID, offset, limit, callback) {
8
+ var resolveFunc = function () { };
9
+ var rejectFunc = function () { };
10
+ var returnPromise = new Promise(function (resolve, reject) {
11
+ resolveFunc = resolve;
12
+ rejectFunc = reject;
13
+ });
14
+
15
+ if (!callback) {
16
+ callback = function (err, data) {
17
+ if (err) return rejectFunc(err);
18
+ resolveFunc(data);
19
+ };
20
+ }
21
+
22
+ var form = {
23
+ thread_id: threadID,
24
+ offset: offset,
25
+ limit: limit
26
+ };
27
+
28
+ defaultFuncs
29
+ .post("https://www.facebook.com/ajax/messaging/attachments/sharedphotos.php", ctx.jar, form)
30
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
31
+ .then(function (resData) {
32
+ if (resData.error) throw resData;
33
+ return Promise.all(
34
+ resData.payload.imagesData.map(function (image) {
35
+ form = {
36
+ thread_id: threadID,
37
+ image_id: image.fbid
38
+ };
39
+ return defaultFuncs
40
+ .post("https://www.facebook.com/ajax/messaging/attachments/sharedphotos.php", ctx.jar, form)
41
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
42
+ .then(function (resData) {
43
+ if (resData.error) throw resData;
44
+ // the response is pretty messy
45
+ var queryThreadID = resData.jsmods.require[0][3][1].query_metadata.query_path[0].message_thread;
46
+ var imageData = resData.jsmods.require[0][3][1].query_results[queryThreadID].message_images.edges[0].node.image2;
47
+ return imageData;
48
+ });
49
+ })
50
+ );
51
+ })
52
+ .then(resData => callback(null, resData))
53
+ .catch(function (err) {
54
+ log.error("Error in getThreadPictures", err);
55
+ callback(err);
56
+ });
57
+ return returnPromise;
58
+ };
59
+ };
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+
6
+ function formatData(data) {
7
+ return {
8
+ userID: utils.formatID(data.uid.toString()),
9
+ photoUrl: data.photo,
10
+ indexRank: data.index_rank,
11
+ name: data.text,
12
+ isVerified: data.is_verified,
13
+ profileUrl: data.path,
14
+ category: data.category,
15
+ score: data.score,
16
+ type: data.type
17
+ };
18
+ }
19
+
20
+ module.exports = function (defaultFuncs, api, ctx) {
21
+ return function getUserID(name, callback) {
22
+ var resolveFunc = function () { };
23
+ var rejectFunc = function () { };
24
+ var returnPromise = new Promise(function (resolve, reject) {
25
+ resolveFunc = resolve;
26
+ rejectFunc = reject;
27
+ });
28
+
29
+ if (!callback) {
30
+ callback = function (err, data) {
31
+ if (err) return rejectFunc(err);
32
+ resolveFunc(data);
33
+ };
34
+ }
35
+
36
+ var form = {
37
+ value: name.toLowerCase(),
38
+ viewer: ctx.userID,
39
+ rsp: "search",
40
+ context: "search",
41
+ path: "/home.php",
42
+ request_id: utils.getGUID()
43
+ };
44
+
45
+ defaultFuncs
46
+ .get("https://www.facebook.com/ajax/typeahead/search.php", ctx.jar, form)
47
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
48
+ .then(function (resData) {
49
+ if (resData.error) throw resData;
50
+
51
+ var data = resData.payload.entries;
52
+ callback(null, data.map(formatData));
53
+ })
54
+ .catch(function (err) {
55
+ log.error("getUserID", err);
56
+ return callback(err);
57
+ });
58
+
59
+ return returnPromise;
60
+ };
61
+ };
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ var log = require("npmlog");
5
+
6
+ function formatData(data) {
7
+ var retObj = {};
8
+
9
+ for (var prop in data) {
10
+ // eslint-disable-next-line no-prototype-builtins
11
+ if (data.hasOwnProperty(prop)) {
12
+ var innerObj = data[prop];
13
+ retObj[prop] = {
14
+ name: innerObj.name,
15
+ firstName: innerObj.firstName,
16
+ vanity: innerObj.vanity,
17
+ thumbSrc: innerObj.thumbSrc,
18
+ profileUrl: innerObj.uri,
19
+ gender: innerObj.gender,
20
+ type: innerObj.type,
21
+ isFriend: innerObj.is_friend,
22
+ isBirthday: !!innerObj.is_birthday
23
+ };
24
+ }
25
+ }
26
+
27
+ return retObj;
28
+ }
29
+
30
+ module.exports = function (defaultFuncs, api, ctx) {
31
+ return function getUserInfo(id, callback) {
32
+ var resolveFunc = function () { };
33
+ var rejectFunc = function () { };
34
+ var returnPromise = new Promise(function (resolve, reject) {
35
+ resolveFunc = resolve;
36
+ rejectFunc = reject;
37
+ });
38
+
39
+ if (!callback) {
40
+ callback = function (err, userInfo) {
41
+ if (err) return rejectFunc(err);
42
+ resolveFunc(userInfo);
43
+ };
44
+ }
45
+
46
+ if (utils.getType(id) !== "Array") id = [id];
47
+
48
+ var form = {};
49
+ id.map(function (v, i) {
50
+ form["ids[" + i + "]"] = v;
51
+ });
52
+ defaultFuncs
53
+ .post("https://www.facebook.com/chat/user_info/", ctx.jar, form)
54
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
55
+ .then(function (resData) {
56
+ if (resData.error) throw resData;
57
+ return callback(null, formatData(resData.payload.profiles));
58
+ })
59
+ .catch(function (err) {
60
+ log.error("getUserInfo", err);
61
+ return callback(err);
62
+ });
63
+
64
+ return returnPromise;
65
+ };
66
+ };