using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using TradeIdeas.XML; using System.Windows.Forms; using TradeIdeas.TIProData; using System.Web; using System.Net; using System.Text.RegularExpressions; using System.Xml; using TradeIdeas.ServerConnection; using System.Threading; using System.Net.Http; using System.IO; using System.Net.Http.Headers; using TradeIdeas.MiscSupport; using IBApi; namespace TradeIdeas.TIProGUI { public partial class SendToForm : Form { /// /// What strategy generated this alert? /// private readonly string _configUrl; /// /// This is a short version of . /// This will be null until the value is ready. /// The value will be filled in from a different thread! /// private volatile string _tinyConfig; private volatile string _tickyLink; private readonly string _twitterHashTag; private readonly int _maxTwitterMessageLength; private AlertForm.SocialNetwork _network; private Dictionary _symbolTranslations = new Dictionary(); private string _windowType; private string _urlPrefix; private string _tickyPrefix; private string _symbol; private string _twitterSymbol; private string _timeString = ""; private string _totalDataString = ""; private string _userMessage = ""; /// /// Remember the user's preference. Even though the window is recreated each time, /// this check box should come up in the same state. /// private static bool PreferredIncludeStrategyChecked = true; private Image _chart = null; public Image Chart { get { return _chart; } set { _chart = value; } } public SendToForm(AlertForm.SocialNetwork network, RowData alert, string config, string windowType, string initialMessage = "") { InitializeComponent(); if (network == AlertForm.SocialNetwork.FBOOK) { includeStrategyCheckbox.Checked = false; //per new Facebook policy, this must remain unchecked because we don't want "pre-filling" of content within textbox- even though user has control to delet it. } else { includeStrategyCheckbox.Checked = PreferredIncludeStrategyChecked; } Font = GuiEnvironment.FontSettings; Icon = GuiEnvironment.Icon; _network = network; _windowType = windowType; switch (_network) { case (AlertForm.SocialNetwork.TWEET): sendToSocialButton.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("SEND_TWITTER").PropertyForCulture("TEXT", "***"); break; case (AlertForm.SocialNetwork.FBOOK): sendToSocialButton.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("SEND_FACEBOOK").PropertyForCulture("TEXT", "***"); break; case (AlertForm.SocialNetwork.LINKEDIN): sendToSocialButton.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("SEND_LINKEDIN").PropertyForCulture("TEXT", "***"); break; case (AlertForm.SocialNetwork.STWITS): sendToSocialButton.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("SEND_STOCKTWITS").PropertyForCulture("TEXT", "***"); break; } if (network == AlertForm.SocialNetwork.FBOOK || _windowType == "CUSTOM_EOC" || _windowType == "AI_STRATEGY_TRADE") { includeStrategyCheckbox.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("INCLUDE_STRATEGY_IN_TWEET").PropertyForCulture("TEXT", "***"); } else { includeStrategyCheckbox.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("INCLUDE_STRATEGY_CONFIG").PropertyForCulture("TEXT", "***"); } cancelButton.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("CANCEL").PropertyForCulture("TEXT", "***"); _urlPrefix = GuiEnvironment.XmlConfig.Node("COLLABORATE_WINDOW").Node("LINK_PREFIX").Property("BASE"); _tickyPrefix = GuiEnvironment.XmlConfig.Node("SEND_TO_DYNAMIC").Node("PHRASES").Node("TICKY_PREFIX").Property("TEXT"); LoadSymbolTranslations(GuiEnvironment.XmlConfig); // The following was moved from DisplayRowData() string configPrefix = ""; switch (_windowType) { case "Alert": configPrefix = GuiEnvironment.XmlConfig.Node("ALERT_WINDOW").Node("CONFIG_PREFIX").PropertyForCulture("BASE", "***"); break; case "TopList": configPrefix = GuiEnvironment.XmlConfig.Node("TOPLIST_WINDOW").Node("CONFIG_PREFIX").PropertyForCulture("BASE", "***"); break; case "MultiStrategy": //individual strategy configPrefix = GuiEnvironment.XmlConfig.Node("ALERT_WINDOW").Node("CONFIG_PREFIX").PropertyForCulture("BASE", "***"); break; case "CompareCount": configPrefix = GuiEnvironment.XmlConfig.Node("ALERT_WINDOW").Node("CONFIG_PREFIX").PropertyForCulture("BASE", "***"); break; } _symbol = ""; if (null != alert) { _symbol = alert.GetSymbol(); } if (config != "") _configUrl = configPrefix + config; new Thread(RequestTinyUrl).Start(); _twitterHashTag = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("TWITTER_HASH").PropertyForCulture("TEXT", "***"); _maxTwitterMessageLength = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Property("TEXT", 140); string exchange = ""; if (null != alert) exchange = alert.GetExchange(); _twitterSymbol = makeTwitterSymbol(_symbol, exchange); Text = GuiEnvironment.XmlConfig.Node("ALERT_WINDOW").Node("PHRASES").Node("SEND_SYMBOL_TO").PropertyForCulture("TEXT", "***"); Text = Text.Replace("{symbol}", _symbol); string description = ""; if (null != alert && (alert.Data.ContainsKey(CommonAlertFields.DESCRIPTION) || alert.Data.ContainsKey(CommonAlertFields.ALT_DESCRIPTION))) //toplist doesn't have a "DESCRIPTION" { DescriptionFormatter formatter = new DescriptionFormatter(); description = formatter.FormatAltDescription(alert); } else description = initialMessage; _timeString = GetTimeString(alert); string initialMessageText = _twitterSymbol + " " + description + " " + /* _tinyConfig + " " + */ _twitterHashTag + _timeString; int startSelection = _twitterSymbol.Length + 1; int selectionLength = description.Length; if (network != AlertForm.SocialNetwork.FBOOK) { textBox1.Text = initialMessageText; } else { textBox1.Text = ""; } textBox1.Select(startSelection, selectionLength); } private void LoadSymbolTranslations(List appConfig) { foreach (XmlElement node in appConfig.Node("SOCIAL_NETWORKING").Node("TWITTER_SYMBOL_TRANSLATIONS").Enum()) { SymbolTranslation.SymbolTranslationArgs rule = new SymbolTranslation.SymbolTranslationArgs(); string pattern = node.Property("PATTERN", null); string replacement = node.Property("REPLACEMENT", null); string exchange = node.Property("EXCHANGE", null); if ((null == pattern) || (null == replacement) || (null == exchange)) continue; rule.Pattern = pattern; rule.Replacement = replacement; if (!_symbolTranslations.ContainsKey(exchange)) _symbolTranslations.Add(exchange, rule); } } /// /// This converts the time to a string. The string is in a format suitable for exporting /// to the world as a whole. It has nothing to do with the user's local time zone or other /// settings. /// /// /// The empty string or a string starting with a space. private static string GetTimeString(RowData alert) { try { DateTime? tryToDecode = alert.GetTime(); // Local time on user's machine. if (null == tryToDecode) { // There is no time, or we were not able to decode the time. This will become // important when we try to display top list entries. This shouldn't happen for // alerts, but as a general rule we assume any field can be missing or invalid. return ""; } DateTime currentAlertTimeLocalMachine = (DateTime)tryToDecode; //*Should* timestamp be displayed? If it's more that 2 min old from the time that //we bring up this form, then it should be displayed in the text box: TimeSpan interval = new TimeSpan(0, 2, 0); //Set up a 2 min. timespan; if ((ServerFormats.Now - currentAlertTimeLocalMachine) < interval) { return ""; //don't display if less than 2 min old. } XmlNode timeZoneConfig = GuiEnvironment.XmlConfig.Node("OPTIONS").Node("TIMEZONES").Node("TIME").Select(); // This is a unique id that we're looking up, not something to display for the user, so // we do NOT want it localized. string requestedTimeZone = timeZoneConfig.Property("TEXT", "***"); TimeZoneInfo displayTimeZone = TimeZoneInfo.FindSystemTimeZoneById(requestedTimeZone); DateTime dateTimeToDisplay = GuiEnvironment.ConvertTimeCheckingKindProperty(currentAlertTimeLocalMachine, ServerFormats.CurrentTimeZone, displayTimeZone); string shortTimeZoneName; if (displayTimeZone.IsDaylightSavingTime(dateTimeToDisplay)) // It seems like C# should provide a function for this. But it does not, so we store // it in the XML file. We do not try to localize this. These are standard well known // values that are made to be shared around the world. shortTimeZoneName = timeZoneConfig.Property("SHORT_DAYLIGHT"); else shortTimeZoneName = timeZoneConfig.Property("SHORT_STANDARD"); string datePortion = ""; DateTime currentTimeInDisplayTimeZone = TimeZoneInfo.ConvertTime(DateTime.Now, displayTimeZone); if (dateTimeToDisplay.Date != currentTimeInDisplayTimeZone.Date) datePortion = " " + dateTimeToDisplay.ToShortDateString(); return " " + dateTimeToDisplay.ToString("H:mm") + shortTimeZoneName + datePortion; //timestamp that will be displayed in 24hr format. } catch { // I don't expect to get here. I've tried to handle the expected errors above. // But this code is very complicated and takes in a lot of inputs from a lot of // places. return ""; } } /// /// This requests the tiny url from a remote server. This is usually quick, but /// sometimes it can take a long time. It's best to run this in a thread. /// private void RequestTinyUrl() { if (null != _configUrl && _configUrl != "") _tinyConfig = GuiEnvironment.makeTinyURL(_urlPrefix + _configUrl); if (_windowType == "CUSTOM_EOC") _tinyConfig = "EOC_" + _symbol; if(_windowType == "AI_STRATEGY_TRADE") _tinyConfig = "AI_" + _symbol; _tickyLink = GuiEnvironment.makeTinyURL(_tickyPrefix + _symbol); if (null != _tickyLink) // Success! this.InvokeIfRequired(TinyUrlAvailable); } /// /// Updates the GUI with the current tiny url. /// private void TinyUrlAvailable() { UpdateBasedOnStrategyCheckbox(); includeStrategyCheckbox.Enabled = true; } private string makeTwitterSymbol(string symbol, string exchange) { if (_symbolTranslations.ContainsKey(exchange)) { SymbolTranslation.SymbolTranslationArgs rule = _symbolTranslations[exchange]; string replacementSymbol = symbol; try { if (Regex.IsMatch(symbol, rule.Pattern)) { replacementSymbol = Regex.Replace(symbol, rule.Pattern, rule.Replacement); } } catch { } return "$" + replacementSymbol; } else { return "$" + symbol; } } private void cancelButton_Click(object sender, EventArgs e) { this.Close(); } private void sendToSocialButton_Click(object sender, EventArgs e) { switch (_network) { case (AlertForm.SocialNetwork.TWEET): SendToTwitter(); break; case (AlertForm.SocialNetwork.FBOOK): SendToFacebook(); break; case (AlertForm.SocialNetwork.LINKEDIN): SendToLinkedIn(); break; case (AlertForm.SocialNetwork.STWITS): SendToStockTwits(); break; } // Remember this for next time. PreferredIncludeStrategyChecked = includeStrategyCheckbox.Checked; } private void includeStrategyCheckbox_CheckedChanged(object sender, EventArgs e) { UpdateBasedOnStrategyCheckbox(); textBox1.Focus(); } /// /// Update the message to match the "Include Strategy" check box. It is explicitly /// safe to call this function more than once. (I.e. this function is idempotent.) /// private void UpdateBasedOnStrategyCheckbox() { if ( (null != _configUrl && null == _tinyConfig) && null == _tickyLink) // We're not ready yet! return; string existingMessage = textBox1.Text; string newMessage = ""; if (includeStrategyCheckbox.Checked) { // Delete the old strategy. This should not exist, but if it does, get // rid of it so we don't have a duplicate. if (_network == AlertForm.SocialNetwork.FBOOK) { string message1 = existingMessage.Replace(_twitterSymbol + " ", ""); string message2 = message1.Replace(_twitterHashTag + " ", ""); newMessage = message2.Replace(_tickyLink, ""); // Strategy link is embedded not here in the message string, but in the actual app display itself. newMessage = _twitterSymbol + " " + _twitterHashTag + " " + _tickyLink + " " + existingMessage; } else { newMessage = existingMessage.Replace(_tinyConfig + " ", ""); newMessage = existingMessage.Replace(" " + _tickyLink, ""); // Insert the new strategy right before the hash tag. if(_windowType != "CUSTOM_EOC" && _windowType != "AI_STRATEGY_TRADE" && _windowType != "CHART") { newMessage = newMessage.Replace(" " + _twitterHashTag, " " + _tinyConfig + " " + _twitterHashTag + " " + _tickyLink); } else { if (_totalDataString == "" || _totalDataString == null) { _totalDataString = _twitterSymbol + " " + _twitterHashTag + " " + _tickyLink + " " + _timeString + " "; } newMessage = _totalDataString + _userMessage ; } } } else { if (_windowType == "CUSTOM_EOC" || _windowType == "AI_STRATEGY_TRADE" || _windowType == "CHART") { string message1 = existingMessage.Replace(_twitterSymbol + " ", ""); string message2 = message1.Replace(_twitterHashTag + " ", ""); string message3 = message2.Replace(_tickyLink + " ", ""); if (_timeString.Length > 0) { _userMessage = (message3.Replace(_timeString, "")).Trim(); newMessage = _userMessage; } } else if (_network == AlertForm.SocialNetwork.FBOOK) { string message1 = existingMessage.Replace(_twitterSymbol + " ", ""); string message2 = message1.Replace(_twitterHashTag + " ", ""); newMessage = (message2.Replace(_tickyLink, "")).Trim(); } else { newMessage = existingMessage.Replace(_tinyConfig + " ", ""); } } int startSelection = textBox1.SelectionStart; int selectionLength = textBox1.SelectionLength; textBox1.Text = newMessage ; try { textBox1.Select(startSelection, selectionLength); } catch { // The mesage could be shorter now than before. } } private void textBox1_TextChanged(object sender, EventArgs e) { int messageLength = textBox1.Text.Length; messageLengthLabel.Text = messageLength + " " + GuiEnvironment.XmlConfig.Node("ALERT_WINDOW").Node("PHRASES").Node("CHARACTERS").PropertyForCulture("TEXT", "***"); if (messageLength > _maxTwitterMessageLength) { messageLengthLabel.ForeColor = Color.Red; sendToSocialButton.Enabled = false; } else { messageLengthLabel.ForeColor = Color.Black; sendToSocialButton.Enabled = true; } } private void SendToTwitter() { sendToSocialButton.Enabled = false; string outgoingMessage = textBox1.Text; new Thread((ThreadStart)delegate { SocialSettings socialSettings = new SocialSettings(); bool IsTwitterEnhanced = !string.IsNullOrEmpty(socialSettings.oAuthTokenTwitter) && !string.IsNullOrEmpty(socialSettings.oAuthTokenSecretTwitter); //Sends tweet automatically or by a in-browser composed message //depending on whether the user's twitter account is configured in //the Settings or not and, also, the use of the external composer //is not assigned in the global settings. if (IsTwitterEnhanced && !GuiEnvironment.TwitterUseExternalComposer) { TwitterRequest request = new TwitterRequest(outgoingMessage, this); // Thread.Sleep(5000); // Test for a possible race condition. this.InvokeIfRequired((MethodInvoker)delegate { txtComments.ForeColor = request.SuccessColor; txtComments.Text = request.Message; changeCancelButtonLabel(request.SuccessColor); // Allow someone to try again, but only on failure. //sendToSocialButton.Enabled = !request.Success; if (!request.Success) { OpenTwitterComposer(outgoingMessage); } }); } else { OpenTwitterComposer(outgoingMessage); } }).Start(); } //Allows sending a message to Twitter using an external composer in case the connection is //not configured or in case the request fails. private void OpenTwitterComposer(string message) { oAuthTwitter oAuth = new oAuthTwitter(); var outgoingMessage = oAuth.UrlEncode(message); System.Diagnostics.Process.Start($"https://twitter.com/intent/tweet?text={outgoingMessage}"); } private void SendToFacebook() { sendToSocialButton.Enabled = false; string outgoingMessage = textBox1.Text; new Thread((ThreadStart)delegate { FacebookRequest request = new FacebookRequest(outgoingMessage, this, _tinyConfig); // Thread.Sleep(5000); // Test for a possible race condition. this.InvokeIfRequired((MethodInvoker)delegate { txtComments.ForeColor = request.SuccessColor; txtComments.Text = request.Message; changeCancelButtonLabel(request.SuccessColor); // Allow someone to try again, but only on failure. sendToSocialButton.Enabled = !request.Success; }); }).Start(); } private void SendToLinkedIn() { sendToSocialButton.Enabled = false; //Sending to linkedin requires that the "payload" be in xml... UPDATE.. It might be able to be sent via JSON. However this is being put on hold because the LinkedIn option is being suspended for now. //string outgoingMessage = textBox1.Text; //string xml = ""; //string outgoing = xml + "" + GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("FB_GREAT_STRATEGY").PropertyForCulture("TEXT", "***") + " " + outgoingMessage + " " + "" + GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("LI_CAPTION").PropertyForCulture("TEXT", "***") + " " + GuiEnvironment.XmlConfig.Node("COLLABORATE_WINDOW").Node("PHRASES").Node("LINK").PropertyForCulture("TEXT", "***") + "http://i1214.photobucket.com/albums/cc481/TradeIdeas/TradeIdeas_3_40x40.pngconnections-only"; string outgoing = textBox1.Text; new Thread((ThreadStart)delegate { LinkedInRequest request = new LinkedInRequest(outgoing, this); // Thread.Sleep(5000); // Test for a possible race condition. this.InvokeIfRequired((MethodInvoker)delegate { txtComments.ForeColor = request.SuccessColor; txtComments.Text = request.Message; changeCancelButtonLabel(request.SuccessColor); // Allow someone to try again, but only on failure. sendToSocialButton.Enabled = !request.Success; }); }).Start(); } private void SendToStockTwits() { sendToSocialButton.Enabled = false; // string outgoingMessage = textBox1.Text; if (null == _chart) { string outgoingMessage = "body=" + textBox1.Text; new Thread((ThreadStart)delegate { StockTwitRequest request = new StockTwitRequest(outgoingMessage, this); // Thread.Sleep(5000); // Test for a possible race condition. this.InvokeIfRequired((MethodInvoker)delegate { txtComments.ForeColor = request.SuccessColor; txtComments.Text = request.Message; changeCancelButtonLabel(request.SuccessColor); // Allow someone to try again, but only on failure. sendToSocialButton.Enabled = !request.Success; }); }).Start(); } else { new Thread((ThreadStart)delegate { bool success = PostToStockTwits(); this.InvokeIfRequired((MethodInvoker)delegate { if (success) txtComments.ForeColor = Color.Green; else txtComments.ForeColor = Color.Red; if (success) txtComments.Text = "Success!"; else txtComments.Text = _errorMessage; changeCancelButtonLabel(Color.Green); // Allow someone to try again, but only on failure. sendToSocialButton.Enabled = !success; }); }).Start(); } } private string SaveChartToTempFile() { string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".png"; _chart.Save(fileName); return fileName; } private string _errorMessage = ""; private bool PostToStockTwits() { bool result = false; oAuthStockTwits oAuth = new oAuthStockTwits(); StockTwitRequest.GetStockTwitsToken(this, oAuth); SocialSettings socialSettings = new SocialSettings(); HttpContent bodyContent = new StringContent(textBox1.Text); HttpContent tokenContent = new StringContent(socialSettings.oAuthTokenStockTwits); string fileName = SaveChartToTempFile(); FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); HttpContent imageContent = new StreamContent(stream); imageContent.LoadIntoBufferAsync(stream.Length); imageContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data"); imageContent.Headers.ContentDisposition.Name = "\"chart\""; imageContent.Headers.ContentDisposition.FileName = "\"chart.png\""; imageContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); string url = "https://api.stocktwits.com/api/2/messages/create.json"; using (var client = new HttpClient()) { MultipartFormDataContent formData = new MultipartFormDataContent(); formData.Add(tokenContent, "\"access_token\""); formData.Add(bodyContent, "\"body\""); formData.Add(imageContent, "chart", "chart.png"); var response = client.PostAsync(url, formData).Result; try { if (response.IsSuccessStatusCode) { _errorMessage = ""; result = true; } else { _errorMessage = response.ReasonPhrase; result = false; } } catch (Exception e) { _errorMessage = e.Message; result = false; } } if (File.Exists(fileName)) File.Delete(fileName); return result; } /// /// If there is a successful message sent, the Cancel button's /// label should be "Done" /// /// Color of the label private void changeCancelButtonLabel(Color c) { if (c == Color.Green) //successful message { cancelButton.Text = GuiEnvironment.XmlConfig.Node("SOCIAL_NETWORKING").Node("PHRASES").Node("DONE").PropertyForCulture("TEXT", "***"); } } } /// /// I need to find a better place for this! /// public static class MiscHelper { /// /// This will try to execute a block of code in the GUI thread. If the control has /// been destroyed, the code will not execute. It is safe to call this from the GUI /// thread, in which case we will just call the code immediately. It is a good idea /// to call this function (or at least InvokeRequired) even if you know that you are /// in the wrong thread. Calling Control.Invoke will fail if the control has been /// destroyed. Calling IsDisposed without calling InvokeRequired can lead to a race /// condition. /// /// We use this to access the GUI thread. /// We try to execute this code. public static void InvokeIfRequired(this Control control, MethodInvoker code) { if (control.InvokeRequired) { try { // Thread.Sleep(5000); // Test for a possible race condition. control.Invoke(code); } catch (InvalidOperationException) { // This probably means that the window was disposed of after the call to InvokeRequired // but before the call to Invoke(). // Note: Most of our code does not check for this race condition. It probably should. // This probably doesn't happen very often. } } else if (!control.IsDisposed) code(); } /// /// Similar to but this calls BeginInvoke rather than Invoke. /// It seems like most people probably want this. But not everyone. We got a few strange /// bugs when we tried to move everyone to this. /// /// If you have a choice, you should use this rather than InvokeIfRequired. You don't want /// the TCP/IP read thread to be locked up while doing a slow GUI operation. /// /// /// public static void BeginInvokeIfRequired(this Control control, MethodInvoker code) { if (control.InvokeRequired) { try { // Thread.Sleep(5000); // Test for a possible race condition. control.BeginInvoke(code); } catch (InvalidOperationException) { // This probably means that the window was disposed of after the call to InvokeRequired // but before the call to Invoke(). // Note: Most of our code does not check for this race condition. It probably should. // This probably doesn't happen very often. } } else if (!control.IsDisposed) code(); } } }