From: Kimura Youichi Date: Mon, 27 May 2024 22:55:12 +0000 (+0900) Subject: MisskeyClient.GetPostByIdメソッドを実装 X-Git-Tag: OpenTween_v3.14.0^2~1^2~15 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=b2545fdc6db15454db7056a8ffae4b052136e358;p=opentween%2Fopen-tween.git MisskeyClient.GetPostByIdメソッドを実装 --- diff --git a/OpenTween.Tests/Api/Misskey/NoteShowRequestTest.cs b/OpenTween.Tests/Api/Misskey/NoteShowRequestTest.cs new file mode 100644 index 00000000..c984ca55 --- /dev/null +++ b/OpenTween.Tests/Api/Misskey/NoteShowRequestTest.cs @@ -0,0 +1,61 @@ +// 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; +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(); + mock.Setup(x => + x.SendAsync(It.IsAny()) + ) + .Callback(x => + { + var request = Assert.IsType(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(); + } + } +} diff --git a/OpenTween.Tests/DateTimeUtcTest.cs b/OpenTween.Tests/DateTimeUtcTest.cs index 1126c326..e54b63b2 100644 --- a/OpenTween.Tests/DateTimeUtcTest.cs +++ b/OpenTween.Tests/DateTimeUtcTest.cs @@ -330,5 +330,17 @@ namespace OpenTween Assert.Equal(expectedParsed, parsed); Assert.Equal(expectedResult, result); } + + public static readonly TheoryData 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 index 00000000..b3529b2c --- /dev/null +++ b/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs @@ -0,0 +1,227 @@ +// 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.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 index 00000000..f2d051f5 --- /dev/null +++ b/OpenTween/Api/Misskey/MisskeyNote.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. + +#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(); + + [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 index 00000000..ef36a7ef --- /dev/null +++ b/OpenTween/Api/Misskey/MisskeyUserLite.cs @@ -0,0 +1,46 @@ +// 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.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 index 00000000..2414083e --- /dev/null +++ b/OpenTween/Api/Misskey/NoteShowRequest.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. + +#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 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() + .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); + } + } +} diff --git a/OpenTween/DateTimeUtc.cs b/OpenTween/DateTimeUtc.cs index cc05e2b4..e8ad7c5e 100644 --- a/OpenTween/DateTimeUtc.cs +++ b/OpenTween/DateTimeUtc.cs @@ -181,5 +181,8 @@ namespace OpenTween result = MinValue; return false; } + + public static DateTimeUtc ParseISO(string input) + => new(DateTimeOffset.Parse(input, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); } } diff --git a/OpenTween/SocialProtocol/Misskey/MisskeyAccount.cs b/OpenTween/SocialProtocol/Misskey/MisskeyAccount.cs index a78ae08f..ad7ff336 100644 --- a/OpenTween/SocialProtocol/Misskey/MisskeyAccount.cs +++ b/OpenTween/SocialProtocol/Misskey/MisskeyAccount.cs @@ -85,7 +85,7 @@ namespace OpenTween.SocialProtocol.Misskey } public bool CanUsePostId(PostId postId) - => throw new NotImplementedException(); + => postId is MisskeyNoteId; public void Dispose() { diff --git a/OpenTween/SocialProtocol/Misskey/MisskeyClient.cs b/OpenTween/SocialProtocol/Misskey/MisskeyClient.cs index 1a89ca4a..3a0ba926 100644 --- a/OpenTween/SocialProtocol/Misskey/MisskeyClient.cs +++ b/OpenTween/SocialProtocol/Misskey/MisskeyClient.cs @@ -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 GetPostById(PostId postId, bool firstLoad) - => throw this.CreateException(); + public async Task 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 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 index 00000000..a744832f --- /dev/null +++ b/OpenTween/SocialProtocol/Misskey/MisskeyNoteId.cs @@ -0,0 +1,38 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 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 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 index 00000000..2e8ae8cd --- /dev/null +++ b/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs @@ -0,0 +1,119 @@ +// 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.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; + } + } +}