Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

メインアカウント以外のホームタイムラインを表示する機能を追加 #318

Merged
merged 3 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
更新履歴

==== Unreleased
* NEW: メインアカウント以外のホームタイムライン表示に対応
- タブ単位で切り替わるマルチアカウント機能です
- 投稿欄やふぁぼ・RT等の機能も表示中のタブに連動して使用するアカウントが変わります
- 現時点ではメインアカウント以外のタブ設定は次回起動時に保持されません
* NEW: Twemoji 15.1.0 に対応しました
- Unicode 15.1 で追加された絵文字が表示されるようになります
* CHG: 設定画面でのアカウント一覧の表示形式を変更
Expand Down
31 changes: 27 additions & 4 deletions OpenTween.Tests/SocialProtocol/AccountCollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ public void LoadFromSettings_ReconfigureTest()
Assert.False(accountItem2.IsDisposed);
}

[Fact]
public void SecondaryAccounts_Test()
{
using var accounts = new AccountCollection();
accounts.LoadFromSettings(new()
{
UserAccounts = new()
{
this.CreateAccountSetting("00000000-0000-4000-8000-000000000000"),
this.CreateAccountSetting("00000000-0000-4000-8000-111111111111"),
},
SelectedAccountKey = new("00000000-0000-4000-8000-000000000000"),
});

var secondaryAccounts = accounts.SecondaryAccounts;
Assert.Single(secondaryAccounts);
Assert.Equal(new("00000000-0000-4000-8000-111111111111"), secondaryAccounts[0].UniqueKey);
}

[Fact]
public void GetAccountForTab_DefaultTest()
{
Expand All @@ -158,7 +177,8 @@ public void GetAccountForTab_DefaultTest()

// SourceAccountId が null のタブに対しては Primary のアカウントを返す
var actual = accounts.GetAccountForTab(tabWithoutAccountId);
Assert.Equal(new("00000000-0000-4000-8000-000000000000"), actual?.UniqueKey);
Assert.IsType<TwitterAccount>(actual);
Assert.Equal(new("00000000-0000-4000-8000-000000000000"), actual.UniqueKey);
}

[Fact]
Expand All @@ -179,7 +199,8 @@ public void GetAccountForTab_SpecifiedAccountTest()

// SourceAccountId が設定されているタブに対しては対応するアカウントを返す
var actual = accounts.GetAccountForTab(tabWithAccountId);
Assert.Equal(new("00000000-0000-4000-8000-111111111111"), actual?.UniqueKey);
Assert.IsType<TwitterAccount>(actual);
Assert.Equal(new("00000000-0000-4000-8000-111111111111"), actual.UniqueKey);
}

[Fact]
Expand All @@ -197,8 +218,10 @@ public void GetAccountForTab_NotExistsTest()

var tabWithAccountId = new RelatedPostsTabModel("hoge", new("00000000-0000-4000-8000-999999999999"), new());

// SourceAccountId に存在しない ID が設定されていた場合は null を返す
Assert.Null(accounts.GetAccountForTab(tabWithAccountId));
// SourceAccountId に存在しない ID が設定されていた場合は InvalidAccount を返す
var actual = accounts.GetAccountForTab(tabWithAccountId);
Assert.IsType<InvalidAccount>(actual);
Assert.Equal(new("00000000-0000-4000-8000-999999999999"), actual.UniqueKey);
}
}
}
52 changes: 52 additions & 0 deletions OpenTween.Tests/SocialProtocol/InvalidAccountTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// OpenTween - Client of Twitter
// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~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 <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.

using System;
using System.Threading.Tasks;
using OpenTween.Connection;
using Xunit;

namespace OpenTween.SocialProtocol
{
public class InvalidAccountTest
{
[Fact]
public async Task Connection_Test()
{
using var account = new InvalidAccount(Guid.NewGuid());
var requeset = new GetRequest { RequestUri = new("https://example.com/aaa") };

await Assert.ThrowsAsync<WebApiException>(
() => account.Connection.SendAsync(requeset)
);
}

[Fact]
public async Task Client_Test()
{
using var account = new InvalidAccount(Guid.NewGuid());

await Assert.ThrowsAsync<WebApiException>(
() => account.Client.VerifyCredentials()
);
}
}
}
16 changes: 16 additions & 0 deletions OpenTween/ApplicationEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using System.Linq;
using System.Windows.Forms;
using OpenTween.Connection;
using OpenTween.Models;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
Expand Down Expand Up @@ -100,6 +101,7 @@ public static int Main(string[] args)
}

SetupAccounts(container.AccountCollection, settings);
AddSecondaryAccountTabs(container.AccountCollection, container.TabInfo);

Application.Run(container.MainForm);

Expand Down Expand Up @@ -176,5 +178,19 @@ private static void VerifyCredentialsSync(ISocialAccount account)
throw new WebApiException(ex.InnerException.Message, ex);
}
}

private static void AddSecondaryAccountTabs(AccountCollection accounts, TabInformations tabInfo)
{
foreach (var account in accounts.Items)
{
var accountKey = account.UniqueKey;
if (accountKey == accounts.Primary.UniqueKey)
continue;

var tabName = tabInfo.MakeTabName($"@{account.UserName}");
var homeTab = new HomeSpecifiedAccountTabModel(tabName, accountKey);
tabInfo.AddTab(homeTab);
}
}
}
}
74 changes: 74 additions & 0 deletions OpenTween/Models/HomeSpecifiedAccountTabModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// OpenTween - Client of Twitter
// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~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 <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.

#nullable enable

using System;
using System.Threading.Tasks;
using OpenTween.SocialProtocol;

namespace OpenTween.Models
{
public class HomeSpecifiedAccountTabModel : InternalStorageTabModel
{
public override MyCommon.TabUsageType TabType
=> MyCommon.TabUsageType.Undefined;

public override bool IsPermanentTabType
=> false;

public override Guid? SourceAccountId { get; }

public HomeSpecifiedAccountTabModel(string tabName, Guid accountId)
: base(tabName)
{
this.SourceAccountId = accountId;
}

public override async Task RefreshAsync(ISocialAccount account, bool backward, IProgress<string> progress)
{
progress.Report("Home refreshing...");

var firstLoad = !this.IsFirstLoadCompleted;
var count = Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, backward, firstLoad);
var cursor = backward ? this.CursorBottom : this.CursorTop;

var response = await account.Client.GetHomeTimeline(count, cursor, firstLoad)
.ConfigureAwait(false);

foreach (var post in response.Posts)
this.AddPostQueue(post);

TabInformations.GetInstance().DistributePosts();

if (response.CursorTop != null && !backward)
this.CursorTop = response.CursorTop;

if (response.CursorBottom != null)
this.CursorBottom = response.CursorBottom;

if (firstLoad)
this.IsFirstLoadCompleted = true;

progress.Report("Home refreshed");
}
}
}
8 changes: 6 additions & 2 deletions OpenTween/SocialProtocol/AccountCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public ISocialAccount Primary
public ISocialAccount[] Items
=> this.accounts.Values.ToArray();

public ISocialAccount[] SecondaryAccounts
=> this.accounts.Values.Where(x => x.UniqueKey != this.primaryId).ToArray();

public void LoadFromSettings(SettingCommon settingCommon)
{
var oldAccounts = this.accounts;
Expand Down Expand Up @@ -85,14 +88,15 @@ private void DisposeAccounts(IEnumerable<ISocialAccount> accounts)
account.Dispose();
}

public ISocialAccount? GetAccountForTab(TabModel tab)
public ISocialAccount GetAccountForTab(TabModel tab)
{
if (tab.SourceAccountId is { } accountId)
{
if (this.accounts.TryGetValue(accountId, out var account))
return account;

return null;
// タブ追加後に設定画面からアカウントの情報を削除した場合
return new InvalidAccount(accountId);
}

return this.Primary;
Expand Down
106 changes: 106 additions & 0 deletions OpenTween/SocialProtocol/InvalidAccount.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// OpenTween - Client of Twitter
// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~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 <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.

#nullable enable

using System;
using System.Threading.Tasks;
using OpenTween.Connection;
using OpenTween.Models;

namespace OpenTween.SocialProtocol
{
public class InvalidAccount : ISocialAccount
{
public string AccountType
=> "InvalidAccount";

public Guid UniqueKey { get; }

public PersonId UserId
=> new TwitterUserId("0");

public string UserName
=> "(Unknown account)";

public IApiConnection Connection { get; } = new InvalidAccountConnection();

public ISocialProtocolClient Client { get; } = new InvalidAccountClient();

public bool IsDisposed { get; private set; }

public InvalidAccount(Guid uniqueKey)
=> this.UniqueKey = uniqueKey;

public void Initialize(UserAccount accountSettings, SettingCommon settingCommon)
{
}

public void Dispose()
=> this.IsDisposed = true;

private class InvalidAccountConnection : IApiConnection
{
public Task<ApiResponse> SendAsync(IHttpRequest request)
=> throw new WebApiException("Invalid account");

public void Dispose()
{
}
}

private class InvalidAccountClient : ISocialProtocolClient
{
public Task<UserInfo> VerifyCredentials()
=> throw this.CreateException();

public Task<PostClass> GetPostById(PostId postId, bool firstLoad)
=> throw this.CreateException();

public Task<TimelineResponse> GetHomeTimeline(int count, IQueryCursor? cursor, bool firstLoad)
=> throw this.CreateException();

public Task<TimelineResponse> GetSearchTimeline(string query, string lang, int count, IQueryCursor? cursor, bool firstLoad)
=> throw this.CreateException();

public Task<PostClass[]> GetRelatedPosts(PostClass targetPost, bool firstLoad)
=> throw this.CreateException();

public Task DeletePost(PostId postId)
=> throw this.CreateException();

public Task FavoritePost(PostId postId)
=> throw this.CreateException();

public Task UnfavoritePost(PostId postId)
=> throw this.CreateException();

public Task<PostClass?> RetweetPost(PostId postId)
=> throw this.CreateException();

public Task UnretweetPost(PostId postId)
=> throw this.CreateException();

private WebApiException CreateException()
=> new("Invalid account");
}
}
}
Loading
Loading