/// <summary>
        /// Get member info in specified group.
        /// </summary>
        /// <param name="groupId">Id of target group. Note that this method only support id of group, not for alias name</param>
        /// <param name="startMemberIndex">
        /// Index of member in target group.
        /// Note that FB display 30 members per page.
        /// So the first page begin at startMemberIndex = 0, second page begin at startMemberIndex = 30 </param>
        /// <returns>List of group member</returns>
        private List <GroupMember> GetGroupMembers(string groupId, int startMemberIndex)
        {
            var groupMembers = new List <GroupMember>();

            int p = startMemberIndex;
            int numberOfMemberFacebookDisplayPerPage = 30;

            while (true)
            {
                string groupMemberUrl =
                    "https://m.facebook.com/browse/group/members/?id=" + groupId +
                    "&start=" + startMemberIndex +
                    "&listType=list_nonfriend";

                HtmlNode           document    = __BuildDomFromUrl(groupMemberUrl);
                HtmlNodeCollection memberNodes = document.SelectNodes("/html/body/div/div/div[2]/div/div[1]/div/table");

                if (memberNodes == null || memberNodes.Count == 0)
                {
                    break;
                }

                foreach (HtmlNode memberNode in memberNodes)
                {
                    var groupMember = new GroupMember();

                    // member id or alias
                    groupMember.UserId = CompiledRegex.Match("Digit", memberNode.GetAttributeValue("id", string.Empty)).Value;

                    // is admin
                    HtmlNode isAdminNode = memberNode.SelectSingleNode("tr/td[2]/div/h3[2]");
                    if (isAdminNode != null)
                    {
                        groupMember.IsAdmin = isAdminNode.InnerText.Contains(Localization.IsGroupAdmin);
                    }

                    // display name
                    HtmlNode nameNode = memberNode.SelectSingleNode("tr/td[2]/div/h3[1]/a") ?? memberNode.SelectSingleNode("tr/td[2]/div/h3[1]");
                    groupMember.DisplayName = (nameNode == null) ? string.Empty : nameNode.InnerText;

                    groupMembers.Add(groupMember);
                }

                startMemberIndex += numberOfMemberFacebookDisplayPerPage;
            }

            return(groupMembers);
        }
Example #2
0
        /// <summary>
        /// Get member info in specified group.
        /// </summary>
        /// <param name="groupId">Id of target group. Note that this method only support id of group, not for alias name</param>
        /// <param name="page">List friends will be paged, to get specified page, pass page value.</param>
        /// <returns>List of group member</returns>
        private List <GroupMember> GetGroupMembers(string groupId, int page)
        {
            var groupMembers = new List <GroupMember>();

            string groupMemberUrl = "https://m.facebook.com/browse/group/members/?id=" + groupId + "&start=" + page + "&listType=list_nonfriend";

            HtmlNode           document = this.BuildDom(groupMemberUrl);
            HtmlNodeCollection members  = document.SelectNodes("/html/body/div/div/div[2]/div/div[1]/div/table");

            if (members == null || members.Count == 0)
            {
                return(groupMembers);
            }

            foreach (HtmlNode member in members)
            {
                var groupMember = new GroupMember();

                // member id or alias
                groupMember.UserId = CompiledRegex.Match("Digit", member.GetAttributeValue("id", string.Empty)).Value;

                // is admin
                HtmlNode isAdminNode = member.SelectSingleNode("tr/td[2]/div/h3[2]");
                if (isAdminNode != null)
                {
                    groupMember.IsAdmin = isAdminNode.InnerText.Contains(ConstString.IsGroupAdmin);
                }

                // display name
                HtmlNode nameNode = member.SelectSingleNode("tr/td[2]/div/h3[1]/a") ?? member.SelectSingleNode("tr/td[2]/div/h3[1]");
                groupMember.DisplayName = (nameNode == null) ? string.Empty : nameNode.InnerText;

                groupMembers.Add(groupMember);
            }

            // TODO (ThinhVu) : Reduce recursion
            List <GroupMember> nextPageMembers = GetGroupMembers(groupId, page + 30);

            groupMembers.AddRange(nextPageMembers);

            return(groupMembers);
        }
Example #3
0
        /// <summary>
        /// Get friend helper method. This method support for GetFriend
        /// </summary>
        /// <param name="firstFriendPageUrl">Url</param>
        /// <param name="type">
        /// Type = 1 if you want to get other friends.
        /// Type = 2 if you want to get your friend.
        /// </param>
        /// <returns>List of facebook user id</returns>
        List <string> GetFriends(string firstFriendPageUrl)
        {
            // Declare list string to store user id
            var friends = new List <string>();

            // using queue to remove recursive search over pages
            var friendPages = new Queue <string>();

            friendPages.Enqueue(firstFriendPageUrl);

            // we will loop to the end of friend list
            while (friendPages.Count > 0)
            {
                string   currentFriendPageUrl = friendPages.Dequeue();
                string   currentHtmlContent   = _http.DownloadContent(currentFriendPageUrl);
                HtmlNode docNode = HtmlHelper.BuildDom(currentHtmlContent);

                // select root node which is div element with id is root, does not real root node
                HtmlNode rootNode = docNode.SelectSingleNode("//div[@id='root']");

                // Because selectNodes and SelectSingleNode method working incorrect.
                // Both method working in entire document node rather than current node when we pass relative xpath expression.
                // So, we maybe get more element than expect.

                // E.g : Trying select table element with role=presentation in rootNode will search in entire document
                // var friendTables1 = rootNode.SelectNodes("//table[@role='presentation']");

                // If we only want to search in InnerHtml of current node, just load it to another HtmlDocument object
                // Maybe isn't the best way to do our job. But at the moment, i think it still good.
                docNode = HtmlHelper.BuildDom(rootNode.InnerHtml);

                // Now search table in new document
                HtmlNodeCollection friendAnchors =
                    docNode.SelectNodes("//table[@role='presentation']/tr/td[2]/a") ??
                    docNode.SelectNodes("//table[@role='presentation']/tbody/tr/td[2]/a");
                if (friendAnchors == null)
                {
                    return(friends);
                }

                // Loop through all node and trying to get user alias or id
                foreach (HtmlNode friendAnchor in friendAnchors)
                {
                    string id = string.Empty;
                    string userProfileHref = friendAnchor.GetAttributeValue("href", null);

                    if (userProfileHref != null)
                    {
                        if (!userProfileHref.Contains("profile.php"))
                        {
                            // if userProfileHref does't contain "profile.php", userProfileHref contain user alias.
                            // E.g : https://m.facebook.com:443/user.alias.here?fref=fr_tab&amp;refid=17/about
                            int questionMarkIndex = userProfileHref.IndexOf("?");

                            if (questionMarkIndex > -1)
                            {
                                userProfileHref = userProfileHref.Substring(1, questionMarkIndex - 1);
                            }
                            else
                            {
                                userProfileHref = userProfileHref.Substring(1);
                            }

                            friends.Add(userProfileHref);
                        }
                        else
                        {
                            // Extract user id from href profile.php?id=user_id&fre...
                            // If extract not success then we need to log this error
                            Match match = CompiledRegex.Match("UserId", userProfileHref);
                            if (match.Success)
                            {
                                friends.Add(match.Groups["id"].Value);
                            }
                            else
                            {
                                _logger.WriteLine("Match user id by CompiledRege.Match(UserId) is fail. Addition info : url=" + firstFriendPageUrl + " and user profile is " + userProfileHref);
                            }
                        }
                    }
                    else
                    {
                        // If we go to this code block, there are some case happend :
                        // - Our bot has been block by this user or facebook.
                        // - This is deleted user.
                        // - We need provide more pattern to detect user id

                        // now i will log it for later fix
                        _logger.WriteLine("Maybe " + friendAnchor.InnerText + " has been banned. Access this link from browser to check again.");
                    }
                }

                HtmlNode moreFriend = rootNode.SelectSingleNode("//div[@id='m_more_friends']/a");
                if (moreFriend == null)
                {
                    continue;
                }

                var nextUrl = WebUtility.HtmlDecode(moreFriend.GetAttributeValue("href", null));

                if (nextUrl != null)
                {
                    friendPages.Enqueue("https://m.facebook.com" + nextUrl);
                }
                else
                {
                    _logger.WriteLine("This is last page.");
                }
            }

            return(friends);
        }
Example #4
0
        /// <summary>
        /// Get friend's id of someone -- heavy method -- need refactor
        /// </summary>
        /// <param name="userIdOrAlias">
        /// If userId passed is blank then you will get your friend list.
        /// Else you will get friend list of this id.
        /// </param>
        /// <returns>List id of friends</returns>
        public UserInfo GetUserInfo(string userIdOrAlias, bool includeUserAbout = false, bool includedFriendList = false)
        {
            // TODO : messy method - we need refactor it.

            if (string.IsNullOrWhiteSpace(userIdOrAlias))
            {
                throw new ArgumentException("userIdOrAlias must not null or empty.");
            }

            UserInfo userInfo = new UserInfo();

            // TODO : Reduce check
            // The first time we passed userIdOrAlias
            // We don't know it is userIdOrAlias so we need to check
            // The second time when we known passed param is user id or alias
            // But we still need check again. it make perf decrease.

            string userAboutUrl = string.Empty;
            bool   isUserId     = !CompiledRegex.Match("NonDigit", userIdOrAlias).Success;

            if (isUserId)
            {
                userAboutUrl   = "https://m.facebook.com/profile.php?v=info&id=" + userIdOrAlias;
                userInfo.Id    = userIdOrAlias;
                userInfo.Alias = string.Empty;
            }
            else
            {
                userAboutUrl   = "https://m.facebook.com/" + userIdOrAlias + "/about";
                userInfo.Id    = string.Empty;
                userInfo.Alias = userIdOrAlias;
            }

            HtmlNode htmlDom = this.BuildDom(userAboutUrl);

            // Get avatar anchor tag :

            // avatarAnchorElem contain avatar image source, user display name and maybe contain id.
            // if userIdOrAlias is current user or other user with animated avatar then 1st xpath is wrong
            // pick another anchor
            HtmlNode avatarAnchor = htmlDom.SelectSingleNode("//div[@id='root']/div/div/div/div/div/a");

            if (avatarAnchor == null ||
                avatarAnchor.InnerText == ConstString.EditProfilePicture || // for current user
                avatarAnchor.InnerText == ConstString.AddProfilePicture)    // for animate avatar
            {
                avatarAnchor = htmlDom.SelectSingleNode("//div[@id='root']/div/div/div/div/div/div/a");
            }
            else if (avatarAnchor.SelectSingleNode("div/a/img") != null ||
                     (avatarAnchor.PreviousSibling != null && avatarAnchor.PreviousSibling.SelectSingleNode("/a/img") != null))
            {
                HtmlNodeCollection anchors = avatarAnchor.SelectNodes("div/a");
                if (anchors != null)
                {
                    foreach (HtmlNode anchor in anchors)
                    {
                        if (anchor.SelectSingleNode("img") != null)
                        {
                            avatarAnchor = anchor;
                            break;
                        }
                    }
                }
            }
            else
            {
                // Support another xpath to get user id
            }

            // get user id
            if (!isUserId && avatarAnchor != null)
            {
                Match idMatch = Match.Empty;

                // trying get id from avatar href
                var avatarHref = avatarAnchor.GetAttributeValue("href", null);

                // If we found avatar href, we might using it to detect user id
                if (avatarHref != null)
                {
                    // There is 3 pattern to detect user id
                    // If we get another url format, return this url for detect later.
                    // /photo.php?fbid=704517456378829&id=100004617430839&...
                    // /profile/picture/view/?profile_id=100003877944061&...
                    // /story.php\?story_fbid=\d+&amp;id=(?<id>\d+) for animate avatar
                    if ((idMatch = CompiledRegex.Match("UserIdFromAvatar1", avatarHref)).Success ||
                        (idMatch = CompiledRegex.Match("UserIdFromAvatar2", avatarHref)).Success ||
                        (idMatch = CompiledRegex.Match("UserIdFromAvatar3", avatarHref)).Success)
                    {
                        userInfo.Id = idMatch.Groups["id"].Value;
                    }
                }

                // try another way
                if (string.IsNullOrEmpty(userInfo.Id))
                {
                    // Trying to detect user id from hyperlink :
                    // Timeline · Friends · Photos · Likes · Followers · Following · [Activity Log]
                    // NOTE :
                    //      - Activity log only show in current user about page
                    //
                    // Important : /div/div/div/a must select before /div/div/a. Do not swap SelectNodes order.
                    HtmlNodeCollection anchors = htmlDom.SelectNodes("//div[@id='root']/div/div/div/a")
                                                 ?? htmlDom.SelectNodes("//div[@id='root']/div/div/a");

                    if (anchors != null && anchors.Count > 0)
                    {
                        foreach (HtmlNode anchor in anchors)
                        {
                            // Get and check hrefAttr and innerText
                            // If both of them have value then we can detect it using compiled pattern
                            // NOTE : Check pattern if you think it's incorrect.
                            string hrefAttr  = anchor.GetAttributeValue("href", string.Empty);
                            string innerText = anchor.InnerText;

                            if (!string.IsNullOrWhiteSpace(innerText) &&
                                (idMatch = CompiledRegex.Match(innerText, hrefAttr)).Success)
                            {
                                userInfo.Id = idMatch.Groups["id"].Value;
                                break;
                            }
                        }
                    }
                }

                // Try another way if id still empty
                if (string.IsNullOrEmpty(userInfo.Id))
                {
                    // Step 3 :
                    // trying get uid from action button : Add Friend, Message, Follow, More
                    // I only select the 1st xpath,the second xpath will be check in the future.
                    HtmlNodeCollection btnHrefts = htmlDom.SelectNodes("//div[@id='root']/div/div/div/table/tr/td/a");
                    // ??htmlDom.SelectNodes("//div[@id='root']/div/div/div");

                    // If we found some button nodes :
                    //      - Add Friend node if we does not add friend with this user
                    //      - Message if this user allow we can send message to him/her
                    //      - Follow if this user allow we can follow him/her and we not follow him/her before
                    //      - More if we can see more about user - i think that, maybe is incorrect.
                    if (btnHrefts != null && btnHrefts.Count > 0)
                    {
                        foreach (var btnHreft in btnHrefts)
                        {
                            // if href and innertext not null then we can trying detect user id by compiled regex
                            // NOTE :
                            //      - Check CompiledRegex if you think it not correct anymore
                            //      - Edit Key if you use another Language rather than English to access FB
                            string hrefAttr  = btnHreft.GetAttributeValue("href", string.Empty);
                            string innerText = btnHreft.InnerText;

                            if (!string.IsNullOrWhiteSpace(innerText) &&
                                (idMatch = CompiledRegex.Match(innerText, hrefAttr)).Success)
                            {
                                userInfo.Id = idMatch.Groups["id"].Value;
                                break;
                            }
                        }
                    }
                }
            }

            if (string.IsNullOrEmpty(userInfo.Id))
            {
                _logger.WriteLine("Could not detect id from " + userAboutUrl);
                return(null);
            }

            // if user id has been detected, check if we want to get user about too
            if (includeUserAbout && avatarAnchor != null)
            {
                HtmlNode avatar = avatarAnchor.SelectSingleNode("img");
                if (avatar != null)
                {
                    // Get name and avatar
                    userInfo.DispayName = WebUtility.HtmlDecode(avatar.GetAttributeValue("alt", string.Empty));
                    userInfo.AvatarUrl  = WebUtility.HtmlDecode(avatar.GetAttributeValue("src", string.Empty));
                }
                else
                {
                    _logger.WriteLine("Img tag in avatar is null. Addition info : " + userAboutUrl);
                }
            }

            // get user friend list if included
            // at this step we do not need to check user id anymore
            // if user id is null then we had return before.
            if (includedFriendList)
            {
                var firstFriendPageUrl = "https://m.facebook.com/profile.php?v=friends&startindex=0&id=" + userInfo.Id;
                userInfo.Friends = GetFriends(firstFriendPageUrl);
            }

            // all step have done
            return(userInfo);
        }
        private List <string> getFriends(string friendPage)
        {
            var ctx = "_GetFriends";
            // Declare list string to store user id
            var friends = new List <string>();

            // friendPage will be update each loop
            // if there is no more friend page, this loop will be terminate.
            while (true)
            {
                HtmlNode docNode = __BuildDomFromUrl(friendPage);
                // Because selectNodes and SelectSingleNode method working incorrect.
                // Both method working in entire document node rather than current node when we pass relative xpath expression.
                // So, we maybe get more element than expect.

                // E.g : Trying select table element with role=presentation in rootNode will search in entire document
                // var friendTables1 = rootNode.SelectNodes("//table[@role='presentation']");

                // If we only want to search in InnerHtml of current node, just load it to another HtmlDocument object
                // Maybe isn't the best way to do our job. But at the moment, i think it still good.
                HtmlNode           rootNode      = __BuildDomFromHtmlContent(docNode.SelectSingleNode("//div[@id='root']").InnerHtml);
                HtmlNodeCollection friendAnchors =
                    rootNode.SelectNodes("//table[@role='presentation']/tr/td[2]/a") ??
                    rootNode.SelectNodes("//table[@role='presentation']/tbody/tr/td[2]/a");
                if (friendAnchors == null)
                {
                    Logger.WriteLine(ctx + ":friendAnchors:Maybe last page or xpath error");
                    Logger.WriteLine("\tMore details:");
                    Logger.WriteLine(ctx + "\t" + rootNode.InnerHtml + "\t" + "//table[@role='presentation']/tr/td[2]/a || //table[@role='presentation']/tbody/tr/td[2]/a");
                    return(friends);
                }

                // Loop through all node and trying to get user alias or id
                foreach (HtmlNode friendAnchor in friendAnchors)
                {
                    string id = string.Empty;
                    string userProfileHref = friendAnchor.GetAttributeValue("href", null);

                    if (userProfileHref != null)
                    {
                        if (!userProfileHref.Contains("profile.php"))
                        {
                            // if userProfileHref does't contain "profile.php", userProfileHref contain user alias.
                            // E.g : https://m.facebook.com:443/user.alias.here?fref=fr_tab&amp;refid=17/about
                            int questionMarkIndex = userProfileHref.IndexOf("?");

                            if (questionMarkIndex > -1)
                            {
                                userProfileHref = userProfileHref.Substring(1, questionMarkIndex - 1);
                            }
                            else
                            {
                                userProfileHref = userProfileHref.Substring(1);
                            }

                            friends.Add(userProfileHref);
                        }
                        else
                        {
                            // Extract user id from href profile.php?id=user_id&fre...
                            // If extract not success then we need to log this error
                            Match match = CompiledRegex.Match(Pattern.UserId, userProfileHref);
                            if (match.Success)
                            {
                                friends.Add(match.Groups["id"].Value);
                            }
                            else
                            {
                                Logger.WriteLine("Match user id by CompiledRege.Match(UserId) is fail. Addition info : url=" + friendPage + " and user profile is " + userProfileHref);
                            }
                        }
                    }
                    else
                    {
                        // If we go to this code block, there are some case happend :
                        // - Our bot has been block by this user or facebook.
                        // - This is deleted user.
                        // - We need provide more pattern to detect user id
                        Logger.WriteLine(ctx + ":userProfileHref:Could not detect.");
                        Logger.WriteLine("\tMaybe our bot blocked by this user or FB, or this user " + friendAnchor.InnerText + " has been banned or our xpath does not match anymore");
                        Logger.WriteLine("\tLink : " + friendPage);
                        Logger.WriteLine("-------------");
                    }
                }

                // get more friend
                HtmlNode moreFriend = rootNode.SelectSingleNode("//div[@id='m_more_friends']/a");
                if (moreFriend == null)
                {
                    Logger.WriteLine(ctx + ":moreFriend:No more friends page at : " + friendPage);
                    break;
                }

                var nextUrl = WebUtility.HtmlDecode(moreFriend.GetAttributeValue("href", string.Empty));
                if (nextUrl != null)
                {
                    friendPage = "https://m.facebook.com" + nextUrl;
                }
                else
                {
                    Logger.WriteLine(".GetFriends");
                    Logger.WriteLine("\t\tNext Url is empty. Maybe this is last page.");
                    Logger.WriteLine("\t\tLink : " + friendPage);
                    // exit loop
                    break;
                }
            }

            return(friends);
        }
        public UserInfo GetUserInfo(string userIdOrAlias, UserInfoOption option)
        {
            var ctx = "GetUserInfo";

            if (string.IsNullOrWhiteSpace(userIdOrAlias))
            {
                throw new ArgumentException("userIdOrAlias must not null or empty.");
            }

            var userInfo = new UserInfo {
                FBInfo = new FacebookInfo()
            };
            var  userAboutUrl = string.Empty;
            bool isUserId     = !CompiledRegex.Match(Pattern.NonDigit, userIdOrAlias).Success;

            if (isUserId)
            {
                userAboutUrl       = "https://m.facebook.com/profile.php?v=info&id=" + userIdOrAlias;
                userInfo.FBInfo.Id = userIdOrAlias;
            }
            else
            {
                userAboutUrl          = "https://m.facebook.com/" + userIdOrAlias + "/about";
                userInfo.FBInfo.Alias = userIdOrAlias;
            }

            HtmlNode htmlDom = __BuildDomFromUrl(userAboutUrl);

            // Get avatar anchor tag :

            // avatarAnchorElem contain avatar image source, user display name and maybe contain id.
            // if userIdOrAlias is "me user" or "other users with animated avatar" then 1st xpath is wrong
            // so we need to pick another anchor
            HtmlNode avatarAnchor = htmlDom.SelectSingleNode("//div[@id='root']/div/div/div/div/div/a");

            if (avatarAnchor == null ||                                        // FB change structure
                avatarAnchor.InnerText == Localization.EditProfilePicture ||   // Me user
                avatarAnchor.InnerText == Localization.AddProfilePicture)      // Me user
            {
                // pick the second pattern
                avatarAnchor = htmlDom.SelectSingleNode("//div[@id='root']/div/div/div/div/div/div/a");
            }
            else if ((avatarAnchor.SelectSingleNode("div/a/img") != null) ||
                     (avatarAnchor.PreviousSibling != null && avatarAnchor.PreviousSibling.SelectSingleNode("/a/img") != null))
            {
                HtmlNodeCollection anchors = avatarAnchor.SelectNodes("div/a");
                if (anchors != null)
                {
                    foreach (HtmlNode anchor in anchors)
                    {
                        if (anchor.SelectSingleNode("img") != null)
                        {
                            avatarAnchor = anchor;
                            break;
                        }
                    }
                }
                else
                {
                    Logger.WriteLine(ctx + ":Empty avatar anchor node-001");
                    Logger.WriteLine("-------");
                }
            }
            // Update 25-0802917
            else
            {
                if (avatarAnchor.SelectSingleNode("img") == null)
                {
                    Logger.WriteLine(ctx + ":Empty avatar anchor node-002");
                    Logger.WriteLine("-------");
                }
            }

            // require user id
            if (!isUserId && option.FbInfoOption.IncludeUserId)
            {
                Match idMatch = Match.Empty;

                // trying get id from avatar href
                var avatarHref = avatarAnchor.GetAttributeValue("href", null);

                // If we found avatar href, we might using it to detect user id
                if (avatarHref != null)
                {
                    // There is 3 pattern to detect user id
                    // If both 3 pattern can not detect user id, return this url for detect later.
                    // /photo.php?fbid=704517456378829&id=100004617430839&...
                    // /profile/picture/view/?profile_id=100003877944061&...
                    // /story.php\?story_fbid=\d+&amp;id=(?<id>\d+) for animate avatar
                    if ((idMatch = CompiledRegex.Match(Pattern.UserIdFromAvatar1, avatarHref)).Success ||
                        (idMatch = CompiledRegex.Match(Pattern.UserIdFromAvatar2, avatarHref)).Success ||
                        (idMatch = CompiledRegex.Match(Pattern.UserIdFromAvatar3, avatarHref)).Success)
                    {
                        userInfo.FBInfo.Id = idMatch.Groups["id"].Value;
                    }
                }

                // now avatarHref is null or we cannot detect user id from avatarHref
                // so we need try another way
                if (string.IsNullOrEmpty(userInfo.FBInfo.Id))
                {
                    // Trying to detect user id from hyperlink :
                    // Timeline · Friends · Photos · Likes · Followers · Following · [Activity Log]
                    // NOTE :
                    //      - Activity log only show in "Me users" about page
                    //
                    // Important : /div/div/div/a must select before /div/div/a. Do not swap SelectNodes order.
                    HtmlNodeCollection anchors = htmlDom.SelectNodes("//div[@id='root']/div/div/div/a")
                                                 ?? htmlDom.SelectNodes("//div[@id='root']/div/div/a");

                    if (anchors != null && anchors.Count > 0)
                    {
                        foreach (HtmlNode anchor in anchors)
                        {
                            // Get and check hrefAttr and innerText
                            // If both of them have value then we can detect it using compiled pattern
                            string hrefAttr  = anchor.GetAttributeValue("href", string.Empty);
                            string innerText = anchor.InnerText;
                            if (!string.IsNullOrWhiteSpace(innerText) &&
                                (idMatch = CompiledRegex.Match(innerText, hrefAttr)).Success)
                            {
                                userInfo.FBInfo.Id = idMatch.Groups["id"].Value;
                                break;
                            }
                        }
                    }
                }

                // Try another way if id still empty
                if (string.IsNullOrEmpty(userInfo.FBInfo.Id))
                {
                    // Step 3 :
                    // trying get uid from action button : Add Friend, Message, Follow, More
                    // I only select the 1st xpath,the second xpath will be check in the future.
                    HtmlNodeCollection btnHrefs = htmlDom.SelectNodes("//div[@id='root']/div/div/div/table/tr/td/a");
                    // ??htmlDom.SelectNodes("//div[@id='root']/div/div/div");

                    // If we found some button nodes :
                    //      - Add Friend node if we does not add friend with this user
                    //      - Message if this user allow we can send message to him/her
                    //      - Follow if this user allow we can follow him/her and we not follow him/her before
                    //      - More if we can see more about user - i guess
                    if (btnHrefs != null && btnHrefs.Count > 0)
                    {
                        foreach (var btnHref in btnHrefs)
                        {
                            // if href and innertext not null then we can trying detect user id by compiled regex
                            // NOTE :
                            //      - Check CompiledRegex if you think it not correct anymore
                            //      - Edit Key if you use another Language rather than English to access FB
                            string hrefAttr  = btnHref.GetAttributeValue("href", string.Empty);
                            string innerText = btnHref.InnerText;
                            if (!string.IsNullOrWhiteSpace(innerText) &&
                                (idMatch = CompiledRegex.Match(innerText, hrefAttr)).Success)
                            {
                                userInfo.FBInfo.Id = idMatch.Groups["id"].Value;
                                break;
                            }
                        }
                    }
                }
            }

            #region IncludeUserId
            // id or alias [at least one]
            if (option.FbInfoOption.IncludeUserId && string.IsNullOrWhiteSpace(userInfo.FBInfo.Id))
            {
                // Cause these are so many pattern to detect user id so we won't log each xpath to each file
                // In stead, we need to log specify link and check all xpath later.
                Logger.WriteLine(ctx + ": Require user id but user id empty");
                Logger.WriteLine("\tLink: " + userAboutUrl);
                Logger.WriteLine("-------------");
            }
            #endregion

            #region IncludeAvatarUrl || IncludeUserDisplayName
            // user name and avatar url [optional]
            // check avatarAnchor is null or not -- fault tolerant -- cuz these info doesn't important
            if ((option.FbInfoOption.IncludeAvatarUrl || option.FbInfoOption.IncludeUserDisplayName))
            {
                if (avatarAnchor != null)
                {
                    // TODO: Avater node has been changed into Cover image photo
                    HtmlNode avatar = avatarAnchor.SelectSingleNode("img");
                    if (avatar != null)
                    {
                        // Get name and avatar
                        if (option.FbInfoOption.IncludeUserDisplayName)
                        {
                            userInfo.FBInfo.DisplayName = WebUtility.HtmlDecode(avatar.GetAttributeValue("alt", string.Empty));
                        }
                        if (option.FbInfoOption.IncludeAvatarUrl)
                        {
                            userInfo.FBInfo.AvatarUrl = WebUtility.HtmlDecode(avatar.GetAttributeValue("src", string.Empty));
                        }
                    }
                    else
                    {
                        Logger.WriteLine(ctx + "__" + avatarAnchor.InnerHtml + "__img");
                    }
                }
                else
                {
                    Logger.WriteLine(ctx + ":IncludeAvatarUrl||IncludeUserDisplayName:Empty avatar anchor node");
                    Logger.WriteLine("----");
                }
            }
            #endregion

            #region IncludeAddressInfo
            if (option.IncludeAddressInfo)
            {
                // livingNode contains information about City and HomeTown
                HtmlNode livingNode = htmlDom.SelectSingleNode("//div[@id='living']");
                if (livingNode != null)
                {
                    // Notice: See Note 001
                    HtmlNodeCollection trNodes = __BuildDomFromHtmlContent(livingNode.InnerHtml).SelectNodes("//tr");
                    userInfo.Address = new AddressInfo();
                    foreach (var trNode in trNodes)
                    {
                        HtmlNodeCollection tds = trNode.SelectNodes("td");
                        // only get which td have 2 td
                        // td[0] is topic
                        // td[1] is value
                        if (tds == null && tds.Count != 2)
                        {
                            continue;
                        }

                        string   value     = string.Empty;
                        HtmlNode valueNode = null;
                        if (((valueNode = tds[1].SelectSingleNode("div/span/span")) != null) ||
                            ((valueNode = tds[1].SelectSingleNode("div/span")) != null) ||
                            ((valueNode = tds[1].SelectSingleNode("div/a")) != null) ||
                            ((valueNode = tds[1].SelectSingleNode("div")) != null))
                        {
                            value = valueNode.InnerText;
                            // store address
                            if (tds[0].InnerText.Contains(Localization.CurrentCity))
                            {
                                userInfo.Address.City = value;
                            }
                            else if (tds[0].InnerText.Contains(Localization.HomeTown))
                            {
                                userInfo.Address.HomeTown = value;
                            }
                        }
                        else
                        {
                            Logger.WriteLine(ctx + ":IncludeAddressInfo:Could not detect value");
                            Logger.WriteLine("\tMore details:");
                            Logger.WriteLine(ctx + "\t" + tds[1].InnerHtml + "\t" + "div/span/span || div/span || div/a || div");
                            Logger.WriteLine("-------------");
                        }
                    }
                }
                else
                {
                    Logger.WriteLine(ctx + "\t" + htmlDom.InnerHtml + "\t" + "//div[@id='living']");
                }
            }
            #endregion

            #region IncludeContactInfo
            if (option.IncludeContactInfo)
            {
                userInfo.Contact = new ContactInfo();

                HtmlNode contactInfo = htmlDom.SelectSingleNode("//div[@id='contact-info']");
                if (contactInfo != null)
                {
                    // extract local DOM
                    HtmlNodeCollection trNodes = __BuildDomFromHtmlContent(contactInfo.InnerHtml).SelectNodes("//tr");
                    foreach (var trNode in trNodes)
                    {
                        var tds = __BuildDomFromHtmlContent(trNode.InnerHtml).SelectNodes("//td");
                        if (tds == null || tds.Count != 2)
                        {
                            continue; // ignore
                        }
                        // get contact value
                        string   value     = string.Empty;
                        HtmlNode valueNode = null;
                        if ((valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div/span/span")) != null ||
                            (valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div/span")) != null ||
                            (valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div/a")) != null ||
                            (valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div")) != null)
                        {
                            value = WebUtility.HtmlDecode(valueNode.InnerText);
                            // store contact
                            if (tds[0].InnerText.Contains("Mobile"))
                            {
                                userInfo.Contact.Mobile = value;
                            }
                            else if (tds[0].InnerText.Contains("Email"))
                            {
                                userInfo.Contact.Email = value;
                            }
                            else if (tds[0].InnerText.Contains("Websites"))
                            {
                                userInfo.Contact.Website = value;
                            }
                        }
                        else
                        {
                            Logger.WriteLine(ctx + ":IncludeContactInfo:Could not detect value");
                            Logger.WriteLine("\tMore details:");
                            Logger.WriteLine(ctx + "\t" + tds[1].InnerHtml + "\t" + "div/span/span || div/span || div/a || div");
                            Logger.WriteLine("-------------");
                        }
                    }
                }
                else
                {
                    Logger.WriteLine(ctx + "\t" + htmlDom.InnerHtml + "\t" + "//div[@id='contact-info']");
                }
            }
            #endregion

            #region IncludeBasicInfo
            if (option.IncludeBasicInfo)
            {
                userInfo.BasicInfo = new BasicInfo();

                HtmlNode           contactInfo = htmlDom.SelectSingleNode("//div[@id='basic-info']");
                HtmlNodeCollection trNodes     = __BuildDomFromHtmlContent(contactInfo.InnerHtml).SelectNodes("//tr");
                foreach (var trNode in trNodes)
                {
                    var tds = __BuildDomFromHtmlContent(trNode.InnerHtml).SelectNodes("//td");
                    if (tds == null || tds.Count != 2)
                    {
                        continue; // ignore
                    }
                    // get contact value
                    string   value     = string.Empty;
                    HtmlNode valueNode = null;
                    if ((valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div/span/span")) != null ||
                        (valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div/span")) != null ||
                        (valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div/a")) != null ||
                        (valueNode = __BuildDomFromHtmlContent(tds[1].InnerHtml).SelectSingleNode("//div")) != null)
                    {
                        value = valueNode.InnerText;

                        // store contact
                        if (tds[0].InnerText.Contains("Birthday"))
                        {
                            userInfo.BasicInfo.BirthDay = value;
                        }
                        else if (tds[0].InnerText.Contains("Gender"))
                        {
                            userInfo.BasicInfo.Gender = value;
                        }
                        else if (tds[0].InnerText.Contains("Interested In"))
                        {
                            userInfo.BasicInfo.InterestedIn = value;
                        }
                        else if (tds[0].InnerText.Contains("Languages"))
                        {
                            userInfo.BasicInfo.Languages = value;
                        }
                        else if (tds[0].InnerText.Contains("Religious Views"))
                        {
                            userInfo.BasicInfo.ReligiousViews = value;
                        }
                        else if (tds[0].InnerText.Contains("Political Views"))
                        {
                            userInfo.BasicInfo.PolictialViews = value;
                        }
                    }
                    else
                    {
                        Logger.WriteLine(ctx + ":IncludeBasicInfo:Could not detect value");
                        Logger.WriteLine("\tMore details:");
                        Logger.WriteLine(ctx + "\t" + tds[1].InnerHtml + "\t" + "div/span/span || div/span || div/a || div");
                        Logger.WriteLine("-------------");
                    }
                }
            }
            #endregion

            #region IncludeEduInfo
            if (option.IncludeEduInfo)
            {
                // coming not soon
            }
            #endregion

            #region IncludeRelationshipInfo
            if (option.IncludeRelationshipInfo)
            {
                // not current ver
            }
            #endregion

            #region IncludeWorkInfo
            if (option.IncludeWorkInfo)
            {
                // not for current ver
            }
            #endregion

            #region IncludeFbFriends
            // get user friend list if included
            // at this step we do not need to check user id anymore
            // if user id is null then we had return before.
            if (option.FbInfoOption.IncludeFbFriends)
            {
                if (string.IsNullOrWhiteSpace(userInfo.FBInfo.Id))
                {
                    Logger.WriteLine(".GetUserInfo : Include FB Friends but User Id empty. No friends included.");
                }
                else
                {
                    var friendPageUrl = "https://m.facebook.com/profile.php?v=friends&startindex=0&id=" + userInfo.FBInfo.Id;
                    userInfo.FBInfo.FbFriends = this.getFriends(friendPageUrl);
                }
            }
            #endregion

            // all step have done
            return(userInfo);
        }