OSDN Git Service

Cookie使用時に account/verify_credentials.json ではなくViewerを使用する
authorKimura Youichi <kim.upsilon@bucyou.net>
Fri, 17 May 2024 14:59:30 +0000 (23:59 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Fri, 17 May 2024 15:24:44 +0000 (00:24 +0900)
OpenTween.Tests/Api/GraphQL/ViewerRequestTest.cs [new file with mode: 0644]
OpenTween.Tests/Resources/Responses/Viewer.json [new file with mode: 0644]
OpenTween/Api/GraphQL/ViewerRequest.cs [new file with mode: 0644]
OpenTween/Twitter.cs

diff --git a/OpenTween.Tests/Api/GraphQL/ViewerRequestTest.cs b/OpenTween.Tests/Api/GraphQL/ViewerRequestTest.cs
new file mode 100644 (file)
index 0000000..e06210e
--- /dev/null
@@ -0,0 +1,59 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System.Threading.Tasks;
+using Moq;
+using OpenTween.Connection;
+using Xunit;
+
+namespace OpenTween.Api.GraphQL
+{
+    public class ViewerRequestTest
+    {
+        [Fact]
+        public async Task Send_Test()
+        {
+            using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/Viewer.json");
+
+            var mock = new Mock<IApiConnection>();
+            mock.Setup(x =>
+                    x.SendAsync(It.IsAny<IHttpRequest>())
+                )
+                .Callback<IHttpRequest>(x =>
+                {
+                    var request = Assert.IsType<GetRequest>(x);
+                    Assert.Equal(new("https://api.twitter.com/graphql/-876iyxD1O_0X0BqeykjZA/Viewer"), request.RequestUri);
+                    var query = request.Query!;
+                    Assert.Contains("variables", query);
+                    Assert.Contains("features", query);
+                    Assert.Contains("fieldToggles", query);
+                    Assert.Equal("Viewer", request.EndpointName);
+                })
+                .ReturnsAsync(apiResponse);
+
+            var request = new ViewerRequest();
+            var user = await request.Send(mock.Object);
+            Assert.Equal("514241801", user.ToTwitterUser().IdStr);
+
+            mock.VerifyAll();
+        }
+    }
+}
diff --git a/OpenTween.Tests/Resources/Responses/Viewer.json b/OpenTween.Tests/Resources/Responses/Viewer.json
new file mode 100644 (file)
index 0000000..0b7c204
--- /dev/null
@@ -0,0 +1,103 @@
+{
+  "data": {
+    "viewer": {
+      "has_community_memberships": false,
+      "create_community_action_result": {
+        "__typename": "CommunityCreateActionUnavailable",
+        "reason": "NotVerified",
+        "message": "Subscribe to Premium to be able to create a community."
+      },
+      "user_features": [
+        {
+          "feature": "mediatool_studio_library",
+          "enabled": false
+        }
+      ],
+      "user_results": {
+        "result": {
+          "__typename": "User",
+          "id": "VXNlcjo1MTQyNDE4MDE=",
+          "rest_id": "514241801",
+          "affiliates_highlighted_label": {},
+          "has_graduated_access": false,
+          "is_blue_verified": false,
+          "profile_image_shape": "Circle",
+          "legacy": {
+            "can_dm": true,
+            "can_media_tag": false,
+            "created_at": "Sun Mar 04 11:33:45 +0000 2012",
+            "default_profile": false,
+            "default_profile_image": false,
+            "description": "Windows 用 Twitter クライアント OpenTween のアカウントです。",
+            "entities": {
+              "description": {
+                "urls": []
+              },
+              "url": {
+                "urls": [
+                  {
+                    "display_url": "opentween.org",
+                    "expanded_url": "https://www.opentween.org/",
+                    "url": "https://t.co/An6OJeC28u",
+                    "indices": [
+                      0,
+                      23
+                    ]
+                  }
+                ]
+              }
+            },
+            "fast_followers_count": 0,
+            "favourites_count": 0,
+            "followers_count": 311,
+            "friends_count": 1,
+            "has_custom_timelines": false,
+            "is_translator": false,
+            "listed_count": 14,
+            "location": "",
+            "media_count": 0,
+            "name": "OpenTween",
+            "needs_phone_verification": false,
+            "normal_followers_count": 311,
+            "pinned_tweet_ids_str": [
+              "1617124615347908609"
+            ],
+            "possibly_sensitive": false,
+            "profile_image_url_https": "https://pbs.twimg.com/profile_images/661168792488153088/-UAFci6G_normal.png",
+            "profile_interstitial_type": "",
+            "screen_name": "opentween",
+            "statuses_count": 31,
+            "translator_type": "none",
+            "url": "https://t.co/An6OJeC28u",
+            "verified": false,
+            "want_retweets": false,
+            "withheld_in_countries": []
+          },
+          "tipjar_settings": {},
+          "legacy_extended_profile": {},
+          "is_profile_translatable": true,
+          "super_follows_application_status": "NotStarted",
+          "creator_subscriptions_count": 0
+        }
+      },
+      "educationFlags": [
+        {
+          "flag": "DMC16JPCompliancePrompt",
+          "timestamp": 1642842546424
+        },
+        {
+          "flag": "PinnedConversationsEducation",
+          "timestamp": 1643731261814
+        },
+        {
+          "flag": "NewUserPromptEducation",
+          "timestamp": 1700845271740
+        }
+      ],
+      "is_tfe_restricted_session": false,
+      "is_active_creator": false,
+      "super_followers_count": 0
+    },
+    "is_super_follow_subscriber": false
+  }
+}
diff --git a/OpenTween/Api/GraphQL/ViewerRequest.cs b/OpenTween/Api/GraphQL/ViewerRequest.cs
new file mode 100644 (file)
index 0000000..6eff39b
--- /dev/null
@@ -0,0 +1,85 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Xml.XPath;
+using OpenTween.Connection;
+
+namespace OpenTween.Api.GraphQL
+{
+    public class ViewerRequest
+    {
+        public static readonly string EndpointName = "Viewer";
+
+        private static readonly Uri EndpointUri = new("https://api.twitter.com/graphql/-876iyxD1O_0X0BqeykjZA/Viewer");
+
+        public Dictionary<string, string> CreateParameters()
+        {
+            return new()
+            {
+                ["variables"] = """
+                    {"withCommunitiesMemberships":true}
+                    """,
+                ["features"] = """
+                    {"rweb_tipjar_consumption_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true}
+                    """,
+                ["fieldToggles"] = """
+                    {"isDelegate":false,"withAuxiliaryUserLabels":false}
+                    """,
+            };
+        }
+
+        public async Task<TwitterGraphqlUser> 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);
+
+            ErrorResponse.ThrowIfError(rootElm);
+
+            try
+            {
+                var userElm = rootElm.XPathSelectElement("/data/viewer/user_results/result")
+                    ?? throw new WebApiException("Parse error");
+
+                return new(userElm);
+            }
+            catch (WebApiException ex)
+            {
+                ex.ResponseText = JsonUtils.JsonXmlToString(rootElm);
+                throw;
+            }
+        }
+    }
+}
index f48ff06..19995ed 100644 (file)
@@ -215,8 +215,21 @@ namespace OpenTween
 
         public async Task VerifyCredentialsAsync()
         {
-            var user = await this.Api.AccountVerifyCredentials()
-                .ConfigureAwait(false);
+            TwitterUser user;
+
+            if (this.Api.AuthType == APIAuthType.TwitterComCookie)
+            {
+                var request = new ViewerRequest();
+                var graphqlUser = await request.Send(this.Api.Connection)
+                    .ConfigureAwait(false);
+
+                user = graphqlUser.ToTwitterUser();
+            }
+            else
+            {
+                user = await this.Api.AccountVerifyCredentials()
+                    .ConfigureAwait(false);
+            }
 
             this.AccountState.UpdateFromUser(user);
         }