Пример #1
0
        public void ConvertToOriginalPost_Test()
        {
            var retweetPost = new PostClass
            {
                StatusId   = 100L,
                ScreenName = "@aaa",
                UserId     = 1L,

                RetweetedId       = 50L,
                RetweetedBy       = "@bbb",
                RetweetedByUserId = 2L,
                RetweetedCount    = 0,
            };

            var originalPost = retweetPost.ConvertToOriginalPost();

            Assert.Equal(50L, originalPost.StatusId);
            Assert.Equal("@aaa", originalPost.ScreenName);
            Assert.Equal(1L, originalPost.UserId);

            Assert.Equal(null, originalPost.RetweetedId);
            Assert.Equal("", originalPost.RetweetedBy);
            Assert.Equal(null, originalPost.RetweetedByUserId);
            Assert.Equal(1, originalPost.RetweetedCount);
        }
Пример #2
0
        public void CloneTest()
        {
            var post      = new PostClass();
            var clonePost = post.Clone();

            TestUtils.CheckDeepCloning(post, clonePost);
        }
Пример #3
0
        public void EnqueueRemovePost_SetIsDeletedTest()
        {
            var tab = new PublicSearchTabModel("search")
            {
                UnreadManage = true,
            };

            var post = new PostClass {
                StatusId = 100L, IsRead = false
            };

            tab.AddPostQueue(post);
            tab.AddSubmit();

            Assert.Equal(1, tab.AllCount);
            Assert.Equal(1, tab.UnreadCount);

            tab.EnqueueRemovePost(100L, setIsDeleted: true);

            // この時点ではタブからの削除は行われないが、PostClass.IsDeleted は true にセットされる
            Assert.Equal(1, tab.AllCount);
            Assert.Equal(1, tab.UnreadCount);
            Assert.True(post.IsDeleted);

            var removedIds = tab.RemoveSubmit();

            Assert.Equal(0, tab.AllCount);
            Assert.Equal(0, tab.UnreadCount);
            Assert.Equal(new[] { 100L }, removedIds.AsEnumerable());
        }
Пример #4
0
        public PostClass[] this[int startIndex, int endIndex]
        {
            get
            {
                if (startIndex < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(startIndex));
                }
                if (endIndex >= this.AllCount)
                {
                    throw new ArgumentOutOfRangeException(nameof(endIndex));
                }
                if (startIndex > endIndex)
                {
                    throw new ArgumentException($"{nameof(startIndex)} must be equal to or less than {nameof(endIndex)}.", nameof(startIndex));
                }

                var length = endIndex - startIndex + 1;
                var posts  = new PostClass[length];

                var i = 0;
                foreach (var idx in Enumerable.Range(startIndex, length))
                {
                    var statusId = this.GetStatusIdAt(idx);
                    this.Posts.TryGetValue(statusId, out posts[i++]);
                }

                return(posts);
            }
        }
Пример #5
0
        public void IsMuted_MuteTabRules_NotInHomeTimelineTest()
        {
            this.tabinfo.MuteUserIds = new HashSet <long> {
            };

            var muteTab = new MuteTabModel();

            muteTab.AddFilter(new PostFilterRule
            {
                FilterName  = "foo",
                MoveMatches = true,
            });
            this.tabinfo.AddTab(muteTab);

            // ミュートタブによるミュートはリプライも対象とする
            var post = new PostClass
            {
                UserId     = 12345L,
                ScreenName = "foo",
                Text       = "@hoge hogehoge",
                IsReply    = true,
            };

            Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: false));
        }
Пример #6
0
        public void TextSingleLineTest(string text, string expected)
        {
            var post = new PostClass {
                TextFromApi = text
            };

            Assert.Equal(expected, post.TextSingleLine);
        }
Пример #7
0
        public virtual void AddPostQueue(PostClass post)
        {
            if (!this.Posts.ContainsKey(post.StatusId))
            {
                throw new ArgumentException("Specified post not exists in storage", nameof(post));
            }

            this.addQueue.Enqueue(new TemporaryId(post.StatusId, post.IsRead));
        }
Пример #8
0
        public void ConvertToOriginalPost_ErrorTest()
        {
            // 公式 RT でないツイート
            var post = new PostClass {
                StatusId = 100L, RetweetedId = null
            };

            Assert.Throws <InvalidOperationException>(() => post.ConvertToOriginalPost());
        }
Пример #9
0
        public override void AddPostQueue(PostClass post)
        {
            if (TabInformations.GetInstance().IsMuted(post, isHomeTimeline: false))
            {
                return;
            }

            this.internalPosts.TryAdd(post.StatusId, post);

            base.AddPostQueue(post);
        }
Пример #10
0
        public bool AddQuoteTweet(PostClass item)
        {
            lock (LockObj)
            {
                if (IsMuted(item, isHomeTimeline: false) || BlockIds.Contains(item.UserId))
                {
                    return(false);
                }

                _quotes[item.StatusId] = item;
                return(true);
            }
        }
Пример #11
0
        public void IsMuted_NotMutingTest()
        {
            this.tabinfo.MuteUserIds = new HashSet <long> {
                12345L
            };

            var post = new PostClass
            {
                UserId = 11111L,
                Text   = "hogehoge",
            };

            Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
        }
Пример #12
0
        public void IsMuted_NotInHomeTimelineTest()
        {
            this.tabinfo.MuteUserIds = new HashSet <long> {
                12345L
            };

            // Recent以外のタブ(検索など)の場合は対象外とする
            var post = new PostClass
            {
                UserId = 12345L,
                Text   = "hogehoge",
            };

            Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: false));
        }
Пример #13
0
        public void IsMuted_RetweetTest()
        {
            this.tabinfo.MuteUserIds = new HashSet <long> {
                12345L
            };

            var post = new PostClass
            {
                UserId            = 11111L,
                RetweetedByUserId = 12345L,
                Text = "hogehoge",
            };

            Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
        }
Пример #14
0
        public void IsMuted_ReplyTest()
        {
            this.tabinfo.MuteUserIds = new HashSet <long> {
                12345L
            };

            // ミュート対象のユーザーであってもリプライの場合は対象外とする
            var post = new PostClass
            {
                UserId  = 12345L,
                Text    = "@foo hogehoge",
                IsReply = true,
            };

            Assert.False(this.tabinfo.IsMuted(post, isHomeTimeline: true));
        }
Пример #15
0
        public async Task ExpandedUrls_BasicScenario()
        {
            var post = new PostClass
            {
                Text         = "<a href=\"http://t.co/aaaaaaa\" title=\"http://t.co/aaaaaaa\">bit.ly/abcde</a>",
                ExpandedUrls = new[]
                {
                    new FakeExpandedUrlInfo(
                        // 展開前の t.co ドメインの URL
                        url:  "http://t.co/aaaaaaa",

                        // Entity の expanded_url に含まれる URL
                        expandedUrl: "http://bit.ly/abcde",

                        // expandedUrl をさらに ShortUrl クラスで再帰的に展開する
                        deepExpand: true
                        ),
                },
            };

            var urlInfo = (FakeExpandedUrlInfo)post.ExpandedUrls.Single();

            // ExpandedUrlInfo による展開が完了していない状態
            //   → この段階では Entity に含まれる expanded_url の URL が使用される
            Assert.False(urlInfo.ExpandedCompleted);
            Assert.Equal("http://bit.ly/abcde", urlInfo.ExpandedUrl);
            Assert.Equal("http://bit.ly/abcde", post.GetExpandedUrl("http://t.co/aaaaaaa"));
            Assert.Equal(new[] { "http://bit.ly/abcde" }, post.GetExpandedUrls());
            Assert.Equal("<a href=\"http://t.co/aaaaaaa\" title=\"http://bit.ly/abcde\">bit.ly/abcde</a>", post.Text);

            // bit.ly 展開後の URL は「http://example.com/abcde」
            urlInfo.fakeResult.SetResult("http://example.com/abcde");
            await urlInfo.ExpandTask;

            // ExpandedUrlInfo による展開が完了した後の状態
            //   → 再帰的な展開後の URL が使用される
            Assert.True(urlInfo.ExpandedCompleted);
            Assert.Equal("http://example.com/abcde", urlInfo.ExpandedUrl);
            Assert.Equal("http://example.com/abcde", post.GetExpandedUrl("http://t.co/aaaaaaa"));
            Assert.Equal(new[] { "http://example.com/abcde" }, post.GetExpandedUrls());
            Assert.Equal("<a href=\"http://t.co/aaaaaaa\" title=\"http://example.com/abcde\">bit.ly/abcde</a>", post.Text);
        }
Пример #16
0
        public void IsMuted_MuteTabRulesTest()
        {
            this.tabinfo.MuteUserIds = new HashSet <long> {
            };

            var muteTab = new MuteTabModel();

            muteTab.AddFilter(new PostFilterRule
            {
                FilterName  = "foo",
                MoveMatches = true,
            });
            this.tabinfo.AddTab(muteTab);

            var post = new PostClass
            {
                UserId     = 12345L,
                ScreenName = "foo",
                Text       = "hogehoge",
            };

            Assert.True(this.tabinfo.IsMuted(post, isHomeTimeline: true));
        }
Пример #17
0
        public bool IsMuted(PostClass post, bool isHomeTimeline)
        {
            var muteTab = this.GetTabByType <MuteTabModel>();

            if (muteTab != null && muteTab.AddFiltered(post) == MyCommon.HITRESULT.Move)
            {
                return(true);
            }

            // これ以降は Twitter 標準のミュート機能に準じた判定
            // 参照: https://support.twitter.com/articles/20171399-muting-users-on-twitter

            // ホームタイムライン以外 (検索・リストなど) は対象外
            if (!isHomeTimeline)
            {
                return(false);
            }

            // リプライはミュート対象外
            if (post.IsReply)
            {
                return(false);
            }

            if (this.MuteUserIds.Contains(post.UserId))
            {
                return(true);
            }

            if (post.RetweetedByUserId != null && this.MuteUserIds.Contains(post.RetweetedByUserId.Value))
            {
                return(true);
            }

            return(false);
        }
Пример #18
0
 public override void AddPostQueue(PostClass post)
 {
 }
Пример #19
0
        public MyCommon.HITRESULT AddFiltered(PostClass post, bool immediately = false)
        {
            if (this.IsInnerStorageTabType)
            {
                return(MyCommon.HITRESULT.None);
            }

            var rslt = MyCommon.HITRESULT.None;

            //全フィルタ評価(優先順位あり)
            lock (this.lockObjFilters)
            {
                foreach (var ft in _filters)
                {
                    try
                    {
                        switch (ft.ExecFilter(post))   //フィルタクラスでヒット判定
                        {
                        case MyCommon.HITRESULT.None:
                            break;

                        case MyCommon.HITRESULT.Copy:
                            if (rslt != MyCommon.HITRESULT.CopyAndMark)
                            {
                                rslt = MyCommon.HITRESULT.Copy;
                            }
                            break;

                        case MyCommon.HITRESULT.CopyAndMark:
                            rslt = MyCommon.HITRESULT.CopyAndMark;
                            break;

                        case MyCommon.HITRESULT.Move:
                            rslt = MyCommon.HITRESULT.Move;
                            break;

                        case MyCommon.HITRESULT.Exclude:
                            rslt = MyCommon.HITRESULT.Exclude;
                            goto exit_for;
                        }
                    }
                    catch (NullReferenceException)
                    {
                        // ExecFilterでNullRef出る場合あり。暫定対応
                        MyCommon.TraceOut("ExecFilterでNullRef: " + ft);
                        rslt = MyCommon.HITRESULT.None;
                    }
                }
exit_for:
                ;
            }

            if (this.TabType != MyCommon.TabUsageType.Mute &&
                rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
            {
                if (immediately)
                {
                    this.AddPostImmediately(post.StatusId, post.IsRead);
                }
                else
                {
                    this.AddPostQueue(post);
                }
            }

            return(rslt); //マーク付けは呼び出し元で行うこと
        }
Пример #20
0
        private int UpdateRetweetCount(PostClass retweetPost)
        {
            var retweetedId = retweetPost.RetweetedId.Value;

            return(this.retweetsCount.AddOrUpdate(retweetedId, 1, (k, v) => v >= 10 ? 1 : v + 1));
        }
Пример #21
0
        public void AddPost(PostClass Item)
        {
            Debug.Assert(!Item.IsDm, "DM は TabClass.AddPostToInnerStorage を使用する");

            lock (LockObj)
            {
                if (this.IsMuted(Item, isHomeTimeline: true))
                {
                    return;
                }

                if (_statuses.TryGetValue(Item.StatusId, out var status))
                {
                    if (Item.IsFav)
                    {
                        if (Item.RetweetedId == null)
                        {
                            status.IsFav = true;
                        }
                        else
                        {
                            Item.IsFav = false;
                        }
                    }
                    else
                    {
                        return;        //追加済みなら何もしない
                    }
                }
                else
                {
                    if (Item.IsFav && Item.RetweetedId != null)
                    {
                        Item.IsFav = false;
                    }

                    //既に持っている公式RTは捨てる
                    if (Item.RetweetedId != null && SettingManager.Common.HideDuplicatedRetweets)
                    {
                        var retweetCount = this.UpdateRetweetCount(Item);

                        if (retweetCount > 1 && !Item.IsMe)
                        {
                            return;
                        }
                    }

                    if (BlockIds.Contains(Item.UserId))
                    {
                        return;
                    }

                    _statuses.TryAdd(Item.StatusId, Item);
                }
                if (Item.IsFav && this.retweetsCount.ContainsKey(Item.StatusId))
                {
                    return;    //Fav済みのRetweet元発言は追加しない
                }
                this.addQueue.Enqueue(Item.StatusId);
            }
        }
Пример #22
0
 public override void AddPostQueue(PostClass post)
 {
     base.AddPostQueue(post);
     this.UpdateTimelineSpeed(post.CreatedAt);
 }
Пример #23
0
 public RelatedPostsTabModel(string tabName, PostClass targetPost)
     : base(tabName)
 {
     this.TargetPost = targetPost;
 }
Пример #24
0
        public int SubmitUpdate(out string soundFile, out PostClass[] notifyPosts,
                                out bool newMentionOrDm, out bool isDeletePost)
        {
            // 注:メインスレッドから呼ぶこと
            lock (this.LockObj)
            {
                soundFile      = "";
                notifyPosts    = new PostClass[0];
                newMentionOrDm = false;
                isDeletePost   = false;

                var addedCountTotal = 0;
                var removedIdsAll   = new List <long>();
                var notifyPostsList = new List <PostClass>();

                var currentNotifyPriority = -1;

                foreach (var tab in this._tabs.Values)
                {
                    // 振分確定 (各タブに反映)
                    var addedIds = tab.AddSubmit();

                    if (tab.TabType == MyCommon.TabUsageType.Mentions ||
                        tab.TabType == MyCommon.TabUsageType.DirectMessage)
                    {
                        if (addedIds.Count > 0)
                        {
                            newMentionOrDm = true;
                        }
                    }

                    if (addedIds.Count != 0)
                    {
                        if (tab.Notify)
                        {
                            // 通知対象のリストに追加
                            foreach (var statusId in addedIds)
                            {
                                if (tab.Posts.TryGetValue(statusId, out var post))
                                {
                                    notifyPostsList.Add(post);
                                }
                            }
                        }

                        // 通知サウンドは TabClass.Notify の値に関わらず鳴らす
                        // SettingCommon.PlaySound が false であれば TweenMain 側で無効化される
                        if (!string.IsNullOrEmpty(tab.SoundFile))
                        {
                            if (!this.notifyPriorityByTabType.TryGetValue(tab.TabType, out var notifyPriority))
                            {
                                notifyPriority = 0;
                            }

                            if (notifyPriority > currentNotifyPriority)
                            {
                                // より優先度の高い通知を再生する
                                soundFile             = tab.SoundFile;
                                currentNotifyPriority = notifyPriority;
                            }
                        }
                    }

                    addedCountTotal += addedIds.Count;

                    var removedIds = tab.RemoveSubmit();
                    removedIdsAll.AddRange(removedIds);
                }

                notifyPosts = notifyPostsList.Distinct().ToArray();

                if (removedIdsAll.Count > 0)
                {
                    isDeletePost = true;
                }

                foreach (var removedId in removedIdsAll.Distinct())
                {
                    var orphaned = true;
                    foreach (var tab in this.Tabs.Values)
                    {
                        if (tab.Contains(removedId))
                        {
                            orphaned = false;
                            break;
                        }
                    }

                    // 全てのタブから表示されなくなった発言は this._statuses からも削除する
                    if (orphaned)
                    {
                        this._statuses.TryRemove(removedId, out var removedPost);
                    }
                }

                return(addedCountTotal);
            }
        }