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); }
public void CloneTest() { var post = new PostClass(); var clonePost = post.Clone(); TestUtils.CheckDeepCloning(post, clonePost); }
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()); }
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); } }
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)); }
public void TextSingleLineTest(string text, string expected) { var post = new PostClass { TextFromApi = text }; Assert.Equal(expected, post.TextSingleLine); }
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)); }
public void ConvertToOriginalPost_ErrorTest() { // 公式 RT でないツイート var post = new PostClass { StatusId = 100L, RetweetedId = null }; Assert.Throws <InvalidOperationException>(() => post.ConvertToOriginalPost()); }
public override void AddPostQueue(PostClass post) { if (TabInformations.GetInstance().IsMuted(post, isHomeTimeline: false)) { return; } this.internalPosts.TryAdd(post.StatusId, post); base.AddPostQueue(post); }
public bool AddQuoteTweet(PostClass item) { lock (LockObj) { if (IsMuted(item, isHomeTimeline: false) || BlockIds.Contains(item.UserId)) { return(false); } _quotes[item.StatusId] = item; return(true); } }
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)); }
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)); }
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)); }
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)); }
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); }
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)); }
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); }
public override void AddPostQueue(PostClass post) { }
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); //マーク付けは呼び出し元で行うこと }
private int UpdateRetweetCount(PostClass retweetPost) { var retweetedId = retweetPost.RetweetedId.Value; return(this.retweetsCount.AddOrUpdate(retweetedId, 1, (k, v) => v >= 10 ? 1 : v + 1)); }
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); } }
public override void AddPostQueue(PostClass post) { base.AddPostQueue(post); this.UpdateTimelineSpeed(post.CreatedAt); }
public RelatedPostsTabModel(string tabName, PostClass targetPost) : base(tabName) { this.TargetPost = targetPost; }
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); } }