using System; using System.Drawing; using System.Text; using System.Windows.Forms; using CefSharp; using CefSharp.WinForms; using TweetDuck.Configuration; using TweetDuck.Core.Bridge; using TweetDuck.Core.Controls; using TweetDuck.Core.Handling; using TweetDuck.Core.Handling.General; using TweetDuck.Core.Notification; using TweetDuck.Core.Other.Interfaces; using TweetDuck.Core.Utils; using TweetDuck.Resources; namespace TweetDuck.Core{ sealed class TweetDeckBrowser : ITweetDeckBrowser, IDisposable{ public bool Ready { get; private set; } public bool Enabled{ get => browser.Enabled; set => browser.Enabled = value; } public bool IsTweetDeckWebsite{ get{ if (!Ready){ return false; } using(IFrame frame = browser.GetBrowser().MainFrame){ return TwitterUtils.IsTweetDeckWebsite(frame); } } } private readonly ChromiumWebBrowser browser; private string prevSoundNotificationPath = null; public TweetDeckBrowser(FormBrowser owner, TweetDeckBridge bridge){ RequestHandlerBrowser requestHandler = new RequestHandlerBrowser(); this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){ DialogHandler = new FileDialogHandler(), DragHandler = new DragHandlerBrowser(requestHandler), MenuHandler = new ContextMenuBrowser(owner), JsDialogHandler = new JavaScriptDialogHandler(), KeyboardHandler = new KeyboardHandlerBrowser(owner), LifeSpanHandler = new LifeSpanHandler(), RequestHandler = requestHandler }; this.browser.LoadingStateChanged += browser_LoadingStateChanged; this.browser.FrameLoadStart += browser_FrameLoadStart; this.browser.FrameLoadEnd += browser_FrameLoadEnd; this.browser.LoadError += browser_LoadError; this.browser.RegisterAsyncJsObject("$TD", bridge); this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb(); this.browser.Dock = DockStyle.None; this.browser.Location = ControlExtensions.InvisibleLocation; this.browser.SetupResourceHandler(TweetNotification.AppLogo); this.browser.SetupResourceHandler(TwitterUtils.LoadingSpinner); owner.Controls.Add(browser); Program.UserConfig.MuteToggled += UserConfig_MuteToggled; Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged; Program.UserConfig.SoundNotificationChanged += UserConfig_SoundNotificationInfoChanged; } // setup and management private void OnBrowserReady(){ if (!Ready){ browser.Location = Point.Empty; browser.Dock = DockStyle.Fill; Ready = true; } } public void Focus(){ browser.Focus(); } public void SetThrottle(bool throttle){ if (throttle && browser.Dock == DockStyle.Fill){ browser.Dock = DockStyle.None; browser.Size = Size.Empty; } else if (!throttle && browser.Dock == DockStyle.None){ browser.Dock = DockStyle.Fill; } } public void Dispose(){ Program.UserConfig.MuteToggled -= UserConfig_MuteToggled; Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged; Program.UserConfig.SoundNotificationChanged -= UserConfig_SoundNotificationInfoChanged; browser.Dispose(); } void ITweetDeckBrowser.RegisterBridge(string name, object obj){ browser.RegisterAsyncJsObject(name, obj); } void ITweetDeckBrowser.OnFrameLoaded(Action<IFrame> callback){ browser.FrameLoadEnd += (sender, args) => { IFrame frame = args.Frame; if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){ callback(frame); } }; } void ITweetDeckBrowser.ExecuteFunction(string name, params object[] args){ browser.ExecuteScriptAsync(name, args); } // event handlers private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ if (!e.IsLoading){ foreach(string word in TwitterUtils.DictionaryWords){ browser.AddWordToDictionary(word); } browser.BeginInvoke(new Action(OnBrowserReady)); browser.LoadingStateChanged -= browser_LoadingStateChanged; } } private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ IFrame frame = e.Frame; if (frame.IsMain){ if (Program.UserConfig.ZoomLevel != 100){ BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel); } if (TwitterUtils.IsTwitterWebsite(frame)){ ScriptLoader.ExecuteFile(frame, "twitter.js", browser); } frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride); } } private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ IFrame frame = e.Frame; if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){ UpdateProperties(); TweetDeckBridge.RestoreSessionData(frame); ScriptLoader.ExecuteFile(frame, "code.js", browser); ScriptLoader.ExecuteFile(frame, "update.js", browser); InjectBrowserCSS(); ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS); UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty); TweetDeckBridge.ResetStaticProperties(); if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){ ScriptLoader.ExecuteScript(frame, @"TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr"); } if (Program.UserConfig.FirstRun){ ScriptLoader.ExecuteFile(frame, "introduction.js", browser); } } } private void browser_LoadError(object sender, LoadErrorEventArgs e){ if (e.ErrorCode == CefErrorCode.Aborted){ return; } if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){ string errorPage = ScriptLoader.LoadResource("pages/error.html", true); if (errorPage != null){ browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error"); } } } private void UserConfig_MuteToggled(object sender, EventArgs e){ UpdateProperties(); } private void UserConfig_ZoomLevelChanged(object sender, EventArgs e){ BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel); } private void UserConfig_SoundNotificationInfoChanged(object sender, EventArgs e){ const string soundUrl = "https://ton.twimg.com/tduck/updatesnd"; bool hasCustomSound = Program.UserConfig.IsCustomSoundNotificationSet; if (prevSoundNotificationPath != Program.UserConfig.NotificationSoundPath){ browser.SetupResourceHandler(soundUrl, hasCustomSound ? SoundNotification.CreateFileHandler(Program.UserConfig.NotificationSoundPath) : null); prevSoundNotificationPath = Program.UserConfig.NotificationSoundPath; } browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Program.UserConfig.NotificationSoundVolume); } // external handling public void HideVideoOverlay(bool focus){ if (focus){ browser.GetBrowser().GetHost().SendFocusEvent(true); } browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()"); } // javascript calls public void ReloadToTweetDeck(){ browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'"); } public void UpdateProperties(){ browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser)); } public void InjectBrowserCSS(){ browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", false, browser)?.TrimEnd() ?? string.Empty); } public void ReinjectCustomCSS(string css){ browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); } public void OnMouseClickExtra(IntPtr param){ browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF); } public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){ browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl); } public void AddSearchColumn(string query){ browser.ExecuteScriptAsync("TDGF_performSearch", query); } public void TriggerTweetScreenshot(){ browser.ExecuteScriptAsync("TDGF_triggerScreenshot()"); } public void ReloadColumns(){ browser.ExecuteScriptAsync("TDGF_reloadColumns()"); } public void PlaySoundNotification(){ browser.ExecuteScriptAsync("TDGF_playSoundNotification()"); } public void ApplyROT13(){ browser.ExecuteScriptAsync("TDGF_applyROT13()"); } public void ShowUpdateNotification(string versionTag, string releaseNotes){ browser.ExecuteScriptAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes))); } } }