OSDN Git Service

ISocialAccountインタフェースを追加
[opentween/open-tween.git] / OpenTween / Tween.cs
index b4481ab..93c423c 100644 (file)
@@ -52,12 +52,15 @@ using System.Threading.Tasks;
 using System.Windows.Forms;
 using OpenTween.Api;
 using OpenTween.Api.DataModel;
+using OpenTween.Api.GraphQL;
 using OpenTween.Api.TwitterV2;
 using OpenTween.Connection;
 using OpenTween.MediaUploadServices;
 using OpenTween.Models;
 using OpenTween.OpenTweenCustomControl;
 using OpenTween.Setting;
+using OpenTween.SocialProtocol;
+using OpenTween.SocialProtocol.Twitter;
 using OpenTween.Thumbnail;
 
 namespace OpenTween
@@ -97,27 +100,7 @@ namespace OpenTween
 
         private readonly object syncObject = new(); // ロック用
 
-        private const string DetailHtmlFormatHead =
-            """<head><meta http-equiv="X-UA-Compatible" content="IE=8">"""
-            + """<style type="text/css"><!-- """
-            + "body, p, pre {margin: 0;} "
-            + """body {font-family: "%FONT_FAMILY%", "Segoe UI Emoji", sans-serif; font-size: %FONT_SIZE%pt; background-color:rgb(%BG_COLOR%); word-wrap: break-word; color:rgb(%FONT_COLOR%);} """
-            + "pre {font-family: inherit;} "
-            + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
-            + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
-            + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
-            + ".quote-tweet.reply {border-color: rgb(%BG_REPLY_COLOR%);} "
-            + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
-            + "--></style>"
-            + "</head>";
-
-        private const string DetailHtmlFormatTemplateMono =
-            $"<html>{DetailHtmlFormatHead}<body><pre>%CONTENT_HTML%</pre></body></html>";
-
-        private const string DetailHtmlFormatTemplateNormal =
-            $"<html>{DetailHtmlFormatHead}<body><p>%CONTENT_HTML%</p></body></html>";
-
-        private string detailHtmlFormatPreparedTemplate = null!;
+        private readonly DetailsHtmlBuilder detailsHtmlBuilder = new();
 
         private bool myStatusError = false;
         private bool myStatusOnline = false;
@@ -127,8 +110,12 @@ namespace OpenTween
         // 設定ファイル
         private readonly SettingManager settings;
 
-        // twitter解析部
-        private readonly Twitter tw;
+        // ユーザーアカウント
+        private readonly AccountCollection accounts;
+
+#pragma warning disable SA1300
+        private Twitter tw => ((TwitterAccount)this.accounts.Primary).Legacy; // AccountCollection への移行用
+#pragma warning restore SA1300
 
         // Growl呼び出し部
         private readonly GrowlHelper gh = new(ApplicationSettings.ApplicationName);
@@ -159,10 +146,7 @@ namespace OpenTween
         private readonly ThumbnailGenerator thumbGenerator;
 
         /// <summary>発言履歴</summary>
-        private readonly List<StatusTextHistory> history = new();
-
-        /// <summary>発言履歴カレントインデックス</summary>
-        private int hisIdx;
+        private readonly StatusTextHistory history = new();
 
         // 発言投稿時のAPI引数(発言編集時に設定。手書きreplyでは設定されない)
 
@@ -207,7 +191,9 @@ namespace OpenTween
         private readonly DebounceTimer saveConfigDebouncer;
 
         private readonly string recommendedStatusFooter;
-        private bool urlMultibyteSplit = false;
+
+        internal bool SeparateUrlAndFullwidthCharacter { get; set; } = false;
+
         private bool preventSmsCommand = true;
 
         // URL短縮のUndo用
@@ -256,213 +242,12 @@ namespace OpenTween
             PrevSearch,
         }
 
-        private readonly record struct StatusTextHistory(
-            string Status,
-            (PostId StatusId, string ScreenName)? InReplyTo = null
-        );
-
         private readonly HookGlobalHotkey hookGlobalHotkey;
 
-        private void TweenMain_Activated(object sender, EventArgs e)
-        {
-            // 画面がアクティブになったら、発言欄の背景色戻す
-            if (this.StatusText.Focused)
-            {
-                this.StatusText_Enter(this.StatusText, System.EventArgs.Empty);
-            }
-        }
-
-        private bool disposed = false;
-
-        /// <summary>
-        /// 使用中のリソースをすべてクリーンアップします。
-        /// </summary>
-        /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
-        protected override void Dispose(bool disposing)
-        {
-            base.Dispose(disposing);
-
-            if (this.disposed)
-                return;
-
-            if (disposing)
-            {
-                this.components?.Dispose();
-
-                // 後始末
-                this.SearchDialog.Dispose();
-                this.urlDialog.Dispose();
-                this.themeManager.Dispose();
-                this.sfTab.Dispose();
-
-                this.timelineScheduler.Dispose();
-                this.workerCts.Cancel();
-                this.thumbnailTokenSource?.Dispose();
-
-                this.hookGlobalHotkey.Dispose();
-            }
-
-            // 終了時にRemoveHandlerしておかないとメモリリークする
-            // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx
-            Microsoft.Win32.SystemEvents.PowerModeChanged -= this.SystemEvents_PowerModeChanged;
-            Microsoft.Win32.SystemEvents.TimeChanged -= this.SystemEvents_TimeChanged;
-
-            this.disposed = true;
-        }
-
-        private void InitColumns(ListView list, bool startup)
-        {
-            this.InitColumnText();
-
-            ColumnHeader[]? columns = null;
-            try
-            {
-                if (this.Use2ColumnsMode)
-                {
-                    columns = new[]
-                    {
-                        new ColumnHeader(), // アイコン
-                        new ColumnHeader(), // 本文
-                    };
-
-                    columns[0].Text = this.columnText[0];
-                    columns[1].Text = this.columnText[2];
-
-                    if (startup)
-                    {
-                        var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width;
-
-                        columns[0].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[0]);
-                        columns[1].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[2]);
-                        columns[0].DisplayIndex = 0;
-                        columns[1].DisplayIndex = 1;
-                    }
-                    else
-                    {
-                        var idx = 0;
-                        foreach (var curListColumn in this.CurrentListView.Columns.Cast<ColumnHeader>())
-                        {
-                            columns[idx].Width = curListColumn.Width;
-                            columns[idx].DisplayIndex = curListColumn.DisplayIndex;
-                            idx++;
-                        }
-                    }
-                }
-                else
-                {
-                    columns = new[]
-                    {
-                        new ColumnHeader(), // アイコン
-                        new ColumnHeader(), // ニックネーム
-                        new ColumnHeader(), // 本文
-                        new ColumnHeader(), // 日付
-                        new ColumnHeader(), // ユーザID
-                        new ColumnHeader(), // 未読
-                        new ColumnHeader(), // マーク&プロテクト
-                        new ColumnHeader(), // ソース
-                    };
-
-                    foreach (var i in Enumerable.Range(0, columns.Length))
-                        columns[i].Text = this.columnText[i];
-
-                    if (startup)
-                    {
-                        var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width;
-
-                        foreach (var (column, index) in columns.WithIndex())
-                        {
-                            column.Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[index]);
-                            column.DisplayIndex = this.settings.Local.ColumnsOrder[index];
-                        }
-                    }
-                    else
-                    {
-                        var idx = 0;
-                        foreach (var curListColumn in this.CurrentListView.Columns.Cast<ColumnHeader>())
-                        {
-                            columns[idx].Width = curListColumn.Width;
-                            columns[idx].DisplayIndex = curListColumn.DisplayIndex;
-                            idx++;
-                        }
-                    }
-                }
-
-                list.Columns.AddRange(columns);
-
-                columns = null;
-            }
-            finally
-            {
-                if (columns != null)
-                {
-                    foreach (var column in columns)
-                        column?.Dispose();
-                }
-            }
-        }
-
-        private void InitColumnText()
-        {
-            this.columnText[0] = "";
-            this.columnText[1] = Properties.Resources.AddNewTabText2;
-            this.columnText[2] = Properties.Resources.AddNewTabText3;
-            this.columnText[3] = Properties.Resources.AddNewTabText4_2;
-            this.columnText[4] = Properties.Resources.AddNewTabText5;
-            this.columnText[5] = "";
-            this.columnText[6] = "";
-            this.columnText[7] = "Source";
-
-            this.columnOrgText[0] = "";
-            this.columnOrgText[1] = Properties.Resources.AddNewTabText2;
-            this.columnOrgText[2] = Properties.Resources.AddNewTabText3;
-            this.columnOrgText[3] = Properties.Resources.AddNewTabText4_2;
-            this.columnOrgText[4] = Properties.Resources.AddNewTabText5;
-            this.columnOrgText[5] = "";
-            this.columnOrgText[6] = "";
-            this.columnOrgText[7] = "Source";
-
-            var c = this.statuses.SortMode switch
-            {
-                ComparerMode.Nickname => 1, // ニックネーム
-                ComparerMode.Data => 2, // 本文
-                ComparerMode.Id => 3, // 時刻=発言Id
-                ComparerMode.Name => 4, // 名前
-                ComparerMode.Source => 7, // Source
-                _ => 0,
-            };
-
-            if (this.Use2ColumnsMode)
-            {
-                if (this.statuses.SortOrder == SortOrder.Descending)
-                {
-                    // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
-                    this.columnText[2] = this.columnOrgText[2] + "▾";
-                }
-                else
-                {
-                    // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
-                    this.columnText[2] = this.columnOrgText[2] + "▴";
-                }
-            }
-            else
-            {
-                if (this.statuses.SortOrder == SortOrder.Descending)
-                {
-                    // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
-                    this.columnText[c] = this.columnOrgText[c] + "▾";
-                }
-                else
-                {
-                    // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
-                    this.columnText[c] = this.columnOrgText[c] + "▴";
-                }
-            }
-        }
-
         public TweenMain(
             SettingManager settingManager,
             TabInformations tabInfo,
-            Twitter twitter,
+            AccountCollection accounts,
             ImageCache imageCache,
             IconAssetsManager iconAssets,
             ThumbnailGenerator thumbGenerator
@@ -470,7 +255,7 @@ namespace OpenTween
         {
             this.settings = settingManager;
             this.statuses = tabInfo;
-            this.tw = twitter;
+            this.accounts = accounts;
             this.iconCache = imageCache;
             this.iconAssets = iconAssets;
             this.thumbGenerator = thumbGenerator;
@@ -501,7 +286,6 @@ namespace OpenTween
             this.InitializeShortcuts();
 
             this.ignoreConfigSave = true;
-            this.Visible = false;
 
             this.TraceOutToolStripMenuItem.Checked = MyCommon.TraceFlag;
 
@@ -521,40 +305,13 @@ namespace OpenTween
             // 現在の DPI と設定保存時の DPI との比を取得する
             var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
 
-            // 認証関連
-            var account = this.settings.Common.SelectedAccount;
-            if (account != null)
-                this.tw.Initialize(account.GetTwitterAppToken(), account.Token, account.TokenSecret, account.Username, account.UserId);
-            else
-                this.tw.Initialize(TwitterAppToken.GetDefault(), "", "", "", 0L);
-
             this.initial = true;
 
-            this.tw.RestrictFavCheck = this.settings.Common.RestrictFavCheck;
-            this.tw.ReadOwnPost = this.settings.Common.ReadOwnPost;
-
-            // アクセストークンが有効であるか確認する
-            // ここが Twitter API への最初のアクセスになるようにすること
-            try
-            {
-                this.tw.VerifyCredentials();
-            }
-            catch (WebApiException ex)
-            {
-                MessageBox.Show(
-                    this,
-                    string.Format(Properties.Resources.StartupAuthError_Text, ex.Message),
-                    ApplicationSettings.ApplicationName,
-                    MessageBoxButtons.OK,
-                    MessageBoxIcon.Warning);
-            }
-
             // サムネイル関連の初期化
             // プロキシ設定等の通信まわりの初期化が済んでから処理する
             var imgazyobizinet = this.thumbGenerator.ImgAzyobuziNet;
             imgazyobizinet.Enabled = this.settings.Common.EnableImgAzyobuziNet;
             imgazyobizinet.DisabledInDM = this.settings.Common.ImgAzyobuziNetDisabledInDM;
-            imgazyobizinet.AutoUpdate = true;
 
             Thumbnail.Services.TonTwitterCom.GetApiConnection = () => this.tw.Api.Connection;
 
@@ -577,19 +334,17 @@ namespace OpenTween
 
             // フォント&文字色&背景色保持
             this.themeManager = new(this.settings.Local);
-            this.tweetDetailsView.Initialize(this, this.iconCache, this.themeManager);
+            this.tweetDetailsView.Initialize(this, this.iconCache, this.themeManager, this.detailsHtmlBuilder);
 
             // StringFormatオブジェクトへの事前設定
             this.sfTab.Alignment = StringAlignment.Center;
             this.sfTab.LineAlignment = StringAlignment.Center;
 
-            this.InitDetailHtmlFormat();
+            this.detailsHtmlBuilder.Prepare(this.settings.Common, this.themeManager);
             this.tweetDetailsView.ClearPostBrowser();
 
             this.recommendedStatusFooter = " [TWNv" + Regex.Replace(MyCommon.FileVersion.Replace(".", ""), "^0*", "") + "]";
 
-            this.history.Add(new StatusTextHistory(""));
-            this.hisIdx = 0;
             this.inReplyTo = null;
 
             // 各種ダイアログ設定
@@ -744,9 +499,9 @@ namespace OpenTween
             this.SetMainWindowTitle();
             this.SetNotifyIconText();
 
-            if (!this.settings.Common.MinimizeToTray || this.WindowState != FormWindowState.Minimized)
+            if (this.settings.Common.MinimizeToTray && this.WindowState == FormWindowState.Minimized)
             {
-                this.Visible = true;
+                this.Visible = false;
             }
 
             // タイマー設定
@@ -775,29 +530,204 @@ namespace OpenTween
             this.TimerRefreshIcon.Enabled = false;
 
             this.ignoreConfigSave = false;
-            this.TweenMain_Resize(this, EventArgs.Empty);
+            this.ApplyLayoutFromSettings();
+        }
 
-            if (this.settings.IsFirstRun)
+        private void TweenMain_Activated(object sender, EventArgs e)
+        {
+            // 画面がアクティブになったら、発言欄の背景色戻す
+            if (this.StatusText.Focused)
             {
-                // 初回起動時だけ右下のメニューを目立たせる
-                this.HashStripSplitButton.ShowDropDown();
+                this.StatusText_Enter(this.StatusText, System.EventArgs.Empty);
             }
         }
 
-        private void InitDetailHtmlFormat()
+        private bool disposed = false;
+
+        /// <summary>
+        /// 使用中のリソースをすべてクリーンアップします。
+        /// </summary>
+        /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
+        protected override void Dispose(bool disposing)
         {
-            var htmlTemplate = this.settings.Common.IsMonospace ? DetailHtmlFormatTemplateMono : DetailHtmlFormatTemplateNormal;
+            base.Dispose(disposing);
+
+            if (this.disposed)
+                return;
+
+            if (disposing)
+            {
+                this.components?.Dispose();
+
+                // 後始末
+                this.SearchDialog.Dispose();
+                this.urlDialog.Dispose();
+                this.themeManager.Dispose();
+                this.sfTab.Dispose();
+
+                this.timelineScheduler.Dispose();
+                this.workerCts.Cancel();
+                this.thumbnailTokenSource?.Dispose();
 
-            static string ColorToRGBString(Color color)
-                => $"{color.R},{color.G},{color.B}";
+                this.hookGlobalHotkey.Dispose();
+            }
 
-            this.detailHtmlFormatPreparedTemplate = htmlTemplate
-                .Replace("%FONT_FAMILY%", this.themeManager.FontDetail.Name)
-                .Replace("%FONT_SIZE%", this.themeManager.FontDetail.Size.ToString())
-                .Replace("%FONT_COLOR%", ColorToRGBString(this.themeManager.ColorDetail))
-                .Replace("%LINK_COLOR%", ColorToRGBString(this.themeManager.ColorDetailLink))
-                .Replace("%BG_COLOR%", ColorToRGBString(this.themeManager.ColorDetailBackcolor))
-                .Replace("%BG_REPLY_COLOR%", ColorToRGBString(this.themeManager.ColorAtTo));
+            // 終了時にRemoveHandlerしておかないとメモリリークする
+            // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx
+            Microsoft.Win32.SystemEvents.PowerModeChanged -= this.SystemEvents_PowerModeChanged;
+            Microsoft.Win32.SystemEvents.TimeChanged -= this.SystemEvents_TimeChanged;
+            MyCommon.TwitterApiInfo.AccessLimitUpdated -= this.TwitterApiStatus_AccessLimitUpdated;
+
+            this.disposed = true;
+        }
+
+        private void InitColumns(ListView list, bool startup)
+        {
+            this.InitColumnText();
+
+            ColumnHeader[]? columns = null;
+            try
+            {
+                if (this.Use2ColumnsMode)
+                {
+                    columns = new[]
+                    {
+                        new ColumnHeader(), // アイコン
+                        new ColumnHeader(), // 本文
+                    };
+
+                    columns[0].Text = this.columnText[0];
+                    columns[1].Text = this.columnText[2];
+
+                    if (startup)
+                    {
+                        var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width;
+
+                        columns[0].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[0]);
+                        columns[1].Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[2]);
+                        columns[0].DisplayIndex = 0;
+                        columns[1].DisplayIndex = 1;
+                    }
+                    else
+                    {
+                        var idx = 0;
+                        foreach (var curListColumn in this.CurrentListView.Columns.Cast<ColumnHeader>())
+                        {
+                            columns[idx].Width = curListColumn.Width;
+                            columns[idx].DisplayIndex = curListColumn.DisplayIndex;
+                            idx++;
+                        }
+                    }
+                }
+                else
+                {
+                    columns = new[]
+                    {
+                        new ColumnHeader(), // アイコン
+                        new ColumnHeader(), // ニックネーム
+                        new ColumnHeader(), // 本文
+                        new ColumnHeader(), // 日付
+                        new ColumnHeader(), // ユーザID
+                        new ColumnHeader(), // 未読
+                        new ColumnHeader(), // マーク&プロテクト
+                        new ColumnHeader(), // ソース
+                    };
+
+                    foreach (var i in Enumerable.Range(0, columns.Length))
+                        columns[i].Text = this.columnText[i];
+
+                    if (startup)
+                    {
+                        var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this.settings.Local.ScaleDimension.Width;
+
+                        foreach (var (column, index) in columns.WithIndex())
+                        {
+                            column.Width = ScaleBy(widthScaleFactor, this.settings.Local.ColumnsWidth[index]);
+                            column.DisplayIndex = this.settings.Local.ColumnsOrder[index];
+                        }
+                    }
+                    else
+                    {
+                        var idx = 0;
+                        foreach (var curListColumn in this.CurrentListView.Columns.Cast<ColumnHeader>())
+                        {
+                            columns[idx].Width = curListColumn.Width;
+                            columns[idx].DisplayIndex = curListColumn.DisplayIndex;
+                            idx++;
+                        }
+                    }
+                }
+
+                list.Columns.AddRange(columns);
+
+                columns = null;
+            }
+            finally
+            {
+                if (columns != null)
+                {
+                    foreach (var column in columns)
+                        column?.Dispose();
+                }
+            }
+        }
+
+        private void InitColumnText()
+        {
+            this.columnText[0] = "";
+            this.columnText[1] = Properties.Resources.AddNewTabText2;
+            this.columnText[2] = Properties.Resources.AddNewTabText3;
+            this.columnText[3] = Properties.Resources.AddNewTabText4_2;
+            this.columnText[4] = Properties.Resources.AddNewTabText5;
+            this.columnText[5] = "";
+            this.columnText[6] = "";
+            this.columnText[7] = "Source";
+
+            this.columnOrgText[0] = "";
+            this.columnOrgText[1] = Properties.Resources.AddNewTabText2;
+            this.columnOrgText[2] = Properties.Resources.AddNewTabText3;
+            this.columnOrgText[3] = Properties.Resources.AddNewTabText4_2;
+            this.columnOrgText[4] = Properties.Resources.AddNewTabText5;
+            this.columnOrgText[5] = "";
+            this.columnOrgText[6] = "";
+            this.columnOrgText[7] = "Source";
+
+            var c = this.statuses.SortMode switch
+            {
+                ComparerMode.Nickname => 1, // ニックネーム
+                ComparerMode.Data => 2, // 本文
+                ComparerMode.Id => 3, // 時刻=発言Id
+                ComparerMode.Name => 4, // 名前
+                ComparerMode.Source => 7, // Source
+                _ => 0,
+            };
+
+            if (this.Use2ColumnsMode)
+            {
+                if (this.statuses.SortOrder == SortOrder.Descending)
+                {
+                    // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
+                    this.columnText[2] = this.columnOrgText[2] + "▾";
+                }
+                else
+                {
+                    // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
+                    this.columnText[2] = this.columnOrgText[2] + "▴";
+                }
+            }
+            else
+            {
+                if (this.statuses.SortOrder == SortOrder.Descending)
+                {
+                    // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
+                    this.columnText[c] = this.columnOrgText[c] + "▾";
+                }
+                else
+                {
+                    // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
+                    this.columnText[c] = this.columnOrgText[c] + "▴";
+                }
+            }
         }
 
         private void ListTab_DrawItem(object sender, DrawItemEventArgs e)
@@ -887,7 +817,7 @@ namespace OpenTween
             _ = this.saveConfigDebouncer.Call();
         }
 
-        private void RefreshTimeline()
+        internal void RefreshTimeline()
         {
             var curListView = this.CurrentListView;
 
@@ -1164,14 +1094,8 @@ namespace OpenTween
 
         private void StatusTextHistoryBack()
         {
-            if (!string.IsNullOrWhiteSpace(this.StatusText.Text))
-                this.history[this.hisIdx] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
-
-            this.hisIdx -= 1;
-            if (this.hisIdx < 0)
-                this.hisIdx = 0;
-
-            var historyItem = this.history[this.hisIdx];
+            this.history.SetCurrentItem(this.StatusText.Text, this.inReplyTo);
+            var historyItem = this.history.Back();
             this.inReplyTo = historyItem.InReplyTo;
             this.StatusText.Text = historyItem.Status;
             this.StatusText.SelectionStart = this.StatusText.Text.Length;
@@ -1179,14 +1103,8 @@ namespace OpenTween
 
         private void StatusTextHistoryForward()
         {
-            if (!string.IsNullOrWhiteSpace(this.StatusText.Text))
-                this.history[this.hisIdx] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
-
-            this.hisIdx += 1;
-            if (this.hisIdx > this.history.Count - 1)
-                this.hisIdx = this.history.Count - 1;
-
-            var historyItem = this.history[this.hisIdx];
+            this.history.SetCurrentItem(this.StatusText.Text, this.inReplyTo);
+            var historyItem = this.history.Forward();
             this.inReplyTo = historyItem.InReplyTo;
             this.StatusText.Text = historyItem.Status;
             this.StatusText.SelectionStart = this.StatusText.Text.Length;
@@ -1230,8 +1148,6 @@ namespace OpenTween
                     return;
             }
 
-            this.history[this.history.Count - 1] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
-
             if (this.settings.Common.Nicoms)
             {
                 this.StatusText.SelectionStart = this.StatusText.Text.Length;
@@ -1244,7 +1160,7 @@ namespace OpenTween
             var status = new PostStatusParams();
 
             var statusTextCompat = this.FormatStatusText(this.StatusText.Text);
-            if (this.GetRestStatusCount(statusTextCompat) >= 0)
+            if (this.GetRestStatusCount(statusTextCompat) >= 0 && this.tw.Api.AuthType == APIAuthType.OAuth1)
             {
                 // auto_populate_reply_metadata や attachment_url を使用しなくても 140 字以内に
                 // 収まる場合はこれらのオプションを使用せずに投稿する
@@ -1291,10 +1207,10 @@ namespace OpenTween
                 uploadService = this.ImageSelector.Model.GetService(serviceName);
             }
 
+            this.history.AddLast(this.StatusText.Text, this.inReplyTo);
+
             this.inReplyTo = null;
             this.StatusText.Text = "";
-            this.history.Add(new StatusTextHistory(""));
-            this.hisIdx = this.history.Count - 1;
             if (!this.settings.Common.FocusLockToStatusText)
                 this.CurrentListView.Focus();
             this.urlUndoBuffer = null;
@@ -2402,8 +2318,7 @@ namespace OpenTween
                             {
                                 // 自分が RT したツイート (自分が RT した自分のツイートも含む)
                                 //   => RT を取り消し
-                                await this.tw.Api.StatusesDestroy(post.StatusId.ToTwitterStatusId())
-                                    .IgnoreResponse();
+                                await this.tw.DeleteRetweet(post);
                             }
                             else
                             {
@@ -2413,15 +2328,13 @@ namespace OpenTween
                                     {
                                         // 他人に RT された自分のツイート
                                         //   => RT 元の自分のツイートを削除
-                                        await this.tw.Api.StatusesDestroy(post.RetweetedId.ToTwitterStatusId())
-                                            .IgnoreResponse();
+                                        await this.tw.DeleteTweet(post.RetweetedId.ToTwitterStatusId());
                                     }
                                     else
                                     {
                                         // 自分のツイート
                                         //   => ツイートを削除
-                                        await this.tw.Api.StatusesDestroy(post.StatusId.ToTwitterStatusId())
-                                            .IgnoreResponse();
+                                        await this.tw.DeleteTweet(post.StatusId.ToTwitterStatusId());
                                     }
                                 }
                             }
@@ -2585,18 +2498,7 @@ namespace OpenTween
                 {
                     this.settings.ApplySettings();
 
-                    if (MyCommon.IsNullOrEmpty(this.settings.Common.Token))
-                        this.tw.ClearAuthInfo();
-
-                    var account = this.settings.Common.SelectedAccount;
-                    if (account != null)
-                        this.tw.Initialize(account.GetTwitterAppToken(), account.Token, account.TokenSecret, account.Username, account.UserId);
-                    else
-                        this.tw.Initialize(TwitterAppToken.GetDefault(), "", "", "", 0L);
-
-                    this.tw.RestrictFavCheck = this.settings.Common.RestrictFavCheck;
-                    this.tw.ReadOwnPost = this.settings.Common.ReadOwnPost;
-
+                    this.accounts.LoadFromSettings(this.settings.Common);
                     this.ImageSelector.Model.InitializeServices(this.tw, this.tw.Configuration);
 
                     try
@@ -2685,7 +2587,7 @@ namespace OpenTween
 
                     try
                     {
-                        this.InitDetailHtmlFormat();
+                        this.detailsHtmlBuilder.Prepare(this.settings.Common, this.themeManager);
                     }
                     catch (Exception ex)
                     {
@@ -3563,14 +3465,17 @@ namespace OpenTween
             return this.FormatStatusText(statusText);
         }
 
+        internal string FormatStatusText(string statusText)
+            => this.FormatStatusText(statusText, Control.ModifierKeys);
+
         /// <summary>
         /// ツイート投稿前のフッター付与などの前処理を行います
         /// </summary>
-        private string FormatStatusText(string statusText)
+        internal string FormatStatusText(string statusText, Keys modifierKeys)
         {
             statusText = statusText.Replace("\r\n", "\n");
 
-            if (this.urlMultibyteSplit)
+            if (this.SeparateUrlAndFullwidthCharacter)
             {
                 // URLと全角文字の切り離し
                 statusText = Regex.Replace(statusText, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+", "$& ");
@@ -3589,14 +3494,14 @@ namespace OpenTween
             bool disableFooter;
             if (this.settings.Common.PostShiftEnter)
             {
-                disableFooter = MyCommon.IsKeyDown(Keys.Control);
+                disableFooter = MyCommon.IsKeyDown(modifierKeys, Keys.Control);
             }
             else
             {
-                if (this.StatusText.Multiline && !this.settings.Common.PostCtrlEnter)
-                    disableFooter = MyCommon.IsKeyDown(Keys.Control);
+                if (this.settings.Local.StatusMultiline && !this.settings.Common.PostCtrlEnter)
+                    disableFooter = MyCommon.IsKeyDown(modifierKeys, Keys.Control);
                 else
-                    disableFooter = MyCommon.IsKeyDown(Keys.Shift);
+                    disableFooter = MyCommon.IsKeyDown(modifierKeys, Keys.Shift);
             }
 
             if (statusText.Contains("RT @"))
@@ -4192,9 +4097,6 @@ namespace OpenTween
             this.DispSelectedPost();
         }
 
-        public string CreateDetailHtml(string orgdata)
-            => this.detailHtmlFormatPreparedTemplate.Replace("%CONTENT_HTML%", orgdata);
-
         private void DispSelectedPost()
             => this.DispSelectedPost(false);
 
@@ -4217,12 +4119,8 @@ namespace OpenTween
             if (!forceupdate && currentPost.Equals(oldDisplayPost))
                 return;
 
-            var loadTasks = new List<Task>
-            {
-                this.tweetDetailsView.ShowPostDetails(currentPost),
-            };
-
-            this.SplitContainer3.Panel2Collapsed = true;
+            var loadTasks = new TaskCollection();
+            loadTasks.Add(() => this.tweetDetailsView.ShowPostDetails(currentPost));
 
             if (this.settings.Common.PreviewEnable)
             {
@@ -4230,22 +4128,36 @@ namespace OpenTween
                 oldTokenSource?.Cancel();
 
                 var token = this.thumbnailTokenSource!.Token;
-                loadTasks.Add(this.tweetThumbnail1.Model.PrepareThumbnails(currentPost, token));
+                loadTasks.Add(() => this.PrepareThumbnailControl(currentPost, token));
             }
-
-            async Task DelayedTasks()
+            else
             {
-                try
-                {
-                    await Task.WhenAll(loadTasks);
-                }
-                catch (OperationCanceledException)
-                {
-                }
+                this.SplitContainer3.Panel2Collapsed = true;
             }
 
             // サムネイルの読み込みを待たずに次に選択されたツイートを表示するため await しない
-            _ = DelayedTasks();
+            _ = loadTasks
+                .IgnoreException(x => x is OperationCanceledException)
+                .RunAll();
+        }
+
+        private async Task PrepareThumbnailControl(PostClass post, CancellationToken token)
+        {
+            var prepareTask = this.tweetThumbnail1.Model.PrepareThumbnails(post, token);
+
+            var timeout = Task.Delay(100);
+            if ((await Task.WhenAny(prepareTask, timeout)) == timeout)
+            {
+                token.ThrowIfCancellationRequested();
+
+                // サムネイル情報の読み込みに時間が掛かっている場合は一旦サムネイル領域を非表示にする
+                this.SplitContainer3.Panel2Collapsed = true;
+            }
+
+            await prepareTask;
+            token.ThrowIfCancellationRequested();
+
+            this.SplitContainer3.Panel2Collapsed = !this.tweetThumbnail1.Model.ThumbnailAvailable;
         }
 
         private async void MatomeMenuItem_Click(object sender, EventArgs e)
@@ -5720,10 +5632,6 @@ namespace OpenTween
             this.ModifySettingCommon = false;
             lock (this.syncObject)
             {
-                this.settings.Common.UserName = this.tw.Username;
-                this.settings.Common.UserId = this.tw.UserId;
-                this.settings.Common.Token = this.tw.AccessToken;
-                this.settings.Common.TokenSecret = this.tw.AccessTokenSecret;
                 this.settings.Common.SortOrder = (int)this.statuses.SortOrder;
                 this.settings.Common.SortColumn = this.statuses.SortMode switch
                 {
@@ -5800,6 +5708,7 @@ namespace OpenTween
                         break;
                     case UserTimelineTabModel userTab:
                         tabSetting.User = userTab.ScreenName;
+                        tabSetting.UserId = userTab.UserId;
                         break;
                     case PublicSearchTabModel searchTab:
                         tabSetting.SearchWords = searchTab.SearchWords;
@@ -6965,8 +6874,8 @@ namespace OpenTween
                     ttl.Append("Ver:").Append(MyCommon.GetReadableVersion());
                     break;
                 case MyCommon.DispTitleEnum.Post:
-                    if (this.history != null && this.history.Count > 1)
-                        ttl.Append(this.history[this.history.Count - 2].Status.Replace("\r\n", " "));
+                    if (this.history.Peek() is { } lastItem)
+                        ttl.Append(lastItem.Status.Replace("\r\n", " "));
                     break;
                 case MyCommon.DispTitleEnum.UnreadRepCount:
                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText1, this.statuses.MentionTab.UnreadCount + this.statuses.DirectMessageTab.UnreadCount);
@@ -7075,17 +6984,22 @@ namespace OpenTween
 
             if (endpointName == null)
             {
+                var authByCookie = this.tw.Api.AuthType == APIAuthType.TwitterComCookie;
+
                 // 表示中のタブに応じて更新
                 endpointName = tabType switch
                 {
-                    MyCommon.TabUsageType.Home => GetTimelineRequest.EndpointName,
+                    MyCommon.TabUsageType.Home => "/statuses/home_timeline",
                     MyCommon.TabUsageType.UserDefined => "/statuses/home_timeline",
                     MyCommon.TabUsageType.Mentions => "/statuses/mentions_timeline",
                     MyCommon.TabUsageType.Favorites => "/favorites/list",
                     MyCommon.TabUsageType.DirectMessage => "/direct_messages/events/list",
-                    MyCommon.TabUsageType.UserTimeline => "/statuses/user_timeline",
-                    MyCommon.TabUsageType.Lists => "/lists/statuses",
-                    MyCommon.TabUsageType.PublicSearch => "/search/tweets",
+                    MyCommon.TabUsageType.UserTimeline =>
+                        authByCookie ? UserTweetsAndRepliesRequest.EndpointName : "/statuses/user_timeline",
+                    MyCommon.TabUsageType.Lists =>
+                        authByCookie ? ListLatestTweetsTimelineRequest.EndpointName : "/lists/statuses",
+                    MyCommon.TabUsageType.PublicSearch =>
+                        authByCookie ? SearchTimelineRequest.EndpointName : "/search/tweets",
                     MyCommon.TabUsageType.Related => "/statuses/show/:id",
                     _ => null,
                 };
@@ -7093,31 +7007,8 @@ namespace OpenTween
             }
             else
             {
-                // 表示中のタブに関連する endpoint であれば更新
-                bool update;
-                if (endpointName == GetTimelineRequest.EndpointName)
-                {
-                    update = tabType == MyCommon.TabUsageType.Home || tabType == MyCommon.TabUsageType.UserDefined;
-                }
-                else
-                {
-                    update = endpointName switch
-                    {
-                        "/statuses/mentions_timeline" => tabType == MyCommon.TabUsageType.Mentions,
-                        "/favorites/list" => tabType == MyCommon.TabUsageType.Favorites,
-                        "/direct_messages/events/list" => tabType == MyCommon.TabUsageType.DirectMessage,
-                        "/statuses/user_timeline" => tabType == MyCommon.TabUsageType.UserTimeline,
-                        "/lists/statuses" => tabType == MyCommon.TabUsageType.Lists,
-                        "/search/tweets" => tabType == MyCommon.TabUsageType.PublicSearch,
-                        "/statuses/show/:id" => tabType == MyCommon.TabUsageType.Related,
-                        _ => false,
-                    };
-                }
-
-                if (update)
-                {
-                    this.toolStripApiGauge.ApiEndpoint = endpointName;
-                }
+                var currentEndpointName = this.toolStripApiGauge.ApiEndpoint;
+                this.toolStripApiGauge.ApiEndpoint = currentEndpointName;
             }
         }
 
@@ -7223,56 +7114,56 @@ namespace OpenTween
             {
                 this.Visible = false;
             }
-            if (this.initialLayout && this.settings.Local != null && this.WindowState == FormWindowState.Normal && this.Visible)
+            if (this.WindowState != FormWindowState.Minimized)
             {
-                // 現在の DPI と設定保存時の DPI との比を取得する
-                var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
+                this.formWindowState = this.WindowState;
+            }
+        }
 
-                this.ClientSize = ScaleBy(configScaleFactor, this.settings.Local.FormSize);
+        private void ApplyLayoutFromSettings()
+        {
+            // 現在の DPI と設定保存時の DPI との比を取得する
+            var configScaleFactor = this.settings.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
 
-                // Splitterの位置設定
-                var splitterDistance = ScaleBy(configScaleFactor.Height, this.settings.Local.SplitterDistance);
-                if (splitterDistance > this.SplitContainer1.Panel1MinSize &&
-                    splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth)
-                {
-                    this.SplitContainer1.SplitterDistance = splitterDistance;
-                }
+            this.ClientSize = ScaleBy(configScaleFactor, this.settings.Local.FormSize);
 
-                // 発言欄複数行
-                this.StatusText.Multiline = this.settings.Local.StatusMultiline;
-                if (this.StatusText.Multiline)
-                {
-                    var statusTextHeight = ScaleBy(configScaleFactor.Height, this.settings.Local.StatusTextHeight);
-                    var dis = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth;
-                    if (dis > this.SplitContainer2.Panel1MinSize && dis < this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth)
-                    {
-                        this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth;
-                    }
-                    this.StatusText.Height = statusTextHeight;
-                }
-                else
+            // Splitterの位置設定
+            var splitterDistance = ScaleBy(configScaleFactor.Height, this.settings.Local.SplitterDistance);
+            if (splitterDistance > this.SplitContainer1.Panel1MinSize &&
+                splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth)
+            {
+                this.SplitContainer1.SplitterDistance = splitterDistance;
+            }
+
+            // 発言欄複数行
+            this.StatusText.Multiline = this.settings.Local.StatusMultiline;
+            if (this.StatusText.Multiline)
+            {
+                var statusTextHeight = ScaleBy(configScaleFactor.Height, this.settings.Local.StatusTextHeight);
+                var dis = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth;
+                if (dis > this.SplitContainer2.Panel1MinSize && dis < this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth)
                 {
-                    if (this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth > 0)
-                    {
-                        this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth;
-                    }
+                    this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - statusTextHeight - this.SplitContainer2.SplitterWidth;
                 }
-
-                var previewDistance = ScaleBy(configScaleFactor.Width, this.settings.Local.PreviewDistance);
-                if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth)
+                this.StatusText.Height = statusTextHeight;
+            }
+            else
+            {
+                if (this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth > 0)
                 {
-                    this.SplitContainer3.SplitterDistance = previewDistance;
+                    this.SplitContainer2.SplitterDistance = this.SplitContainer2.Height - this.SplitContainer2.Panel2MinSize - this.SplitContainer2.SplitterWidth;
                 }
-
-                // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない
-                this.SplitContainer3.Panel2Collapsed = true;
-
-                this.initialLayout = false;
             }
-            if (this.WindowState != FormWindowState.Minimized)
+
+            var previewDistance = ScaleBy(configScaleFactor.Width, this.settings.Local.PreviewDistance);
+            if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth)
             {
-                this.formWindowState = this.WindowState;
+                this.SplitContainer3.SplitterDistance = previewDistance;
             }
+
+            // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない
+            this.SplitContainer3.Panel2Collapsed = true;
+            this.initialLayout = false;
         }
 
         private void PlaySoundMenuItem_CheckedChanged(object sender, EventArgs e)
@@ -7988,30 +7879,39 @@ namespace OpenTween
         private async void TweenMain_Shown(object sender, EventArgs e)
         {
             this.NotifyIcon1.Visible = true;
+            this.StartTimers();
+
+            if (this.settings.IsFirstRun)
+            {
+                // 初回起動時だけ右下のメニューを目立たせる
+                this.HashStripSplitButton.ShowDropDown();
+            }
 
             if (this.IsNetworkAvailable())
             {
-                var loadTasks = new List<Task>
-                {
-                    this.RefreshMuteUserIdsAsync(),
-                    this.RefreshBlockIdsAsync(),
-                    this.RefreshNoRetweetIdsAsync(),
-                    this.RefreshTwitterConfigurationAsync(),
-                    this.RefreshTabAsync<HomeTabModel>(),
-                    this.RefreshTabAsync<MentionsTabModel>(),
-                    this.RefreshTabAsync<DirectMessagesTabModel>(),
-                    this.RefreshTabAsync<PublicSearchTabModel>(),
-                    this.RefreshTabAsync<UserTimelineTabModel>(),
-                    this.RefreshTabAsync<ListTimelineTabModel>(),
-                };
+                var loadTasks = new TaskCollection();
+
+                loadTasks.Add(new[]
+                {
+                    this.RefreshMuteUserIdsAsync,
+                    this.RefreshBlockIdsAsync,
+                    this.RefreshNoRetweetIdsAsync,
+                    this.RefreshTwitterConfigurationAsync,
+                    this.RefreshTabAsync<HomeTabModel>,
+                    this.RefreshTabAsync<MentionsTabModel>,
+                    this.RefreshTabAsync<DirectMessagesTabModel>,
+                    this.RefreshTabAsync<PublicSearchTabModel>,
+                    this.RefreshTabAsync<UserTimelineTabModel>,
+                    this.RefreshTabAsync<ListTimelineTabModel>,
+                });
 
                 if (this.settings.Common.StartupFollowers)
-                    loadTasks.Add(this.RefreshFollowerIdsAsync());
+                    loadTasks.Add(this.RefreshFollowerIdsAsync);
 
                 if (this.settings.Common.GetFav)
-                    loadTasks.Add(this.RefreshTabAsync<FavoritesTabModel>());
+                    loadTasks.Add(this.RefreshTabAsync<FavoritesTabModel>);
 
-                var allTasks = Task.WhenAll(loadTasks);
+                var allTasks = loadTasks.RunAll();
 
                 var i = 0;
                 while (true)
@@ -8051,23 +7951,31 @@ namespace OpenTween
                 }
 
                 // 取得失敗の場合は再試行する
-                var reloadTasks = new List<Task>();
+                var reloadTasks = new TaskCollection();
 
                 if (!this.tw.GetFollowersSuccess && this.settings.Common.StartupFollowers)
-                    reloadTasks.Add(this.RefreshFollowerIdsAsync());
+                    reloadTasks.Add(() => this.RefreshFollowerIdsAsync());
 
                 if (!this.tw.GetNoRetweetSuccess)
-                    reloadTasks.Add(this.RefreshNoRetweetIdsAsync());
+                    reloadTasks.Add(() => this.RefreshNoRetweetIdsAsync());
 
                 if (this.tw.Configuration.PhotoSizeLimit == 0)
-                    reloadTasks.Add(this.RefreshTwitterConfigurationAsync());
+                    reloadTasks.Add(() => this.RefreshTwitterConfigurationAsync());
 
-                await Task.WhenAll(reloadTasks);
+                await reloadTasks.RunAll();
             }
 
             this.initial = false;
+        }
+
+        private void StartTimers()
+        {
+            if (!this.StopRefreshAllMenuItem.Checked)
+                this.timelineScheduler.Enabled = true;
 
-            this.timelineScheduler.Enabled = true;
+            this.selectionDebouncer.Enabled = true;
+            this.saveConfigDebouncer.Enabled = true;
+            this.thumbGenerator.ImgAzyobuziNet.AutoUpdate = true;
         }
 
         private async Task DoGetFollowersMenu()
@@ -8143,18 +8051,17 @@ namespace OpenTween
         {
             if (!this.ExistCurrentPost) return;
             this.doFavRetweetFlags = true;
-            var retweetTask = this.DoReTweetOfficial(true);
+
+            var tasks = new TaskCollection();
+            tasks.Add(() => this.DoReTweetOfficial(true));
+
             if (this.doFavRetweetFlags)
             {
                 this.doFavRetweetFlags = false;
-                var favoriteTask = this.FavoriteChange(true, false);
-
-                await Task.WhenAll(retweetTask, favoriteTask);
-            }
-            else
-            {
-                await retweetTask;
+                tasks.Add(() => this.FavoriteChange(true, false));
             }
+
+            await tasks.RunAll();
         }
 
         private async Task FavoritesRetweetUnofficial()
@@ -8216,14 +8123,14 @@ namespace OpenTween
 
         private void MenuItemHelp_DropDownOpening(object sender, EventArgs e)
         {
-            if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock, Keys.Control, Keys.Shift))
+            if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock | Keys.Control | Keys.Shift))
                 this.DebugModeToolStripMenuItem.Visible = true;
             else
                 this.DebugModeToolStripMenuItem.Visible = false;
         }
 
         private void UrlMultibyteSplitMenuItem_CheckedChanged(object sender, EventArgs e)
-            => this.urlMultibyteSplit = ((ToolStripMenuItem)sender).Checked;
+            => this.SeparateUrlAndFullwidthCharacter = ((ToolStripMenuItem)sender).Checked;
 
         private void PreventSmsCommandMenuItem_CheckedChanged(object sender, EventArgs e)
             => this.preventSmsCommand = ((ToolStripMenuItem)sender).Checked;
@@ -8245,7 +8152,7 @@ namespace OpenTween
 
         private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e)
         {
-            this.UrlMultibyteSplitMenuItem.Checked = this.urlMultibyteSplit;
+            this.UrlMultibyteSplitMenuItem.Checked = this.SeparateUrlAndFullwidthCharacter;
             this.PreventSmsCommandMenuItem.Checked = this.preventSmsCommand;
             this.UrlAutoShortenMenuItem.Checked = this.settings.Common.UrlConvertAuto;
             this.IdeographicSpaceToSpaceMenuItem.Checked = this.settings.Common.WideSpaceConvert;
@@ -8255,7 +8162,7 @@ namespace OpenTween
 
         private void ContextMenuPostMode_Opening(object sender, CancelEventArgs e)
         {
-            this.UrlMultibyteSplitPullDownMenuItem.Checked = this.urlMultibyteSplit;
+            this.UrlMultibyteSplitPullDownMenuItem.Checked = this.SeparateUrlAndFullwidthCharacter;
             this.PreventSmsCommandPullDownMenuItem.Checked = this.preventSmsCommand;
             this.UrlAutoShortenPullDownMenuItem.Checked = this.settings.Common.UrlConvertAuto;
             this.IdeographicSpaceToSpacePullDownMenuItem.Checked = this.settings.Common.WideSpaceConvert;
@@ -9012,7 +8919,7 @@ namespace OpenTween
 
                 try
                 {
-                    var task = this.tw.Api.UsersShow(id);
+                    var task = this.tw.GetUserInfo(id);
                     user = await dialog.WaitForAsync(this, task);
                 }
                 catch (WebApiException ex)
@@ -9031,7 +8938,7 @@ namespace OpenTween
 
         private async Task DoShowUserStatus(TwitterUser user)
         {
-            using var userDialog = new UserInfoDialog(this, this.tw.Api);
+            using var userDialog = new UserInfoDialog(this, this.tw.Api, this.detailsHtmlBuilder);
             var showUserTask = userDialog.ShowUserAsync(user);
             userDialog.ShowDialog(this);
 
@@ -9498,9 +9405,6 @@ namespace OpenTween
             this.AboutMenuItem.Text = MyCommon.ReplaceAppName(this.AboutMenuItem.Text);
         }
 
-        private void TweetThumbnailControl_ThumbnailLoading(object sender, EventArgs e)
-            => this.SplitContainer3.Panel2Collapsed = false;
-
         private async void TwitterApiStatusToolStripMenuItem_Click(object sender, EventArgs e)
             => await MyCommon.OpenInBrowserAsync(this, Twitter.ServiceAvailabilityStatusUrl);