From bb8cab72a50b7aa8595fb4f496cf0917ea058e18 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 25 May 2024 06:06:29 +0900 Subject: [PATCH] =?utf8?q?=E7=99=BA=E8=A8=80=E6=8A=95=E7=A8=BF=E6=99=82?= =?utf8?q?=E3=81=AE=E6=96=87=E5=AD=97=E6=95=B0=E3=82=AB=E3=82=A6=E3=83=B3?= =?utf8?q?=E3=83=88=E3=81=8A=E3=82=88=E3=81=B3attachment=5Furl,=20exclude?= =?utf8?q?=5Freply=5Fuser=5Fids=E3=81=AE=E6=8A=BD=E5=87=BA=E5=87=A6?= =?utf8?q?=E7=90=86=E3=82=92=E5=88=A5=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AB?= =?utf8?q?=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../Twitter/CreateTweetFormatterTest.cs | 177 +++++++++++++++++++++ OpenTween/PostStatusParams.cs | 7 +- .../SocialProtocol/Twitter/CreateTweetFormatter.cs | 152 ++++++++++++++++++ .../SocialProtocol/Twitter/CreateTweetParams.cs | 60 +++++++ OpenTween/Tween.cs | 163 +++++-------------- OpenTween/Twitter.cs | 2 +- 6 files changed, 431 insertions(+), 130 deletions(-) create mode 100644 OpenTween.Tests/SocialProtocol/Twitter/CreateTweetFormatterTest.cs create mode 100644 OpenTween/SocialProtocol/Twitter/CreateTweetFormatter.cs create mode 100644 OpenTween/SocialProtocol/Twitter/CreateTweetParams.cs diff --git a/OpenTween.Tests/SocialProtocol/Twitter/CreateTweetFormatterTest.cs b/OpenTween.Tests/SocialProtocol/Twitter/CreateTweetFormatterTest.cs new file mode 100644 index 00000000..c5abf2e7 --- /dev/null +++ b/OpenTween.Tests/SocialProtocol/Twitter/CreateTweetFormatterTest.cs @@ -0,0 +1,177 @@ +// 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 OpenTween.Models; +using Xunit; + +namespace OpenTween.SocialProtocol.Twitter +{ + public class CreateTweetFormatterTest + { + [Theory] + [InlineData("", 280)] + [InlineData("a", 279)] + [InlineData("D OpenTween ", 10_000)] + [InlineData("D OpenTween a", 9_999)] + [InlineData("hoge https://twitter.com/twitterapi/status/22634515958", 276)] + public void GetTextLengthRemain_Test(string text, int expected) + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + var formatter = new CreateTweetFormatter(twAccount); + + var postParams = new PostStatusParams(text); + Assert.Equal(expected, formatter.GetTextLengthRemain(postParams)); + } + + [Fact] + public void CreateParams_SimpleTextTest() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + var formatter = new CreateTweetFormatter(twAccount); + + var postParams = new PostStatusParams(Text: "hoge"); + var expected = new CreateTweetParams(Text: "hoge"); + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + + [Fact] + public void CreateParams_RemoveAutoPopuratedMentions_SingleTest() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + + var formatter = new CreateTweetFormatter(twAccount); + var inReplyToPost = new PostClass + { + StatusId = new TwitterStatusId("1"), + UserId = new TwitterUserId("12345"), + ScreenName = "foo", + }; + + // auto_populate_reply_metadata により自動で付与される Mentions を Text から削除する + var postParams = new PostStatusParams(Text: "@foo hoge", inReplyToPost); + var expected = new CreateTweetParams(Text: "hoge", inReplyToPost) + { + AutoPopulateReplyMetadata = true, + ExcludeReplyUserIds = Array.Empty(), + }; + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + + [Fact] + public void CreateParams_RemoveAutoPopuratedMentions_MultipleTest() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + + var formatter = new CreateTweetFormatter(twAccount); + var inReplyToPost = new PostClass + { + StatusId = new TwitterStatusId("1"), + UserId = new TwitterUserId("12345"), + ScreenName = "foo", + Text = "@bar tetete", + ReplyToList = new() { (new TwitterUserId("67890"), "bar") }, + }; + + // auto_populate_reply_metadata により自動で付与される Mentions を Text から削除する + var postParams = new PostStatusParams(Text: "@foo @bar hoge", inReplyToPost); + var expected = new CreateTweetParams(Text: "hoge", inReplyToPost) + { + AutoPopulateReplyMetadata = true, + ExcludeReplyUserIds = Array.Empty(), + }; + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + + [Fact] + public void CreateParams_RemoveAutoPopuratedMentions_ExcludeTest() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + + var formatter = new CreateTweetFormatter(twAccount); + var inReplyToPost = new PostClass + { + StatusId = new TwitterStatusId("1"), + UserId = new TwitterUserId("12345"), + ScreenName = "foo", + Text = "@bar tetete", + ReplyToList = new() { (new TwitterUserId("67890"), "bar") }, + }; + + // auto_populate_reply_metadata により自動で付与される Mentions を Text から削除する + var postParams = new PostStatusParams(Text: "@foo hoge", inReplyToPost); + var expected = new CreateTweetParams(Text: "hoge", inReplyToPost) + { + AutoPopulateReplyMetadata = true, + ExcludeReplyUserIds = new PersonId[] { new TwitterUserId("67890") }, + }; + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + + [Fact] + public void CreateParams_RemoveAttachmentUrl_Test() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + var formatter = new CreateTweetFormatter(twAccount); + + // 引用ツイートの URL を Text から除去する + var postParams = new PostStatusParams(Text: "hoge https://twitter.com/twitterapi/status/22634515958"); + var expected = new CreateTweetParams(Text: "hoge") + { + AttachmentUrl = "https://twitter.com/twitterapi/status/22634515958", + }; + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + + [Fact] + public void CreateParams_RemoveAttachmentUrl_MultipleTest() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + var formatter = new CreateTweetFormatter(twAccount); + + // attachment_url は複数指定できないため末尾の URL のみ Text から除去する + var postParams = new PostStatusParams(Text: "hoge https://twitter.com/muji_net/status/21984934471 https://twitter.com/twitterapi/status/22634515958"); + var expected = new CreateTweetParams(Text: "hoge https://twitter.com/muji_net/status/21984934471") + { + AttachmentUrl = "https://twitter.com/twitterapi/status/22634515958", + }; + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + + [Fact] + public void CreateParams_RemoveAttachmentUrl_WithMediaTest() + { + using var twAccount = new TwitterAccount(Guid.NewGuid()); + var formatter = new CreateTweetFormatter(twAccount); + + // 引用ツイートと画像添付は併用できないため attachment_url は使用しない(現在は許容されているかも?) + var postParams = new PostStatusParams(Text: "hoge https://twitter.com/twitterapi/status/22634515958") + { + MediaIds = new[] { 1234L }, + }; + var expected = new CreateTweetParams(Text: "hoge https://twitter.com/twitterapi/status/22634515958") + { + MediaIds = new[] { 1234L }, + }; + Assert.Equal(expected, formatter.CreateParams(postParams)); + } + } +} diff --git a/OpenTween/PostStatusParams.cs b/OpenTween/PostStatusParams.cs index 72ff3c6b..87419ea1 100644 --- a/OpenTween/PostStatusParams.cs +++ b/OpenTween/PostStatusParams.cs @@ -34,14 +34,9 @@ namespace OpenTween public record PostStatusParams( string Text, PostClass? InReplyTo = null, - IReadOnlyList? MediaIds = null, - bool AutoPopulateReplyMetadata = false, - IReadOnlyList? ExcludeReplyUserIds = null, - string? AttachmentUrl = null + IReadOnlyList? MediaIds = null ) { public IReadOnlyList MediaIds { get; init; } = MediaIds ?? Array.Empty(); - - public IReadOnlyList ExcludeReplyUserIds { get; init; } = ExcludeReplyUserIds ?? Array.Empty(); } } diff --git a/OpenTween/SocialProtocol/Twitter/CreateTweetFormatter.cs b/OpenTween/SocialProtocol/Twitter/CreateTweetFormatter.cs new file mode 100644 index 00000000..8308b182 --- /dev/null +++ b/OpenTween/SocialProtocol/Twitter/CreateTweetFormatter.cs @@ -0,0 +1,152 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2007-2011 kiri_feather (@kiri_feather) +// (c) 2008-2011 Moz (@syo68k) +// (c) 2008-2011 takeshik (@takeshik) +// (c) 2010-2011 anis774 (@anis774) +// (c) 2010-2011 fantasticswallow (@f_swallow) +// (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.Collections.Generic; +using System.Linq; +using OpenTween.Models; + +namespace OpenTween.SocialProtocol.Twitter +{ + public class CreateTweetFormatter + { + private readonly TwitterAccount account; + + public CreateTweetFormatter(TwitterAccount account) + => this.account = account; + + public int GetTextLengthRemain(PostStatusParams formState) + { + var createParams = this.CreateParams(formState); + + return this.account.Legacy.GetTextLengthRemain(createParams.Text); + } + + public CreateTweetParams CreateParams(PostStatusParams formState) + { + var createParams = new CreateTweetParams( + formState.Text, + formState.InReplyTo, + formState.MediaIds + ); + + // DM の場合はこれ以降の処理を行わない + if (createParams.Text.StartsWith("D ", StringComparison.OrdinalIgnoreCase)) + return createParams; + + createParams = this.FormatStatusTextExtended(createParams, out var autoPopulatedUserIds); + + // リプライ先がセットされていても autoPopulatedUserIds が空の場合は auto_populate_reply_metadata を有効にしない + // (非公式 RT の場合など) + if (createParams.InReplyTo != null && autoPopulatedUserIds.Length != 0) + { + createParams = createParams with + { + AutoPopulateReplyMetadata = true, + + // ReplyToList のうち autoPopulatedUserIds に含まれていないユーザー ID を抽出 + ExcludeReplyUserIds = createParams.InReplyTo.ReplyToList.Select(x => x.UserId).Except(autoPopulatedUserIds) + .ToArray(), + }; + } + + return createParams; + } + + /// + /// 拡張モードで140字にカウントされない文字列の除去を行います + /// + private CreateTweetParams FormatStatusTextExtended(CreateTweetParams createParams, out PersonId[] autoPopulatedUserIds) + { + createParams = this.RemoveAutoPopuratedMentions(createParams, out autoPopulatedUserIds); + + createParams = this.RemoveAttachmentUrl(createParams); + + return createParams; + } + + /// + /// 投稿時に auto_populate_reply_metadata オプションによって自動で追加されるメンションを除去します + /// + private CreateTweetParams RemoveAutoPopuratedMentions(CreateTweetParams createParams, out PersonId[] autoPopulatedUserIds) + { + autoPopulatedUserIds = Array.Empty(); + + var replyToPost = createParams.InReplyTo; + if (replyToPost == null) + return createParams; + + var tweetText = createParams.Text; + var autoPopulatedUserIdList = new List(); + + if (tweetText.StartsWith($"@{replyToPost.ScreenName} ", StringComparison.Ordinal)) + { + tweetText = tweetText.Substring(replyToPost.ScreenName.Length + 2); + autoPopulatedUserIdList.Add(replyToPost.UserId); + + foreach (var (userId, screenName) in replyToPost.ReplyToList) + { + if (tweetText.StartsWith($"@{screenName} ", StringComparison.Ordinal)) + { + tweetText = tweetText.Substring(screenName.Length + 2); + autoPopulatedUserIdList.Add(userId); + } + } + } + + autoPopulatedUserIds = autoPopulatedUserIdList.ToArray(); + + return createParams with { Text = tweetText }; + } + + /// + /// attachment_url に指定可能な URL が含まれていれば除去 + /// + private CreateTweetParams RemoveAttachmentUrl(CreateTweetParams createParams) + { + // attachment_url は media_id と同時に使用できない + if (createParams.MediaIds.Count > 0) + return createParams; + + var tweetText = createParams.Text; + var match = OpenTween.Twitter.AttachmentUrlRegex.Match(tweetText); + if (!match.Success) + return createParams; + + var attachmentUrl = match.Value; + + // マッチした URL を空白に置換 + tweetText = tweetText.Substring(0, match.Index); + + // テキストと URL の間にスペースが含まれていれば除去 + tweetText = tweetText.TrimEnd(' '); + + return createParams with { Text = tweetText, AttachmentUrl = attachmentUrl }; + } + } +} diff --git a/OpenTween/SocialProtocol/Twitter/CreateTweetParams.cs b/OpenTween/SocialProtocol/Twitter/CreateTweetParams.cs new file mode 100644 index 00000000..7a9cde7d --- /dev/null +++ b/OpenTween/SocialProtocol/Twitter/CreateTweetParams.cs @@ -0,0 +1,60 @@ +// 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.Collections.Generic; +using System.Linq; +using OpenTween.Models; + +namespace OpenTween.SocialProtocol.Twitter +{ + public record CreateTweetParams( + string Text, + PostClass? InReplyTo = null, + IReadOnlyList? MediaIds = null, + bool AutoPopulateReplyMetadata = false, + IReadOnlyList? ExcludeReplyUserIds = null, + string? AttachmentUrl = null + ) + { + public IReadOnlyList MediaIds { get; init; } = MediaIds ?? Array.Empty(); + + public IReadOnlyList ExcludeReplyUserIds { get; init; } = ExcludeReplyUserIds ?? Array.Empty(); + +#pragma warning disable CS8851 // テストコードでしか使用しないため GetHashCode の実装は省略 + public virtual bool Equals(CreateTweetParams? other) + { + if (object.ReferenceEquals(this, other)) + return true; + + return other != null && + this.Text == other.Text && + EqualityComparer.Default.Equals(this.InReplyTo, other.InReplyTo) && + this.MediaIds.SequenceEqual(other.MediaIds) && + this.AutoPopulateReplyMetadata == other.AutoPopulateReplyMetadata && + this.ExcludeReplyUserIds.SequenceEqual(other.ExcludeReplyUserIds) && + this.AttachmentUrl == other.AttachmentUrl; + } +#pragma warning restore CS8851 + } +} diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 2e23d59c..a2a4ef64 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -424,7 +424,7 @@ namespace OpenTween this.StatusLabel.AutoToolTip = false; this.StatusLabel.ToolTipText = ""; // 文字カウンタ初期化 - this.lblLen.Text = this.GetRestStatusCount(this.FormatStatusTextExtended(new("")).Text).ToString(); + this.lblLen.Text = this.GetRestStatusCount().ToString(); this.JumpReadOpMenuItem.ShortcutKeyDisplayString = "Space"; this.CopySTOTMenuItem.ShortcutKeyDisplayString = "Ctrl+C"; @@ -1155,37 +1155,7 @@ namespace OpenTween this.StatusText.SelectionStart = this.StatusText.Text.Length; this.CheckReplyTo(this.StatusText.Text); - var statusText = this.StatusText.Text; - var replyToPost = this.inReplyTo is (var inReplyToStatusId, _) ? this.statuses[inReplyToStatusId] : null; - var status = new PostStatusParams(statusText, replyToPost); - - var statusTextCompat = this.FormatStatusText(status); - if (this.GetRestStatusCount(statusTextCompat.Text) >= 0 && this.tw.Api.AuthType == APIAuthType.OAuth1) - { - // auto_populate_reply_metadata や attachment_url を使用しなくても 140 字以内に - // 収まる場合はこれらのオプションを使用せずに投稿する - status = statusTextCompat; - } - else - { - status = this.FormatStatusTextExtended(status, out var autoPopulatedUserIds); - - // リプライ先がセットされていても autoPopulatedUserIds が空の場合は auto_populate_reply_metadata を有効にしない - // (非公式 RT の場合など) - if (status.InReplyTo != null && autoPopulatedUserIds.Length != 0) - { - status = status with - { - AutoPopulateReplyMetadata = true, - - // ReplyToList のうち autoPopulatedUserIds に含まれていないユーザー ID を抽出 - ExcludeReplyUserIds = status.InReplyTo.ReplyToList.Select(x => x.UserId).Except(autoPopulatedUserIds) - .ToArray(), - }; - } - } - - if (this.GetRestStatusCount(status.Text) < 0) + if (this.GetRestStatusCount() < 0) { // 文字数制限を超えているが強制的に投稿するか var ret = MessageBox.Show(Properties.Resources.PostLengthOverMessage1, Properties.Resources.PostLengthOverMessage2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2); @@ -1193,6 +1163,8 @@ namespace OpenTween return; } + var status = this.CreatePostStatusParams(); + IMediaUploadService? uploadService = null; IMediaItem[]? uploadItems = null; if (this.ImageSelector.Visible) @@ -1618,7 +1590,11 @@ namespace OpenTween .ConfigureAwait(false); } - post = await this.tw.PostStatus(postParamsWithMedia) + var twAccount = (TwitterAccount)this.CurrentTabAccount; + var formatter = new CreateTweetFormatter(twAccount); + var createTweetParams = formatter.CreateParams(postParamsWithMedia); + + post = await twAccount.Legacy.PostStatus(createTweetParams) .ConfigureAwait(false); }); @@ -3206,10 +3182,7 @@ namespace OpenTween private void StatusText_TextChanged(object sender, EventArgs e) { // 文字数カウント - var statusText = this.StatusText.Text; - var replyToPost = this.inReplyTo is (var inReplyToStatusId, _) ? this.statuses[inReplyToStatusId] : null; - var statusParams = new PostStatusParams(statusText, replyToPost); - var pLen = this.GetRestStatusCount(this.FormatStatusTextExtended(statusParams).Text); + var pLen = this.GetRestStatusCount(); this.lblLen.Text = pLen.ToString(); if (pLen < 0) { @@ -3254,78 +3227,6 @@ namespace OpenTween return true; } - /// - /// 投稿時に auto_populate_reply_metadata オプションによって自動で追加されるメンションを除去します - /// - private PostStatusParams RemoveAutoPopuratedMentions(PostStatusParams statusParams, out PersonId[] autoPopulatedUserIds) - { - var statusText = statusParams.Text; - var autoPopulatedUserIdList = new List(); - - var replyToPost = statusParams.InReplyTo; - if (replyToPost != null) - { - if (statusText.StartsWith($"@{replyToPost.ScreenName} ", StringComparison.Ordinal)) - { - statusText = statusText.Substring(replyToPost.ScreenName.Length + 2); - autoPopulatedUserIdList.Add(replyToPost.UserId); - - foreach (var (userId, screenName) in replyToPost.ReplyToList) - { - if (statusText.StartsWith($"@{screenName} ", StringComparison.Ordinal)) - { - statusText = statusText.Substring(screenName.Length + 2); - autoPopulatedUserIdList.Add(userId); - } - } - } - } - - autoPopulatedUserIds = autoPopulatedUserIdList.ToArray(); - - return statusParams with { Text = statusText }; - } - - /// - /// attachment_url に指定可能な URL が含まれていれば除去 - /// - private PostStatusParams RemoveAttachmentUrl(PostStatusParams statusParams) - { - // attachment_url は media_id と同時に使用できない - if (this.ImageSelector.Visible && this.ImageSelector.Model.SelectedMediaService is { IsNativeUploadService: true }) - return statusParams; - - var statusText = statusParams.Text; - var match = Twitter.AttachmentUrlRegex.Match(statusText); - if (!match.Success) - return statusParams; - - var attachmentUrl = match.Value; - - // マッチした URL を空白に置換 - statusText = statusText.Substring(0, match.Index); - - // テキストと URL の間にスペースが含まれていれば除去 - statusText = statusText.TrimEnd(' '); - - return statusParams with { Text = statusText, AttachmentUrl = attachmentUrl }; - } - - private PostStatusParams FormatStatusTextExtended(PostStatusParams statusParams) - => this.FormatStatusTextExtended(statusParams, out _); - - /// - /// に加えて、拡張モードで140字にカウントされない文字列の除去を行います - /// - private PostStatusParams FormatStatusTextExtended(PostStatusParams statusParams, out PersonId[] autoPopulatedUserIds) - { - statusParams = this.RemoveAutoPopuratedMentions(statusParams, out autoPopulatedUserIds); - - statusParams = this.RemoveAttachmentUrl(statusParams); - - return this.FormatStatusText(statusParams); - } - internal PostStatusParams FormatStatusText(PostStatusParams statusParams) => this.FormatStatusText(statusParams, Control.ModifierKeys); @@ -3368,18 +3269,6 @@ namespace OpenTween if (statusText.Contains("RT @")) disableFooter = true; - // 自分宛のリプライの場合は先頭の「@screen_name 」の部分を除去する (in_reply_to_status_id は維持される) - var primaryUserName = this.CurrentTabAccount.UserName; - if (statusParams.InReplyTo != null && statusParams.InReplyTo.ScreenName == primaryUserName) - { - var mentionSelf = $"@{primaryUserName} "; - if (statusText.StartsWith(mentionSelf, StringComparison.OrdinalIgnoreCase)) - { - if (statusText.Length > mentionSelf.Length || this.GetSelectedImageService() != null) - statusText = statusText.Substring(mentionSelf.Length); - } - } - var header = ""; var footer = ""; @@ -3424,12 +3313,40 @@ namespace OpenTween return statusParams with { Text = statusText }; } + private PostStatusParams CreatePostStatusParams(bool setFakeMediaIds = false) + { + var statusText = this.StatusText.Text; + var replyToPost = this.inReplyTo is (var inReplyToStatusId, _) ? this.statuses[inReplyToStatusId] : null; + var mediaIds = Array.Empty(); + if (setFakeMediaIds) + { + // 文字数計算のために仮の mediaId を設定する + var useNativeUpload = this.ImageSelector.Visible && this.ImageSelector.Model.SelectedMediaService is { IsNativeUploadService: true }; + if (useNativeUpload) + mediaIds = new[] { -1L }; + } + var statusParams = new PostStatusParams(statusText, replyToPost, mediaIds); + + return this.FormatStatusText(statusParams); + } + /// /// 投稿欄に表示する入力可能な文字数を計算します /// - private int GetRestStatusCount(string statusText) + private int GetRestStatusCount() { - var remainCount = this.tw.GetTextLengthRemain(statusText); + var statusParams = this.CreatePostStatusParams(setFakeMediaIds: true); + + int remainCount; + if (this.CurrentTabAccount is TwitterAccount twAccount) + { + var formatter = new CreateTweetFormatter(twAccount); + remainCount = formatter.GetTextLengthRemain(statusParams); + } + else + { + remainCount = 140 - statusParams.Text.Length; + } var uploadService = this.GetSelectedImageService(); if (uploadService != null) diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index 9a3a0595..adb834bc 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -206,7 +206,7 @@ namespace OpenTween this.Api.Initialize(apiConnection); } - public async Task PostStatus(PostStatusParams param) + public async Task PostStatus(CreateTweetParams param) { this.CheckAccountState(); -- 2.11.0