OSDN Git Service

MisskeyClient.GetPostByIdメソッドを実装
authorKimura Youichi <kim.upsilon@bucyou.net>
Mon, 27 May 2024 22:55:12 +0000 (07:55 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Mon, 10 Jun 2024 15:20:49 +0000 (00:20 +0900)
OpenTween.Tests/Api/Misskey/NoteShowRequestTest.cs [new file with mode: 0644]
OpenTween.Tests/DateTimeUtcTest.cs
OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs [new file with mode: 0644]
OpenTween/Api/Misskey/MisskeyNote.cs [new file with mode: 0644]
OpenTween/Api/Misskey/MisskeyUserLite.cs [new file with mode: 0644]
OpenTween/Api/Misskey/NoteShowRequest.cs [new file with mode: 0644]
OpenTween/DateTimeUtc.cs
OpenTween/SocialProtocol/Misskey/MisskeyAccount.cs
OpenTween/SocialProtocol/Misskey/MisskeyClient.cs
OpenTween/SocialProtocol/Misskey/MisskeyNoteId.cs [new file with mode: 0644]
OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs [new file with mode: 0644]

diff --git a/OpenTween.Tests/Api/Misskey/NoteShowRequestTest.cs b/OpenTween.Tests/Api/Misskey/NoteShowRequestTest.cs
new file mode 100644 (file)
index 0000000..c984ca5
--- /dev/null
@@ -0,0 +1,61 @@
+// 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 NoteShowRequestTest
+    {
+        [Fact]
+        public async Task Send_Test()
+        {
+            var response = TestUtils.CreateApiResponse(new MisskeyNote());
+
+            var mock = new Mock<IApiConnection>();
+            mock.Setup(x =>
+                    x.SendAsync(It.IsAny<IHttpRequest>())
+                )
+                .Callback<IHttpRequest>(x =>
+                {
+                    var request = Assert.IsType<PostJsonRequest>(x);
+                    Assert.Equal(new("notes/show", UriKind.Relative), request.RequestUri);
+                    Assert.Equal(
+                        """{"noteId":"abc123"}""",
+                        request.JsonString
+                    );
+                })
+                .ReturnsAsync(response);
+
+            var request = new NoteShowRequest
+            {
+                NoteId = new("abc123"),
+            };
+            await request.Send(mock.Object);
+
+            mock.VerifyAll();
+        }
+    }
+}
index 1126c32..e54b63b 100644 (file)
@@ -330,5 +330,17 @@ namespace OpenTween
             Assert.Equal(expectedParsed, parsed);
             Assert.Equal(expectedResult, result);
         }
+
+        public static readonly TheoryData<string, DateTimeUtc> ParseISOFixtures = new()
+        {
+            { "2024-01-02T12:34:56.789Z", new(2024, 1, 2, 12, 34, 56, 789) },
+            { "2024-01-02T12:34:56.789+09:00", new(2024, 1, 2, 3, 34, 56, 789) },
+            { "2024-01-02", new(2024, 1, 2, 0, 0, 0, 0) },
+        };
+
+        [Theory]
+        [MemberData(nameof(ParseISOFixtures))]
+        public void ParseISO_Test(string input, DateTimeUtc expected)
+            => Assert.Equal(expected, DateTimeUtc.ParseISO(input));
     }
 }
diff --git a/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs b/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs
new file mode 100644 (file)
index 0000000..b3529b2
--- /dev/null
@@ -0,0 +1,227 @@
+// 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 OpenTween.Api.Misskey;
+using Xunit;
+
+namespace OpenTween.SocialProtocol.Misskey
+{
+    public class MisskeyPostFactoryTest
+    {
+        [Fact]
+        public void CreateFromNote_LocalNoteTest()
+        {
+            var note = new MisskeyNote
+            {
+                Id = "abcdef",
+                CreatedAt = "2024-01-01T01:02:03.456Z",
+                Text = "foo",
+                User = new()
+                {
+                    Id = "ghijkl",
+                    Username = "bar",
+                },
+                Visibility = "public",
+            };
+            var factory = new MisskeyPostFactory(new());
+            var accountState = new MisskeyAccountState(new("https://example.com/"), new("aaaa"), "hoge");
+
+            var post = factory.CreateFromNote(note, accountState, firstLoad: false);
+            Assert.Equal(new MisskeyNoteId("abcdef"), post.StatusId);
+            Assert.Equal(new(2024, 1, 1, 1, 2, 3, 456), post.CreatedAtForSorting);
+            Assert.Equal(new(2024, 1, 1, 1, 2, 3, 456), post.CreatedAt);
+            Assert.Equal(new("https://example.com/notes/abcdef"), post.PostUri);
+            Assert.Equal("foo", post.Text);
+            Assert.Equal("foo", post.TextFromApi);
+            Assert.Equal("foo", post.AccessibleText);
+            Assert.Empty(post.QuoteStatusIds);
+            Assert.False(post.IsFav);
+            Assert.False(post.IsReply);
+            Assert.Null(post.InReplyToStatusId);
+            Assert.Null(post.InReplyToUser);
+            Assert.Null(post.InReplyToUserId);
+            Assert.False(post.IsProtect);
+            Assert.Equal(new MisskeyUserId("ghijkl"), post.UserId);
+            Assert.Equal("bar", post.ScreenName);
+            Assert.Equal("bar", post.Nickname);
+            Assert.Equal("", post.ImageUrl);
+            Assert.Null(post.RetweetedId);
+            Assert.Null(post.RetweetedBy);
+            Assert.Null(post.RetweetedByUserId);
+            Assert.False(post.IsRead);
+        }
+
+        [Fact]
+        public void CreateFromNote_RemoteNoteTest()
+        {
+            var note = new MisskeyNote
+            {
+                Id = "abcdef",
+                CreatedAt = "2024-01-01T01:02:03.456Z",
+                Text = "foo",
+                User = new()
+                {
+                    Id = "ghijkl",
+                    Username = "bar",
+                    Host = "bbb.example.com",
+                },
+                Visibility = "public",
+            };
+            var factory = new MisskeyPostFactory(new());
+            var accountState = new MisskeyAccountState(new("https://aaa.example.com/"), new("aaaa"), "hoge");
+
+            var post = factory.CreateFromNote(note, accountState, firstLoad: false);
+            Assert.Equal(new("https://aaa.example.com/notes/abcdef"), post.PostUri);
+            Assert.Equal("bar@bbb.example.com", post.ScreenName);
+        }
+
+        [Fact]
+        public void CreateFromNote_ReplyTest()
+        {
+            var repliedNote = new MisskeyNote
+            {
+                Id = "aaaaa",
+                CreatedAt = "2023-12-31T00:00:00.000Z",
+                Text = "hoge",
+                UserId = "abcdef",
+                User = new()
+                {
+                    Id = "abcdef",
+                    Username = "foo",
+                },
+                Visibility = "public",
+            };
+            var note = new MisskeyNote
+            {
+                Id = "bbbbb",
+                CreatedAt = "2024-01-01T01:02:03.456Z",
+                Text = "@foo tetete",
+                User = new()
+                {
+                    Id = "ghijkl",
+                    Username = "bar",
+                },
+                Reply = repliedNote,
+                Visibility = "public",
+            };
+            var factory = new MisskeyPostFactory(new());
+            var accountState = new MisskeyAccountState(new("https://example.com/"), new("aaaa"), "hoge");
+
+            var post = factory.CreateFromNote(note, accountState, firstLoad: false);
+            Assert.Equal(new MisskeyNoteId("bbbbb"), post.StatusId);
+            Assert.Equal("@foo tetete", post.Text);
+            Assert.Equal(new MisskeyUserId("ghijkl"), post.UserId);
+            Assert.Equal("bar", post.ScreenName);
+            Assert.Equal(new MisskeyNoteId("aaaaa"), post.InReplyToStatusId);
+            Assert.Equal("foo", post.InReplyToUser);
+            Assert.Equal(new MisskeyUserId("abcdef"), post.InReplyToUserId);
+        }
+
+        [Fact]
+        public void CreateFromNote_RenoteTest()
+        {
+            var renotedNote = new MisskeyNote
+            {
+                Id = "aaaaa",
+                CreatedAt = "2023-12-31T00:00:00.000Z",
+                Text = "hoge",
+                User = new()
+                {
+                    Id = "abcdef",
+                    Username = "foo",
+                },
+                Visibility = "public",
+            };
+            var note = new MisskeyNote
+            {
+                Id = "bbbbb",
+                CreatedAt = "2024-01-01T01:02:03.456Z",
+                Text = null,
+                User = new()
+                {
+                    Id = "ghijkl",
+                    Username = "bar",
+                },
+                Renote = renotedNote,
+                Visibility = "public",
+            };
+            var factory = new MisskeyPostFactory(new());
+            var accountState = new MisskeyAccountState(new("https://example.com/"), new("aaaa"), "hoge");
+
+            var post = factory.CreateFromNote(note, accountState, firstLoad: false);
+            Assert.Equal(new MisskeyNoteId("bbbbb"), post.StatusId);
+            Assert.Equal(new(2024, 1, 1, 1, 2, 3, 456), post.CreatedAtForSorting);
+            Assert.Equal(new(2023, 12, 31, 0, 0, 0, 0), post.CreatedAt);
+            Assert.Equal(new("https://example.com/notes/aaaaa"), post.PostUri);
+            Assert.Equal("hoge", post.Text);
+            Assert.Equal(new MisskeyUserId("abcdef"), post.UserId);
+            Assert.Equal("foo", post.ScreenName);
+            Assert.Equal(new MisskeyNoteId("aaaaa"), post.RetweetedId);
+            Assert.Equal("bar", post.RetweetedBy);
+            Assert.Equal(new MisskeyUserId("ghijkl"), post.RetweetedByUserId);
+        }
+
+        [Fact]
+        public void CreateFromNote_QuotedNoteTest()
+        {
+            var quotedNote = new MisskeyNote
+            {
+                Id = "aaaaa",
+                CreatedAt = "2023-12-31T00:00:00.000Z",
+                Text = "hoge",
+                User = new()
+                {
+                    Id = "abcdef",
+                    Username = "foo",
+                },
+                Visibility = "public",
+            };
+            var note = new MisskeyNote
+            {
+                Id = "bbbbb",
+                CreatedAt = "2024-01-01T01:02:03.456Z",
+                Text = "tetete", // Text が空でなく Renote がある場合は引用とみなす
+                User = new()
+                {
+                    Id = "ghijkl",
+                    Username = "bar",
+                },
+                Renote = quotedNote,
+                Visibility = "public",
+            };
+            var factory = new MisskeyPostFactory(new());
+            var accountState = new MisskeyAccountState(new("https://example.com/"), new("aaaa"), "hoge");
+
+            var post = factory.CreateFromNote(note, accountState, firstLoad: false);
+            Assert.Equal(new MisskeyNoteId("bbbbb"), post.StatusId);
+            Assert.Equal(new(2024, 1, 1, 1, 2, 3, 456), post.CreatedAtForSorting);
+            Assert.Equal(new(2024, 1, 1, 1, 2, 3, 456), post.CreatedAt);
+            Assert.Equal(new("https://example.com/notes/bbbbb"), post.PostUri);
+            Assert.Equal("tetete", post.Text);
+            Assert.Equal(new MisskeyUserId("ghijkl"), post.UserId);
+            Assert.Equal("bar", post.ScreenName);
+            Assert.Null(post.RetweetedId);
+            Assert.Null(post.RetweetedBy);
+            Assert.Null(post.RetweetedByUserId);
+            Assert.Equal(new[] { new MisskeyNoteId("aaaaa") }, post.QuoteStatusIds);
+        }
+    }
+}
diff --git a/OpenTween/Api/Misskey/MisskeyNote.cs b/OpenTween/Api/Misskey/MisskeyNote.cs
new file mode 100644 (file)
index 0000000..f2d051f
--- /dev/null
@@ -0,0 +1,68 @@
+// 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.Runtime.Serialization;
+
+namespace OpenTween.Api.Misskey
+{
+    [DataContract]
+    public class MisskeyNote
+    {
+        [DataMember(Name = "id")]
+        public string Id { get; set; } = "";
+
+        [DataMember(Name = "createdAt")]
+        public string CreatedAt { get; set; } = "";
+
+        [DataMember(Name = "text")]
+        public string? Text { get; set; }
+
+        [DataMember(Name = "userId")]
+        public string UserId { get; set; } = "";
+
+        [DataMember(Name = "user")]
+        public MisskeyUserLite User { get; set; } = new();
+
+        [DataMember(Name = "replyId", IsRequired = false)]
+        public string? ReplyId { get; set; }
+
+        [DataMember(Name = "reply", IsRequired = false)]
+        public MisskeyNote? Reply { get; set; }
+
+        [DataMember(Name = "renoteId", IsRequired = false)]
+        public string? RenoteId { get; set; }
+
+        [DataMember(Name = "renote", IsRequired = false)]
+        public MisskeyNote? Renote { get; set; }
+
+        [DataMember(Name = "visibility")]
+        public string Visibility { get; set; } = "";
+
+        [DataMember(Name = "mentions", IsRequired = false)]
+        public string[] Mentions { get; set; } = Array.Empty<string>();
+
+        [DataMember(Name = "myReaction", IsRequired = false)]
+        public string? MyReaction { get; set; }
+    }
+}
diff --git a/OpenTween/Api/Misskey/MisskeyUserLite.cs b/OpenTween/Api/Misskey/MisskeyUserLite.cs
new file mode 100644 (file)
index 0000000..ef36a7e
--- /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.Runtime.Serialization;
+
+namespace OpenTween.Api.Misskey
+{
+    [DataContract]
+    public class MisskeyUserLite
+    {
+        [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 = "host")]
+        public string? Host { get; set; }
+
+        [DataMember(Name = "avatarUrl")]
+        public string? AvatarUrl { get; set; }
+    }
+}
diff --git a/OpenTween/Api/Misskey/NoteShowRequest.cs b/OpenTween/Api/Misskey/NoteShowRequest.cs
new file mode 100644 (file)
index 0000000..2414083
--- /dev/null
@@ -0,0 +1,68 @@
+// 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.Runtime.Serialization;
+using System.Threading.Tasks;
+using OpenTween.Connection;
+using OpenTween.SocialProtocol.Misskey;
+
+namespace OpenTween.Api.Misskey
+{
+    public class NoteShowRequest
+    {
+        public required MisskeyNoteId NoteId { get; set; }
+
+        public async Task<MisskeyNote> Send(IApiConnection apiConnection)
+        {
+            var request = new PostJsonRequest
+            {
+                RequestUri = new("notes/show", UriKind.Relative),
+                JsonString = this.CreateRequestJson(),
+            };
+
+            using var response = await apiConnection.SendAsync(request)
+                .ConfigureAwait(false);
+
+            var note = await response.ReadAsJson<MisskeyNote>()
+                .ConfigureAwait(false);
+
+            return note;
+        }
+
+        [DataContract]
+        private record RequestBody(
+            [property: DataMember(Name = "noteId")]
+            string NoteId
+        );
+
+        private string CreateRequestJson()
+        {
+            var body = new RequestBody(
+                NoteId: this.NoteId.Id
+            );
+
+            return JsonUtils.SerializeJsonByDataContract(body);
+        }
+    }
+}
index cc05e2b..e8ad7c5 100644 (file)
@@ -181,5 +181,8 @@ namespace OpenTween
             result = MinValue;
             return false;
         }
+
+        public static DateTimeUtc ParseISO(string input)
+            => new(DateTimeOffset.Parse(input, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
     }
 }
index a78ae08..ad7ff33 100644 (file)
@@ -85,7 +85,7 @@ namespace OpenTween.SocialProtocol.Misskey
         }
 
         public bool CanUsePostId(PostId postId)
-            => throw new NotImplementedException();
+            => postId is MisskeyNoteId;
 
         public void Dispose()
         {
index 1a89ca4..3a0ba92 100644 (file)
@@ -30,6 +30,7 @@ namespace OpenTween.SocialProtocol.Misskey
     public class MisskeyClient : ISocialProtocolClient
     {
         private readonly MisskeyAccount account;
+        private readonly MisskeyPostFactory postFactory = new();
 
         public MisskeyClient(MisskeyAccount account)
             => this.account = account;
@@ -45,8 +46,20 @@ namespace OpenTween.SocialProtocol.Misskey
             return new();
         }
 
-        public Task<PostClass> GetPostById(PostId postId, bool firstLoad)
-            => throw this.CreateException();
+        public async Task<PostClass> GetPostById(PostId postId, bool firstLoad)
+        {
+            var request = new NoteShowRequest
+            {
+                NoteId = this.AssertMisskeyNoteId(postId),
+            };
+
+            var note = await request.Send(this.account.Connection)
+                .ConfigureAwait(false);
+
+            var post = this.CreatePostFromNote(note, firstLoad);
+
+            return post;
+        }
 
         public Task<TimelineResponse> GetHomeTimeline(int count, IQueryCursor? cursor, bool firstLoad)
             => throw this.CreateException();
@@ -92,5 +105,15 @@ namespace OpenTween.SocialProtocol.Misskey
 
         private WebApiException CreateException()
             => new("Not implemented");
+
+        private MisskeyNoteId AssertMisskeyNoteId(PostId postId)
+        {
+            return postId is MisskeyNoteId noteId
+                ? noteId
+                : throw new WebApiException($"Not supported type: {postId.GetType()}");
+        }
+
+        private PostClass CreatePostFromNote(MisskeyNote note, bool firstLoad)
+            => this.postFactory.CreateFromNote(note, this.account.AccountState, firstLoad);
     }
 }
diff --git a/OpenTween/SocialProtocol/Misskey/MisskeyNoteId.cs b/OpenTween/SocialProtocol/Misskey/MisskeyNoteId.cs
new file mode 100644 (file)
index 0000000..a744832
--- /dev/null
@@ -0,0 +1,38 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 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 OpenTween.Models;
+
+namespace OpenTween.SocialProtocol.Misskey
+{
+    public class MisskeyNoteId : PostId
+    {
+        public override string IdType => "misskey_note";
+
+        public override string Id { get; }
+
+        public MisskeyNoteId(string id)
+            => this.Id = id ?? throw new ArgumentNullException(nameof(id));
+    }
+}
diff --git a/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs b/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs
new file mode 100644 (file)
index 0000000..2e8ae8c
--- /dev/null
@@ -0,0 +1,119 @@
+// 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.Linq;
+using OpenTween.Api.Misskey;
+using OpenTween.Models;
+using OpenTween.Setting;
+
+namespace OpenTween.SocialProtocol.Misskey
+{
+    public class MisskeyPostFactory
+    {
+        private readonly SettingCommon settingCommon;
+
+        public MisskeyPostFactory()
+            : this(SettingManager.Instance.Common)
+        {
+        }
+
+        public MisskeyPostFactory(SettingCommon settingCommon)
+            => this.settingCommon = settingCommon;
+
+        public PostClass CreateFromNote(MisskeyNote note, MisskeyAccountState accountState, bool firstLoad)
+        {
+            var noteUser = note.User;
+            var noteUserId = new MisskeyUserId(noteUser.Id);
+
+            var renotedNote = note.Renote;
+            var renoterUser = renotedNote != null ? noteUser : null;
+
+            // リツイートであるか否かに関わらず常にオリジナルのツイート及びユーザーを指す
+            var originalNote = renotedNote ?? note;
+            var originalNoteId = new MisskeyNoteId(originalNote.Id);
+            var originalNoteUser = originalNote.User;
+            var originalNoteUserId = new MisskeyUserId(originalNoteUser.Id);
+            var originalNoteUserAcct = originalNoteUser.Host is { } host ? $"{originalNoteUser.Username}@{host}" : originalNoteUser.Username;
+
+            var replyToNote = originalNote.Reply;
+
+            var isMe = noteUserId == accountState.UserId;
+            var reactionSent = note.MyReaction != null;
+
+            var originalText = originalNote.Text ?? "";
+            var urlEntities = TweetExtractor.ExtractUrlEntities(originalText);
+            var textHtml = TweetFormatter.AutoLinkHtml(originalText, urlEntities);
+
+            return new()
+            {
+                // note から生成
+                StatusId = new MisskeyNoteId(note.Id),
+                CreatedAtForSorting = DateTimeUtc.ParseISO(note.CreatedAt),
+                IsMe = isMe,
+
+                // originalNote から生成
+                PostUri = CreateLocalPostUri(accountState.ServerUri, originalNoteId),
+                CreatedAt = DateTimeUtc.ParseISO(originalNote.CreatedAt),
+                Text = textHtml,
+                TextFromApi = originalText,
+                AccessibleText = originalText,
+                IsFav = reactionSent,
+                IsReply = renotedNote == null && originalNote.Mentions?.Any(x => x == accountState.UserId.Id) == true,
+                InReplyToStatusId = replyToNote?.Id is { } replyToIdStr ? new MisskeyNoteId(replyToIdStr) : null,
+                InReplyToUser = replyToNote?.User.Username,
+                InReplyToUserId = replyToNote?.UserId is { } replyToUserIdStr ? new MisskeyUserId(replyToUserIdStr) : null,
+                IsProtect = originalNote.Visibility is not "public" or "home",
+
+                // originalNoteUser から生成
+                UserId = originalNoteUserId,
+                ScreenName = originalNoteUserAcct,
+                Nickname = originalNoteUser.Name ?? originalNoteUser.Username,
+                ImageUrl = originalNoteUser.AvatarUrl ?? "",
+
+                // renotedNote から生成
+                RetweetedId = renotedNote?.Id is { } renotedId ? new MisskeyNoteId(renotedId) : null,
+
+                // renoterUser から生成
+                RetweetedBy = renoterUser?.Username,
+                RetweetedByUserId = renoterUser?.Id is { } renoterId ? new MisskeyUserId(renoterId) : null,
+
+                IsRead = this.DetermineUnreadState(isMe, firstLoad),
+            };
+        }
+
+        public static Uri CreateLocalPostUri(Uri serverUri, MisskeyNoteId noteId)
+            => new(serverUri, $"/notes/{noteId.Id}");
+
+        private bool DetermineUnreadState(bool isMe, bool firstLoad)
+        {
+            if (isMe && this.settingCommon.ReadOwnPost)
+                return true;
+
+            if (firstLoad && this.settingCommon.Read)
+                return true;
+
+            return false;
+        }
+    }
+}