--- /dev/null
+// 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();
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
+using OpenTween.Api;
using OpenTween.Connection;
using Xunit;
};
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);
+ }
}
}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+}
--- /dev/null
+// 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; } = "";
+ }
+}
--- /dev/null
+// 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; }
+ }
+}
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
{
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()
{
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();
public AccountKey UniqueKey { get; }
- public MisskeyClient Client { get; private set; } = new();
+ public MisskeyClient Client { get; private set; }
ISocialProtocolClient ISocialAccount.Client
=> this.Client;
private MisskeyApiConnection? connection;
public MisskeyAccount(AccountKey accountKey)
- => this.UniqueKey = accountKey;
+ {
+ this.UniqueKey = accountKey;
+ this.Client = new(this);
+ }
public void Initialize(UserAccount accountSettings, SettingCommon settingCommon)
{
using System;
using System.Collections.Generic;
using OpenTween.Api;
+using OpenTween.Api.Misskey;
using OpenTween.Models;
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;
+ }
}
}
#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();