OSDN Git Service

MisskeyClient.VerifyCredentialsメソッドを実装
authorKimura Youichi <kim.upsilon@bucyou.net>
Sun, 26 May 2024 00:18:24 +0000 (09:18 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Mon, 10 Jun 2024 15:20:42 +0000 (00:20 +0900)
OpenTween.Tests/Api/Misskey/MeRequestTest.cs [new file with mode: 0644]
OpenTween.Tests/SocialProtocol/Misskey/MisskeyAccountStateTest.cs [new file with mode: 0644]
OpenTween.Tests/TestUtils.cs
OpenTween/Api/Misskey/MeRequest.cs [new file with mode: 0644]
OpenTween/Api/Misskey/MisskeyApiException.cs [new file with mode: 0644]
OpenTween/Api/Misskey/MisskeyError.cs [new file with mode: 0644]
OpenTween/Api/Misskey/MisskeyUser.cs [new file with mode: 0644]
OpenTween/Connection/MisskeyApiConnection.cs
OpenTween/SocialProtocol/Misskey/MisskeyAccount.cs
OpenTween/SocialProtocol/Misskey/MisskeyAccountState.cs
OpenTween/SocialProtocol/Misskey/MisskeyClient.cs

diff --git a/OpenTween.Tests/Api/Misskey/MeRequestTest.cs b/OpenTween.Tests/Api/Misskey/MeRequestTest.cs
new file mode 100644 (file)
index 0000000..c8f6418
--- /dev/null
@@ -0,0 +1,55 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Threading.Tasks;
+using Moq;
+using OpenTween.Connection;
+using Xunit;
+
+namespace OpenTween.Api.Misskey
+{
+    public class MeRequestTest
+    {
+        [Fact]
+        public async Task Send_Test()
+        {
+            var response = TestUtils.CreateApiResponse(new MisskeyUser());
+
+            var mock = new Mock<IApiConnection>();
+            mock.Setup(x =>
+                    x.SendAsync(It.IsAny<IHttpRequest>())
+                )
+                .Callback<IHttpRequest>(x =>
+                {
+                    var request = Assert.IsType<PostRequest>(x);
+                    Assert.Equal(new("i", UriKind.Relative), request.RequestUri);
+                    Assert.Null(request.Query);
+                })
+                .ReturnsAsync(response);
+
+            var request = new MeRequest();
+            await request.Send(mock.Object);
+
+            mock.VerifyAll();
+        }
+    }
+}
diff --git a/OpenTween.Tests/SocialProtocol/Misskey/MisskeyAccountStateTest.cs b/OpenTween.Tests/SocialProtocol/Misskey/MisskeyAccountStateTest.cs
new file mode 100644 (file)
index 0000000..1a9b60f
--- /dev/null
@@ -0,0 +1,53 @@
+// 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 Xunit;
+
+namespace OpenTween.SocialProtocol.Misskey
+{
+    public class MisskeyAccountStateTest
+    {
+        [Fact]
+        public void UpdateFromUser_Test()
+        {
+            var accountState = new MisskeyAccountState(new("https://example.com/"), new("abcdef"), "hoge");
+
+            Assert.Equal("hoge", accountState.UserName);
+            Assert.Null(accountState.StatusesCount);
+            Assert.Null(accountState.FollowersCount);
+            Assert.Null(accountState.FriendsCount);
+
+            accountState.UpdateFromUser(new()
+            {
+                Id = "abcdef",
+                Username = "foo",
+                NotesCount = 100,
+                FollowersCount = 30,
+                FollowingCount = 20,
+            });
+
+            Assert.Equal("foo", accountState.UserName);
+            Assert.Equal(100, accountState.StatusesCount);
+            Assert.Equal(30, accountState.FollowersCount);
+            Assert.Equal(20, accountState.FriendsCount);
+        }
+    }
+}
index 9d5848a..7b8786e 100644 (file)
@@ -29,6 +29,7 @@ using System.Net.Http;
 using System.Reflection;
 using System.Threading.Tasks;
 using System.Windows.Forms;
+using OpenTween.Api;
 using OpenTween.Connection;
 using Xunit;
 
@@ -168,6 +169,16 @@ namespace OpenTween
             };
             return new ApiResponse(responseMessage);
         }
+
+        public static ApiResponse CreateApiResponse<T>(T value)
+        {
+            var responseMessage = new HttpResponseMessage
+            {
+                StatusCode = HttpStatusCode.OK,
+                Content = new StringContent(JsonUtils.SerializeJsonByDataContract(value)),
+            };
+            return new ApiResponse(responseMessage);
+        }
     }
 }
 
diff --git a/OpenTween/Api/Misskey/MeRequest.cs b/OpenTween/Api/Misskey/MeRequest.cs
new file mode 100644 (file)
index 0000000..94b52b8
--- /dev/null
@@ -0,0 +1,46 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Threading.Tasks;
+using OpenTween.Connection;
+
+namespace OpenTween.Api.Misskey
+{
+    public class MeRequest
+    {
+        public async Task<MisskeyUser> Send(IApiConnection apiConnection)
+        {
+            var request = new PostRequest
+            {
+                RequestUri = new("i", UriKind.Relative),
+            };
+
+            using var response = await apiConnection.SendAsync(request)
+                .ConfigureAwait(false);
+
+            return await response.ReadAsJson<MisskeyUser>()
+                .ConfigureAwait(false);
+        }
+    }
+}
diff --git a/OpenTween/Api/Misskey/MisskeyApiException.cs b/OpenTween/Api/Misskey/MisskeyApiException.cs
new file mode 100644 (file)
index 0000000..db73bda
--- /dev/null
@@ -0,0 +1,93 @@
+// 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.Net;
+using System.Net.Http;
+using System.Runtime.Serialization;
+
+namespace OpenTween.Api.Misskey
+{
+    public class MisskeyApiException : WebApiException
+    {
+        public HttpStatusCode StatusCode { get; }
+
+        public MisskeyError? ErrorResponse { get; }
+
+        public MisskeyApiException()
+        {
+        }
+
+        public MisskeyApiException(string message)
+            : base(message)
+        {
+        }
+
+        public MisskeyApiException(string message, Exception innerException)
+            : base(message, innerException)
+        {
+        }
+
+        public MisskeyApiException(HttpStatusCode statusCode, string responseText)
+            : base(statusCode.ToString(), responseText)
+        {
+            this.StatusCode = statusCode;
+        }
+
+        public MisskeyApiException(HttpStatusCode statusCode, MisskeyError error, string responseText)
+            : base(error.Error.Message, responseText)
+        {
+            this.StatusCode = statusCode;
+            this.ErrorResponse = error;
+        }
+
+        protected MisskeyApiException(SerializationInfo info, StreamingContext context)
+            : base(info, context)
+        {
+            this.StatusCode = (HttpStatusCode)info.GetValue("StatusCode", typeof(HttpStatusCode));
+            this.ErrorResponse = (MisskeyError?)info.GetValue("ErrorResponse", typeof(MisskeyError));
+        }
+
+        private MisskeyApiException(string message, string responseText, Exception innerException)
+            : base(message, responseText, innerException)
+        {
+        }
+
+        public override void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            base.GetObjectData(info, context);
+
+            info.AddValue("StatusCode", this.StatusCode);
+            info.AddValue("ErrorResponse", this.ErrorResponse);
+        }
+
+        public static MisskeyApiException CreateFromException(HttpRequestException ex)
+            => new(ex.InnerException?.Message ?? ex.Message, ex);
+
+        public static MisskeyApiException CreateFromException(OperationCanceledException ex)
+            => new("Timeout", ex);
+
+        public static MisskeyApiException CreateFromException(SerializationException ex, string responseText)
+            => new("Invalid JSON", responseText, ex);
+    }
+}
diff --git a/OpenTween/Api/Misskey/MisskeyError.cs b/OpenTween/Api/Misskey/MisskeyError.cs
new file mode 100644 (file)
index 0000000..253be45
--- /dev/null
@@ -0,0 +1,51 @@
+// 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.Runtime.Serialization;
+
+namespace OpenTween.Api.Misskey
+{
+    [DataContract]
+    public class MisskeyError
+    {
+        [DataMember(Name = "error")]
+        public MisskeyErrorItem Error { get; set; } = new();
+
+        /// <exception cref="SerializationException"/>
+        public static MisskeyError ParseJson(string json)
+            => MyCommon.CreateDataFromJson<MisskeyError>(json);
+    }
+
+    [DataContract]
+    public class MisskeyErrorItem
+    {
+        [DataMember(Name = "code")]
+        public string Code { get; set; } = "";
+
+        [DataMember(Name = "message")]
+        public string Message { get; set; } = "";
+
+        [DataMember(Name = "id")]
+        public string Id { get; set; } = "";
+    }
+}
diff --git a/OpenTween/Api/Misskey/MisskeyUser.cs b/OpenTween/Api/Misskey/MisskeyUser.cs
new file mode 100644 (file)
index 0000000..723809a
--- /dev/null
@@ -0,0 +1,49 @@
+// 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.Runtime.Serialization;
+
+namespace OpenTween.Api.Misskey
+{
+    [DataContract]
+    public class MisskeyUser
+    {
+        [DataMember(Name = "id")]
+        public string Id { get; set; } = "";
+
+        [DataMember(Name = "name")]
+        public string Name { get; set; } = "";
+
+        [DataMember(Name = "username")]
+        public string Username { get; set; } = "";
+
+        [DataMember(Name = "followersCount")]
+        public int FollowersCount { get; set; }
+
+        [DataMember(Name = "followingCount")]
+        public int FollowingCount { get; set; }
+
+        [DataMember(Name = "notesCount")]
+        public int NotesCount { get; set; }
+    }
+}
index a10eb1c..366e751 100644 (file)
@@ -25,8 +25,10 @@ using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Net.Cache;
 using System.Net.Http;
+using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using OpenTween.Api.Misskey;
 
 namespace OpenTween.Connection
 {
@@ -57,8 +59,42 @@ namespace OpenTween.Connection
             this.Http.Timeout = Timeout.InfiniteTimeSpan;
         }
 
-        public Task<ApiResponse> SendAsync(IHttpRequest request)
-            => throw new WebApiException("Not implemented");
+        public async Task<ApiResponse> SendAsync(IHttpRequest request)
+        {
+            using var requestMessage = request.CreateMessage(this.apiBaseUri);
+
+            if (!MyCommon.IsNullOrEmpty(this.accessToken))
+                requestMessage.Headers.Authorization = new("Bearer", this.accessToken);
+
+            HttpResponseMessage? responseMessage = null;
+            try
+            {
+                responseMessage = await TwitterApiConnection.HandleTimeout(
+                    (token) => this.Http.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, token),
+                    request.Timeout
+                );
+
+                await CheckStatusCode(responseMessage)
+                    .ConfigureAwait(false);
+
+                var response = new ApiResponse(responseMessage);
+                responseMessage = null; // responseMessage は ApiResponse で使用するため破棄されないようにする
+
+                return response;
+            }
+            catch (HttpRequestException ex)
+            {
+                throw MisskeyApiException.CreateFromException(ex);
+            }
+            catch (OperationCanceledException ex)
+            {
+                throw MisskeyApiException.CreateFromException(ex);
+            }
+            finally
+            {
+                responseMessage?.Dispose();
+            }
+        }
 
         public void Dispose()
         {
@@ -74,6 +110,35 @@ namespace OpenTween.Connection
         private void Networking_WebProxyChanged(object sender, EventArgs e)
             => this.InitializeHttpClients();
 
+        private static async Task CheckStatusCode(HttpResponseMessage response)
+        {
+            var statusCode = response.StatusCode;
+
+            if ((int)statusCode >= 200 && (int)statusCode <= 299)
+                return;
+
+            string responseText;
+            using (var content = response.Content)
+            {
+                responseText = await content.ReadAsStringAsync()
+                    .ConfigureAwait(false);
+            }
+
+            if (string.IsNullOrWhiteSpace(responseText))
+                throw new MisskeyApiException(statusCode, responseText);
+
+            try
+            {
+                var error = MisskeyError.ParseJson(responseText);
+
+                throw new MisskeyApiException(statusCode, error, responseText);
+            }
+            catch (SerializationException)
+            {
+                throw new MisskeyApiException(statusCode, responseText);
+            }
+        }
+
         private static HttpClient InitializeHttpClient()
         {
             var builder = Networking.CreateHttpClientBuilder();
index bdcca24..a78ae08 100644 (file)
@@ -35,7 +35,7 @@ namespace OpenTween.SocialProtocol.Misskey
 
         public AccountKey UniqueKey { get; }
 
-        public MisskeyClient Client { get; private set; } = new();
+        public MisskeyClient Client { get; private set; }
 
         ISocialProtocolClient ISocialAccount.Client
             => this.Client;
@@ -62,7 +62,10 @@ namespace OpenTween.SocialProtocol.Misskey
         private MisskeyApiConnection? connection;
 
         public MisskeyAccount(AccountKey accountKey)
-            => this.UniqueKey = accountKey;
+        {
+            this.UniqueKey = accountKey;
+            this.Client = new(this);
+        }
 
         public void Initialize(UserAccount accountSettings, SettingCommon settingCommon)
         {
index 63df89c..2ea7857 100644 (file)
@@ -24,6 +24,7 @@
 using System;
 using System.Collections.Generic;
 using OpenTween.Api;
+using OpenTween.Api.Misskey;
 using OpenTween.Models;
 
 namespace OpenTween.SocialProtocol.Misskey
@@ -64,5 +65,15 @@ namespace OpenTween.SocialProtocol.Misskey
             this.UserId = userId;
             this.UserName = userName;
         }
+
+        /// <summary>ユーザー情報を更新します</summary>
+        public void UpdateFromUser(MisskeyUser self)
+        {
+            this.UserId = new(self.Id);
+            this.UserName = self.Username;
+            this.FollowersCount = self.FollowersCount;
+            this.FriendsCount = self.FollowingCount;
+            this.StatusesCount = self.NotesCount;
+        }
     }
 }
index ccb4d68..1a89ca4 100644 (file)
 #nullable enable
 
 using System.Threading.Tasks;
+using OpenTween.Api.Misskey;
 using OpenTween.Models;
 
 namespace OpenTween.SocialProtocol.Misskey
 {
     public class MisskeyClient : ISocialProtocolClient
     {
-        public Task<UserInfo> VerifyCredentials()
-            => throw this.CreateException();
+        private readonly MisskeyAccount account;
+
+        public MisskeyClient(MisskeyAccount account)
+            => this.account = account;
+
+        public async Task<UserInfo> VerifyCredentials()
+        {
+            var request = new MeRequest();
+            var user = await request.Send(this.account.Connection)
+                .ConfigureAwait(false);
+
+            this.account.AccountState.UpdateFromUser(user);
+
+            return new();
+        }
 
         public Task<PostClass> GetPostById(PostId postId, bool firstLoad)
             => throw this.CreateException();