From bac88a2e68644473e52a6d9bbd6189763026486c Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 5 May 2024 04:56:17 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E5=90=8D=E3=82=84=E7=99=BA=E8=A8=80=E6=95=B0=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E3=81=AE=E5=8F=AF?= =?UTF-8?q?=E5=A4=89=E3=81=AA=E6=83=85=E5=A0=B1=E3=82=92TwitterAccountStat?= =?UTF-8?q?e=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AB=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/Api/TwitterApiTest.cs | 20 +++--- .../Twitter/TwitterAccountStateTest.cs | 52 ++++++++++++++++ OpenTween/Api/TwitterApi.cs | 17 +++--- OpenTween/AppendSettingDialog.cs | 2 +- .../SocialProtocol/Twitter/TwitterAccount.cs | 9 ++- .../Twitter/TwitterAccountState.cs | 61 +++++++++++++++++++ OpenTween/Tween.cs | 10 ++- OpenTween/Twitter.cs | 32 ++++------ 8 files changed, 156 insertions(+), 47 deletions(-) create mode 100644 OpenTween.Tests/SocialProtocol/Twitter/TwitterAccountStateTest.cs create mode 100644 OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs diff --git a/OpenTween.Tests/Api/TwitterApiTest.cs b/OpenTween.Tests/Api/TwitterApiTest.cs index 58e754ced..05289f1ff 100644 --- a/OpenTween.Tests/Api/TwitterApiTest.cs +++ b/OpenTween.Tests/Api/TwitterApiTest.cs @@ -32,6 +32,7 @@ using Moq; using OpenTween.Api.DataModel; using OpenTween.Connection; +using OpenTween.SocialProtocol.Twitter; using Xunit; namespace OpenTween.Api @@ -57,26 +58,24 @@ public void Initialize_Test() Assert.IsType(apiConnection.Credential); var credential = new TwitterCredentialOAuth1(TwitterAppToken.GetDefault(), "*** AccessToken ***", "*** AccessSecret ***"); - twitterApi.Initialize(credential, userId: 100L, screenName: "hogehoge"); + var accountState = new TwitterAccountState(100L, "hogehoge"); + twitterApi.Initialize(credential, accountState); apiConnection = Assert.IsType(twitterApi.Connection); Assert.Same(credential, apiConnection.Credential); - - Assert.Equal(100L, twitterApi.CurrentUserId); - Assert.Equal("hogehoge", twitterApi.CurrentScreenName); + Assert.Same(accountState, twitterApi.AccountState); // 複数回 Initialize を実行した場合は新たに TwitterApiConnection が生成される var credential2 = new TwitterCredentialOAuth1(TwitterAppToken.GetDefault(), "*** AccessToken2 ***", "*** AccessSecret2 ***"); - twitterApi.Initialize(credential2, userId: 200L, screenName: "foobar"); + var accountState2 = new TwitterAccountState(200L, "foobar"); + twitterApi.Initialize(credential2, accountState2); var oldApiConnection = apiConnection; Assert.True(oldApiConnection.IsDisposed); apiConnection = Assert.IsType(twitterApi.Connection); Assert.Same(credential2, apiConnection.Credential); - - Assert.Equal(200L, twitterApi.CurrentUserId); - Assert.Equal("foobar", twitterApi.CurrentScreenName); + Assert.Same(accountState2, twitterApi.AccountState); } private Mock CreateApiConnectionMock(Action verifyRequest) @@ -1173,10 +1172,7 @@ public async Task AccountVerifyCredentials_Test() using var twitterApi = new TwitterApi(); twitterApi.ApiConnection = mock.Object; - await twitterApi.AccountVerifyCredentials(); - - Assert.Equal(100L, twitterApi.CurrentUserId); - Assert.Equal("opentween", twitterApi.CurrentScreenName); + var user = await twitterApi.AccountVerifyCredentials(); mock.VerifyAll(); } diff --git a/OpenTween.Tests/SocialProtocol/Twitter/TwitterAccountStateTest.cs b/OpenTween.Tests/SocialProtocol/Twitter/TwitterAccountStateTest.cs new file mode 100644 index 000000000..0035cdf00 --- /dev/null +++ b/OpenTween.Tests/SocialProtocol/Twitter/TwitterAccountStateTest.cs @@ -0,0 +1,52 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2024 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using OpenTween.Api.DataModel; +using Xunit; + +namespace OpenTween.SocialProtocol.Twitter +{ + public class TwitterAccountStateTest + { + [Fact] + public void UpdateFromUser_Test() + { + var accountState = new TwitterAccountState(); + + var twitterUser = new TwitterUser + { + Id = 514241801L, + IdStr = "514241801", + ScreenName = "OpenTween", + StatusesCount = 31, + FriendsCount = 1, + FollowersCount = 302, + }; + accountState.UpdateFromUser(twitterUser); + + Assert.Equal(514241801L, accountState.UserId); + Assert.Equal("OpenTween", accountState.UserName); + Assert.Equal(31, accountState.StatusesCount); + Assert.Equal(1, accountState.FriendsCount); + Assert.Equal(302, accountState.FollowersCount); + } + } +} diff --git a/OpenTween/Api/TwitterApi.cs b/OpenTween/Api/TwitterApi.cs index 0db253ef8..f8b1a83b5 100644 --- a/OpenTween/Api/TwitterApi.cs +++ b/OpenTween/Api/TwitterApi.cs @@ -32,14 +32,17 @@ using OpenTween.Api.DataModel; using OpenTween.Connection; using OpenTween.Models; +using OpenTween.SocialProtocol.Twitter; namespace OpenTween.Api { public sealed class TwitterApi : IDisposable { - public long CurrentUserId { get; private set; } + public long CurrentUserId + => this.AccountState.UserId; - public string CurrentScreenName { get; private set; } = ""; + public string CurrentScreenName + => this.AccountState.UserName; public IApiConnection Connection => this.ApiConnection; @@ -47,10 +50,12 @@ public sealed class TwitterApi : IDisposable public APIAuthType AuthType { get; private set; } = APIAuthType.None; + public TwitterAccountState AccountState { get; private set; } = new(); + public TwitterApi() => this.ApiConnection = new TwitterApiConnection(new TwitterCredentialNone()); - public void Initialize(ITwitterCredential credential, long userId, string screenName) + public void Initialize(ITwitterCredential credential, TwitterAccountState accountState) { this.AuthType = credential.AuthType; @@ -58,8 +63,7 @@ public void Initialize(ITwitterCredential credential, long userId, string screen var oldInstance = Interlocked.Exchange(ref this.ApiConnection, newInstance); oldInstance?.Dispose(); - this.CurrentUserId = userId; - this.CurrentScreenName = screenName; + this.AccountState = accountState; } public async Task StatusesHomeTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null) @@ -978,9 +982,6 @@ public async Task AccountVerifyCredentials() var user = await response.ReadAsJson() .ConfigureAwait(false); - this.CurrentUserId = user.Id; - this.CurrentScreenName = user.ScreenName; - return user; } diff --git a/OpenTween/AppendSettingDialog.cs b/OpenTween/AppendSettingDialog.cs index 092ecf23c..8ce7e7c8d 100644 --- a/OpenTween/AppendSettingDialog.cs +++ b/OpenTween/AppendSettingDialog.cs @@ -188,7 +188,7 @@ private async void AddAccountButton_Click(object sender, EventArgs e) }; using var twitterApi = new TwitterApi(); - twitterApi.Initialize(new TwitterCredentialCookie(appToken), 0L, ""); + twitterApi.Initialize(new TwitterCredentialCookie(appToken), new()); var twitterUser = await twitterApi.AccountVerifyCredentials(); newAccount.UserId = twitterUser.Id; newAccount.Username = twitterUser.ScreenName; diff --git a/OpenTween/SocialProtocol/Twitter/TwitterAccount.cs b/OpenTween/SocialProtocol/Twitter/TwitterAccount.cs index b9c012c2f..962145909 100644 --- a/OpenTween/SocialProtocol/Twitter/TwitterAccount.cs +++ b/OpenTween/SocialProtocol/Twitter/TwitterAccount.cs @@ -35,14 +35,16 @@ public class TwitterAccount : ISocialAccount public bool IsDisposed { get; private set; } + public TwitterAccountState AccountState { get; private set; } = new(); + public OpenTween.Twitter Legacy => this.twLegacy; public long UserId - => this.Legacy.UserId; + => this.AccountState.UserId; public string UserName - => this.Legacy.Username; + => this.AccountState.UserName; public APIAuthType AuthType => this.Legacy.Api.AuthType; @@ -58,7 +60,8 @@ public void Initialize(UserAccount accountSettings, SettingCommon settingCommon) Debug.Assert(accountSettings.UniqueKey == this.UniqueKey, "UniqueKey must be same as current value."); var credential = accountSettings.GetTwitterCredential(); - this.twLegacy.Initialize(credential, accountSettings.Username, accountSettings.UserId); + this.AccountState = new TwitterAccountState(accountSettings.UserId, accountSettings.Username); + this.twLegacy.Initialize(credential, this.AccountState); this.twLegacy.RestrictFavCheck = settingCommon.RestrictFavCheck; } diff --git a/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs b/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs new file mode 100644 index 000000000..bdf5084c0 --- /dev/null +++ b/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs @@ -0,0 +1,61 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2024 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using OpenTween.Api.DataModel; + +namespace OpenTween.SocialProtocol.Twitter +{ + public class TwitterAccountState + { + public long UserId { get; private set; } + + public string UserName { get; private set; } + + public int? FollowersCount { get; private set; } + + public int? FriendsCount { get; private set; } + + public int? StatusesCount { get; private set; } + + public TwitterAccountState() + : this(0L, "") + { + } + + public TwitterAccountState(long userId, string userName) + { + this.UserId = userId; + this.UserName = userName; + } + + /// ユーザー情報を更新します + public void UpdateFromUser(TwitterUser self) + { + this.UserId = self.Id; + this.UserName = self.ScreenName; + this.FollowersCount = self.FollowersCount; + this.FriendsCount = self.FriendsCount; + this.StatusesCount = self.StatusesCount; + } + } +} diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index aa71d7d32..68a940832 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -6774,8 +6774,14 @@ private void SetMainWindowTitle() ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText4, ur, al); break; case MyCommon.DispTitleEnum.OwnStatus: - if (followers == 0 && this.tw.FollowersCount > 0) followers = this.tw.FollowersCount; - ttl.AppendFormat(Properties.Resources.OwnStatusTitle, this.tw.StatusesCount, this.tw.FriendsCount, this.tw.FollowersCount, this.tw.FollowersCount - followers); + if (followers == 0 && this.tw.FollowersCount != null) followers = this.tw.FollowersCount.Value; + ttl.AppendFormat( + Properties.Resources.OwnStatusTitle, + this.tw.StatusesCount?.ToString() ?? "-", + this.tw.FriendsCount?.ToString() ?? "-", + this.tw.FollowersCount?.ToString() ?? "-", + this.tw.FollowersCount != null ? this.tw.FollowersCount.Value - followers : "-" + ); break; } diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 0fcbc92d2..5c9eeca4c 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -49,6 +49,7 @@ using OpenTween.Connection; using OpenTween.Models; using OpenTween.Setting; +using OpenTween.SocialProtocol.Twitter; namespace OpenTween { @@ -217,17 +218,17 @@ public async Task VerifyCredentialsAsync() var user = await this.Api.AccountVerifyCredentials() .ConfigureAwait(false); - this.UpdateUserStats(user); + this.Api.AccountState.UpdateFromUser(user); } - public void Initialize(ITwitterCredential credential, string username, long userId) + public void Initialize(ITwitterCredential credential, TwitterAccountState accountState) { // OAuth認証 if (credential is TwitterCredentialNone) Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid; this.ResetApiStatus(); - this.Api.Initialize(credential, userId, username); + this.Api.Initialize(credential, accountState); } public async Task PostStatus(PostStatusParams param) @@ -275,7 +276,7 @@ await this.SendDirectMessage(param.Text, mediaId) .ConfigureAwait(false); } - this.UpdateUserStats(status.User); + this.Api.AccountState.UpdateFromUser(status.User); if (status.IdStr == this.previousStatusId) throw new WebApiException("OK:Delaying?"); @@ -533,25 +534,14 @@ public long UserId public bool RestrictFavCheck { get; set; } - public int FollowersCount { get; private set; } + public int? FollowersCount + => this.Api.AccountState.FollowersCount; - public int FriendsCount { get; private set; } + public int? FriendsCount + => this.Api.AccountState.FriendsCount; - public int StatusesCount { get; private set; } - - public string Location { get; private set; } = ""; - - public string Bio { get; private set; } = ""; - - /// ユーザーのフォロワー数などの情報を更新します - private void UpdateUserStats(TwitterUser self) - { - this.FollowersCount = self.FollowersCount; - this.FriendsCount = self.FriendsCount; - this.StatusesCount = self.StatusesCount; - this.Location = self.Location ?? ""; - this.Bio = self.Description ?? ""; - } + public int? StatusesCount + => this.Api.AccountState.StatusesCount; /// /// 渡された取得件数がWORKERTYPEに応じた取得可能範囲に収まっているか検証する From eb7dd7fd3f6ffeef4086dbfd9c3bb3a6de806305 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Mon, 6 May 2024 18:02:45 +0900 Subject: [PATCH 2/2] =?UTF-8?q?TwitterAccountState=E3=81=AB=20FollowerIds,?= =?UTF-8?q?=20NoRetweetUserIds=20=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SocialProtocol/Twitter/TwitterAccountState.cs | 5 +++++ OpenTween/Twitter.cs | 13 ++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs b/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs index bdf5084c0..c9bbb26fd 100644 --- a/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs +++ b/OpenTween/SocialProtocol/Twitter/TwitterAccountState.cs @@ -21,6 +21,7 @@ #nullable enable +using System.Collections.Generic; using OpenTween.Api.DataModel; namespace OpenTween.SocialProtocol.Twitter @@ -37,6 +38,10 @@ public class TwitterAccountState public int? StatusesCount { get; private set; } + public ISet FollowerIds { get; set; } = new HashSet(); + + public ISet NoRetweetUserIds { get; set; } = new HashSet(); + public TwitterAccountState() : this(0L, "") { diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 5c9eeca4c..a064b9b9a 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -171,8 +171,6 @@ public class Twitter : IDisposable private delegate void GetIconImageDelegate(PostClass post); private readonly object lockObj = new(); - private ISet followerId = new HashSet(); - private long[] noRTId = Array.Empty(); private readonly TwitterPostFactory postFactory; private readonly PostUrlExpander urlExpander; @@ -826,7 +824,7 @@ private PostClass CreatePostsFromStatusData(TwitterStatus status, bool firstLoad private PostClass CreatePostsFromStatusData(TwitterStatus status, bool firstLoad, bool favTweet) { - var post = this.postFactory.CreateFromStatus(status, this.UserId, this.followerId, firstLoad, favTweet); + var post = this.postFactory.CreateFromStatus(status, this.UserId, this.Api.AccountState.FollowerIds, firstLoad, favTweet); _ = this.urlExpander.Expand(post); return post; @@ -842,7 +840,7 @@ private PostClass[] CreatePostsFromJson(TwitterStatus[] statuses, bool firstLoad } private PostClass[] FilterNoRetweetUserPosts(PostClass[] posts) - => posts.Where(x => x.RetweetedByUserId == null || !this.noRTId.Contains(x.RetweetedByUserId.Value)).ToArray(); + => posts.Where(x => x.RetweetedByUserId == null || !this.Api.AccountState.NoRetweetUserIds.Contains(x.RetweetedByUserId.Value)).ToArray(); public async Task GetListStatus(ListTimelineTabModel tab, bool more, bool firstLoad) { @@ -1287,8 +1285,8 @@ public async Task RefreshFollowerIds() } while (cursor != 0); - this.followerId = newFollowerIds.ToHashSet(); - TabInformations.GetInstance().RefreshOwl(this.followerId); + this.Api.AccountState.FollowerIds = newFollowerIds.ToHashSet(); + TabInformations.GetInstance().RefreshOwl(this.Api.AccountState.FollowerIds); this.GetFollowersSuccess = true; } @@ -1301,9 +1299,10 @@ public async Task RefreshNoRetweetIds() { if (MyCommon.EndingFlag) return; - this.noRTId = await this.Api.NoRetweetIds() + var noRetweetUserIds = await this.Api.NoRetweetIds() .ConfigureAwait(false); + this.Api.AccountState.NoRetweetUserIds = new HashSet(noRetweetUserIds); this.GetNoRetweetSuccess = true; }