From 3d5c4f769d44139a8b652eb8337820fa31d2ec40 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 23 Dec 2023 00:53:02 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=20v3.10.2-dev=20=E9=96=8B=E7=99=BA=E9=96=8B=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 2 ++ OpenTween/Properties/AssemblyInfo.cs | 2 +- OpenTween/Properties/Resources.Designer.cs | 4 +++- appveyor.yml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5b94876bc..9bac3b075 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,7 @@ 更新履歴 +==== Unreleased + ==== Ver 3.10.1(2023/12/23) * FIX: OAuth 1.0a によるAPIアクセスに失敗する不具合を修正 diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index d01a31e19..e56c84003 100644 --- a/OpenTween/Properties/AssemblyInfo.cs +++ b/OpenTween/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です [assembly: Guid("2d0ae0ba-adac-49a2-9b10-26fd69e695bf")] -[assembly: AssemblyVersion("3.10.1.0")] +[assembly: AssemblyVersion("3.10.1.1")] [assembly: InternalsVisibleTo("OpenTween.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // for Moq diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index 72428ee55..7a1ca94f7 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -580,6 +580,8 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// + ///==== Unreleased + /// ///==== Ver 3.10.1(2023/12/23) /// * FIX: OAuth 1.0a によるAPIアクセスに失敗する不具合を修正 /// @@ -595,7 +597,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// - この問題が起きるユーザーのツイートが含まれているとタイムラインの読み込みに失敗する問題も改善されます /// ///==== Ver 3.8.0(2023/11/29) - /// * NEW: graphqlエンドポイントを使用した [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// * NEW: [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get { diff --git a/appveyor.yml b/appveyor.yml index fb643672c..b60cfa1cd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.10.0.{build} +version: 3.10.1.{build} os: Visual Studio 2022 From a23ac08d64abece37264b3a3ee37c6d6a412a59d Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 6 Jan 2024 13:26:55 +0900 Subject: [PATCH 2/7] =?UTF-8?q?AsyncExceptionBoundary=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/AsyncExceptionBoundaryTest.cs | 128 ++++++++++++++++++ OpenTween/AsyncExceptionBoundary.cs | 73 ++++++++++ OpenTween/ImageCache.cs | 18 +-- OpenTween/TaskCollection.cs | 16 +-- OpenTween/TweetThumbnail.cs | 22 +-- 5 files changed, 208 insertions(+), 49 deletions(-) create mode 100644 OpenTween.Tests/AsyncExceptionBoundaryTest.cs create mode 100644 OpenTween/AsyncExceptionBoundary.cs diff --git a/OpenTween.Tests/AsyncExceptionBoundaryTest.cs b/OpenTween.Tests/AsyncExceptionBoundaryTest.cs new file mode 100644 index 000000000..f17e34b4d --- /dev/null +++ b/OpenTween.Tests/AsyncExceptionBoundaryTest.cs @@ -0,0 +1,128 @@ +// 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 System; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween +{ + public class AsyncExceptionBoundaryTest + { + [Fact] + public async Task Wrap_SynchronousTest() + { + static Task AsyncFunc() + => throw new OperationCanceledException(); + + // 例外を無視して終了する + await AsyncExceptionBoundary.Wrap(AsyncFunc); + } + + [Fact] + public async Task Wrap_AsynchronousTest() + { + static async Task AsyncFunc() + { + await Task.Yield(); + throw new OperationCanceledException(); + } + + // 例外を無視して終了する + await AsyncExceptionBoundary.Wrap(AsyncFunc); + } + + [Fact] + public async Task Wrap_IgnoredTest() + { + static Task AsyncFunc() + => throw new OperationCanceledException(); + + static bool IgnoreException(Exception ex) + => ex is OperationCanceledException; + + // 例外を無視して終了する + await AsyncExceptionBoundary.Wrap(AsyncFunc, IgnoreException); + } + + [Fact] + public async Task Wrap_NotIgnoredTest() + { + static Task AsyncFunc() + => throw new IOException(); + + static bool IgnoreException(Exception ex) + => ex is OperationCanceledException; + + // 例外を返して終了する + await Assert.ThrowsAsync( + () => AsyncExceptionBoundary.Wrap(AsyncFunc, IgnoreException) + ); + } + + [Fact] + public async Task IgnoreException_Test() + { + var task = Task.FromException(new OperationCanceledException()); + + // 例外を無視して終了する + await AsyncExceptionBoundary.IgnoreException(task); + } + + [Fact] + public async Task IgnoreExceptionAndDispose_Test() + { + var task = Task.FromException(new OperationCanceledException()); + + // 例外を無視して終了する + await AsyncExceptionBoundary.IgnoreExceptionAndDispose(task); + } + + [Fact] + public async Task IgnoreExceptionAndDispose_DisposeTest() + { + using var image = TestUtils.CreateDummyImage(); + var task = Task.FromResult(image); // IDisposable であることを静的に判定できない場合も想定 + + // 正常終了したとき Result が IDisposable だった場合は破棄する + await AsyncExceptionBoundary.IgnoreExceptionAndDispose(task); + Assert.True(image.IsDisposed); + } + + [Fact] + public async Task IgnoreExceptionAndDispose_IEnumerableTest() + { + using var image1 = TestUtils.CreateDummyImage(); + using var image2 = TestUtils.CreateDummyImage(); + var tasks = new[] + { + Task.FromResult(image1), // IDisposable であることを静的に判定できない場合も想定 + Task.FromResult(image2), + }; + + // 正常終了したとき Result が IDisposable だった場合は破棄する + await AsyncExceptionBoundary.IgnoreExceptionAndDispose(tasks); + Assert.True(image1.IsDisposed); + Assert.True(image2.IsDisposed); + } + } +} diff --git a/OpenTween/AsyncExceptionBoundary.cs b/OpenTween/AsyncExceptionBoundary.cs new file mode 100644 index 000000000..61d7b6da2 --- /dev/null +++ b/OpenTween/AsyncExceptionBoundary.cs @@ -0,0 +1,73 @@ +// 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 System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace OpenTween +{ + public static class AsyncExceptionBoundary + { + public static Task Wrap(Func func) + => Wrap(func, _ => true); + + public static async Task Wrap(Func func, Func ignoreException) + { + try + { + await func(); + } + catch (Exception ex) when (ignoreException(ex)) + { + } + } + + public static async Task IgnoreException(Task task) + { + try + { + await task.ConfigureAwait(false); + } + catch + { + } + } + + public static async Task IgnoreExceptionAndDispose(Task task) + { + try + { + var ret = await task.ConfigureAwait(false); + (ret as IDisposable)?.Dispose(); + } + catch + { + } + } + + public static Task IgnoreExceptionAndDispose(IEnumerable> tasks) + => Task.WhenAll(tasks.Select(x => IgnoreExceptionAndDispose(x))); + } +} diff --git a/OpenTween/ImageCache.cs b/OpenTween/ImageCache.cs index f16725384..19173df5a 100644 --- a/OpenTween/ImageCache.cs +++ b/OpenTween/ImageCache.cs @@ -56,25 +56,11 @@ public ImageCache() this.InnerDictionary = new LRUCacheDictionary>(trimLimit: 300, autoTrimCount: 100); this.InnerDictionary.CacheRemoved += (s, e) => { - // まだ参照されている場合もあるのでDisposeはファイナライザ任せ this.CacheRemoveCount++; + // まだ参照されている場合もあるのでDisposeはファイナライザ任せ var task = e.Item.Value; - if (task.Status != TaskStatus.RanToCompletion || task.IsFaulted) - { - // Task の例外がハンドルされないまま破棄されると AggregateException が発生するため try-catch で処理する Task を挟む - static async Task HandleException(Task t) - { - try - { - _ = await t.ConfigureAwait(false); - } - catch - { - } - } - _ = HandleException(task); - } + _ = AsyncExceptionBoundary.IgnoreException(task); }; this.cancelTokenSource = new CancellationTokenSource(); diff --git a/OpenTween/TaskCollection.cs b/OpenTween/TaskCollection.cs index 9569860a5..e70ad91c3 100644 --- a/OpenTween/TaskCollection.cs +++ b/OpenTween/TaskCollection.cs @@ -59,21 +59,13 @@ public Task RunAll(bool runOnThreadPool) private Task WrapAsyncFunc(Func func, bool runOnThreadPool) { - async Task TaskExceptionBoundary(Func func) - { - try - { - await func(); - } - catch (Exception ex) when (this.ignoreExceptionFunc(ex)) - { - } - } + Task WrappedFunc() + => AsyncExceptionBoundary.Wrap(func, this.ignoreExceptionFunc); if (runOnThreadPool) - return Task.Run(() => TaskExceptionBoundary(func)); + return Task.Run(WrappedFunc); else - return TaskExceptionBoundary(func); + return WrappedFunc(); } } } diff --git a/OpenTween/TweetThumbnail.cs b/OpenTween/TweetThumbnail.cs index 042a8d135..90c26219a 100644 --- a/OpenTween/TweetThumbnail.cs +++ b/OpenTween/TweetThumbnail.cs @@ -156,27 +156,7 @@ private async Task> GetThumbailInfoAsync(PostClass po private void DisposeImages() { var oldImageTasks = this.loadImageTasks.OfType>().ToArray(); - - static async Task DisposeTaskResults(Task[] tasks) - { - try - { - await Task.WhenAll(tasks).ConfigureAwait(false); - } - catch - { - } - - foreach (var task in tasks) - { - if (task.IsFaulted || task.IsCanceled) - continue; - - task.Result.Dispose(); - } - } - - _ = DisposeTaskResults(oldImageTasks); + _ = AsyncExceptionBoundary.IgnoreExceptionAndDispose(oldImageTasks); } } } From 5e0e339f2c75389f52f9620d8db7756fcd32466f Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 6 Jan 2024 14:00:07 +0900 Subject: [PATCH 3/7] =?UTF-8?q?TwitterApiConnection=E3=81=A7=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6=E3=83=88=E6=99=82=E3=81=AB?= =?UTF-8?q?ApiResponse=E3=81=8C=E7=A0=B4=E6=A3=84=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84=E4=B8=8D=E5=85=B7=E5=90=88=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: 143acec4 ("IApiConnection, IHttpRequest, ApiResponseで構成する新しいTwitterApiConnectionを実装") --- CHANGELOG.txt | 1 + OpenTween/Connection/TwitterApiConnection.cs | 12 +----------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9bac3b075..b96840f7b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * FIX: APIリクエストのタイムアウト時に接続が切断されない場合がある不具合を修正 ==== Ver 3.10.1(2023/12/23) * FIX: OAuth 1.0a によるAPIアクセスに失敗する不具合を修正 diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 410359737..64833db4f 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -156,17 +156,7 @@ public static async Task HandleTimeout(Func> fu // タイムアウト // キャンセル後のタスクで発生した例外は無視する - static async Task IgnoreExceptions(Task task) - { - try - { - await task.ConfigureAwait(false); - } - catch - { - } - } - _ = IgnoreExceptions(task); + _ = AsyncExceptionBoundary.IgnoreExceptionAndDispose(task); cts.Cancel(); throw new OperationCanceledException("Timeout", cancellactionToken); From 6f3b97fe5e8a5b4d5ba898810f160c6b1fd6e2c2 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 6 Jan 2024 14:24:22 +0900 Subject: [PATCH 4/7] =?UTF-8?q?graphql=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=88=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97?= =?UTF-8?q?=E3=81=9F=E9=96=A2=E9=80=A3=E7=99=BA=E8=A8=80=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + OpenTween/Twitter.cs | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b96840f7b..6306a2da5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * NEW: Cookie使用時の関連発言表示に対応 * FIX: APIリクエストのタイムアウト時に接続が切断されない場合がある不具合を修正 ==== Ver 3.10.1(2023/12/23) diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 341b69631..ab03071df 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -1073,10 +1073,27 @@ private async Task GetConversationPosts(PostClass firstPost, PostCl else query += $" from:{targetPost.ScreenName} to:{targetPost.ScreenName}"; - var statuses = await this.Api.SearchTweets(query, count: 100) - .ConfigureAwait(false); + TwitterStatus[] statuses; + if (this.Api.AuthType == APIAuthType.TwitterComCookie) + { + var request = new SearchTimelineRequest(query); + var response = await request.Send(this.Api.Connection) + .ConfigureAwait(false); + + statuses = response.Tweets + .Where(x => !x.IsTombstone) + .Select(x => x.ToTwitterStatus()) + .ToArray(); + } + else + { + var response = await this.Api.SearchTweets(query, count: 100) + .ConfigureAwait(false); + + statuses = response.Statuses; + } - return statuses.Statuses.Select(x => this.CreatePostsFromStatusData(x)).ToArray(); + return statuses.Select(x => this.CreatePostsFromStatusData(x)).ToArray(); } public async Task GetSearch(bool read, PublicSearchTabModel tab, bool more) From 9cdcf0aec783ee4aa8b23dd157b058418187e35e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 7 Jan 2024 00:38:03 +0900 Subject: [PATCH 5/7] =?UTF-8?q?TimelineResponse.ToTwitterStatuses=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/GraphQL/TimelineResponseTest.cs | 41 +++++++++++++++++++ OpenTween/Api/GraphQL/TimelineResponse.cs | 14 ++++--- OpenTween/Twitter.cs | 20 +++------ 3 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 OpenTween.Tests/Api/GraphQL/TimelineResponseTest.cs diff --git a/OpenTween.Tests/Api/GraphQL/TimelineResponseTest.cs b/OpenTween.Tests/Api/GraphQL/TimelineResponseTest.cs new file mode 100644 index 000000000..4a793df84 --- /dev/null +++ b/OpenTween.Tests/Api/GraphQL/TimelineResponseTest.cs @@ -0,0 +1,41 @@ +// 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 System.Threading.Tasks; +using Xunit; + +namespace OpenTween.Api.GraphQL +{ + public class TimelineResponseTest + { + [Fact] + public async Task ToTwitterStatuses_Test() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/SearchTimeline_SimpleTweet.json"); + var tweets = TimelineTweet.ExtractTimelineTweets(await apiResponse.ReadAsJsonXml()); + var timelineResponse = new TimelineResponse(tweets, "", ""); + + var statuses = timelineResponse.ToTwitterStatuses(); + Assert.Single(statuses); + Assert.Equal("1619433164757413894", statuses[0].IdStr); + } + } +} diff --git a/OpenTween/Api/GraphQL/TimelineResponse.cs b/OpenTween/Api/GraphQL/TimelineResponse.cs index d92430a07..052ea6108 100644 --- a/OpenTween/Api/GraphQL/TimelineResponse.cs +++ b/OpenTween/Api/GraphQL/TimelineResponse.cs @@ -21,11 +21,8 @@ #nullable enable -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using OpenTween.Api.DataModel; namespace OpenTween.Api.GraphQL { @@ -33,5 +30,12 @@ public record TimelineResponse( TimelineTweet[] Tweets, string? CursorTop, string? CursorBottom - ); + ) + { + public TwitterStatus[] ToTwitterStatuses() + => this.Tweets + .Where(x => !x.IsTombstone) + .Select(x => x.ToTwitterStatus()) + .ToArray(); + } } diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index ab03071df..63ba0066c 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -677,9 +677,7 @@ public async Task GetUserTimelineApi(bool read, UserTimelineTabModel tab, bool m var response = await request.Send(this.Api.Connection) .ConfigureAwait(false); - statuses = response.Tweets - .Where(x => !x.IsTombstone) - .Select(x => x.ToTwitterStatus()) + statuses = response.ToTwitterStatuses() .Where(x => x.User.IdStr == userId) // リプライツリーに含まれる他ユーザーのツイートを除外 .ToArray(); @@ -884,12 +882,10 @@ public async Task GetListStatus(bool read, ListTimelineTabModel tab, bool more, var response = await request.Send(this.Api.Connection) .ConfigureAwait(false); - var convertedStatuses = response.Tweets - .Where(x => !x.IsTombstone) - .Select(x => x.ToTwitterStatus()); + var convertedStatuses = response.ToTwitterStatuses(); if (!SettingManager.Instance.Common.IsListsIncludeRts) - convertedStatuses = convertedStatuses.Where(x => x.RetweetedStatus == null); + convertedStatuses = convertedStatuses.Where(x => x.RetweetedStatus == null).ToArray(); statuses = convertedStatuses.ToArray(); tab.CursorBottom = response.CursorBottom; @@ -1080,10 +1076,7 @@ private async Task GetConversationPosts(PostClass firstPost, PostCl var response = await request.Send(this.Api.Connection) .ConfigureAwait(false); - statuses = response.Tweets - .Where(x => !x.IsTombstone) - .Select(x => x.ToTwitterStatus()) - .ToArray(); + statuses = response.ToTwitterStatuses(); } else { @@ -1111,10 +1104,7 @@ public async Task GetSearch(bool read, PublicSearchTabModel tab, bool more) var response = await request.Send(this.Api.Connection) .ConfigureAwait(false); - statuses = response.Tweets - .Where(x => !x.IsTombstone) - .Select(x => x.ToTwitterStatus()) - .ToArray(); + statuses = response.ToTwitterStatuses(); tab.CursorBottom = response.CursorBottom; From 781fc24623e1b386cb84a93ce69fcd924830fbaf Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 7 Jan 2024 01:29:33 +0900 Subject: [PATCH 6/7] =?UTF-8?q?UserByScreenName=E3=81=A7=E7=A9=BA=E3=81=AE?= =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E6=83=85=E5=A0=B1=E3=81=8C?= =?UTF-8?q?=E8=BF=94=E3=81=A3=E3=81=A6=E3=81=8D=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E6=89=B1=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + .../GraphQL/UserByScreenNameRequestTest.cs | 25 +++++++++++++++++++ .../Responses/UserByScreenName_Empty.json | 3 +++ .../Api/GraphQL/UserByScreenNameRequest.cs | 25 ++++++++++++++----- 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 OpenTween.Tests/Resources/Responses/UserByScreenName_Empty.json diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6306a2da5..31c14d379 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ ==== Unreleased * NEW: Cookie使用時の関連発言表示に対応 * FIX: APIリクエストのタイムアウト時に接続が切断されない場合がある不具合を修正 + * FIX: 存在しないユーザーのプロフィールを取得しようとした場合のエラーが適切に処理されない不具合を修正 ==== Ver 3.10.1(2023/12/23) * FIX: OAuth 1.0a によるAPIアクセスに失敗する不具合を修正 diff --git a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs index 9c4e287d9..292fbadad 100644 --- a/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs @@ -81,5 +81,30 @@ public async Task Send_UserUnavailableTest() mock.VerifyAll(); } + + [Fact] + public async Task Send_EmptyTest() + { + // ユーザーが存在しない場合にエラー情報を含まない空のオブジェクトが返されることがある + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/UserByScreenName_Empty.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .ReturnsAsync(apiResponse); + + var request = new UserByScreenNameRequest + { + ScreenName = "==INVALID==", + }; + + var ex = await Assert.ThrowsAsync( + () => request.Send(mock.Object) + ); + Assert.Equal("User is not available.", ex.Message); + + mock.VerifyAll(); + } } } diff --git a/OpenTween.Tests/Resources/Responses/UserByScreenName_Empty.json b/OpenTween.Tests/Resources/Responses/UserByScreenName_Empty.json new file mode 100644 index 000000000..73cd5c32b --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/UserByScreenName_Empty.json @@ -0,0 +1,3 @@ +{ + "data": {} +} diff --git a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs index 4b3c8ccad..f3d627dff 100644 --- a/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs +++ b/OpenTween/Api/GraphQL/UserByScreenNameRequest.cs @@ -71,20 +71,33 @@ public async Task Send(IApiConnection apiConnection) ErrorResponse.ThrowIfError(rootElm); - var userElm = rootElm.XPathSelectElement("/data/user/result"); - this.ThrowIfUserUnavailable(userElm); + try + { + var userElm = rootElm.XPathSelectElement("/data/user/result"); + this.ThrowIfUserUnavailable(userElm); - return new(userElm); + return new(userElm); + } + catch (WebApiException ex) + { + ex.ResponseText = JsonUtils.JsonXmlToString(rootElm); + throw; + } } - private void ThrowIfUserUnavailable(XElement userElm) + private void ThrowIfUserUnavailable(XElement? userElm) { + if (userElm == null) + { + var errorText = "User is not available."; + throw new WebApiException(errorText); + } + var typeName = userElm.Element("__typename")?.Value; if (typeName == "UserUnavailable") { var errorText = userElm.Element("message")?.Value ?? "User is not available."; - var json = JsonUtils.JsonXmlToString(userElm); - throw new WebApiException(errorText, json); + throw new WebApiException(errorText); } } } From 42c2934d817d5c08fd0877ee2dc029b48d90504d Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 7 Jan 2024 03:18:11 +0900 Subject: [PATCH 7/7] =?UTF-8?q?OpenTween=20v3.11.0=20=E3=83=AA=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 2 +- OpenTween/Properties/AssemblyInfo.cs | 2 +- OpenTween/Properties/Resources.Designer.cs | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 31c14d379..57496d110 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,6 @@ 更新履歴 -==== Unreleased +==== Ver 3.11.0(2024/01/07) * NEW: Cookie使用時の関連発言表示に対応 * FIX: APIリクエストのタイムアウト時に接続が切断されない場合がある不具合を修正 * FIX: 存在しないユーザーのプロフィールを取得しようとした場合のエラーが適切に処理されない不具合を修正 diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index e56c84003..f2841c999 100644 --- a/OpenTween/Properties/AssemblyInfo.cs +++ b/OpenTween/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です [assembly: Guid("2d0ae0ba-adac-49a2-9b10-26fd69e695bf")] -[assembly: AssemblyVersion("3.10.1.1")] +[assembly: AssemblyVersion("3.11.0.0")] [assembly: InternalsVisibleTo("OpenTween.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // for Moq diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index 7a1ca94f7..7a466ce67 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -580,7 +580,10 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// - ///==== Unreleased + ///==== Ver 3.11.0(2024/01/07) + /// * NEW: Cookie使用時の関連発言表示に対応 + /// * FIX: APIリクエストのタイムアウト時に接続が切断されない場合がある不具合を修正 + /// * FIX: 存在しないユーザーのプロフィールを取得しようとした場合のエラーが適切に処理されない不具合を修正 /// ///==== Ver 3.10.1(2023/12/23) /// * FIX: OAuth 1.0a によるAPIアクセスに失敗する不具合を修正 @@ -592,12 +595,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { ///==== Ver 3.9.0(2023/12/03) /// * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 /// * CHG: タイムライン更新時に全件ではなく新着投稿のみ差分を取得する動作に変更 - /// * FIX: 設定したタイムアウト時間を超えてAPI接続が持続する場合がある不具合を修正 - /// * FIX: プロフィール情報のURL欄のパースに失敗する場合がある不具合を修正 - /// - この問題が起きるユーザーのツイートが含まれているとタイムラインの読み込みに失敗する問題も改善されます - /// - ///==== Ver 3.8.0(2023/11/29) - /// * NEW: [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// * FIX: 設定したタイムアウト時間を超えてAPI接続が持続する場合がある不 [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get {