Exemple #1
0
        /// <summary>
        /// Get a completed post from the provided HTML div node.
        /// </summary>
        /// <param name="div">Div node that contains the post.</param>
        /// <returns>Returns a post object with required information.</returns>
        private PostComponents GetPost(HtmlNode div, IQuest quest)
        {
            if (div == null)
            {
                throw new ArgumentNullException(nameof(div));
            }

            string author = "";
            string id;
            string text;
            int    number = 0;

            // id="p12345"
            id = div.Id.Substring(1);


            var inner        = div.GetChildWithClass("div", "inner");
            var postbody     = inner.GetChildWithClass("div", "postbody");
            var authorNode   = postbody.GetChildWithClass("p", "author");
            var authorStrong = authorNode.Descendants("strong").FirstOrDefault();
            var authorAnchor = authorStrong.Element("a");

            author = PostText.CleanupWebString(authorAnchor.InnerText);

            // No way to get the post number??


            // Get the full post text.  Two different layout variants.
            var content = postbody.GetChildWithClass("div", "content");

            if (content == null)
            {
                content = postbody.Elements("div").FirstOrDefault(n => n.Id.StartsWith("post_content", StringComparison.Ordinal));
            }

            text = PostText.ExtractPostText(content, n => false, Host);


            PostComponents post;

            try
            {
                post = new PostComponents(author, id, text, number);
            }
            catch
            {
                post = null;
            }

            return(post);
        }
        public void CompareTest4()
        {
            string author1 = "Kinematics";
            string id1     = "12346";
            string msg1    = @"What do you think they'll be doing now?";

            string author2 = "Kinematics";
            string id2     = "12345";
            string msg2    = @"What do you think they'll be doing now?";

            PostComponents p1 = new PostComponents(author1, id1, msg1);
            PostComponents p2 = new PostComponents(author2, id2, msg2);

            Assert.IsTrue(p1.CompareTo(p2) > 0);
        }
        public void ConstructorTest9()
        {
            string msg =
                @"What do you think they'll be doing now?
[x] Ferris wheel
[x] 『color=#ff00AA』Teacups『/color』";

            PostComponents p = new PostComponents(author, id, msg);

            Assert.IsTrue(p.IsVote);
            Assert.IsNotNull(p.VoteStrings);
            Assert.AreEqual(2, p.VoteStrings.Count);
            Assert.AreEqual(@"[x] Ferris wheel", p.VoteStrings[0]);
            Assert.AreEqual(@"[x] 『color=#ff00AA』Teacups『/color』", p.VoteStrings[1]);
        }
        public void ConstructorTest6()
        {
            string msg =
                @"What do you think they'll be doing now?
『b』[x] Ferris wheel
-[x] At the top『/b』";

            PostComponents p = new PostComponents(author, id, msg);

            Assert.IsTrue(p.IsVote);
            Assert.IsNotNull(p.VoteStrings);
            Assert.AreEqual(2, p.VoteStrings.Count);
            Assert.AreEqual(@"[x] Ferris wheel", p.VoteStrings[0]);
            Assert.AreEqual(@"-[x] At the top", p.VoteStrings[1]);
        }
Exemple #5
0
        public async Task NameReferenceTest()
        {
            // Check for non-case sensitivity in referencing other voters.
            PostComponents        p1    = new PostComponents("Beyogi", "12345", "[x] Vote for something");
            PostComponents        p2    = new PostComponents("Mini", "12345", "[x] beyogi");
            List <PostComponents> posts = new List <PostComponents>();

            posts.Add(p1);
            posts.Add(p2);
            Task  t = voteCounter.TallyPosts(posts, sampleQuest, CancellationToken.None);
            await t;

            Assert.AreEqual(2, voteCounter.GetVotersCollection(VoteType.Vote).Count);
            Assert.AreEqual(1, voteCounter.GetVotesCollection(VoteType.Vote).Count);
            Assert.IsTrue(voteCounter.HasVote("[x] Vote for something\r\n", VoteType.Vote));
        }
        public void ConstructorTest2()
        {
            string msg =
                @"What do you think they'll be doing now?
[x] Ferris wheel
Additional:
[x] Ice cream";

            PostComponents p = new PostComponents(author, id, msg);

            Assert.IsTrue(p.IsVote);
            Assert.IsNotNull(p.VoteStrings);
            Assert.AreEqual(2, p.VoteStrings.Count);
            Assert.AreEqual(@"[x] Ferris wheel", p.VoteStrings[0]);
            Assert.AreEqual(@"[x] Ice cream", p.VoteStrings[1]);
        }
Exemple #7
0
        public async Task TestSinglePostParsing(string vote, List <string> results)
        {
            string author     = "User1";
            string postId     = "123456";
            int    postNumber = 100;

            PostComponents        post  = new PostComponents(author, postId, vote, postNumber);
            List <PostComponents> posts = new List <PostComponents>();

            posts.Add(post);

            await ViewModelService.MainViewModel.VoteCounter.TallyPosts(posts, sampleQuest, CancellationToken.None);

            var votes = GetVotesBy(author, VoteType.Vote);

            CollectionAssert.AreEqual(results, votes);
        }
Exemple #8
0
        /// <summary>
        /// Gets a list of all full-vote plans (of which there will only be one, if found).
        /// </summary>
        /// <param name="post">The post to extract plans from.</param>
        /// <returns>Returns a list of plans (which are lists of vote lines).</returns>
        private List <List <string> > GetAllFullPostPlans(PostComponents post)
        {
            List <List <string> > results = new List <List <string> >();

            if (post.VoteLines.Any())
            {
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);

                // If the vote has any plans with content in them, we can't make this a full-post plan.
                if (!voteBlocks.Any(b => b.Count() > 1 && VoteString.GetPlanName(b.Key) != null))
                {
                    // The post must have more than one line to count for a plan label.
                    if (post.VoteLines.Count > 1)
                    {
                        var firstLine = post.VoteLines.First();

                        string planname = VoteString.GetPlanName(firstLine);

                        if (planname != null)
                        {
                            // If it's named after a user, it must be the post author.  Otherwise, anything is fine.
                            if (VoteCounter.ReferenceVoters.Contains(planname, Agnostic.StringComparer))
                            {
                                if (Agnostic.StringComparer.Equals(planname, post.Author))
                                {
                                    results.Add(post.VoteLines);
                                }
                            }
                            else
                            {
                                results.Add(post.VoteLines);
                            }
                        }
                    }
                }
            }

            return(results);
        }
Exemple #9
0
        /// <summary>
        /// First pass review of posts to extract and store plans.
        /// In this pass, only plans that have actual content (ie: indented
        /// sub-lines) are considered.
        /// </summary>
        /// <param name="post">Post to be examined.</param>
        /// <param name="quest">Quest being tallied.</param>
        public void PreprocessPlansWithContent(PostComponents post, IQuest quest)
        {
            if (post == null)
            {
                throw new ArgumentNullException(nameof(post));
            }
            if (quest == null)
            {
                throw new ArgumentNullException(nameof(quest));
            }

            var plans = GetAllPlansWithContent(post);

            var filteredPlans = FilterPlansByTask(plans, quest);

            StorePlanReferences(filteredPlans);

            ProcessPlans(filteredPlans, post, quest.PartitionMode);
        }
Exemple #10
0
        /// <summary>
        /// Gets a list of all plans within the post that have defined content (child lines).
        /// </summary>
        /// <param name="post">The post to extract plans from.</param>
        /// <returns>Returns a list of plans (which are lists of vote lines).</returns>
        private List <List <string> > GetAllPlansWithContent(PostComponents post)
        {
            List <List <string> > results = new List <List <string> >();

            results.AddRange(post.BasePlans.Select(a => a.ToList()));

            if (post.VoteLines.Any())
            {
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);

                foreach (var block in voteBlocks)
                {
                    if (block.Count() > 1)
                    {
                        string planname = VoteString.GetPlanName(block.Key);

                        if (planname != null)
                        {
                            // Add a named vote that is named after a user only if it matches the post author's name.
                            if (VoteCounter.ReferenceVoters.Contains(planname, Agnostic.StringComparer))
                            {
                                if (Agnostic.StringComparer.Equals(planname, post.Author))
                                {
                                    results.Add(block.ToList());
                                }
                            }
                            else
                            {
                                // If it's not named after a user, add it normally.
                                results.Add(block.ToList());
                            }
                        }
                    }
                }
            }

            return(results);
        }
Exemple #11
0
        public async Task ProcessPostContentsWholeWithReferralTest1()
        {
            sampleQuest.PartitionMode = PartitionMode.None;

            string         testVote   = @"[x] Text Nagisa's uncle about her visiting today. Establish a specific time. (Keep in mind Sayaka's hospital visit.)
[x] Telepathy Oriko and Kirika. They probably need to pick up some groceries at this point. It should be fine if you go with them. And of course, you can cleanse their gems too.
[x] Head over to Oriko's.
-[x] 20 minutes roof hopping practice. Then fly the rest of the way.
-[x] Cleansing.
-[x] Take both of them food shopping (or whoever wants to go.)
-[x] Light conversation. No need for serious precog questions right now.";
            string         author     = "Muramasa";
            string         postId     = "123456";
            int            postNumber = 100;
            PostComponents p1         = new PostComponents(author, postId, testVote, postNumber);

            p1.SetWorkingVote(voteConstructor.GetWorkingVote);

            voteConstructor.ProcessPost(p1, sampleQuest, CancellationToken.None);

            string         referralVote = @"[x] Muramasa";
            string         refAuthor    = "Gerbil";
            string         refID        = "123457";
            int            refPostNum   = 101;
            PostComponents p2           = new PostComponents(refAuthor, refID, referralVote, refPostNum);

            ViewModelService.MainViewModel.VoteCounter.PostsList.Add(p1);
            ViewModelService.MainViewModel.VoteCounter.PostsList.Add(p2);

            List <PostComponents> posts = new List <PostComponents>();

            posts.Add(p1);
            posts.Add(p2);

            await ViewModelService.MainViewModel.VoteCounter.TallyPosts(posts, sampleQuest, CancellationToken.None);

            var votes  = ViewModelService.MainViewModel.VoteCounter.GetVotesCollection(VoteType.Vote);
            var voters = ViewModelService.MainViewModel.VoteCounter.GetVotersCollection(VoteType.Vote);

            Assert.IsTrue(votes.Count == 1);
            Assert.IsTrue(votes.All(v => v.Value.Count == 2));
            Assert.IsTrue(voters.Count == 2);
        }
Exemple #12
0
        /// <summary>
        /// Put any plans found in the grouped vote lines into the standard tracking sets,
        /// after handling any partitioning needed.
        /// </summary>
        /// <param name="plans">List of plans to be processed.</param>
        /// <param name="post">Post the plans were pulled from.</param>
        /// <param name="partitionMode">Partition mode being used.</param>
        private void ProcessPlans(IEnumerable <List <string> > plans, PostComponents post, PartitionMode partitionMode)
        {
            foreach (var plan in plans)
            {
                string planName  = VoteString.GetMarkedPlanName(plan.First());
                string cleanName = VoteString.RemoveBBCode(planName);
                cleanName = VoteString.DeUrlContent(cleanName);

                if (!VoteCounter.HasPlan(cleanName))
                {
                    var nPlan = NormalizePlanName(plan);

                    // Get the list of all vote partitions, built according to current preferences.
                    // One of: By line, By block, or By post (ie: entire vote)
                    var votePartitions = GetVotePartitions(nPlan, partitionMode, VoteType.Plan, post.Author);

                    VoteCounter.AddVotes(votePartitions, cleanName, post.ID, VoteType.Plan);
                }
            }
        }
Exemple #13
0
        /// <summary>
        /// Determines whether the authof of this post has made a newer vote
        /// submission.
        /// </summary>
        /// <param name="post">The post being checked.</param>
        /// <returns>Returns true if the voter has a newer vote
        /// already submitted to the counter.</returns>
        public bool HasNewerVote(PostComponents post)
        {
            if (post == null)
            {
                throw new ArgumentNullException(nameof(post));
            }

            if (!HasVoter(post.Author, VoteType.Vote))
            {
                return(false);
            }

            int submittedID = 0;

            if (!int.TryParse(GetVotersCollection(VoteType.Vote)[post.Author], out submittedID))
            {
                return(string.CompareOrdinal(GetVotersCollection(VoteType.Vote)[post.Author], post.ID) > 0);
            }

            return(submittedID > post.IDValue);
        }
Exemple #14
0
        /// <summary>
        /// Gets a list of all full-vote plans (of which there will only be one, if found).
        /// </summary>
        /// <param name="post">The post to extract plans from.</param>
        /// <returns>Returns a list of plans (which are lists of vote lines).</returns>
        private List <List <string> > GetAllOneLinePlans(PostComponents post)
        {
            List <List <string> > results = new List <List <string> >();

            if (post.VoteLines.Any())
            {
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);

                foreach (var block in voteBlocks)
                {
                    if (block.Count() == 1)
                    {
                        string planname = VoteString.GetPlanName(block.Key);

                        if (planname != null)
                        {
                            if (VoteCounter.ReferenceVoters.Contains(planname, Agnostic.StringComparer))
                            {
                                if (Agnostic.StringComparer.Equals(planname, post.Author))
                                {
                                    results.Add(block.ToList());
                                }
                            }
                            else
                            {
                                results.Add(block.ToList());
                            }
                        }
                    }
                }
            }

            return(results);
        }
Exemple #15
0
        public async Task TestReferencePostParsing(List <string> votes, List <string> authors, List <List <string> > results)
        {
            List <PostComponents> posts = new List <PostComponents>();

            for (int i = 0; i < votes.Count; i++)
            {
                string postID     = $"{12345 + i}";
                int    postNumber = 100 + i;

                PostComponents post = new PostComponents(authors[i], postID, votes[i], postNumber);
                posts.Add(post);
            }

            await ViewModelService.MainViewModel.VoteCounter.TallyPosts(posts, sampleQuest, CancellationToken.None);

            for (int i = 0; i < results.Count; i++)
            {
                var post      = ViewModelService.MainViewModel.VoteCounter.PostsList[i];
                var userVotes = GetVotesBy(post.Author, VoteType.Vote);
                CollectionAssert.AreEqual(results[i], userVotes);
            }
        }
Exemple #16
0
        public void TestPartitionNone()
        {
            string testVote =
                @"[X][Action] Plan One
-[X] Ambush
-[X][Decision] Kill
-[X] Run";
            List <string> expected = new List <string> {
                testVote
            };

            sampleQuest.PartitionMode = PartitionMode.None;
            string         author = "Me";
            string         postId = "123456";
            PostComponents p1     = new PostComponents(author, postId, testVote);

            p1.SetWorkingVote(voteConstructor.GetWorkingVote);

            voteConstructor.ProcessPost(p1, sampleQuest, CancellationToken.None);
            var votes = ViewModelService.MainViewModel.VoteCounter.GetVotesCollection(VoteType.Vote);

            Assert.IsTrue(votes.Keys.SequenceEqual(expected, Agnostic.StringComparer));
        }
Exemple #17
0
        public void ProcessPostContentsTallyTest()
        {
            string testVote = @"『b』Vote Tally『/b』
『color=transparent』##### NetTally 1.0『/color』
[x] Text Nagisa's uncle about her visiting today. Establish a specific time. (Keep in mind Sayaka's hospital visit.)
[x] Telepathy Oriko and Kirika. They probably need to pick up some groceries at this point. It should be fine if you go with them. And of course, you can cleanse their gems too.
[x] Head over to Oriko's.
-[x] 20 minutes roof hopping practice. Then fly the rest of the way.
-[x] Cleansing.
-[x] Take both of them food shopping (or whoever wants to go.)
-[x] Light conversation. No need for serious precog questions right now.";

            string author = "Muramasa";
            string postId = "123456";

            PostComponents p = new PostComponents(author, postId, testVote);

            p.SetWorkingVote(voteConstructor.GetWorkingVote);

            Assert.IsFalse(p.IsVote);

            voteConstructor.ProcessPost(p, sampleQuest, CancellationToken.None);
        }
Exemple #18
0
        /// <summary>
        /// Third pass review of posts to extract and store plans.
        /// In this pass, plans that are simply labels for the entire post,
        /// and don't have any content themselves, are considered.
        /// The overall vote may have just one line.
        /// </summary>
        /// <param name="post">Post to be examined.</param>
        /// <param name="quest">Quest being tallied.</param>
        public void PreprocessPlanLabelsWithoutContent(PostComponents post, IQuest quest)
        {
            if (post == null)
            {
                throw new ArgumentNullException(nameof(post));
            }
            if (quest == null)
            {
                throw new ArgumentNullException(nameof(quest));
            }

            if (quest.ForbidVoteLabelPlanNames)
            {
                return;
            }

            var plans = GetAllOneLinePlans(post);

            var filteredPlans = FilterPlansByTask(plans, quest);

            StorePlanReferences(filteredPlans);

            ProcessPlans(filteredPlans, post, quest.PartitionMode);
        }
        /// <summary>
        /// Get a completed post from the provided HTML div node.
        /// </summary>
        /// <param name="postDiv">Div node that contains the post.</param>
        /// <returns>Returns a post object with required information.</returns>
        private PostComponents GetPost(HtmlNode postDiv)
        {
            if (postDiv == null)
            {
                throw new ArgumentNullException(nameof(postDiv));
            }

            string author = "";
            string id;
            string text;
            int    number = 0;

            var postTable = postDiv.Descendants("table").FirstOrDefault(a => a.Id.StartsWith("post", StringComparison.Ordinal));

            if (postTable == null)
            {
                return(null);
            }

            id = postTable.Id.Substring("post".Length);

            string postAuthorDivID = "postmenu_" + id;

            var authorAnchor = postTable.OwnerDocument.GetElementbyId(postAuthorDivID).Element("a");

            if (authorAnchor != null)
            {
                author = authorAnchor.InnerText;

                // ??
                if (authorAnchor.Element("span") != null)
                {
                    author = authorAnchor.Element("span").InnerText;
                }
            }

            string postNumberAnchorID = "postcount" + id;

            var anchor = postTable.OwnerDocument.GetElementbyId(postNumberAnchorID);

            if (anchor != null)
            {
                string postNumText = anchor.GetAttributeValue("name", "");
                number = int.Parse(postNumText);
            }

            string postMessageId = "post_message_" + id;

            var postContents = postTable.OwnerDocument.GetElementbyId(postMessageId);

            // Predicate filtering out elements that we don't want to include
            var exclusion = PostText.GetClassExclusionPredicate("bbcode_quote");

            // Get the full post text.
            text = PostText.ExtractPostText(postContents, exclusion, Host);


            PostComponents post;

            try
            {
                post = new PostComponents(author, id, text, number);
            }
            catch
            {
                post = null;
            }

            return(post);
        }
        public void BadConstruction3()
        {
            //string msg = @"What do you think they'll be doing now?";

            PostComponents p = new PostComponents(author, id, null);
        }
Exemple #21
0
        /// <summary>
        /// Get a completed post from the provided HTML list item node.
        /// </summary>
        /// <param name="li">List item node that contains the post.</param>
        /// <returns>Returns a post object with required information.</returns>
        private PostComponents GetPost(HtmlNode li)
        {
            if (li == null)
            {
                throw new ArgumentNullException(nameof(li));
            }

            string author;
            string id;
            string text;
            int    number;

            // Author and ID are in the basic list item attributes
            author = PostText.CleanupWebString(li.GetAttributeValue("data-author", ""));
            id     = li.Id.Substring("post-".Length);

            if (AdvancedOptions.Instance.DebugMode)
            {
                author = $"{author}_{id}";
            }

            // Get the primary content of the list item
            HtmlNode primaryContent = li.GetChildWithClass("primaryContent");

            // On one branch, we can get the post text
            HtmlNode messageContent = primaryContent.GetChildWithClass("messageContent");
            HtmlNode postBlock      = messageContent.Element("article").Element("blockquote");

            // Predicate filtering out elements that we don't want to include
            List <string> excludedClasses = new List <string> {
                "bbCodeQuote", "messageTextEndMarker", "advbbcodebar_encadre",
                "advbbcodebar_article", "adv_tabs_wrapper", "adv_slider_wrapper"
            };

            if (AdvancedOptions.Instance.IgnoreSpoilers)
            {
                excludedClasses.Add("bbCodeSpoilerContainer");
            }

            var exclusions = PostText.GetClassesExclusionPredicate(excludedClasses);

            // Get the full post text.
            text = PostText.ExtractPostText(postBlock, exclusions, Host);

            // On another branch of the primary content, we can get the post number.
            HtmlNode messageMeta = primaryContent.GetChildWithClass("messageMeta");

            // HTML parsing of the post was corrupted somehow.
            if (messageMeta == null)
            {
                return(null);
            }
            HtmlNode publicControls = messageMeta.GetChildWithClass("publicControls");
            HtmlNode postNumber     = publicControls.GetChildWithClass("postNumber");

            string postNumberText = postNumber.InnerText;

            // Skip the leading # character.
            if (postNumberText.StartsWith("#", StringComparison.Ordinal))
            {
                postNumberText = postNumberText.Substring(1);
            }

            number = int.Parse(postNumberText);

            PostComponents post;

            try
            {
                post = new PostComponents(author, id, text, number);
            }
            catch (Exception e)
            {
                ErrorLog.Log(e);
                post = null;
            }

            return(post);
        }
        public void BadConstruction5()
        {
            string msg = @"What do you think they'll be doing now?";

            PostComponents p = new PostComponents(author, "", msg);
        }
        /// <summary>
        /// Get a completed post from the provided HTML list item.
        /// </summary>
        /// <param name="li">List item that contains the post.</param>
        /// <returns>Returns a post object with required information.</returns>
        private PostComponents GetPost(HtmlNode li)
        {
            if (li == null)
            {
                throw new ArgumentNullException(nameof(li));
            }

            string author = "";
            string id     = "";
            string text   = "";
            int    number = 0;

            // ID
            id = li.GetAttributeValue("data-node-id", "");

            if (string.IsNullOrEmpty(id))
            {
                return(null);
            }

            // Author
            var postAuthorNode = li.Descendants("div").FirstOrDefault(a => a.GetAttributeValue("itemprop", "") == "author");
            var authorNode     = postAuthorNode?.GetDescendantWithClass("div", "author");

            if (authorNode != null)
            {
                author = PostText.CleanupWebString(authorNode.InnerText);
            }

            var contentArea = li.GetDescendantWithClass("div", "b-post__content");

            // Number
            var postCountAnchor = contentArea.GetDescendantWithClass("a", "b-post__count");

            if (postCountAnchor != null)
            {
                string postNumText = postCountAnchor.InnerText;
                if (postNumText.StartsWith("#", StringComparison.Ordinal))
                {
                    postNumText = postNumText.Substring(1);
                }

                number = int.Parse(postNumText);
            }

            // Text
            var postTextNode = contentArea.Descendants("div").FirstOrDefault(a => a.GetAttributeValue("itemprop", "") == "text");

            // Predicate filtering out elements that we don't want to include
            var exclusion = PostText.GetClassExclusionPredicate("bbcode_quote");

            // Get the full post text.
            text = PostText.ExtractPostText(postTextNode, exclusion, Host);


            PostComponents post;

            try
            {
                post = new PostComponents(author, id, text, number);
            }
            catch
            {
                post = null;
            }

            return(post);
        }
Exemple #24
0
        /// <summary>
        /// Second pass processing of a post, to handle actual vote processing.
        /// </summary>
        /// <param name="post">The post to process.</param>
        /// <param name="quest">The quest being tallied.</param>
        /// <returns>True if the post was processed, false if it was not.</returns>
        public bool ProcessPost(PostComponents post, IQuest quest, CancellationToken token)
        {
            if (post == null)
            {
                throw new ArgumentNullException(nameof(post));
            }
            if (quest == null)
            {
                throw new ArgumentNullException(nameof(quest));
            }
            if (!post.IsVote)
            {
                throw new ArgumentException("Post is not a valid vote.");
            }

            token.ThrowIfCancellationRequested();

            // If the vote has content, deal with it
            if (post.WorkingVote != null && post.WorkingVote.Count > 0)
            {
                // If it has a reference to a plan or voter that has not been processed yet,
                // delay processing.
                if (HasFutureReference(post, quest))
                {
                    VoteCounter.FutureReferences.Add(post);
                    return(false);
                }

                // If a newer vote has been registered in the vote counter, that means
                // that this post was a prior future reference that got overridden later.
                // Indicate that it has been processed so that it doesn't try to
                // re-submit it later.
                if (VoteCounter.HasNewerVote(post))
                {
                    return(true);
                }

                // Get the list of all vote partitions, built according to current preferences.
                // One of: By line, By block, or By post (ie: entire vote)
                List <string> votePartitions = GetVotePartitions(post.WorkingVote, quest.PartitionMode, VoteType.Vote, post.Author);

                var filteredPartitions = FilterVotesByTask(votePartitions, quest);

                // Add those to the vote counter.
                VoteCounter.AddVotes(filteredPartitions, post.Author, post.ID, VoteType.Vote);
            }

            // Handle ranking votes, if applicable.
            if (AdvancedOptions.Instance.AllowRankedVotes)
            {
                var rankings = GetRankingsFromPost(post, quest);

                if (rankings.Count > 0)
                {
                    ProcessRankings(rankings, post);
                }
            }

            post.Processed = true;
            return(true);
        }
Exemple #25
0
        /// <summary>
        /// Determine if there are any references to future (unprocessed) user votes
        /// within the current vote.
        /// </summary>
        /// <param name="post">Post containing the current vote.</param>
        /// <returns>Returns true if a future reference is found. Otherwise false.</returns>
        private bool HasFutureReference(PostComponents post, IQuest quest)
        {
            // If we decide it has to be forced, ignore all checks in here.
            if (post.ForceProcess)
            {
                return(false);
            }

            // If proxy votes are disabled, we don't need to look for proxy names, so there can't be future references.
            // Likewise, if we're forcing all proxy votes to be pinned, there can't be any future references.
            if (quest.DisableProxyVotes || quest.ForcePinnedProxyVotes)
            {
                return(false);
            }

            foreach (var line in post.WorkingVote)
            {
                // Get the possible proxy references this line contains
                var refNames = VoteString.GetVoteReferenceNames(line);

                // Pinned references (^ or pin keywords) are explicitly not future references
                if (refNames[ReferenceType.Label].Any(a => a == "^" || a == "pin"))
                {
                    continue;
                }

                // Any references to plans automatically work, as they are defined in a preprocess phase.
                if (refNames[ReferenceType.Plan].Any(VoteCounter.HasPlan))
                {
                    continue;
                }

                string refVoter = refNames[ReferenceType.Voter].FirstOrDefault(n => VoteCounter.ReferenceVoters.Contains(n, Agnostic.StringComparer))
                                  ?.AgnosticMatch(VoteCounter.ReferenceVoters);

                if (refVoter != null && refVoter != post.Author)
                {
                    var refVoterPosts = VoteCounter.PostsList.Where(p => p.Author == refVoter).ToList();

                    // If ref voter has no posts (how did we get here?), it can't be a future reference.
                    if (!refVoterPosts.Any())
                    {
                        continue;
                    }

                    // If the referenced voter never made a real vote (eg: only made base plans or rank votes),
                    // then this can't be treated as a future reference.
                    var refWorkingVotes = refVoterPosts.Where(p => p.WorkingVote.Count > 0);

                    if (!refWorkingVotes.Any())
                    {
                        continue;
                    }

                    // If there's no 'plan' label, then we need to verify that the last vote that the
                    // ref voter made (has a working vote) was processed.
                    // If it's been processed, then we're OK to let this vote through.
                    if (refWorkingVotes.Last().Processed)
                    {
                        continue;
                    }

                    // If none of the conditions above are met, then consider this a future reference.
                    return(true);
                }
            }

            // No future references were found.
            return(false);
        }
Exemple #26
0
        /// <summary>
        /// Get the lines of the vote that we will be processing out of the post.
        /// Only take the .VoteLines, and condense any instances of known plans
        /// to just a reference to the plan name.
        /// </summary>
        /// <param name="post">The post we're getting the vote from.</param>
        /// <returns>Returns the vote with plans compressed.</returns>
        public List <string> GetWorkingVote(PostComponents post)
        {
            List <string> vote = new List <string>();

            if (post == null || !post.IsVote)
            {
                return(vote);
            }

            // First determine if any base plans are copies of an original definition, or being defined in this post.
            // If they're just copies, then embed them in the working vote.

            if (post.BasePlans.Any())
            {
                var    voters    = VoteCounter.GetVotersCollection(VoteType.Plan);
                bool   checkPlan = true;
                string planName;

                foreach (var bPlan in post.BasePlans)
                {
                    planName = VoteString.GetMarkedPlanName(bPlan.Key);
                    if (planName == null)
                    {
                        continue;
                    }

                    // As long as we keep finding base plans that are defined in this post, keep skipping.
                    if (checkPlan)
                    {
                        if (VoteCounter.HasPlan(planName) && voters[planName] == post.ID)
                        {
                            continue;
                        }
                    }

                    checkPlan = false;

                    // If we reach here, any further plans are copy/pastes of defined plans, and should
                    // have the key added to the working vote.
                    vote.Add(bPlan.Key);
                }
            }

            // Then make sure there are actual vote lines to process.
            if (!post.VoteLines.Any())
            {
                return(vote);
            }

            // Then check if the *entire post* should be treated as a complete plan.
            string postPlanName = VoteString.GetPlanName(post.VoteLines.First());

            if (postPlanName != null && VoteCounter.ReferencePlans.ContainsKey(postPlanName) &&
                VoteCounter.ReferencePlans[postPlanName].Skip(1).SequenceEqual(post.VoteLines.Skip(1), Agnostic.StringComparer))
            {
                // Replace known plans with just the plan key.  They'll be expanded later.
                vote.Add(post.VoteLines.First());
            }
            else
            {
                // If the entire post isn't an auto-plan, break it down into blocks.

                // Break the remainder of the vote into blocks so that we can compare vs auto-plans.
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);


                foreach (var block in voteBlocks)
                {
                    // Multi-line blocks might be a plan.  Check.
                    if (block.Count() > 1)
                    {
                        // See if the block key marks a known plan.
                        string planName = VoteString.GetPlanName(block.Key);

                        if (planName != null && VoteCounter.ReferencePlans.ContainsKey(planName) &&
                            VoteCounter.ReferencePlans[planName].Skip(1).SequenceEqual(block.Skip(1), Agnostic.StringComparer))
                        {
                            // Replace known plans with just the plan key.  They'll be expanded later.
                            vote.Add(block.Key);
                        }
                        else
                        {
                            // If it's not a known plan, pass everything through.
                            vote.AddRange(block);
                        }
                    }
                    else
                    {
                        // Single lines can be added normally
                        vote.AddRange(block);
                        //vote.Add(block.Key);
                    }
                }
            }

            return(vote);
        }
        public void BadConstruction1()
        {
            string msg = @"What do you think they'll be doing now?";

            PostComponents p = new PostComponents(null, id, msg);
        }