metacord 0.0.1-security → 1.2.0-Beta
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.
Potentially problematic release.
This version of metacord might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/MetaCord_Config.json +11 -0
- package/MetaCord_Database/Database.sqlite +0 -0
- package/MetaCord_Database/Do not delete this folder or any of the files in it +0 -0
- package/README.md +207 -5
- package/index.js +678 -0
- package/logger.js +16 -0
- package/package.json +38 -3
- package/src/addExternalModule.js +16 -0
- package/src/addUserToGroup.js +78 -0
- package/src/changeAdminStatus.js +78 -0
- package/src/changeArchivedStatus.js +41 -0
- package/src/changeBio.js +65 -0
- package/src/changeBlockedStatus.js +36 -0
- package/src/changeGroupImage.js +106 -0
- package/src/changeNickname.js +45 -0
- package/src/changeThreadColor.js +62 -0
- package/src/changeThreadEmoji.js +42 -0
- package/src/createNewGroup.js +70 -0
- package/src/createPoll.js +60 -0
- package/src/deleteMessage.js +45 -0
- package/src/deleteThread.js +43 -0
- package/src/forwardAttachment.js +48 -0
- package/src/getCurrentUserID.js +7 -0
- package/src/getEmojiUrl.js +27 -0
- package/src/getFriendsList.js +73 -0
- package/src/getOnlineTime.js +31 -0
- package/src/getThreadHistory.js +193 -0
- package/src/getThreadInfo.js +197 -0
- package/src/getThreadList.js +213 -0
- package/src/getThreadPictures.js +59 -0
- package/src/getUID.js +57 -0
- package/src/getUserID.js +62 -0
- package/src/getUserInfo.js +66 -0
- package/src/handleFriendRequest.js +49 -0
- package/src/handleMessageRequest.js +49 -0
- package/src/httpGet.js +49 -0
- package/src/httpPost.js +48 -0
- package/src/httpPostFormData.js +41 -0
- package/src/listenMqtt.js +634 -0
- package/src/logout.js +68 -0
- package/src/markAsDelivered.js +48 -0
- package/src/markAsRead.js +70 -0
- package/src/markAsReadAll.js +43 -0
- package/src/markAsSeen.js +51 -0
- package/src/muteThread.js +47 -0
- package/src/removeUserFromGroup.js +49 -0
- package/src/resolvePhotoUrl.js +37 -0
- package/src/searchForThread.js +43 -0
- package/src/sendMessage.js +334 -0
- package/src/sendTypingIndicator.js +80 -0
- package/src/setMessageReaction.js +109 -0
- package/src/setPostReaction.js +102 -0
- package/src/setTitle.js +74 -0
- package/src/threadColors.js +39 -0
- package/src/unfriend.js +43 -0
- package/src/unsendMessage.js +40 -0
- package/utils/Database.js +42 -0
- package/utils/Extension.js +143 -0
- package/utils/Html/MetaCord.png +0 -0
- package/utils/Html/index.html +200 -0
- package/utils/StateCrypt.js +53 -0
- package/utils.js +1249 -0
| @@ -0,0 +1,197 @@ | |
| 1 | 
            +
            "use strict";
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            var utils = require("../utils");
         | 
| 4 | 
            +
            var log = require("npmlog");
         | 
| 5 | 
            +
            var logger = require("../logger");
         | 
| 6 | 
            +
            var { CreateJson, GetJson } = require("../utils/Database");
         | 
| 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 | 
            +
                // @TODO verify this
         | 
| 16 | 
            +
                locationCoordinates: reminder.location_coordinates,
         | 
| 17 | 
            +
                locationPage: reminder.location_page,
         | 
| 18 | 
            +
                eventStatus: reminder.lightweight_event_status.toLowerCase(),
         | 
| 19 | 
            +
                note: reminder.note,
         | 
| 20 | 
            +
                repeatMode: reminder.repeat_mode.toLowerCase(),
         | 
| 21 | 
            +
                eventTitle: reminder.event_title,
         | 
| 22 | 
            +
                triggerMessage: reminder.trigger_message,
         | 
| 23 | 
            +
                secondsToNotifyBefore: reminder.seconds_to_notify_before,
         | 
| 24 | 
            +
                allowsRsvp: reminder.allows_rsvp,
         | 
| 25 | 
            +
                relatedEvent: reminder.related_event,
         | 
| 26 | 
            +
                members: reminder.event_reminder_members.edges.map(function(member) {
         | 
| 27 | 
            +
                  return {
         | 
| 28 | 
            +
                    memberID: member.node.id,
         | 
| 29 | 
            +
                    state: member.guest_list_state.toLowerCase()
         | 
| 30 | 
            +
                  };
         | 
| 31 | 
            +
                })
         | 
| 32 | 
            +
              };
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            function formatThreadGraphQLResponse(data) {
         | 
| 36 | 
            +
              var messageThread = data.o0.data.message_thread;
         | 
| 37 | 
            +
              var threadID = messageThread.thread_key.thread_fbid ? messageThread.thread_key.thread_fbid : messageThread.thread_key.other_user_id;
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              // Remove me
         | 
| 40 | 
            +
              var lastM = messageThread.last_message;
         | 
| 41 | 
            +
              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;
         | 
| 42 | 
            +
              var snippetText = lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
         | 
| 43 | 
            +
              var lastR = messageThread.last_read_receipt;
         | 
| 44 | 
            +
              var lastReadTimestamp = lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise ? lastR.nodes[0].timestamp_precise : null;
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              return {
         | 
| 47 | 
            +
                threadID: threadID,
         | 
| 48 | 
            +
                threadName: messageThread.name,
         | 
| 49 | 
            +
                participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id),
         | 
| 50 | 
            +
                userInfo: messageThread.all_participants.edges.map(d => ({
         | 
| 51 | 
            +
                  id: d.node.messaging_actor.id,
         | 
| 52 | 
            +
                  name: d.node.messaging_actor.name,
         | 
| 53 | 
            +
                  firstName: d.node.messaging_actor.short_name,
         | 
| 54 | 
            +
                  vanity: d.node.messaging_actor.username,
         | 
| 55 | 
            +
                  thumbSrc: d.node.messaging_actor.big_image_src.uri,
         | 
| 56 | 
            +
                  profileUrl: d.node.messaging_actor.big_image_src.uri,
         | 
| 57 | 
            +
                  gender: d.node.messaging_actor.gender,
         | 
| 58 | 
            +
                  type: d.node.messaging_actor.__typename,
         | 
| 59 | 
            +
                  isFriend: d.node.messaging_actor.is_viewer_friend,
         | 
| 60 | 
            +
                  isBirthday: !!d.node.messaging_actor.is_birthday //not sure?
         | 
| 61 | 
            +
                })),
         | 
| 62 | 
            +
                unreadCount: messageThread.unread_count,
         | 
| 63 | 
            +
                messageCount: messageThread.messages_count,
         | 
| 64 | 
            +
                timestamp: messageThread.updated_time_precise,
         | 
| 65 | 
            +
                muteUntil: messageThread.mute_until,
         | 
| 66 | 
            +
                isGroup: messageThread.thread_type == "GROUP",
         | 
| 67 | 
            +
                isSubscribed: messageThread.is_viewer_subscribed,
         | 
| 68 | 
            +
                isArchived: messageThread.has_viewer_archived,
         | 
| 69 | 
            +
                folder: messageThread.folder,
         | 
| 70 | 
            +
                cannotReplyReason: messageThread.cannot_reply_reason,
         | 
| 71 | 
            +
                eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders) : null,
         | 
| 72 | 
            +
                emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
         | 
| 73 | 
            +
                color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
         | 
| 74 | 
            +
                nicknames:
         | 
| 75 | 
            +
                  messageThread.customization_info &&
         | 
| 76 | 
            +
                    messageThread.customization_info.participant_customizations
         | 
| 77 | 
            +
                    ? messageThread.customization_info.participant_customizations.reduce(function(res, val) {
         | 
| 78 | 
            +
                      if (val.nickname) res[val.participant_id] = val.nickname;
         | 
| 79 | 
            +
                      return res;
         | 
| 80 | 
            +
                    }, {})
         | 
| 81 | 
            +
                    : {},
         | 
| 82 | 
            +
                adminIDs: messageThread.thread_admins,
         | 
| 83 | 
            +
                approvalMode: Boolean(messageThread.approval_mode),
         | 
| 84 | 
            +
                approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({
         | 
| 85 | 
            +
                  inviterID: a.inviter.id,
         | 
| 86 | 
            +
                  requesterID: a.requester.id,
         | 
| 87 | 
            +
                  timestamp: a.request_timestamp,
         | 
| 88 | 
            +
                  request_source: a.request_source // @Undocumented
         | 
| 89 | 
            +
                })),
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                // @Undocumented
         | 
| 92 | 
            +
                reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
         | 
| 93 | 
            +
                mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
         | 
| 94 | 
            +
                isPinProtected: messageThread.is_pin_protected,
         | 
| 95 | 
            +
                relatedPageThread: messageThread.related_page_thread,
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                // @Legacy
         | 
| 98 | 
            +
                name: messageThread.name,
         | 
| 99 | 
            +
                snippet: snippetText,
         | 
| 100 | 
            +
                snippetSender: snippetID,
         | 
| 101 | 
            +
                snippetAttachments: [],
         | 
| 102 | 
            +
                serverTimestamp: messageThread.updated_time_precise,
         | 
| 103 | 
            +
                imageSrc: messageThread.image ? messageThread.image.uri : null,
         | 
| 104 | 
            +
                isCanonicalUser: messageThread.is_canonical_neo_user,
         | 
| 105 | 
            +
                isCanonical: messageThread.thread_type != "GROUP",
         | 
| 106 | 
            +
                recipientsLoadable: true,
         | 
| 107 | 
            +
                hasEmailParticipant: false,
         | 
| 108 | 
            +
                readOnly: false,
         | 
| 109 | 
            +
                canReply: messageThread.cannot_reply_reason == null,
         | 
| 110 | 
            +
                lastMessageTimestamp: messageThread.last_message ? messageThread.last_message.timestamp_precise : null,
         | 
| 111 | 
            +
                lastMessageType: "message",
         | 
| 112 | 
            +
                lastReadTimestamp: lastReadTimestamp,
         | 
| 113 | 
            +
                threadType: messageThread.thread_type == "GROUP" ? 2 : 1
         | 
| 114 | 
            +
              };
         | 
| 115 | 
            +
            }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            module.exports = function(defaultFuncs, api, ctx) {
         | 
| 118 | 
            +
              return function getThreadInfoGraphQL(threadID, callback) {
         | 
| 119 | 
            +
                var path = require("path");
         | 
| 120 | 
            +
                const { writeFileSync } = require('fs-extra');
         | 
| 121 | 
            +
                CreateJson("TheardInfo.json", [])
         | 
| 122 | 
            +
                var threadData = GetJson("TheardInfo.json");
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                var threadJson = path.resolve(process.cwd(), 'MetaCord_Database', 'TheardInfo.json');
         | 
| 125 | 
            +
                if (threadData.some(i => i.data.threadID == threadID)) {
         | 
| 126 | 
            +
                  var thread = threadData.find(i => i.data.threadID == threadID);
         | 
| 127 | 
            +
                  if (((Date.now() - thread.time) / 1000).toFixed() >= 60 * 60 * 2) {
         | 
| 128 | 
            +
                    const index = threadData.findIndex(i => i.data.threadID == threadID);
         | 
| 129 | 
            +
                    threadData.splice(index, 1);
         | 
| 130 | 
            +
                    setTimeout(function() {
         | 
| 131 | 
            +
                      writeFileSync(threadJson, JSON.stringify(threadData, null, 4));
         | 
| 132 | 
            +
                    }, 2000);
         | 
| 133 | 
            +
                  }
         | 
| 134 | 
            +
                  return thread.data
         | 
| 135 | 
            +
                }
         | 
| 136 | 
            +
                else {
         | 
| 137 | 
            +
                  var resolveFunc = function() { };
         | 
| 138 | 
            +
                  var rejectFunc = function() { };
         | 
| 139 | 
            +
                  var returnPromise = new Promise(function(resolve, reject) {
         | 
| 140 | 
            +
                    resolveFunc = resolve;
         | 
| 141 | 
            +
                    rejectFunc = reject;
         | 
| 142 | 
            +
                  });
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  if (utils.getType(callback) != "Function" && utils.getType(callback) != "AsyncFunction") {
         | 
| 145 | 
            +
                    callback = function(err, data) {
         | 
| 146 | 
            +
                      if (err) return rejectFunc(err);
         | 
| 147 | 
            +
                      resolveFunc(data);
         | 
| 148 | 
            +
                    };
         | 
| 149 | 
            +
                  }
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  // `queries` has to be a string. I couldn't tell from the dev console. This
         | 
| 152 | 
            +
                  // took me a really long time to figure out. I deserve a cookie for this.
         | 
| 153 | 
            +
                  var form = {
         | 
| 154 | 
            +
                    queries: JSON.stringify({
         | 
| 155 | 
            +
                      o0: {
         | 
| 156 | 
            +
                        // This doc_id is valid as of July 20th, 2020
         | 
| 157 | 
            +
                        doc_id: "3449967031715030",
         | 
| 158 | 
            +
                        query_params: {
         | 
| 159 | 
            +
                          id: threadID,
         | 
| 160 | 
            +
                          message_limit: 0,
         | 
| 161 | 
            +
                          load_messages: false,
         | 
| 162 | 
            +
                          load_read_receipts: false,
         | 
| 163 | 
            +
                          before: null
         | 
| 164 | 
            +
                        }
         | 
| 165 | 
            +
                      }
         | 
| 166 | 
            +
                    }),
         | 
| 167 | 
            +
                    batch_name: "MessengerGraphQLThreadFetcher"
         | 
| 168 | 
            +
                  };
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  defaultFuncs
         | 
| 171 | 
            +
                    .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
         | 
| 172 | 
            +
                    .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
         | 
| 173 | 
            +
                    .then(function(resData) {
         | 
| 174 | 
            +
                      if (resData.error) throw resData;
         | 
| 175 | 
            +
                      // This returns us an array of things. The last one is the success /
         | 
| 176 | 
            +
                      // failure one.
         | 
| 177 | 
            +
                      // @TODO What do we do in this case?
         | 
| 178 | 
            +
                      if (resData[resData.length - 1].error_results !== 0) {
         | 
| 179 | 
            +
                        console.log(resData); //Log more info
         | 
| 180 | 
            +
                        throw new Error("well darn there was an error_result");
         | 
| 181 | 
            +
                      }
         | 
| 182 | 
            +
                      threadData.push({
         | 
| 183 | 
            +
                        data: formatThreadGraphQLResponse(resData[0]),
         | 
| 184 | 
            +
                        time: Date.now()
         | 
| 185 | 
            +
                      })
         | 
| 186 | 
            +
                      writeFileSync(threadJson, JSON.stringify(threadData, null, 4));
         | 
| 187 | 
            +
                      logger("Successfully Initiate Database for Group: " + threadID)
         | 
| 188 | 
            +
                      callback(null, formatThreadGraphQLResponse(resData[0]));
         | 
| 189 | 
            +
                    })
         | 
| 190 | 
            +
                    .catch(function(err) {
         | 
| 191 | 
            +
                      log.error("getThreadInfoGraphQL", err);
         | 
| 192 | 
            +
                      return callback(err);
         | 
| 193 | 
            +
                    });
         | 
| 194 | 
            +
                  return returnPromise;
         | 
| 195 | 
            +
                };
         | 
| 196 | 
            +
              }
         | 
| 197 | 
            +
            };
         | 
| @@ -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", "Lỗi: getThreadList Có Thể Do Bạn Spam Quá Nhiều, Hãy Thử Lại !");
         | 
| 208 | 
            +
                    return callback(err);
         | 
| 209 | 
            +
                  });
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                return returnPromise;
         | 
| 212 | 
            +
              };
         | 
| 213 | 
            +
            };
         | 
| @@ -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 | 
            +
            };
         | 
    
        package/src/getUID.js
    ADDED
    
    | @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            /* eslint-disable linebreak-style */
         | 
| 2 | 
            +
            "use strict";
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module.exports = function (_defaultFuncs, _ctx) {
         | 
| 5 | 
            +
                return function getUID(link, callback) {
         | 
| 6 | 
            +
                  var resolveFunc = function () { };
         | 
| 7 | 
            +
                  var rejectFunc = function () { };
         | 
| 8 | 
            +
                  var returnPromise = new Promise(function (resolve, reject) {
         | 
| 9 | 
            +
                    resolveFunc = resolve;
         | 
| 10 | 
            +
                    rejectFunc = reject;
         | 
| 11 | 
            +
                  });
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
                  if (!callback) {
         | 
| 14 | 
            +
                    callback = function (err, uid) {
         | 
| 15 | 
            +
                      if (err) return rejectFunc(err);
         | 
| 16 | 
            +
                      resolveFunc(uid);
         | 
| 17 | 
            +
                    };
         | 
| 18 | 
            +
                  }
         | 
| 19 | 
            +
                  
         | 
| 20 | 
            +
                try {
         | 
| 21 | 
            +
                    var Link = String(link);
         | 
| 22 | 
            +
                    var FindUID = require('../utils/Extension').getUID;
         | 
| 23 | 
            +
                    if (Link.includes('facebook.com') || Link.includes('Facebook.com') || Link.includes('fb')) {
         | 
| 24 | 
            +
                        var LinkSplit = Link.split('/');
         | 
| 25 | 
            +
                        if (LinkSplit.indexOf("https:") == 0) {
         | 
| 26 | 
            +
                          if (!isNaN(LinkSplit[3]) && !Link.split('=')[1]  && !isNaN(Link.split('=')[1])) {
         | 
| 27 | 
            +
                            callback('Wrong link, link should be formatted as follows: facebook.com/Lazic.Kanzu', null);
         | 
| 28 | 
            +
                          }
         | 
| 29 | 
            +
                          else if (!isNaN(Link.split('=')[1]) && Link.split('=')[1]) {
         | 
| 30 | 
            +
                            var Format = `https://www.facebook.com/profile.php?id=${Link.split('=')[1]}`;
         | 
| 31 | 
            +
                            FindUID(Format, (err, data) => {
         | 
| 32 | 
            +
                              callback(err, data);
         | 
| 33 | 
            +
                            });
         | 
| 34 | 
            +
                          } 
         | 
| 35 | 
            +
                          else {
         | 
| 36 | 
            +
                            FindUID(Link, (err, data) => {
         | 
| 37 | 
            +
                              callback(err, data);
         | 
| 38 | 
            +
                            });
         | 
| 39 | 
            +
                          }
         | 
| 40 | 
            +
                        }
         | 
| 41 | 
            +
                        else {
         | 
| 42 | 
            +
                            var Form = `https://www.facebook.com/${LinkSplit[1]}`;
         | 
| 43 | 
            +
                            FindUID(Form, (err, data) => {
         | 
| 44 | 
            +
                              callback(err, data);
         | 
| 45 | 
            +
                            });
         | 
| 46 | 
            +
                        }
         | 
| 47 | 
            +
                    }
         | 
| 48 | 
            +
                    else {
         | 
| 49 | 
            +
                        callback('Wrong link, link needs to be Facebook', null);
         | 
| 50 | 
            +
                    }
         | 
| 51 | 
            +
                }
         | 
| 52 | 
            +
                catch (e) {
         | 
| 53 | 
            +
                  return callback(null, e);
         | 
| 54 | 
            +
                }
         | 
| 55 | 
            +
                return returnPromise;
         | 
| 56 | 
            +
                };
         | 
| 57 | 
            +
              };
         | 
    
        package/src/getUserID.js
    ADDED
    
    | @@ -0,0 +1,62 @@ | |
| 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 | 
            +
             | 
| 53 | 
            +
                    callback(null, data.map(formatData));
         | 
| 54 | 
            +
                  })
         | 
| 55 | 
            +
                  .catch(function (err) {
         | 
| 56 | 
            +
                    log.error("getUserID", err);
         | 
| 57 | 
            +
                    return callback(err);
         | 
| 58 | 
            +
                  });
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                return returnPromise;
         | 
| 61 | 
            +
              };
         | 
| 62 | 
            +
            };
         | 
| @@ -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", "Lỗi: getUserInfo Có Thể Do Bạn Spam Quá Nhiều !,Hãy Thử Lại !");
         | 
| 61 | 
            +
                    return callback(err);
         | 
| 62 | 
            +
                  });
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                return returnPromise;
         | 
| 65 | 
            +
              };
         | 
| 66 | 
            +
            };
         |