From f303281750914cc164d5530c7f9b5a69c7e9d835 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 20 Jan 2024 00:32:08 +0900 Subject: [PATCH 01/17] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=20v3.12.1-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 | 5 +++-- appveyor.yml | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b8f640b80..45ca40998 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,7 @@ 更新履歴 +==== Unreleased + ==== Ver 3.12.0(2024/01/20) * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index 976abfe17..6d00dfaa3 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.12.0.0")] +[assembly: AssemblyVersion("3.12.0.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 32f7a5628..cf608ae96 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.12.0(2024/01/20) /// * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 /// @@ -596,8 +598,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// * FIX: APIリクエストがタイムアウトした場合のキャンセル処理を改善 /// ///==== Ver 3.9.0(2023/12/03) - /// * NEW: graphqlエンドポイントに対するレートリミットの表示に対応 - /// * CHG: タ [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// * NEW: graphqlエンドポイントに対するレートリミ [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get { diff --git a/appveyor.yml b/appveyor.yml index 001f7c2fe..1a1f8122e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.11.0.{build} +version: 3.12.0.{build} os: Visual Studio 2022 From 9f72f9d9a37970ed893b1ff7156f009d08bb747f Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 20 Jan 2024 01:16:06 +0900 Subject: [PATCH 02/17] =?UTF-8?q?graphql=E3=82=A8=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E4=BD=BF=E7=94=A8=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=83=84=E3=82=A4=E3=83=BC=E3=83=88=E6=A4=9C=E7=B4=A2?= =?UTF-8?q?=E3=81=AE=E8=A8=80=E8=AA=9E=E6=8C=87=E5=AE=9A=E3=81=8C=E5=8A=B9?= =?UTF-8?q?=E3=81=8B=E3=81=AA=E3=81=84=E4=B8=8D=E5=85=B7=E5=90=88=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + OpenTween/Twitter.cs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 45ca40998..fd65ac285 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 ==== Ver 3.12.0(2024/01/20) * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 4e1030f55..f3d0caca3 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -1113,7 +1113,12 @@ public async Task GetSearch(bool read, PublicSearchTabModel tab, bool more) TwitterStatus[] statuses; if (this.Api.AuthType == APIAuthType.TwitterComCookie) { - var request = new SearchTimelineRequest(tab.SearchWords) + var query = tab.SearchWords; + + if (!MyCommon.IsNullOrEmpty(tab.SearchLang)) + query = $"({query}) lang:{tab.SearchLang}"; + + var request = new SearchTimelineRequest(query) { Count = count, Cursor = more ? tab.CursorBottom : tab.CursorTop, From edbb5b56479724f49880164e09fb08928d7dc305 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 20 Jan 2024 01:59:37 +0900 Subject: [PATCH 03/17] =?UTF-8?q?=E6=A4=9C=E7=B4=A2=E3=82=BF=E3=83=96?= =?UTF-8?q?=E3=81=AESearchWords=E3=81=8C=E5=A4=89=E6=9B=B4=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=82=82cursor=E3=81=8C=E3=83=AA=E3=82=BB?= =?UTF-8?q?=E3=83=83=E3=83=88=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E4=B8=8D?= =?UTF-8?q?=E5=85=B7=E5=90=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: 0533dad6 ("cursorを使用した新着投稿の取得に対応") --- CHANGELOG.txt | 1 + OpenTween/Models/PublicSearchTabModel.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index fd65ac285..690ed78fd 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,7 @@ ==== Unreleased * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 + * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 ==== Ver 3.12.0(2024/01/20) * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 diff --git a/OpenTween/Models/PublicSearchTabModel.cs b/OpenTween/Models/PublicSearchTabModel.cs index 5c1c73f3e..51fab0dc1 100644 --- a/OpenTween/Models/PublicSearchTabModel.cs +++ b/OpenTween/Models/PublicSearchTabModel.cs @@ -99,12 +99,14 @@ await tw.GetSearch(read, this, backward) } /// - /// タブ更新時に使用する SinceId, OldestId をリセットする + /// 差分更新用の cursor をリセットする(検索条件が変更された時に使用する) /// public void ResetFetchIds() { this.SinceId = null; this.OldestId = null; + this.CursorTop = null; + this.CursorBottom = null; } } } From de9915235164ffb98caa0e91e26ccaf99423ad7d Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 20 Jan 2024 02:49:01 +0900 Subject: [PATCH 04/17] =?UTF-8?q?graphql=E3=82=A8=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E4=BD=BF=E7=94=A8=E6=99=82?= =?UTF-8?q?=E3=81=ABRecent=E3=82=BF=E3=83=96=E3=81=AE=E3=83=AC=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=83=AA=E3=83=9F=E3=83=83=E3=83=88=E3=81=8C=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E4=B8=8D=E5=85=B7?= =?UTF-8?q?=E5=90=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: 2ae6a548 ("graphqlエンドポイントを使用したホームタイムラインの取得に対応") --- CHANGELOG.txt | 1 + OpenTween/Tween.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 690ed78fd..9ff06b6e8 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ ==== Unreleased * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 + * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 ==== Ver 3.12.0(2024/01/20) * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index ea324c85f..e066b92bf 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -6996,8 +6996,10 @@ private void SetApiStatusLabel(string? endpointName = null) // 表示中のタブに応じて更新 endpointName = tabType switch { - MyCommon.TabUsageType.Home => "/statuses/home_timeline", - MyCommon.TabUsageType.UserDefined => "/statuses/home_timeline", + MyCommon.TabUsageType.Home => + authByCookie ? HomeLatestTimelineRequest.EndpointName : "/statuses/home_timeline", + MyCommon.TabUsageType.UserDefined => + authByCookie ? HomeLatestTimelineRequest.EndpointName : "/statuses/home_timeline", MyCommon.TabUsageType.Mentions => "/statuses/mentions_timeline", MyCommon.TabUsageType.Favorites => "/favorites/list", MyCommon.TabUsageType.DirectMessage => "/direct_messages/events/list", @@ -7007,7 +7009,8 @@ private void SetApiStatusLabel(string? endpointName = null) authByCookie ? ListLatestTweetsTimelineRequest.EndpointName : "/lists/statuses", MyCommon.TabUsageType.PublicSearch => authByCookie ? SearchTimelineRequest.EndpointName : "/search/tweets", - MyCommon.TabUsageType.Related => "/statuses/show/:id", + MyCommon.TabUsageType.Related => + authByCookie ? TweetDetailRequest.EndpointName : "/statuses/show/:id", _ => null, }; this.toolStripApiGauge.ApiEndpoint = endpointName; From da1189a5cb56b8a5b7818b1d481cfab887abd54e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 20 Jan 2024 07:50:34 +0900 Subject: [PATCH 05/17] =?UTF-8?q?/notifications/mentions.json=20=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9FReply=E3=82=BF=E3=83=96?= =?UTF-8?q?=E3=81=AE=E6=9B=B4=E6=96=B0=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 + .../NotificationsMentionsRequestTest.cs | 98 +++ .../Responses/NotificationsMentions.json | 732 ++++++++++++++++++ .../TwitterV2/NotificationsMentionsRequest.cs | 188 +++++ OpenTween/Models/MentionsTabModel.cs | 4 + OpenTween/Tween.cs | 3 +- OpenTween/Twitter.cs | 28 +- 7 files changed, 1049 insertions(+), 5 deletions(-) create mode 100644 OpenTween.Tests/Api/TwitterV2/NotificationsMentionsRequestTest.cs create mode 100644 OpenTween.Tests/Resources/Responses/NotificationsMentions.json create mode 100644 OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9ff06b6e8..b2de0f81e 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 diff --git a/OpenTween.Tests/Api/TwitterV2/NotificationsMentionsRequestTest.cs b/OpenTween.Tests/Api/TwitterV2/NotificationsMentionsRequestTest.cs new file mode 100644 index 000000000..d65c7aee4 --- /dev/null +++ b/OpenTween.Tests/Api/TwitterV2/NotificationsMentionsRequestTest.cs @@ -0,0 +1,98 @@ +// 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 Moq; +using OpenTween.Api.GraphQL; +using OpenTween.Connection; +using Xunit; + +namespace OpenTween.Api.TwitterV2 +{ + public class NotificationsMentionsRequestTest + { + [Fact] + public async Task Send_Test() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/NotificationsMentions.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/2/notifications/mentions.json"), request.RequestUri); + var query = request.Query!; + Assert.Equal("20", query["count"]); + Assert.DoesNotContain("cursor", query); + Assert.Equal("/2/notifications/mentions", request.EndpointName); + }) + .ReturnsAsync(apiResponse); + + var request = new NotificationsMentionsRequest() + { + Count = 20, + }; + + var response = await request.Send(mock.Object); + var status = Assert.Single(response.Statuses); + Assert.Equal("1748671085438988794", status.IdStr); + Assert.Equal("40480664", status.User.IdStr); + + Assert.Equal("DAABDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", response.CursorTop); + Assert.Equal("DAACDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", response.CursorBottom); + + mock.VerifyAll(); + } + + [Fact] + public async Task Send_RequestCursorTest() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/NotificationsMentions.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/2/notifications/mentions.json"), request.RequestUri); + var query = request.Query!; + Assert.Equal("20", query["count"]); + Assert.Equal("aaa", query["cursor"]); + Assert.Equal("/2/notifications/mentions", request.EndpointName); + }) + .ReturnsAsync(apiResponse); + + var request = new NotificationsMentionsRequest() + { + Count = 20, + Cursor = "aaa", + }; + + await request.Send(mock.Object); + mock.VerifyAll(); + } + } +} diff --git a/OpenTween.Tests/Resources/Responses/NotificationsMentions.json b/OpenTween.Tests/Resources/Responses/NotificationsMentions.json new file mode 100644 index 000000000..e505f6ef1 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/NotificationsMentions.json @@ -0,0 +1,732 @@ +{ + "globalObjects": { + "users": { + "771871124": { + "id": 771871124, + "id_str": "771871124", + "name": "OpenTween 新着コミット", + "screen_name": "OpenTweenCommit", + "location": null, + "description": "最新の開発版OpenTweenは https://t.co/a0mUFAT58Y から試せます", + "url": null, + "entities": { + "description": { + "urls": [ + { + "url": "https://t.co/a0mUFAT58Y", + "expanded_url": "https://ci.appveyor.com/project/upsilon/opentween/build/artifacts?branch=master", + "display_url": "ci.appveyor.com/project/upsilo…", + "indices": [ + 17, + 40 + ] + } + ] + } + }, + "protected": false, + "followers_count": 40, + "friends_count": 0, + "listed_count": 0, + "created_at": "Tue Aug 21 17:03:01 +0000 2012", + "favourites_count": 0, + "utc_offset": null, + "time_zone": null, + "geo_enabled": false, + "verified": false, + "statuses_count": 1991, + "lang": null, + "contributors_enabled": false, + "is_translator": false, + "is_translation_enabled": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_image_url": "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", + "profile_image_url_https": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", + "profile_link_color": "1DA1F2", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "default_profile": true, + "default_profile_image": true, + "following": null, + "follow_request_sent": null, + "notifications": null, + "blocking": null, + "translator_type": "none", + "withheld_in_countries": [], + "ext_is_blue_verified": false + }, + "40480664": { + "id": 40480664, + "id_str": "40480664", + "name": "upsilon", + "screen_name": "kim_upsilon", + "location": "Funabashi, Chiba, Japan", + "description": null, + "url": "https://t.co/vNMmyHHh15", + "entities": { + "url": { + "urls": [ + { + "url": "https://t.co/vNMmyHHh15", + "expanded_url": "https://m.upsilo.net/@upsilon", + "display_url": "m.upsilo.net/@upsilon", + "indices": [ + 0, + 23 + ] + } + ] + }, + "description": { + "urls": [] + } + }, + "protected": false, + "followers_count": 1281, + "friends_count": 1, + "listed_count": 90, + "created_at": "Sat May 16 15:20:01 +0000 2009", + "favourites_count": 215079, + "utc_offset": null, + "time_zone": null, + "geo_enabled": true, + "verified": false, + "statuses_count": 10081, + "lang": null, + "contributors_enabled": false, + "is_translator": false, + "is_translation_enabled": false, + "profile_background_color": "CFEB81", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_image_url": "http://pbs.twimg.com/profile_images/719076434/____normal.png", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/719076434/____normal.png", + "profile_banner_url": "https://pbs.twimg.com/profile_banners/40480664/1349188016", + "profile_link_color": "999900", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "99FF99", + "profile_text_color": "336666", + "profile_use_background_image": false, + "default_profile": false, + "default_profile_image": false, + "following": false, + "follow_request_sent": null, + "notifications": null, + "blocking": false, + "blocked_by": false, + "want_retweets": false, + "profile_interstitial_type": "", + "translator_type": "regular", + "withheld_in_countries": [], + "followed_by": false, + "ext_is_blue_verified": false, + "ext_highlighted_label": {} + } + }, + "tweets": { + "1748671085438988794": { + "created_at": "Sat Jan 20 11:37:30 +0000 2024", + "id": 1748671085438988800, + "id_str": "1748671085438988794", + "full_text": "@OpenTweenCommit test", + "truncated": false, + "display_text_range": [ + 17, + 21 + ], + "entities": { + "hashtags": [], + "symbols": [], + "user_mentions": [ + { + "screen_name": "OpenTweenCommit", + "name": "OpenTween 新着コミット", + "id": 771871124, + "id_str": "771871124", + "indices": [ + 0, + 16 + ] + } + ], + "urls": [] + }, + "source": "Twitter Web App", + "in_reply_to_status_id": 1617562124569526300, + "in_reply_to_status_id_str": "1617562124569526279", + "in_reply_to_user_id": 771871124, + "in_reply_to_user_id_str": "771871124", + "in_reply_to_screen_name": "OpenTweenCommit", + "user_id": 40480664, + "user_id_str": "40480664", + "geo": null, + "coordinates": null, + "place": null, + "contributors": null, + "is_quote_status": false, + "retweet_count": 0, + "favorite_count": 0, + "reply_count": 0, + "quote_count": 0, + "conversation_id": 1617562124569526300, + "conversation_id_str": "1617562124569526279", + "conversation_muted": false, + "favorited": false, + "retweeted": false, + "lang": "en", + "ext": { + "superFollowMetadata": { + "r": { + "ok": {} + }, + "ttl": -1 + } + } + }, + "1617562124569526279": { + "created_at": "Mon Jan 23 16:37:18 +0000 2023", + "id": 1617562124569526300, + "id_str": "1617562124569526279", + "full_text": "Merge pull request #195 from opentween/reorder-in-mediaselector\n https://t.co/U8OpWWyVD6", + "truncated": false, + "display_text_range": [ + 0, + 92 + ], + "entities": { + "hashtags": [], + "symbols": [], + "user_mentions": [], + "urls": [ + { + "url": "https://t.co/U8OpWWyVD6", + "expanded_url": "https://github.com/opentween/OpenTween/commit/73079c5ca9bd1c3b9e35613b5050f5ba984b4ccc", + "display_url": "github.com/opentween/Open…", + "indices": [ + 69, + 92 + ] + } + ] + }, + "source": "IFTTT", + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "in_reply_to_screen_name": null, + "user_id": 771871124, + "user_id_str": "771871124", + "geo": null, + "coordinates": null, + "place": null, + "contributors": null, + "is_quote_status": false, + "retweet_count": 0, + "favorite_count": 0, + "reply_count": 1, + "quote_count": 0, + "conversation_id": 1617562124569526300, + "conversation_id_str": "1617562124569526279", + "conversation_muted": false, + "favorited": false, + "retweeted": false, + "possibly_sensitive": false, + "card": { + "name": "summary_large_image", + "url": "https://t.co/U8OpWWyVD6", + "card_type_url": "http://card-type-url-is-deprecated.invalid", + "binding_values": { + "vanity_url": { + "type": "STRING", + "string_value": "github.com", + "scribe_key": "vanity_url" + }, + "domain": { + "type": "STRING", + "string_value": "github.com" + }, + "site": { + "type": "USER", + "user_value": { + "id_str": "13334762", + "path": [] + }, + "scribe_key": "publisher_id" + }, + "title": { + "type": "STRING", + "string_value": "Merge pull request #195 from opentween/reorder-in-mediaselector · opentween/OpenTween@73079c5" + }, + "summary_photo_image_alt_text": { + "type": "STRING", + "string_value": "MediaSelectorに追加したメディアの順序変更・削除に対応" + }, + "photo_image_full_size_alt_text": { + "type": "STRING", + "string_value": "MediaSelectorに追加したメディアの順序変更・削除に対応" + }, + "description": { + "type": "STRING", + "string_value": "MediaSelectorに追加したメディアの順序変更・削除に対応" + }, + "thumbnail_image_small": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=144x144", + "width": 144, + "height": 72, + "alt": null + } + }, + "thumbnail_image": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=400x400", + "width": 400, + "height": 200, + "alt": null + } + }, + "thumbnail_image_large": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=600x600", + "width": 600, + "height": 300, + "alt": null + } + }, + "thumbnail_image_x_large": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=png&name=2048x2048_2_exp", + "width": 1200, + "height": 600, + "alt": null + } + }, + "thumbnail_image_color": { + "type": "IMAGE_COLOR", + "image_color_value": { + "palette": [ + { + "percentage": 90.7, + "rgb": { + "red": 255, + "green": 255, + "blue": 255 + } + }, + { + "percentage": 4.25, + "rgb": { + "red": 119, + "green": 123, + "blue": 128 + } + }, + { + "percentage": 3.15, + "rgb": { + "red": 23, + "green": 135, + "blue": 1 + } + }, + { + "percentage": 1.72, + "rgb": { + "red": 118, + "green": 184, + "blue": 105 + } + }, + { + "percentage": 0.12, + "rgb": { + "red": 240, + "green": 202, + "blue": 206 + } + } + ] + } + }, + "thumbnail_image_original": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=orig", + "width": 1200, + "height": 600, + "alt": null + } + }, + "summary_photo_image_small": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=386x202", + "width": 386, + "height": 202, + "alt": null + } + }, + "summary_photo_image": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=600x314", + "width": 600, + "height": 314, + "alt": null + } + }, + "summary_photo_image_large": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=800x419", + "width": 800, + "height": 419, + "alt": null + } + }, + "summary_photo_image_x_large": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=png&name=2048x2048_2_exp", + "width": 1200, + "height": 600, + "alt": null + } + }, + "summary_photo_image_color": { + "type": "IMAGE_COLOR", + "image_color_value": { + "palette": [ + { + "percentage": 90.7, + "rgb": { + "red": 255, + "green": 255, + "blue": 255 + } + }, + { + "percentage": 4.25, + "rgb": { + "red": 119, + "green": 123, + "blue": 128 + } + }, + { + "percentage": 3.15, + "rgb": { + "red": 23, + "green": 135, + "blue": 1 + } + }, + { + "percentage": 1.72, + "rgb": { + "red": 118, + "green": 184, + "blue": 105 + } + }, + { + "percentage": 0.12, + "rgb": { + "red": 240, + "green": 202, + "blue": 206 + } + } + ] + } + }, + "summary_photo_image_original": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=orig", + "width": 1200, + "height": 600, + "alt": null + } + }, + "photo_image_full_size_small": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=386x202", + "width": 386, + "height": 202, + "alt": null + } + }, + "photo_image_full_size": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=600x314", + "width": 600, + "height": 314, + "alt": null + } + }, + "photo_image_full_size_large": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=800x419", + "width": 800, + "height": 419, + "alt": null + } + }, + "photo_image_full_size_x_large": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=png&name=2048x2048_2_exp", + "width": 1200, + "height": 600, + "alt": null + } + }, + "photo_image_full_size_color": { + "type": "IMAGE_COLOR", + "image_color_value": { + "palette": [ + { + "percentage": 90.7, + "rgb": { + "red": 255, + "green": 255, + "blue": 255 + } + }, + { + "percentage": 4.25, + "rgb": { + "red": 119, + "green": 123, + "blue": 128 + } + }, + { + "percentage": 3.15, + "rgb": { + "red": 23, + "green": 135, + "blue": 1 + } + }, + { + "percentage": 1.72, + "rgb": { + "red": 118, + "green": 184, + "blue": 105 + } + }, + { + "percentage": 0.12, + "rgb": { + "red": 240, + "green": 202, + "blue": 206 + } + } + ] + } + }, + "photo_image_full_size_original": { + "type": "IMAGE", + "image_value": { + "url": "https://pbs.twimg.com/card_img/1746775136962027520/BepKshBj?format=jpg&name=orig", + "width": 1200, + "height": 600, + "alt": null + } + }, + "card_url": { + "type": "STRING", + "string_value": "https://t.co/U8OpWWyVD6", + "scribe_key": "card_url" + } + }, + "users": { + "13334762": { + "id": 13334762, + "id_str": "13334762", + "name": "GitHub", + "screen_name": "github", + "location": "San Francisco, CA", + "description": "The AI-powered developer platform to build, scale, and deliver secure software.", + "url": "https://t.co/bbJgfyzcJR", + "entities": { + "url": { + "urls": [ + { + "url": "https://t.co/bbJgfyzcJR", + "expanded_url": "http://github.com", + "display_url": "github.com", + "indices": [ + 0, + 23 + ] + } + ] + }, + "description": { + "urls": [] + } + }, + "protected": false, + "followers_count": 2550286, + "friends_count": 336, + "listed_count": 18218, + "created_at": "Mon Feb 11 04:41:50 +0000 2008", + "favourites_count": 8192, + "utc_offset": null, + "time_zone": null, + "geo_enabled": true, + "verified": false, + "statuses_count": 8814, + "lang": null, + "contributors_enabled": false, + "is_translator": false, + "is_translation_enabled": false, + "profile_background_color": "EEEEEE", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_image_url": "http://pbs.twimg.com/profile_images/1633247750010830848/8zfRrYjA_normal.png", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1633247750010830848/8zfRrYjA_normal.png", + "profile_banner_url": "https://pbs.twimg.com/profile_banners/13334762/1692114901", + "profile_link_color": "981CEB", + "profile_sidebar_border_color": "BBBBBB", + "profile_sidebar_fill_color": "DDDDDD", + "profile_text_color": "000000", + "profile_use_background_image": false, + "default_profile": false, + "default_profile_image": false, + "can_media_tag": null, + "following": false, + "follow_request_sent": null, + "notifications": null, + "blocking": false, + "blocked_by": false, + "profile_interstitial_type": "", + "translator_type": "none", + "withheld_in_countries": [], + "followed_by": false, + "ext_is_blue_verified": true, + "ext_verified_type": "Business", + "ext_highlighted_label": {}, + "ext": { + "highlightedLabel": { + "r": { + "ok": {} + }, + "ttl": -1 + } + } + } + }, + "card_platform": { + "platform": { + "device": { + "name": "Swift", + "version": "12" + }, + "audience": { + "name": "production", + "bucket": null + } + } + } + }, + "lang": "en", + "ext": { + "superFollowMetadata": { + "r": { + "ok": {} + }, + "ttl": -1 + } + } + } + } + }, + "timeline": { + "id": "AAAAAC4B0ZQAAAACm5udsSaolIM", + "instructions": [ + { + "addEntries": { + "entries": [ + { + "entryId": "cursor-top-1705750650164", + "sortIndex": "1705750650164", + "content": { + "operation": { + "cursor": { + "value": "DAABDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", + "cursorType": "Top" + } + } + } + }, + { + "entryId": "notification-AAAAAC4B0ZQAAAACm5udsSaolIMe0Mhk3Nw", + "sortIndex": "1705750650163", + "content": { + "item": { + "content": { + "tweet": { + "id": "1748671085438988794", + "displayType": "Tweet" + } + }, + "clientEventInfo": { + "component": "urt", + "element": "user_replied_to_your_tweet", + "details": { + "notificationDetails": { + "impressionId": "ba99cafcbccaee49f2da523e5b86ac9c", + "metadata": "CwABAAAAM2RkMDgyOTNhNjk5OTQzNGQuNDhlZWY4ZWFlMDNlMDlkZDw6ZGQwODI5M2E2OTk5NDM0ZAsAAgAAACNBQUFBQUM0QjBaUUFBQUFDbTV1ZHNTYW9sSU1lME1oazNOdwsAAwAAABQ3NzE4NzExMjQtLTI2NzU3NDczOAoABAAAAAAAAAABDwAFCgAAAAIYRIcWXJZx-hZyvC6dF8AHCwAGAAAAGnVzZXJfcmVwbGllZF90b195b3VyX3R3ZWV0DwAHCwAAAAEAAAAUNzcxODcxMTI0LS0yNjc1NzQ3MzgA" + } + } + } + } + } + }, + { + "entryId": "cursor-bottom-1705750650162", + "sortIndex": "1705750650162", + "content": { + "operation": { + "cursor": { + "value": "DAACDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", + "cursorType": "Bottom" + } + } + } + } + ] + } + }, + { + "clearEntriesUnreadState": {} + }, + { + "markEntriesUnreadGreaterThanSortIndex": { + "sortIndex": "1697974584014" + } + } + ] + } +} diff --git a/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs b/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs new file mode 100644 index 000000000..08063eaa2 --- /dev/null +++ b/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs @@ -0,0 +1,188 @@ +// 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.Globalization; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using OpenTween.Api.DataModel; +using OpenTween.Api.GraphQL; +using OpenTween.Connection; + +namespace OpenTween.Api.TwitterV2 +{ + public class NotificationsMentionsRequest + { + public static readonly string EndpointName = "/2/notifications/mentions"; + + private static readonly Uri EndpointUri = new("https://twitter.com/i/api/2/notifications/mentions.json"); + + public int Count { get; set; } = 100; + + public string? Cursor { get; set; } + + public Dictionary CreateParameters() + { + var param = new Dictionary() + { + ["include_profile_interstitial_type"] = "1", + ["include_blocking"] = "1", + ["include_blocked_by"] = "1", + ["include_followed_by"] = "1", + ["include_want_retweets"] = "1", + ["include_mute_edge"] = "1", + ["include_can_dm"] = "1", + ["include_can_media_tag"] = "1", + ["include_ext_has_nft_avatar"] = "1", + ["include_ext_is_blue_verified"] = "1", + ["include_ext_verified_type"] = "1", + ["include_ext_profile_image_shape"] = "1", + ["skip_status"] = "1", + ["cards_platform"] = "Web-12", + ["include_cards"] = "1", + ["include_ext_alt_text"] = "true", + ["include_ext_limited_action_results"] = "true", + ["include_quote_count"] = "true", + ["include_reply_count"] = "1", + ["tweet_mode"] = "extended", + ["include_ext_views"] = "true", + ["include_entities"] = "true", + ["include_user_entities"] = "true", + ["include_ext_media_color"] = "true", + ["include_ext_media_availability"] = "true", + ["include_ext_sensitive_media_warning"] = "true", + ["include_ext_trusted_friends_metadata"] = "true", + ["send_error_codes"] = "true", + ["simple_quoted_tweet"] = "true", + ["requestContext"] = "ptr", + ["ext"] = "mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl", + ["count"] = this.Count.ToString(CultureInfo.InvariantCulture), + }; + + if (!MyCommon.IsNullOrEmpty(this.Cursor)) + param["cursor"] = this.Cursor; + + return param; + } + + public async Task Send(IApiConnection apiConnection) + { + var request = new GetRequest + { + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var responseBytes = await response.ReadAsBytes() + .ConfigureAwait(false); + + ResponseRoot parsedObjects; + XElement rootElm; + try + { + parsedObjects = MyCommon.CreateDataFromJson(responseBytes); + + using var jsonReader = JsonReaderWriterFactory.CreateJsonReader( + responseBytes, + XmlDictionaryReaderQuotas.Max + ); + + rootElm = XElement.Load(jsonReader); + } + catch (SerializationException ex) + { + var responseText = Encoding.UTF8.GetString(responseBytes); + throw TwitterApiException.CreateFromException(ex, responseText); + } + catch (XmlException ex) + { + var responseText = Encoding.UTF8.GetString(responseBytes); + throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText }; + } + + ErrorResponse.ThrowIfError(rootElm); + + var tweetIds = rootElm.XPathSelectElements("//content/item/content/tweet/id") + .Select(x => x.Value) + .ToArray(); + + var statuses = new List(tweetIds.Length); + foreach (var tweetId in tweetIds) + { + if (!parsedObjects.GlobalObjects.Tweets.TryGetValue(tweetId, out var tweet)) + continue; + + var userId = tweet.UserId; + if (!parsedObjects.GlobalObjects.Users.TryGetValue(userId, out var user)) + continue; + + tweet.User = user; + statuses.Add(tweet); + } + + var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); + var cursorTop = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Top']]/value")?.Value; + var cursorBottom = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Bottom']]/value")?.Value; + + return new(statuses.ToArray(), cursorTop, cursorBottom); + } + + [DataContract] + private record ResponseRoot( + [property: DataMember(Name = "globalObjects")] + ResponseGlobalObjects GlobalObjects + ); + + [DataContract] + private record ResponseGlobalObjects( + [property: DataMember(Name = "users")] + Dictionary Users, + [property: DataMember(Name = "tweets")] + Dictionary Tweets + ); + + [DataContract] + private class ResponseTweet : TwitterStatus + { + [DataMember(Name = "user_id")] + public string UserId { get; set; } = ""; + } + + public readonly record struct NotificationsResponse( + TwitterStatus[] Statuses, + string? CursorTop, + string? CursorBottom + ); + } +} diff --git a/OpenTween/Models/MentionsTabModel.cs b/OpenTween/Models/MentionsTabModel.cs index 1792b160f..a56675bd9 100644 --- a/OpenTween/Models/MentionsTabModel.cs +++ b/OpenTween/Models/MentionsTabModel.cs @@ -43,6 +43,10 @@ public override MyCommon.TabUsageType TabType public PostId? OldestId { get; set; } + public string? CursorTop { get; set; } + + public string? CursorBottom { get; set; } + public MentionsTabModel() : this(MyCommon.DEFAULTTAB.REPLY) { diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index e066b92bf..9e112de72 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -7000,7 +7000,8 @@ private void SetApiStatusLabel(string? endpointName = null) authByCookie ? HomeLatestTimelineRequest.EndpointName : "/statuses/home_timeline", MyCommon.TabUsageType.UserDefined => authByCookie ? HomeLatestTimelineRequest.EndpointName : "/statuses/home_timeline", - MyCommon.TabUsageType.Mentions => "/statuses/mentions_timeline", + MyCommon.TabUsageType.Mentions => + authByCookie ? NotificationsMentionsRequest.EndpointName : "/statuses/mentions_timeline", MyCommon.TabUsageType.Favorites => "/favorites/list", MyCommon.TabUsageType.DirectMessage => "/direct_messages/events/list", MyCommon.TabUsageType.UserTimeline => diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index f3d0caca3..f9ab5afa1 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -651,15 +651,35 @@ public async Task GetMentionsTimelineApi(bool read, MentionsTabModel tab, bool m var count = GetApiResultCount(MyCommon.WORKERTYPE.Reply, more, startup); TwitterStatus[] statuses; - if (more) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { - statuses = await this.Api.StatusesMentionsTimeline(count, maxId: tab.OldestId as TwitterStatusId) + var request = new NotificationsMentionsRequest + { + Count = Math.Min(count, 50), + Cursor = more ? tab.CursorBottom : tab.CursorTop, + }; + var response = await request.Send(this.Api.Connection) .ConfigureAwait(false); + + statuses = response.Statuses; + + tab.CursorBottom = response.CursorBottom; + + if (!more) + tab.CursorTop = response.CursorTop; } else { - statuses = await this.Api.StatusesMentionsTimeline(count) - .ConfigureAwait(false); + if (more) + { + statuses = await this.Api.StatusesMentionsTimeline(count, maxId: tab.OldestId as TwitterStatusId) + .ConfigureAwait(false); + } + else + { + statuses = await this.Api.StatusesMentionsTimeline(count) + .ConfigureAwait(false); + } } var minimumId = this.CreatePostsFromJson(statuses, MyCommon.WORKERTYPE.Reply, tab, read); From af099f796fb51fd3b9634b5bd04ed3d9e856e4da Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 24 Jan 2024 11:40:49 +0900 Subject: [PATCH 06/17] =?UTF-8?q?TimelineTweet=E3=81=AE=E4=B8=AD=E8=BA=AB?= =?UTF-8?q?=E3=81=8C=E7=A9=BA=E3=81=A0=E3=81=A3=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E7=84=A1=E8=A6=96=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + .../Api/GraphQL/TimelineTweetTest.cs | 15 +++++++++- .../Responses/TimelineTweet_EmptyTweet.json | 6 ++++ OpenTween/Api/GraphQL/TimelineResponse.cs | 2 +- OpenTween/Api/GraphQL/TimelineTweet.cs | 29 ++++++++++++------- 5 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 OpenTween.Tests/Resources/Responses/TimelineTweet_EmptyTweet.json diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b2de0f81e..944406f78 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,6 +5,7 @@ * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 + * FIX: 取得したツイートの中身が空だった場合のエラー処理を改善 ==== Ver 3.12.0(2024/01/20) * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 diff --git a/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs b/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs index f1958321e..a4ccdac91 100644 --- a/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs +++ b/OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs @@ -188,11 +188,24 @@ public void ToStatus_TweetTombstone_Test() var rootElm = this.LoadResponseDocument("TimelineTweet_TweetTombstone.json"); var timelineTweet = new TimelineTweet(rootElm); - Assert.True(timelineTweet.IsTombstone); + Assert.False(timelineTweet.IsAvailable); var ex = Assert.Throws( () => timelineTweet.ToTwitterStatus() ); Assert.Equal("This Post is from a suspended account. Learn more", ex.Message); } + + [Fact] + public void ToStatus_EmptyTweet_Test() + { + var rootElm = this.LoadResponseDocument("TimelineTweet_EmptyTweet.json"); + var timelineTweet = new TimelineTweet(rootElm); + + Assert.False(timelineTweet.IsAvailable); + var ex = Assert.Throws( + () => timelineTweet.ToTwitterStatus() + ); + Assert.Equal("Tweet is not available", ex.Message); + } } } diff --git a/OpenTween.Tests/Resources/Responses/TimelineTweet_EmptyTweet.json b/OpenTween.Tests/Resources/Responses/TimelineTweet_EmptyTweet.json new file mode 100644 index 000000000..70b98651c --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/TimelineTweet_EmptyTweet.json @@ -0,0 +1,6 @@ +{ + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": {}, + "tweetDisplayType": "Tweet" +} diff --git a/OpenTween/Api/GraphQL/TimelineResponse.cs b/OpenTween/Api/GraphQL/TimelineResponse.cs index 052ea6108..a2729df0c 100644 --- a/OpenTween/Api/GraphQL/TimelineResponse.cs +++ b/OpenTween/Api/GraphQL/TimelineResponse.cs @@ -34,7 +34,7 @@ public record TimelineResponse( { public TwitterStatus[] ToTwitterStatuses() => this.Tweets - .Where(x => !x.IsTombstone) + .Where(x => x.IsAvailable) .Select(x => x.ToTwitterStatus()) .ToArray(); } diff --git a/OpenTween/Api/GraphQL/TimelineTweet.cs b/OpenTween/Api/GraphQL/TimelineTweet.cs index af7b1764d..e81403413 100644 --- a/OpenTween/Api/GraphQL/TimelineTweet.cs +++ b/OpenTween/Api/GraphQL/TimelineTweet.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -38,10 +39,10 @@ public class TimelineTweet public XElement Element { get; } - public bool IsTombstone - => this.tombstoneElm != null; + public bool IsAvailable + => this.resultElm != null && !this.IsTombstoneResult(this.resultElm); - private readonly XElement? tombstoneElm; + private readonly XElement? resultElm; public TimelineTweet(XElement element) { @@ -50,19 +51,22 @@ public TimelineTweet(XElement element) throw new ArgumentException($"Invalid itemType: {typeName}", nameof(element)); this.Element = element; - this.tombstoneElm = this.TryGetTombstoneElm(); + this.resultElm = this.TryGetResultElm(); } - private XElement? TryGetTombstoneElm() - => this.Element.XPathSelectElement("tweet_results/result[__typename[text()='TweetTombstone']]"); + private XElement? TryGetResultElm() + => this.Element.XPathSelectElement("tweet_results/result"); + + private bool IsTombstoneResult([NotNullWhen(true)]XElement? resultElm) + => resultElm?.Element("__typename")?.Value == "TweetTombstone"; public TwitterStatus ToTwitterStatus() { - this.ThrowIfTweetIsTombstone(); + this.ThrowIfTweetIsNotAvailable(); try { - var resultElm = this.Element.Element("tweet_results")?.Element("result") ?? throw CreateParseError(); + var resultElm = this.resultElm ?? throw CreateParseError(); var status = TimelineTweet.ParseTweetUnion(resultElm); if (this.Element.Element("promotedMetadata") != null) @@ -78,12 +82,15 @@ public TwitterStatus ToTwitterStatus() } } - public void ThrowIfTweetIsTombstone() + public void ThrowIfTweetIsNotAvailable() { - if (this.tombstoneElm == null) + if (this.IsAvailable) return; - var tombstoneText = this.tombstoneElm.XPathSelectElement("tombstone/text/text")?.Value; + string? tombstoneText = null; + if (this.IsTombstoneResult(this.resultElm)) + tombstoneText = this.resultElm.XPathSelectElement("tombstone/text/text")?.Value; + var message = tombstoneText ?? "Tweet is not available"; var json = JsonUtils.JsonXmlToString(this.Element); From c8e627c635d6cd9d32d3a534b7abae2ed75175f8 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 24 Jan 2024 12:26:48 +0900 Subject: [PATCH 07/17] =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=81=AE=E5=8F=96=E5=BE=97=E7=B5=90=E6=9E=9C?= =?UTF-8?q?=E3=81=AB=E3=83=AC=E3=83=BC=E3=83=88=E3=83=AA=E3=83=9F=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=81=AB=E9=96=A2=E3=81=99=E3=82=8B=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=8C=E5=90=AB=E3=81=BE=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AF=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=A8=E3=81=97=E3=81=A6=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/HomeLatestTimelineRequestTest.cs | 28 ++++++++ .../HomeLatestTimeline_RateLimit.json | 68 +++++++++++++++++++ OpenTween/Api/GraphQL/ErrorResponse.cs | 21 ++++++ .../Api/GraphQL/HomeLatestTimelineRequest.cs | 3 + 5 files changed, 121 insertions(+) create mode 100644 OpenTween.Tests/Resources/Responses/HomeLatestTimeline_RateLimit.json diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 944406f78..9c2b682d9 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -6,6 +6,7 @@ * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 * FIX: 取得したツイートの中身が空だった場合のエラー処理を改善 + * FIX: タイムラインの取得結果にレートリミットに関するメッセージが含まれていた場合はエラーとして扱う ==== Ver 3.12.0(2024/01/20) * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 diff --git a/OpenTween.Tests/Api/GraphQL/HomeLatestTimelineRequestTest.cs b/OpenTween.Tests/Api/GraphQL/HomeLatestTimelineRequestTest.cs index df1fb6efe..4bc2b5622 100644 --- a/OpenTween.Tests/Api/GraphQL/HomeLatestTimelineRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/HomeLatestTimelineRequestTest.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using Moq; +using OpenTween.Api.DataModel; using OpenTween.Connection; using Xunit; @@ -92,5 +93,32 @@ public async Task Send_RequestCursor_Test() await request.Send(mock.Object); mock.VerifyAll(); } + + [Fact] + public async Task Send_RateLimitTest() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/HomeLatestTimeline_RateLimit.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/lAKISuk_McyDUlhS2Zmv4A/HomeLatestTimeline"), request.RequestUri); + }) + .ReturnsAsync(apiResponse); + + var request = new HomeLatestTimelineRequest(); + + var ex = await Assert.ThrowsAsync( + () => request.Send(mock.Object) + ); + var errorItem = Assert.Single(ex.Errors); + Assert.Equal(TwitterErrorCode.RateLimit, errorItem.Code); + + mock.VerifyAll(); + } } } diff --git a/OpenTween.Tests/Resources/Responses/HomeLatestTimeline_RateLimit.json b/OpenTween.Tests/Resources/Responses/HomeLatestTimeline_RateLimit.json new file mode 100644 index 000000000..4a651c37b --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/HomeLatestTimeline_RateLimit.json @@ -0,0 +1,68 @@ +{ + "data": { + "home": { + "home_timeline_urt": { + "instructions": [ + { + "type": "TimelineAddEntries", + "entries": [ + { + "entryId": "messageprompt-1682783911", + "sortIndex": "1749988213990096896", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineMessagePrompt", + "__typename": "TimelineMessagePrompt", + "content": { + "contentType": "TimelineInlinePrompt", + "headerText": "Unlock more posts by subscribing", + "bodyText": "You have reached the limit for seeing posts today. Subscribe to see more posts every day.", + "primaryButtonAction": { + "text": "Subscribe", + "action": { + "url": "https://twitter.com/i/twitter_blue_sign_up", + "dismissOnClick": false + } + } + } + }, + "clientEventInfo": { + "component": "verified_prompt", + "element": "message" + } + } + }, + { + "entryId": "cursor-top-1749988213990096897", + "sortIndex": "1749988213990096897", + "content": { + "entryType": "TimelineTimelineCursor", + "__typename": "TimelineTimelineCursor", + "value": "DAABCgABGEk1AkAAJxEKAAIYSJVb2ltBzQgAAwAAAAEAAA", + "cursorType": "Top" + } + }, + { + "entryId": "cursor-bottom-1749988213990096895", + "sortIndex": "1749988213990096895", + "content": { + "entryType": "TimelineTimelineCursor", + "__typename": "TimelineTimelineCursor", + "value": "DAABCgABGEk1Aj____0KAAIYSJVb2ltBzQgAAwAAAAIAAA", + "cursorType": "Bottom" + } + } + ] + } + ], + "metadata": { + "scribeConfig": { + "page": "following" + } + } + } + } + } +} diff --git a/OpenTween/Api/GraphQL/ErrorResponse.cs b/OpenTween/Api/GraphQL/ErrorResponse.cs index 4f778a081..2a8e6d446 100644 --- a/OpenTween/Api/GraphQL/ErrorResponse.cs +++ b/OpenTween/Api/GraphQL/ErrorResponse.cs @@ -30,6 +30,7 @@ using System.Xml; using System.Xml.Linq; using System.Xml.XPath; +using OpenTween.Api.DataModel; namespace OpenTween.Api.GraphQL { @@ -63,5 +64,25 @@ public static void ThrowIfError(XElement rootElm) throw new WebApiException(messageText, responseJson); } + + public static void ThrowIfContainsRateLimitMessage(XElement rootElm) + { + var messageElm = rootElm.XPathSelectElement("//itemContent[itemType[text()='TimelineMessagePrompt']]"); + if (messageElm == null) + return; + + var bodyText = messageElm.XPathSelectElement("content/bodyText")?.Value ?? ""; + if (bodyText.StartsWith("You have reached the limit")) + { + var error = new TwitterError + { + Errors = new[] + { + new TwitterErrorItem { Code = TwitterErrorCode.RateLimit, Message = "" }, + }, + }; + throw new TwitterApiException(0, error, ""); + } + } } } diff --git a/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs b/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs index d11c0ca77..63bea0665 100644 --- a/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs @@ -72,6 +72,9 @@ public async Task Send(IApiConnection apiConnection) ErrorResponse.ThrowIfError(rootElm); var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); + if (tweets.Length == 0) + ErrorResponse.ThrowIfContainsRateLimitMessage(rootElm); + var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value; var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value; From c27b466cd6abec7d2579d5da03de4db332de107c Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 24 Jan 2024 12:33:53 +0900 Subject: [PATCH 08/17] =?UTF-8?q?TimelineResponseParser=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/GraphQL/HomeLatestTimelineRequest.cs | 12 +---- .../ListLatestTweetsTimelineRequest.cs | 9 +--- .../Api/GraphQL/SearchTimelineRequest.cs | 9 +--- .../Api/GraphQL/TimelineResponseParser.cs | 45 +++++++++++++++++++ .../GraphQL/UserTweetsAndRepliesRequest.cs | 9 +--- 5 files changed, 49 insertions(+), 35 deletions(-) create mode 100644 OpenTween/Api/GraphQL/TimelineResponseParser.cs diff --git a/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs b/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs index 63bea0665..e9fb72ed3 100644 --- a/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/HomeLatestTimelineRequest.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using System.Xml.XPath; using OpenTween.Connection; namespace OpenTween.Api.GraphQL @@ -69,16 +68,7 @@ public async Task Send(IApiConnection apiConnection) var rootElm = await response.ReadAsJsonXml() .ConfigureAwait(false); - ErrorResponse.ThrowIfError(rootElm); - - var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); - if (tweets.Length == 0) - ErrorResponse.ThrowIfContainsRateLimitMessage(rootElm); - - var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value; - var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value; - - return new(tweets, cursorTop, cursorBottom); + return TimelineResponseParser.Parse(rootElm); } } } diff --git a/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs b/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs index 3aa306f6f..29d0614b8 100644 --- a/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using System.Xml.XPath; using OpenTween.Connection; namespace OpenTween.Api.GraphQL @@ -93,13 +92,7 @@ public async Task Send(IApiConnection apiConnection) var rootElm = await response.ReadAsJsonXml() .ConfigureAwait(false); - ErrorResponse.ThrowIfError(rootElm); - - var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); - var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value; - var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value; - - return new(tweets, cursorTop, cursorBottom); + return TimelineResponseParser.Parse(rootElm); } } } diff --git a/OpenTween/Api/GraphQL/SearchTimelineRequest.cs b/OpenTween/Api/GraphQL/SearchTimelineRequest.cs index 568371806..9587d8079 100644 --- a/OpenTween/Api/GraphQL/SearchTimelineRequest.cs +++ b/OpenTween/Api/GraphQL/SearchTimelineRequest.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using System.Xml.XPath; using OpenTween.Connection; namespace OpenTween.Api.GraphQL @@ -95,13 +94,7 @@ public async Task Send(IApiConnection apiConnection) var rootElm = await response.ReadAsJsonXml() .ConfigureAwait(false); - ErrorResponse.ThrowIfError(rootElm); - - var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); - var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value; - var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value; - - return new(tweets, cursorTop, cursorBottom); + return TimelineResponseParser.Parse(rootElm); } } } diff --git a/OpenTween/Api/GraphQL/TimelineResponseParser.cs b/OpenTween/Api/GraphQL/TimelineResponseParser.cs new file mode 100644 index 000000000..8fe315fdf --- /dev/null +++ b/OpenTween/Api/GraphQL/TimelineResponseParser.cs @@ -0,0 +1,45 @@ +// 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.Xml.Linq; +using System.Xml.XPath; + +namespace OpenTween.Api.GraphQL +{ + public class TimelineResponseParser + { + public static TimelineResponse Parse(XElement rootElm) + { + ErrorResponse.ThrowIfError(rootElm); + + var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); + if (tweets.Length == 0) + ErrorResponse.ThrowIfContainsRateLimitMessage(rootElm); + + var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value; + var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value; + + return new(tweets, cursorTop, cursorBottom); + } + } +} diff --git a/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs b/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs index fff651342..c138868bb 100644 --- a/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs +++ b/OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using System.Xml.XPath; using OpenTween.Connection; namespace OpenTween.Api.GraphQL @@ -78,13 +77,7 @@ public async Task Send(IApiConnection apiConnection) var rootElm = await response.ReadAsJsonXml() .ConfigureAwait(false); - ErrorResponse.ThrowIfError(rootElm); - - var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); - var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value; - var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value; - - return new(tweets, cursorTop, cursorBottom); + return TimelineResponseParser.Parse(rootElm); } } } From 358edfe186baabdaeecdefd4bc3113ce15fe12ff Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 24 Jan 2024 12:36:11 +0900 Subject: [PATCH 09/17] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AAExtractTimelin?= =?UTF-8?q?eTweets=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=81=AE=E5=91=BC?= =?UTF-8?q?=E3=81=B3=E5=87=BA=E3=81=97=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: da1189a5 ("/notifications/mentions.json を使用したReplyタブの更新に対応") --- OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs b/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs index 08063eaa2..8f08a5ed8 100644 --- a/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs +++ b/OpenTween/Api/TwitterV2/NotificationsMentionsRequest.cs @@ -151,7 +151,6 @@ public async Task Send(IApiConnection apiConnection) statuses.Add(tweet); } - var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); var cursorTop = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Top']]/value")?.Value; var cursorBottom = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Bottom']]/value")?.Value; From 8ff1185188f5f00681f3b9db53562be89f1f7bf8 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Wed, 24 Jan 2024 14:21:01 +0900 Subject: [PATCH 10/17] =?UTF-8?q?FavoriteTweet/UnfavoriteTweet=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9FFav=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=83=BB=E5=89=8A=E9=99=A4=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 + .../Api/GraphQL/FavoriteTweetRequestTest.cs | 78 +++++++++++++++++++ .../Api/GraphQL/UnfavoriteTweetRequestTest.cs | 58 ++++++++++++++ .../Resources/Responses/FavoriteTweet.json | 5 ++ .../FavoriteTweet_AlreadyFavorited.json | 33 ++++++++ .../Resources/Responses/UnfavoriteTweet.json | 5 ++ OpenTween/Api/GraphQL/FavoriteTweetRequest.cs | 73 +++++++++++++++++ .../Api/GraphQL/UnfavoriteTweetRequest.cs | 65 ++++++++++++++++ OpenTween/Tween.cs | 18 +---- OpenTween/Twitter.cs | 48 ++++++++++++ 10 files changed, 370 insertions(+), 14 deletions(-) create mode 100644 OpenTween.Tests/Api/GraphQL/FavoriteTweetRequestTest.cs create mode 100644 OpenTween.Tests/Api/GraphQL/UnfavoriteTweetRequestTest.cs create mode 100644 OpenTween.Tests/Resources/Responses/FavoriteTweet.json create mode 100644 OpenTween.Tests/Resources/Responses/FavoriteTweet_AlreadyFavorited.json create mode 100644 OpenTween.Tests/Resources/Responses/UnfavoriteTweet.json create mode 100644 OpenTween/Api/GraphQL/FavoriteTweetRequest.cs create mode 100644 OpenTween/Api/GraphQL/UnfavoriteTweetRequest.cs diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9c2b682d9..6dafab17b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,7 @@ ==== Unreleased * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) + * NEW: Cookie使用時のFav追加・削除に対応 * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 diff --git a/OpenTween.Tests/Api/GraphQL/FavoriteTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/FavoriteTweetRequestTest.cs new file mode 100644 index 000000000..cef9242bf --- /dev/null +++ b/OpenTween.Tests/Api/GraphQL/FavoriteTweetRequestTest.cs @@ -0,0 +1,78 @@ +// 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 Moq; +using OpenTween.Connection; +using Xunit; + +namespace OpenTween.Api.GraphQL +{ + public class FavoriteTweetRequestTest + { + [Fact] + public async Task Send_Test() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/FavoriteTweet.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet"), request.RequestUri); + Assert.Equal("""{"variables":{"tweet_id":"12345"},"queryId":"lI07N6Otwv1PhnEgXILM7A"}""", request.JsonString); + }) + .ReturnsAsync(apiResponse); + + var request = new FavoriteTweetRequest + { + TweetId = new("12345"), + }; + + await request.Send(mock.Object); + + mock.VerifyAll(); + } + + [Fact] + public async Task Send_AlreadyFavoritedTest() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/FavoriteTweet_AlreadyFavorited.json"); + + var mock = new Mock(); + mock.Setup(x => x.SendAsync(It.IsAny())) + .ReturnsAsync(apiResponse); + + var request = new FavoriteTweetRequest + { + TweetId = new("12345"), + }; + + // 重複によるエラーレスポンスが返っているが例外を発生させない + await request.Send(mock.Object); + + mock.VerifyAll(); + } + } +} diff --git a/OpenTween.Tests/Api/GraphQL/UnfavoriteTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/UnfavoriteTweetRequestTest.cs new file mode 100644 index 000000000..d1d75f2e6 --- /dev/null +++ b/OpenTween.Tests/Api/GraphQL/UnfavoriteTweetRequestTest.cs @@ -0,0 +1,58 @@ +// 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 Moq; +using OpenTween.Connection; +using Xunit; + +namespace OpenTween.Api.GraphQL +{ + public class UnfavoriteTweetRequestTest + { + [Fact] + public async Task Send_Test() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/UnfavoriteTweet.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet"), request.RequestUri); + Assert.Equal("""{"variables":{"tweet_id":"12345"},"queryId":"ZYKSe-w7KEslx3JhSIk5LA"}""", request.JsonString); + }) + .ReturnsAsync(apiResponse); + + var request = new UnfavoriteTweetRequest + { + TweetId = new("12345"), + }; + + await request.Send(mock.Object); + + mock.VerifyAll(); + } + } +} diff --git a/OpenTween.Tests/Resources/Responses/FavoriteTweet.json b/OpenTween.Tests/Resources/Responses/FavoriteTweet.json new file mode 100644 index 000000000..a3c2d33f8 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/FavoriteTweet.json @@ -0,0 +1,5 @@ +{ + "data": { + "favorite_tweet": "Done" + } +} diff --git a/OpenTween.Tests/Resources/Responses/FavoriteTweet_AlreadyFavorited.json b/OpenTween.Tests/Resources/Responses/FavoriteTweet_AlreadyFavorited.json new file mode 100644 index 000000000..71415d216 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/FavoriteTweet_AlreadyFavorited.json @@ -0,0 +1,33 @@ +{ + "errors": [ + { + "message": "Authorization: Actor (uid: 1234567890) has already favorited tweet (tweetId: 1234567890123456789)", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "favorite_tweet" + ], + "extensions": { + "name": "AuthorizationError", + "source": "Client", + "code": 139, + "kind": "Permissions", + "tracing": { + "trace_id": "87bbb9c871e092a6" + } + }, + "code": 139, + "kind": "Permissions", + "name": "AuthorizationError", + "source": "Client", + "tracing": { + "trace_id": "87bbb9c871e092a6" + } + } + ], + "data": {} +} diff --git a/OpenTween.Tests/Resources/Responses/UnfavoriteTweet.json b/OpenTween.Tests/Resources/Responses/UnfavoriteTweet.json new file mode 100644 index 000000000..11e92e2a4 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/UnfavoriteTweet.json @@ -0,0 +1,5 @@ +{ + "data": { + "unfavorite_tweet": "Done" + } +} diff --git a/OpenTween/Api/GraphQL/FavoriteTweetRequest.cs b/OpenTween/Api/GraphQL/FavoriteTweetRequest.cs new file mode 100644 index 000000000..1abe7ecf3 --- /dev/null +++ b/OpenTween/Api/GraphQL/FavoriteTweetRequest.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.Threading.Tasks; +using OpenTween.Connection; +using OpenTween.Models; + +namespace OpenTween.Api.GraphQL +{ + public class FavoriteTweetRequest + { + private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet"); + + public required TwitterStatusId TweetId { get; set; } + + public string CreateRequestBody() + { + return $$""" + {"variables":{"tweet_id":"{{JsonUtils.EscapeJsonString(this.TweetId.Id)}}"},"queryId":"lI07N6Otwv1PhnEgXILM7A"} + """; + } + + public async Task Send(IApiConnection apiConnection) + { + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + try + { + ErrorResponse.ThrowIfError(rootElm); + } + catch (WebApiException ex) + when (ex.Message.Contains("has already favorited")) + { + // 重複リクエストの場合は成功と見なす + } + + return new(); + } + + public readonly record struct FavoriteTweetResponse(); + } +} diff --git a/OpenTween/Api/GraphQL/UnfavoriteTweetRequest.cs b/OpenTween/Api/GraphQL/UnfavoriteTweetRequest.cs new file mode 100644 index 000000000..c952da207 --- /dev/null +++ b/OpenTween/Api/GraphQL/UnfavoriteTweetRequest.cs @@ -0,0 +1,65 @@ +// 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.Threading.Tasks; +using OpenTween.Connection; +using OpenTween.Models; + +namespace OpenTween.Api.GraphQL +{ + public class UnfavoriteTweetRequest + { + private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet"); + + public required TwitterStatusId TweetId { get; set; } + + public string CreateRequestBody() + { + return $$""" + {"variables":{"tweet_id":"{{JsonUtils.EscapeJsonString(this.TweetId.Id)}}"},"queryId":"ZYKSe-w7KEslx3JhSIk5LA"} + """; + } + + public async Task Send(IApiConnection apiConnection) + { + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + ErrorResponse.ThrowIfError(rootElm); + + return new(); + } + + public readonly record struct UnfavoriteTweetResponse(); + } +} diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 9e112de72..a273dc6a3 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -1381,17 +1381,9 @@ await Task.Run(async () => try { var twitterStatusId = (post.RetweetedId ?? post.StatusId).ToTwitterStatusId(); - try - { - await this.tw.Api.FavoritesCreate(twitterStatusId) - .IgnoreResponse() - .ConfigureAwait(false); - } - catch (TwitterApiException ex) - when (ex.Errors.All(x => x.Code == TwitterErrorCode.AlreadyFavorited)) - { - // エラーコード 139 のみの場合は成功と見なす - } + + await this.tw.PostFavAdd(twitterStatusId) + .ConfigureAwait(false); if (this.settings.Common.RestrictFavCheck) { @@ -1511,9 +1503,7 @@ await Task.Run(async () => try { - await this.tw.Api.FavoritesDestroy(twitterStatusId) - .IgnoreResponse() - .ConfigureAwait(false); + await this.tw.PostFavRemove(twitterStatusId); } catch (WebApiException) { diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index f9ab5afa1..e96c2ab95 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -474,6 +474,54 @@ public async Task GetUserInfo(string screenName) } } + public async Task PostFavAdd(TwitterStatusId statusId) + { + if (this.Api.AuthType == APIAuthType.TwitterComCookie) + { + var request = new FavoriteTweetRequest + { + TweetId = statusId, + }; + + await request.Send(this.Api.Connection) + .ConfigureAwait(false); + } + else + { + try + { + await this.Api.FavoritesCreate(statusId) + .IgnoreResponse() + .ConfigureAwait(false); + } + catch (TwitterApiException ex) + when (ex.Errors.All(x => x.Code == TwitterErrorCode.AlreadyFavorited)) + { + // エラーコード 139 のみの場合は成功と見なす + } + } + } + + public async Task PostFavRemove(TwitterStatusId statusId) + { + if (this.Api.AuthType == APIAuthType.TwitterComCookie) + { + var request = new UnfavoriteTweetRequest + { + TweetId = statusId, + }; + + await request.Send(this.Api.Connection) + .ConfigureAwait(false); + } + else + { + await this.Api.FavoritesDestroy(statusId) + .IgnoreResponse() + .ConfigureAwait(false); + } + } + public string Username => this.Api.CurrentScreenName; From 18aca2fb0b0ccd6efb8808576d32c198d2c4a4a7 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 25 Jan 2024 23:35:27 +0900 Subject: [PATCH 11/17] =?UTF-8?q?Likes=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97?= =?UTF-8?q?=E3=81=9FFavorites=E3=82=BF=E3=83=96=E3=81=AE=E6=9B=B4=E6=96=B0?= =?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 + .../Api/GraphQL/LikesRequestTest.cs | 95 ++++++++++ .../Resources/Responses/Likes.json | 169 ++++++++++++++++++ OpenTween/Api/GraphQL/LikesRequest.cs | 81 +++++++++ OpenTween/Models/FavoritesTabModel.cs | 4 + OpenTween/Twitter.cs | 30 +++- 6 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 OpenTween.Tests/Api/GraphQL/LikesRequestTest.cs create mode 100644 OpenTween.Tests/Resources/Responses/Likes.json create mode 100644 OpenTween/Api/GraphQL/LikesRequest.cs diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6dafab17b..d1f084187 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,6 +2,7 @@ ==== Unreleased * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) + * NEW: Cookie使用時のFavoritesタブの更新に対応 * NEW: Cookie使用時のFav追加・削除に対応 * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 diff --git a/OpenTween.Tests/Api/GraphQL/LikesRequestTest.cs b/OpenTween.Tests/Api/GraphQL/LikesRequestTest.cs new file mode 100644 index 000000000..accc445c9 --- /dev/null +++ b/OpenTween.Tests/Api/GraphQL/LikesRequestTest.cs @@ -0,0 +1,95 @@ +// 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 Moq; +using OpenTween.Connection; +using Xunit; + +namespace OpenTween.Api.GraphQL +{ + public class LikesRequestTest + { + [Fact] + public async Task Send_Test() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/Likes.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/G_zHbTiwSqLm0TAK_3sNWQ/Likes"), request.RequestUri); + var query = request.Query!; + Assert.Equal(2, query.Count); + Assert.Equal("""{"userId":"12345","count":20,"includePromotedContent":false,"withClientEventToken":false,"withBirdwatchNotes":false,"withVoice":true,"withV2Timeline":true}""", query["variables"]); + Assert.True(query.ContainsKey("features")); + Assert.Equal("Likes", request.EndpointName); + }) + .ReturnsAsync(apiResponse); + + var request = new LikesRequest + { + UserId = "12345", + Count = 20, + }; + + var response = await request.Send(mock.Object); + Assert.Single(response.Tweets); + Assert.Equal("DAAHCgABGEs2Ve9AAAELAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAEAAA", response.CursorTop); + Assert.Equal("DAAHCgABGEs2Ve8___8LAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAIAAA", response.CursorBottom); + + mock.VerifyAll(); + } + + [Fact] + public async Task Send_RequestCursor_Test() + { + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/Likes.json"); + + var mock = new Mock(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/G_zHbTiwSqLm0TAK_3sNWQ/Likes"), request.RequestUri); + var query = request.Query!; + Assert.Equal("""{"userId":"12345","count":20,"includePromotedContent":false,"withClientEventToken":false,"withBirdwatchNotes":false,"withVoice":true,"withV2Timeline":true,"cursor":"aaa"}""", query["variables"]); + }) + .ReturnsAsync(apiResponse); + + var request = new LikesRequest + { + UserId = "12345", + Count = 20, + Cursor = "aaa", + }; + + await request.Send(mock.Object); + mock.VerifyAll(); + } + } +} diff --git a/OpenTween.Tests/Resources/Responses/Likes.json b/OpenTween.Tests/Resources/Responses/Likes.json new file mode 100644 index 000000000..454b641ab --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/Likes.json @@ -0,0 +1,169 @@ +{ + "data": { + "user": { + "result": { + "__typename": "User", + "timeline_v2": { + "timeline": { + "instructions": [ + { + "type": "TimelineAddEntries", + "entries": [ + { + "entryId": "tweet-1616555877875744768", + "sortIndex": "1750552622877638656", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1616555877875744768", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0MDQ4MDY2NA==", + "rest_id": "40480664", + "affiliates_highlighted_label": {}, + "has_graduated_access": false, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "can_dm": false, + "can_media_tag": false, + "created_at": "Sat May 16 15:20:01 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "m.upsilo.net/@upsilon", + "expanded_url": "https://m.upsilo.net/@upsilon", + "url": "https://t.co/vNMmyHHh15", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 215013, + "followers_count": 1280, + "friends_count": 1, + "has_custom_timelines": false, + "is_translator": false, + "listed_count": 90, + "location": "Funabashi, Chiba, Japan", + "media_count": 876, + "name": "upsilon", + "normal_followers_count": 1280, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/40480664/1349188016", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/719076434/____normal.png", + "profile_interstitial_type": "", + "screen_name": "kim_upsilon", + "statuses_count": 10080, + "translator_type": "regular", + "url": "https://t.co/vNMmyHHh15", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + } + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1616555877875744768" + ], + "editable_until_msecs": "1674253730000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": true, + "views": { + "count": "1184", + "state": "EnabledWithCount" + }, + "source": "OpenTween (dev)", + "legacy": { + "bookmark_count": 1, + "bookmarked": false, + "created_at": "Fri Jan 20 21:58:50 +0000 2023", + "conversation_id_str": "1616555877875744768", + "display_text_range": [ + 0, + 70 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 8, + "favorited": true, + "full_text": "昨日のOpenTweenのリリース、画像投稿周りに結構手を加えたから不具合出てないか心配だったのにそれ関連のエラー報告上がってないの逆に怖い", + "is_quote_status": false, + "lang": "ja", + "quote_count": 0, + "reply_count": 0, + "retweet_count": 0, + "retweeted": false, + "user_id_str": "40480664", + "id_str": "1616555877875744768" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "cursor-top-1750552622877638657", + "sortIndex": "1750552622877638657", + "content": { + "entryType": "TimelineTimelineCursor", + "__typename": "TimelineTimelineCursor", + "value": "DAAHCgABGEs2Ve9AAAELAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAEAAA", + "cursorType": "Top" + } + }, + { + "entryId": "cursor-bottom-1750552622877638655", + "sortIndex": "1750552622877638655", + "content": { + "entryType": "TimelineTimelineCursor", + "__typename": "TimelineTimelineCursor", + "value": "DAAHCgABGEs2Ve8___8LAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAIAAA", + "cursorType": "Bottom" + } + } + ] + } + ], + "metadata": { + "scribeConfig": { + "page": "favorites" + } + } + } + } + } + } + } +} diff --git a/OpenTween/Api/GraphQL/LikesRequest.cs b/OpenTween/Api/GraphQL/LikesRequest.cs new file mode 100644 index 000000000..deda28a08 --- /dev/null +++ b/OpenTween/Api/GraphQL/LikesRequest.cs @@ -0,0 +1,81 @@ +// 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.Threading.Tasks; +using OpenTween.Connection; + +namespace OpenTween.Api.GraphQL +{ + public class LikesRequest + { + public static readonly string EndpointName = "Likes"; + + private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/G_zHbTiwSqLm0TAK_3sNWQ/Likes"); + + public required string UserId { get; set; } + + public int Count { get; set; } = 20; + + public string? Cursor { get; set; } + + public Dictionary CreateParameters() + { + return new() + { + ["variables"] = "{" + + $@"""userId"":""{JsonUtils.EscapeJsonString(this.UserId)}""," + + $@"""count"":{this.Count}," + + $@"""includePromotedContent"":false," + + $@"""withClientEventToken"":false," + + $@"""withBirdwatchNotes"":false," + + $@"""withVoice"":true," + + $@"""withV2Timeline"":true" + + (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") + + "}", + ["features"] = """ + {"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"c9s_tweet_anatomy_moderator_badge_enabled":true,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":true,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"rweb_video_timestamps_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false} + """, + }; + } + + public async Task Send(IApiConnection apiConnection) + { + var request = new GetRequest + { + RequestUri = EndpointUri, + Query = this.CreateParameters(), + EndpointName = EndpointName, + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + return TimelineResponseParser.Parse(rootElm); + } + } +} diff --git a/OpenTween/Models/FavoritesTabModel.cs b/OpenTween/Models/FavoritesTabModel.cs index 861f1c5c4..4030b2a2a 100644 --- a/OpenTween/Models/FavoritesTabModel.cs +++ b/OpenTween/Models/FavoritesTabModel.cs @@ -43,6 +43,10 @@ public override MyCommon.TabUsageType TabType public long OldestId { get; set; } = long.MaxValue; + public string? CursorTop { get; set; } + + public string? CursorBottom { get; set; } + public FavoritesTabModel() : this(MyCommon.DEFAULTTAB.FAV) { diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index e96c2ab95..aaa8b7c04 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -30,6 +30,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -1316,15 +1317,36 @@ public async Task GetFavoritesApi(bool read, FavoritesTabModel tab, bool backwar var count = GetApiResultCount(MyCommon.WORKERTYPE.Favorites, backward, false); TwitterStatus[] statuses; - if (backward) + if (this.Api.AuthType == APIAuthType.TwitterComCookie) { - statuses = await this.Api.FavoritesList(count, maxId: tab.OldestId) + var request = new LikesRequest + { + UserId = this.UserId.ToString(CultureInfo.InvariantCulture), + Count = count, + Cursor = backward ? tab.CursorBottom : tab.CursorTop, + }; + var response = await request.Send(this.Api.Connection) .ConfigureAwait(false); + + statuses = response.ToTwitterStatuses(); + + tab.CursorBottom = response.CursorBottom; + + if (!backward) + tab.CursorTop = response.CursorTop; } else { - statuses = await this.Api.FavoritesList(count) - .ConfigureAwait(false); + if (backward) + { + statuses = await this.Api.FavoritesList(count, maxId: tab.OldestId) + .ConfigureAwait(false); + } + else + { + statuses = await this.Api.FavoritesList(count) + .ConfigureAwait(false); + } } var minimumId = this.CreateFavoritePostsFromJson(statuses, read); From 8b580d3fc47a97cd1fca36824e8803a5b52a8909 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 27 Jan 2024 14:20:24 +0900 Subject: [PATCH 12/17] =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E6=9B=B4=E6=96=B0=E9=96=93=E9=9A=94=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=AB24=E6=99=82=E9=96=93=E5=88=86=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E5=9B=9E=E6=95=B0=E7=9B=AE=E5=AE=89=E3=81=AE?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + .../Panel/GraphqlRequestEstimationTest.cs | 68 ++++++++++ .../Setting/Panel/TabTypeAggregationTest.cs | 48 +++++++ OpenTween/AppendSettingDialog.cs | 10 +- OpenTween/Properties/Resources.Designer.cs | 18 +++ OpenTween/Properties/Resources.en.resx | 2 + OpenTween/Properties/Resources.resx | 2 + .../Setting/Panel/GetPeriodPanel.Designer.cs | 67 ++++++++++ OpenTween/Setting/Panel/GetPeriodPanel.cs | 48 ++++++- .../Setting/Panel/GetPeriodPanel.en.resx | 16 +++ OpenTween/Setting/Panel/GetPeriodPanel.resx | 118 +++++++++++++++--- .../Setting/Panel/GraphqlRequestEstimation.cs | 50 ++++++++ OpenTween/Setting/Panel/TabTypeAggregation.cs | 58 +++++++++ 13 files changed, 478 insertions(+), 28 deletions(-) create mode 100644 OpenTween.Tests/Setting/Panel/GraphqlRequestEstimationTest.cs create mode 100644 OpenTween.Tests/Setting/Panel/TabTypeAggregationTest.cs create mode 100644 OpenTween/Setting/Panel/GraphqlRequestEstimation.cs create mode 100644 OpenTween/Setting/Panel/TabTypeAggregation.cs diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d1f084187..c6965e6de 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,7 @@ * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) * NEW: Cookie使用時のFavoritesタブの更新に対応 * NEW: Cookie使用時のFav追加・削除に対応 + * NEW: 設定画面の更新間隔ページに24時間分の取得回数目安の表示を追加 * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 diff --git a/OpenTween.Tests/Setting/Panel/GraphqlRequestEstimationTest.cs b/OpenTween.Tests/Setting/Panel/GraphqlRequestEstimationTest.cs new file mode 100644 index 000000000..131b54aa4 --- /dev/null +++ b/OpenTween.Tests/Setting/Panel/GraphqlRequestEstimationTest.cs @@ -0,0 +1,68 @@ +// 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 Xunit; + +namespace OpenTween.Setting.Panel +{ + public class GraphqlRequestEstimationTest + { + [Fact] + public void CalcDailyRequestCount_Test() + { + var tabCounts = new TabTypeAggregation.Result( + HomeTabs: 1, + MentionsTabs: 1, + DMTabs: 1, + SearchTabs: 2, + ListTabs: 1, + UserTabs: 1 + ); + var intervals = new GraphqlRequestEstimation.Intervals( + Home: 90, // 960 requests / day + Search: 180, // 480 requests / day + List: 180, // 480 requests / day + User: 600 // 144 requests / day + ); + Assert.Equal(2544, GraphqlRequestEstimation.CalcDailyRequestCount(intervals, tabCounts)); + } + + [Fact] + public void CalcDailyRequestCount_DisableAutoRefreshTest() + { + var tabCounts = new TabTypeAggregation.Result( + HomeTabs: 1, + MentionsTabs: 1, + DMTabs: 1, + SearchTabs: 2, + ListTabs: 1, + UserTabs: 1 + ); + var intervals = new GraphqlRequestEstimation.Intervals( + Home: 0, // 0 は自動更新の無効化を表す + Search: 0, + List: 0, + User: 0 + ); + Assert.Equal(0, GraphqlRequestEstimation.CalcDailyRequestCount(intervals, tabCounts)); + } + } +} diff --git a/OpenTween.Tests/Setting/Panel/TabTypeAggregationTest.cs b/OpenTween.Tests/Setting/Panel/TabTypeAggregationTest.cs new file mode 100644 index 000000000..074f50fb5 --- /dev/null +++ b/OpenTween.Tests/Setting/Panel/TabTypeAggregationTest.cs @@ -0,0 +1,48 @@ +// 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.Models; +using Xunit; + +namespace OpenTween.Setting.Panel +{ + public class TabTypeAggregationTest + { + [Fact] + public void Aggregate_Test() + { + var tabinfo = new TabInformations(); + tabinfo.AddDefaultTabs(); + tabinfo.AddTab(new PublicSearchTabModel("search1")); + tabinfo.AddTab(new PublicSearchTabModel("search2")); + + var expected = new TabTypeAggregation.Result( + HomeTabs: 1, + MentionsTabs: 1, + DMTabs: 1, + SearchTabs: 2, + ListTabs: 0, + UserTabs: 0 + ); + Assert.Equal(expected, TabTypeAggregation.Aggregate(tabinfo)); + } + } +} diff --git a/OpenTween/AppendSettingDialog.cs b/OpenTween/AppendSettingDialog.cs index 703cd51c0..0f8dfeda0 100644 --- a/OpenTween/AppendSettingDialog.cs +++ b/OpenTween/AppendSettingDialog.cs @@ -27,22 +27,15 @@ #nullable enable using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Data; -using System.Drawing; -using System.IO; using System.Linq; -using System.Net.Http; -using System.Resources; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using OpenTween.Api; using OpenTween.Connection; +using OpenTween.Models; using OpenTween.Setting.Panel; -using OpenTween.Thumbnail; namespace OpenTween { @@ -322,6 +315,7 @@ private void Setting_Shown(object sender, EventArgs e) this.TopMost = this.PreviewPanel.CheckAlwaysTop.Checked; this.GetPeriodPanel.LabelPostAndGet.Visible = this.GetPeriodPanel.CheckPostAndGet.Checked; + this.GetPeriodPanel.UpdateTabCounts(TabInformations.GetInstance()); } private async Task OpenUrl(string url) diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index cf608ae96..e89cb6dfb 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -1158,6 +1158,24 @@ internal static string GetFriendshipInfo8 { } } + /// + /// graphql エンドポイントのリクエスト回数目安: {0:#,0} 回 / 24 時間 に類似しているローカライズされた文字列を検索します。 + /// + internal static string GetPeriodPanel_LabelGraphqlEstimate { + get { + return ResourceManager.GetString("GetPeriodPanel_LabelGraphqlEstimate", resourceCulture); + } + } + + /// + /// (タブ数: {0}) に類似しているローカライズされた文字列を検索します。 + /// + internal static string GetPeriodPanel_LabelTabCount { + get { + return ResourceManager.GetString("GetPeriodPanel_LabelTabCount", resourceCulture); + } + } + /// /// Recent更新完了 に類似しているローカライズされた文字列を検索します。 /// diff --git a/OpenTween/Properties/Resources.en.resx b/OpenTween/Properties/Resources.en.resx index 824d29560..a0fd66aac 100644 --- a/OpenTween/Properties/Resources.en.resx +++ b/OpenTween/Properties/Resources.en.resx @@ -117,6 +117,8 @@ Faild to get relation info. Really Unfollow? Confirm Unfollow + Estimated number of requests for graphql endpoint: {0:#,0} requests / 24 hours + (Tabs: {0}) Recent refreshed Initial fetch completed DMRcv refreshed diff --git a/OpenTween/Properties/Resources.resx b/OpenTween/Properties/Resources.resx index bb846f4f5..3e3c8656e 100644 --- a/OpenTween/Properties/Resources.resx +++ b/OpenTween/Properties/Resources.resx @@ -124,6 +124,8 @@ フォロー状況取得失敗 フォロー解除しますか? フォロー解除確認 + graphql エンドポイントのリクエスト回数目安: {0:#,0} 回 / 24 時間 + (タブ数: {0}) Recent更新完了 起動時読込完了 DMRcv更新完了 diff --git a/OpenTween/Setting/Panel/GetPeriodPanel.Designer.cs b/OpenTween/Setting/Panel/GetPeriodPanel.Designer.cs index 41fd4ba04..222a7310d 100644 --- a/OpenTween/Setting/Panel/GetPeriodPanel.Designer.cs +++ b/OpenTween/Setting/Panel/GetPeriodPanel.Designer.cs @@ -43,6 +43,14 @@ private void InitializeComponent() this.CheckPostAndGet = new System.Windows.Forms.CheckBox(); this.Label5 = new System.Windows.Forms.Label(); this.DMPeriod = new System.Windows.Forms.TextBox(); + this.labelTabCountHome = new System.Windows.Forms.Label(); + this.labelTabCountMentions = new System.Windows.Forms.Label(); + this.labelTabCountDM = new System.Windows.Forms.Label(); + this.labelTabCountSearch = new System.Windows.Forms.Label(); + this.labelTabCountList = new System.Windows.Forms.Label(); + this.labelTabCountUser = new System.Windows.Forms.Label(); + this.labelGraphqlEstimate = new System.Windows.Forms.Label(); + this.labelNoteForGraphqlLimits = new System.Windows.Forms.Label(); this.SuspendLayout(); // // Label21 @@ -54,6 +62,7 @@ private void InitializeComponent() // resources.ApplyResources(this.UserTimelinePeriod, "UserTimelinePeriod"); this.UserTimelinePeriod.Name = "UserTimelinePeriod"; + this.UserTimelinePeriod.Validating += new System.ComponentModel.CancelEventHandler(this.UserTimeline_Validating); // // TimelinePeriod // @@ -121,6 +130,48 @@ private void InitializeComponent() this.DMPeriod.Name = "DMPeriod"; this.DMPeriod.Validating += new System.ComponentModel.CancelEventHandler(this.DMPeriod_Validating); // + // labelTabCountHome + // + resources.ApplyResources(this.labelTabCountHome, "labelTabCountHome"); + this.labelTabCountHome.Name = "labelTabCountHome"; + // + // labelTabCountMentions + // + resources.ApplyResources(this.labelTabCountMentions, "labelTabCountMentions"); + this.labelTabCountMentions.Name = "labelTabCountMentions"; + // + // labelTabCountDM + // + resources.ApplyResources(this.labelTabCountDM, "labelTabCountDM"); + this.labelTabCountDM.Name = "labelTabCountDM"; + // + // labelTabCountSearch + // + resources.ApplyResources(this.labelTabCountSearch, "labelTabCountSearch"); + this.labelTabCountSearch.Name = "labelTabCountSearch"; + // + // labelTabCountList + // + resources.ApplyResources(this.labelTabCountList, "labelTabCountList"); + this.labelTabCountList.Name = "labelTabCountList"; + // + // labelTabCountUser + // + resources.ApplyResources(this.labelTabCountUser, "labelTabCountUser"); + this.labelTabCountUser.Name = "labelTabCountUser"; + // + // labelGraphqlEstimate + // + resources.ApplyResources(this.labelGraphqlEstimate, "labelGraphqlEstimate"); + this.labelGraphqlEstimate.Name = "labelGraphqlEstimate"; + // + // labelNoteForGraphqlLimits + // + resources.ApplyResources(this.labelNoteForGraphqlLimits, "labelNoteForGraphqlLimits"); + this.labelNoteForGraphqlLimits.BackColor = System.Drawing.SystemColors.ActiveCaption; + this.labelNoteForGraphqlLimits.ForeColor = System.Drawing.SystemColors.ActiveCaptionText; + this.labelNoteForGraphqlLimits.Name = "labelNoteForGraphqlLimits"; + // // GetPeriodPanel // resources.ApplyResources(this, "$this"); @@ -139,6 +190,14 @@ private void InitializeComponent() this.Controls.Add(this.CheckPostAndGet); this.Controls.Add(this.Label5); this.Controls.Add(this.DMPeriod); + this.Controls.Add(this.labelNoteForGraphqlLimits); + this.Controls.Add(this.labelGraphqlEstimate); + this.Controls.Add(this.labelTabCountUser); + this.Controls.Add(this.labelTabCountList); + this.Controls.Add(this.labelTabCountSearch); + this.Controls.Add(this.labelTabCountDM); + this.Controls.Add(this.labelTabCountMentions); + this.Controls.Add(this.labelTabCountHome); this.Name = "GetPeriodPanel"; this.ResumeLayout(false); this.PerformLayout(); @@ -160,5 +219,13 @@ private void InitializeComponent() internal System.Windows.Forms.CheckBox CheckPostAndGet; internal System.Windows.Forms.Label Label5; internal System.Windows.Forms.TextBox DMPeriod; + private System.Windows.Forms.Label labelTabCountHome; + private System.Windows.Forms.Label labelTabCountMentions; + private System.Windows.Forms.Label labelTabCountDM; + private System.Windows.Forms.Label labelTabCountSearch; + private System.Windows.Forms.Label labelTabCountList; + private System.Windows.Forms.Label labelTabCountUser; + private System.Windows.Forms.Label labelGraphqlEstimate; + private System.Windows.Forms.Label labelNoteForGraphqlLimits; } } diff --git a/OpenTween/Setting/Panel/GetPeriodPanel.cs b/OpenTween/Setting/Panel/GetPeriodPanel.cs index bf099b689..2be635eeb 100644 --- a/OpenTween/Setting/Panel/GetPeriodPanel.cs +++ b/OpenTween/Setting/Panel/GetPeriodPanel.cs @@ -27,13 +27,9 @@ #nullable enable using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; using System.Windows.Forms; +using OpenTween.Models; namespace OpenTween.Setting.Panel { @@ -41,6 +37,8 @@ public partial class GetPeriodPanel : SettingPanelBase { public event EventHandler? IntervalChanged; + private TabTypeAggregation.Result tabCountByType; + public GetPeriodPanel() => this.InitializeComponent(); @@ -114,6 +112,38 @@ public void SaveConfig(SettingCommon settingCommon) this.IntervalChanged?.Invoke(this, arg); } + public void UpdateTabCounts(TabInformations tabInfo) + { + var tabCountByType = TabTypeAggregation.Aggregate(tabInfo); + this.tabCountByType = tabCountByType; + + static string GetLabelText(int tabCount) + => string.Format(Properties.Resources.GetPeriodPanel_LabelTabCount, tabCount); + + this.labelTabCountHome.Text = GetLabelText(tabCountByType.HomeTabs); + this.labelTabCountMentions.Text = GetLabelText(tabCountByType.MentionsTabs); + this.labelTabCountDM.Text = GetLabelText(tabCountByType.DMTabs); + this.labelTabCountSearch.Text = GetLabelText(tabCountByType.SearchTabs); + this.labelTabCountList.Text = GetLabelText(tabCountByType.ListTabs); + this.labelTabCountUser.Text = GetLabelText(tabCountByType.UserTabs); + + this.EstimateGraphqlRequests(); + } + + private void EstimateGraphqlRequests() + { + var intervals = new GraphqlRequestEstimation.Intervals( + Home: int.Parse(this.TimelinePeriod.Text), + Search: int.Parse(this.PubSearchPeriod.Text), + List: int.Parse(this.ListsPeriod.Text), + User: int.Parse(this.UserTimelinePeriod.Text) + ); + var estimate = GraphqlRequestEstimation.CalcDailyRequestCount(intervals, this.tabCountByType); + + this.labelGraphqlEstimate.Text = + string.Format(Properties.Resources.GetPeriodPanel_LabelGraphqlEstimate, estimate); + } + private void TimelinePeriod_Validating(object sender, CancelEventArgs e) { if (!this.ValidateIntervalStr(this.TimelinePeriod.Text)) @@ -121,6 +151,8 @@ private void TimelinePeriod_Validating(object sender, CancelEventArgs e) MessageBox.Show(Properties.Resources.TimelinePeriod_ValidatingText1); e.Cancel = true; } + + this.EstimateGraphqlRequests(); } private void ReplyPeriod_Validating(object sender, CancelEventArgs e) @@ -148,6 +180,8 @@ private void PubSearchPeriod_Validating(object sender, CancelEventArgs e) MessageBox.Show(Properties.Resources.TimelinePeriod_ValidatingText1); e.Cancel = true; } + + this.EstimateGraphqlRequests(); } private void ListsPeriod_Validating(object sender, CancelEventArgs e) @@ -157,6 +191,8 @@ private void ListsPeriod_Validating(object sender, CancelEventArgs e) MessageBox.Show(Properties.Resources.TimelinePeriod_ValidatingText1); e.Cancel = true; } + + this.EstimateGraphqlRequests(); } private void UserTimeline_Validating(object sender, CancelEventArgs e) @@ -166,6 +202,8 @@ private void UserTimeline_Validating(object sender, CancelEventArgs e) MessageBox.Show(Properties.Resources.TimelinePeriod_ValidatingText1); e.Cancel = true; } + + this.EstimateGraphqlRequests(); } private bool ValidateIntervalStr(string str) diff --git a/OpenTween/Setting/Panel/GetPeriodPanel.en.resx b/OpenTween/Setting/Panel/GetPeriodPanel.en.resx index 949f84f70..851206751 100644 --- a/OpenTween/Setting/Panel/GetPeriodPanel.en.resx +++ b/OpenTween/Setting/Panel/GetPeriodPanel.en.resx @@ -18,6 +18,22 @@ Reply Fetching Interval (sec.) 149, 12 Public Search Interval (sec.) + 383, 12 + Estimated number of requests for graphql endpoint: 0 requests / 24 hours + 383, 12 + When using cookies, the upper limit is 2,111 requests / 24 hours (approx.) 358, 12 Because "Post && fetch" is enabled, the API for each post consumed. + 50, 12 + (Tabs: 0) + 50, 12 + (Tabs: 0) + 50, 12 + (Tabs: 0) + 50, 12 + (Tabs: 0) + 50, 12 + (Tabs: 0) + 50, 12 + (Tabs: 0) diff --git a/OpenTween/Setting/Panel/GetPeriodPanel.resx b/OpenTween/Setting/Panel/GetPeriodPanel.resx index 83e1391f6..aac689004 100644 --- a/OpenTween/Setting/Panel/GetPeriodPanel.resx +++ b/OpenTween/Setting/Panel/GetPeriodPanel.resx @@ -43,10 +43,42 @@ $this System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 7 + labelGraphqlEstimate + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 15 + labelNoteForGraphqlLimits + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 14 LabelPostAndGet $this System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 4 + labelTabCountDM + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 19 + labelTabCountHome + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 21 + labelTabCountList + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 17 + labelTabCountMentions + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 20 + labelTabCountSearch + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 18 + labelTabCountUser + $this + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 16 ListsPeriod $this System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -71,72 +103,128 @@ NoControl 45, 38 84, 16 - 25 + 3 投稿時取得 Disable 259, 92 65, 19 - 29 + 8 True NoControl 23, 182 144, 12 - 34 + 16 UserTimeline更新間隔(秒) True NoControl 23, 20 130, 12 - 23 + 0 タイムライン更新間隔(秒) True NoControl 23, 153 102, 12 - 32 + 13 Lists更新間隔(秒) True NoControl 23, 95 94, 12 - 28 + 7 DM更新間隔(秒) True NoControl 23, 66 123, 12 - 26 + 4 Mentions更新間隔(秒) True NoControl 23, 124 137, 12 - 30 + 10 Twitter検索更新間隔(秒) + True + NoControl + 23, 244 + 3, 15, 3, 0 + 290, 12 + 20 + graphql エンドポイントのリクエスト回数目安: 0 回 / 24 時間 + True + NoControl + 23, 266 + 3, 10, 3, 0 + 311, 12 + 21 + Cookie 使用時は 2,111 回 / 24 時間 (推定) の制限があります True NoControl - 23, 279 + 23, 217 285, 12 - 36 + 19 投稿時取得が有効のため、投稿のたびにAPIを消費します。 + True + NoControl + 337, 95 + 10, 0, 10, 0 + 54, 12 + 9 + (タブ数: 0) + True + NoControl + 337, 20 + 10, 0, 10, 0 + 54, 12 + 2 + (タブ数: 0) + True + NoControl + 337, 153 + 10, 0, 10, 0 + 54, 12 + 15 + (タブ数: 0) + True + NoControl + 337, 66 + 10, 0, 10, 0 + 54, 12 + 6 + (タブ数: 0) + True + NoControl + 337, 124 + 10, 0, 10, 0 + 54, 12 + 12 + (タブ数: 0) + True + NoControl + 337, 182 + 10, 0, 10, 0 + 54, 12 + 18 + (タブ数: 0) Disable 259, 150 65, 19 - 33 + 14 Disable 259, 121 65, 19 - 31 + 11 Disable 259, 63 65, 19 - 27 + 5 Disable 259, 17 65, 19 - 24 + 1 Disable 259, 179 65, 19 - 35 + 17 diff --git a/OpenTween/Setting/Panel/GraphqlRequestEstimation.cs b/OpenTween/Setting/Panel/GraphqlRequestEstimation.cs new file mode 100644 index 000000000..e023d6f27 --- /dev/null +++ b/OpenTween/Setting/Panel/GraphqlRequestEstimation.cs @@ -0,0 +1,50 @@ +// 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 + +namespace OpenTween.Setting.Panel +{ + public class GraphqlRequestEstimation + { + public static int CalcDailyRequestCount(Intervals intervals, TabTypeAggregation.Result tabCounts) + { + const int dayInSeconds = 1 * 24 * 60 * 60; + + static int CalcDailyCount(int interval, int tabCount) + => interval == 0 ? 0 : dayInSeconds / interval * tabCount; + + var homeDailyCount = CalcDailyCount(intervals.Home, tabCounts.HomeTabs); + var searchDailyCount = CalcDailyCount(intervals.Search, tabCounts.SearchTabs); + var listDailyCount = CalcDailyCount(intervals.List, tabCounts.ListTabs); + var userDaylyCount = CalcDailyCount(intervals.User, tabCounts.UserTabs); + + return homeDailyCount + searchDailyCount + listDailyCount + userDaylyCount; + } + + public readonly record struct Intervals( + int Home, + int Search, + int List, + int User + ); + } +} diff --git a/OpenTween/Setting/Panel/TabTypeAggregation.cs b/OpenTween/Setting/Panel/TabTypeAggregation.cs new file mode 100644 index 000000000..e338c8036 --- /dev/null +++ b/OpenTween/Setting/Panel/TabTypeAggregation.cs @@ -0,0 +1,58 @@ +// 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.Linq; +using OpenTween.Models; + +namespace OpenTween.Setting.Panel +{ + public class TabTypeAggregation + { + public static Result Aggregate(TabInformations tabInfo) + { + var countByTabTypes = tabInfo.Tabs.GroupBy(x => x.TabType) + .ToDictionary(x => x.Key, x => x.Count()); + + int GetCountByTabType(MyCommon.TabUsageType tabType) + => countByTabTypes.TryGetValue(tabType, out var count) ? count : 0; + + return new( + HomeTabs: GetCountByTabType(MyCommon.TabUsageType.Home), + MentionsTabs: GetCountByTabType(MyCommon.TabUsageType.Mentions), + DMTabs: GetCountByTabType(MyCommon.TabUsageType.DirectMessage), + SearchTabs: GetCountByTabType(MyCommon.TabUsageType.PublicSearch), + ListTabs: GetCountByTabType(MyCommon.TabUsageType.Lists), + UserTabs: GetCountByTabType(MyCommon.TabUsageType.UserTimeline) + ); + } + + public readonly record struct Result( + int HomeTabs, + int MentionsTabs, + int DMTabs, + int SearchTabs, + int ListTabs, + int UserTabs + ); + } +} From cf63a0f54640a15506207d918712f0fc3e8ba4bb Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 27 Jan 2024 14:32:06 +0900 Subject: [PATCH 13/17] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=96=93=E9=9A=94?= =?UTF-8?q?=E3=81=AE=E5=88=9D=E6=9C=9F=E8=A8=AD=E5=AE=9A=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search, List, User の各タブが 2 枚ずつ開いている状態で 2,000 回 /24h を超えない位の値に調整 --- CHANGELOG.txt | 1 + OpenTween/Setting/SettingCommon.cs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c6965e6de..f15a5e270 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,6 +5,7 @@ * NEW: Cookie使用時のFavoritesタブの更新に対応 * NEW: Cookie使用時のFav追加・削除に対応 * NEW: 設定画面の更新間隔ページに24時間分の取得回数目安の表示を追加 + * CHG: 更新間隔の初期設定を変更 * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 diff --git a/OpenTween/Setting/SettingCommon.cs b/OpenTween/Setting/SettingCommon.cs index c57f1ee2d..dbc4a942a 100644 --- a/OpenTween/Setting/SettingCommon.cs +++ b/OpenTween/Setting/SettingCommon.cs @@ -114,11 +114,11 @@ private string Decrypt(string password) public long UserId = 0; public List TabList = new(); - public int TimelinePeriod = 90; + public int TimelinePeriod = 180; public int ReplyPeriod = 180; public int DMPeriod = 600; - public int PubSearchPeriod = 180; - public int ListsPeriod = 180; + public int PubSearchPeriod = 360; + public int ListsPeriod = 360; /// /// 起動時読み込み分を既読にするか。trueなら既読として処理 @@ -230,7 +230,7 @@ private string Decrypt(string password) public int SearchCountApi = 100; public int FavoritesCountApi = 40; public int UserTimelineCountApi = 20; - public int UserTimelinePeriod = 600; + public int UserTimelinePeriod = 360; public bool OpenUserTimeline = true; public int ListCountApi = 100; public int UseImageService = 0; From c50f7aedacb9706bd9cd34c775f02fb72c37aab6 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 27 Jan 2024 14:47:33 +0900 Subject: [PATCH 14/17] =?UTF-8?q?=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=82=BF=E3=83=96=E8=A8=AD=E5=AE=9A=E3=82=92=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=82=80=E3=82=BF=E3=82=A4=E3=83=9F=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初回起動時の設定画面で更新間隔のタブ数を正しく表示するため --- OpenTween.Tests/TweenMainTest.cs | 2 ++ OpenTween/ApplicationEvents.cs | 2 ++ OpenTween/Tween.cs | 10 ---------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/OpenTween.Tests/TweenMainTest.cs b/OpenTween.Tests/TweenMainTest.cs index 573670771..526fbf025 100644 --- a/OpenTween.Tests/TweenMainTest.cs +++ b/OpenTween.Tests/TweenMainTest.cs @@ -59,6 +59,8 @@ private void UsingTweenMain(Action func) BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.SetField); field.SetValue(null, tabinfo); + tabinfo.AddDefaultTabs(); + using var tweenMain = new TweenMain(settings, tabinfo, accounts, imageCache, iconAssets, thumbnailGenerator); var context = new TestContext(settings, tabinfo); diff --git a/OpenTween/ApplicationEvents.cs b/OpenTween/ApplicationEvents.cs index 9eb8b8b40..a7bf44a9c 100644 --- a/OpenTween/ApplicationEvents.cs +++ b/OpenTween/ApplicationEvents.cs @@ -73,6 +73,8 @@ public static int Main(string[] args) ThemeManager.ApplyGlobalUIFont(settings.Local); container.CultureService.Initialize(); + container.TabInfo.LoadTabsFromSettings(settings.Tabs); + container.TabInfo.AddDefaultTabs(); Networking.Initialize(); settings.ApplySettings(); diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index a273dc6a3..3f7878b76 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -301,10 +301,6 @@ ThumbnailGenerator thumbGenerator this.NotifyIcon1.Icon = this.iconAssets.IconTray; // タスクトレイ this.TabImage.Images.Add(this.iconAssets.IconTab); // タブ見出し - // <<<<<<<<<設定関連>>>>>>>>> - // 設定読み出し - this.LoadConfig(); - // 現在の DPI と設定保存時の DPI との比を取得する var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions); @@ -765,12 +761,6 @@ private void ListTab_DrawItem(object sender, DrawItemEventArgs e) e.Graphics.DrawString(txt, e.Font, fore, e.Bounds, this.sfTab); } - private void LoadConfig() - { - this.statuses.LoadTabsFromSettings(this.settings.Tabs); - this.statuses.AddDefaultTabs(); - } - private void TimerInterval_Changed(object sender, IntervalChangedEventArgs e) { this.RefreshTimelineScheduler(); From ac3b40d18a1952464a1920c7e9d8bc496d5eecf2 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 27 Jan 2024 15:14:17 +0900 Subject: [PATCH 15/17] =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=B9=E3=83=90=E3=83=BC=E3=81=AB=E5=90=84=E3=82=BF=E3=83=96?= =?UTF-8?q?=E3=81=AE=E6=9B=B4=E6=96=B0=E5=9B=9E=E6=95=B0=EF=BC=88=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E6=99=82=E3=81=8B=E3=82=89=E3=81=AE=E5=9B=9E=E6=95=B0?= =?UTF-8?q?=EF=BC=89=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.txt | 1 + OpenTween/Models/TabModel.cs | 7 +++++++ OpenTween/Properties/Resources.Designer.cs | 9 +++++++++ OpenTween/Properties/Resources.en.resx | 1 + OpenTween/Properties/Resources.resx | 1 + OpenTween/Tween.cs | 3 +++ 6 files changed, 22 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f15a5e270..8a5ce1435 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,7 @@ * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) * NEW: Cookie使用時のFavoritesタブの更新に対応 * NEW: Cookie使用時のFav追加・削除に対応 + * NEW: ステータスバーに各タブの更新回数(起動時からの回数)の表示を追加 * NEW: 設定画面の更新間隔ページに24時間分の取得回数目安の表示を追加 * CHG: 更新間隔の初期設定を変更 * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 diff --git a/OpenTween/Models/TabModel.cs b/OpenTween/Models/TabModel.cs index 5989444e0..e5dc4e1d8 100644 --- a/OpenTween/Models/TabModel.cs +++ b/OpenTween/Models/TabModel.cs @@ -113,11 +113,15 @@ public PostClass? AnchorPost set => this.AnchorStatusId = value?.StatusId; } + public int UpdateCount + => this.updateCount; + private IndexedSortedSet ids = new(); private ConcurrentQueue addQueue = new(); private readonly ConcurrentQueue removeQueue = new(); private SortedSet unreadIds = new(); private List selectedStatusIds = new(); + private int updateCount = 0; private readonly object lockObj = new(); @@ -450,5 +454,8 @@ public IEnumerable SearchPostsAll(Func stringComparer, int st } } } + + public void IncrementUpdateCount() + => Interlocked.Increment(ref this.updateCount); } } diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index e89cb6dfb..e6bfc1a04 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -2589,6 +2589,15 @@ internal static string SetStatusLabelText3 { } } + /// + /// [更新: {0:#,0}] に類似しているローカライズされた文字列を検索します。 + /// + internal static string SetStatusLabelText4 { + get { + return ResourceManager.GetString("SetStatusLabelText4", resourceCulture); + } + } + /// /// 「認証開始」ボタンを押すとブラウザが開きます。「連携アプリを認証」し、表示されたPINを画面上部に入力後、「Finish」ボタンを押してください。認証せずに終了してもよろしいですか? に類似しているローカライズされた文字列を検索します。 /// diff --git a/OpenTween/Properties/Resources.en.resx b/OpenTween/Properties/Resources.en.resx index a0fd66aac..62d612824 100644 --- a/OpenTween/Properties/Resources.en.resx +++ b/OpenTween/Properties/Resources.en.resx @@ -257,6 +257,7 @@ Available service: {1} [Tab: {0}/{1} All: {2}/{3} (Reply: {4})] [Spd: Pst {5}/ Fav {6}/ TL {7}] [Interval: -] ] + [Updates: {0:#,0}] Press [Start Authentication] button and [Authorize App]. Key in PIN then press [Finish] button. Will you exit application without validating your account? Failed to write settings to {0}. diff --git a/OpenTween/Properties/Resources.resx b/OpenTween/Properties/Resources.resx index 3e3c8656e..f9cab82d6 100644 --- a/OpenTween/Properties/Resources.resx +++ b/OpenTween/Properties/Resources.resx @@ -289,6 +289,7 @@ [タブ: {0}/{1} 全体: {2}/{3} (返信: {4})] [時速: 投 {5}/ ☆ {6}/ 流 {7}] [間隔: -] ] + [更新: {0:#,0}] 「認証開始」ボタンを押すとブラウザが開きます。「連携アプリを認証」し、表示されたPINを画面上部に入力後、「Finish」ボタンを押してください。認証せずに終了してもよろしいですか? {0} での設定の書き込みに失敗しました。 diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 3f7878b76..89e3dd61e 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -1303,6 +1303,7 @@ private async Task RefreshTabAsync(TabModel tab, bool backward) { this.RefreshTasktrayIcon(); await Task.Run(() => tab.RefreshAsync(this.tw, backward, this.initial, this.workerProgress)); + tab.IncrementUpdateCount(); } catch (WebApiException ex) { @@ -6938,6 +6939,8 @@ private string GetStatusLabelText() { slbl.Append(this.settings.Common.TimelinePeriod + Properties.Resources.SetStatusLabelText3); } + slbl.Append(" "); + slbl.AppendFormat(Properties.Resources.SetStatusLabelText4, this.CurrentTab.UpdateCount); return slbl.ToString(); } From 1102b92b23d5469bd9be32098f2cc711f15c0c85 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 27 Jan 2024 15:32:27 +0900 Subject: [PATCH 16/17] =?UTF-8?q?Cookie=E4=BD=BF=E7=94=A8=E6=99=82?= =?UTF-8?q?=E3=81=ABFavorites=E3=82=BF=E3=83=96=E3=81=AE=E3=83=AC=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=83=AA=E3=83=9F=E3=83=83=E3=83=88=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=8C=E6=9B=B4=E6=96=B0=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84?= =?UTF-8?q?=E4=B8=8D=E5=85=B7=E5=90=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: 18aca2fb ("Likesを使用したFavoritesタブの更新に対応") --- OpenTween/Tween.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 89e3dd61e..e0e5a14f8 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -6985,7 +6985,8 @@ private void SetApiStatusLabel(string? endpointName = null) authByCookie ? HomeLatestTimelineRequest.EndpointName : "/statuses/home_timeline", MyCommon.TabUsageType.Mentions => authByCookie ? NotificationsMentionsRequest.EndpointName : "/statuses/mentions_timeline", - MyCommon.TabUsageType.Favorites => "/favorites/list", + MyCommon.TabUsageType.Favorites => + authByCookie ? LikesRequest.EndpointName : "/favorites/list", MyCommon.TabUsageType.DirectMessage => "/direct_messages/events/list", MyCommon.TabUsageType.UserTimeline => authByCookie ? UserTweetsAndRepliesRequest.EndpointName : "/statuses/user_timeline", From db3cde39ab9eaefd4ded96e7905f796ac5d80f6e Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 27 Jan 2024 15:43:34 +0900 Subject: [PATCH 17/17] =?UTF-8?q?OpenTween=20v3.13.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 | 31 +++++++++------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8a5ce1435..a0e9f8f97 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,6 @@ 更新履歴 -==== Unreleased +==== Ver 3.13.0(2024/01/27) * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) * NEW: Cookie使用時のFavoritesタブの更新に対応 * NEW: Cookie使用時のFav追加・削除に対応 diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index 6d00dfaa3..416189377 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.12.0.1")] +[assembly: AssemblyVersion("3.13.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 e6bfc1a04..c03876d09 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -580,25 +580,18 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// - ///==== Unreleased - /// - ///==== Ver 3.12.0(2024/01/20) - /// * NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応 - /// - ///==== Ver 3.11.0(2024/01/07) - /// * NEW: Cookie使用時の関連発言表示に対応 - /// * FIX: APIリクエストのタイムアウト時に接続が切断されない場合がある不具合を修正 - /// * FIX: 存在しないユーザーのプロフィールを取得しようとした場合のエラーが適切に処理されない不具合を修正 - /// - ///==== Ver 3.10.1(2023/12/23) - /// * FIX: OAuth 1.0a によるAPIアクセスに失敗する不具合を修正 - /// - ///==== Ver 3.10.0(2023/12/16) - /// * NEW: graphqlエンドポイント経由で取得した引用ツイートの表示に対応 - /// * FIX: APIリクエストがタイムアウトした場合のキャンセル処理を改善 - /// - ///==== Ver 3.9.0(2023/12/03) - /// * NEW: graphqlエンドポイントに対するレートリミ [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + ///==== Ver 3.13.0(2024/01/27) + /// * NEW: Cookie使用時のReplyタブの更新に対応(/statuses/mentions_timeline.json 廃止に伴う対応) + /// * NEW: Cookie使用時のFavoritesタブの更新に対応 + /// * NEW: Cookie使用時のFav追加・削除に対応 + /// * NEW: ステータスバーに各タブの更新回数(起動時からの回数)の表示を追加 + /// * NEW: 設定画面の更新間隔ページに24時間分の取得回数目安の表示を追加 + /// * CHG: 更新間隔の初期設定を変更 + /// * FIX: Cookie使用時にツイート検索の言語指定が効かない不具合を修正 + /// * FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正 + /// * FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正 + /// * FIX: 取得したツイートの中身が空だった場合のエラー処理を改善 + /// * FIX: タイムラインの取得結果にレートリミットに関するメッセージが含まれていた [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get {