Lean  $LEAN_TAG$
Notification.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Text;
19 using System.Text.RegularExpressions;
20 using Newtonsoft.Json;
21 using QuantConnect.Util;
22 
24 {
25  /// <summary>
26  /// Local/desktop implementation of messaging system for Lean Engine.
27  /// </summary>
28  [JsonConverter(typeof(NotificationJsonConverter))]
29  public abstract class Notification
30  {
31  /// <summary>
32  /// Method for sending implementations of notification object types.
33  /// </summary>
34  /// <remarks>SMS, Email and Web are all handled by the QC Messaging Handler. To implement your own notification type implement it here.</remarks>
35  public virtual void Send()
36  {
37  //
38  }
39  }
40 
41  /// <summary>
42  /// Web Notification Class
43  /// </summary>
45  {
46  /// <summary>
47  /// Optional email headers
48  /// </summary>
49  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
50  public Dictionary<string, string> Headers { get; set; }
51 
52  /// <summary>
53  /// Send a notification message to this web address
54  /// </summary>
55  public string Address { get; set; }
56 
57  /// <summary>
58  /// Object data to send.
59  /// </summary>
60  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
61  public object Data { get; set; }
62 
63  /// <summary>
64  /// Constructor for sending a notification SMS to a specified phone number
65  /// </summary>
66  /// <param name="address">Address to send to</param>
67  /// <param name="data">Data to send</param>
68  /// <param name="headers">Optional headers to use</param>
69  public NotificationWeb(string address, object data = null, Dictionary<string, string> headers = null)
70  {
71  Address = address;
72  Data = data;
73  Headers = headers;
74  }
75  }
76 
77  /// <summary>
78  /// Sms Notification Class
79  /// </summary>
81  {
82  /// <summary>
83  /// Send a notification message to this phone number
84  /// </summary>
85  public string PhoneNumber { get; set; }
86 
87  /// <summary>
88  /// Message to send. Limited to 160 characters
89  /// </summary>
90  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
91  public string Message { get; set; }
92 
93  /// <summary>
94  /// Constructor for sending a notification SMS to a specified phone number
95  /// </summary>
96  /// <param name="number"></param>
97  /// <param name="message"></param>
98  public NotificationSms(string number, string message)
99  {
100  PhoneNumber = number;
101  Message = message;
102  }
103  }
104 
105  /// <summary>
106  /// Email notification data.
107  /// </summary>
109  {
110  /// <summary>
111  /// Optional email headers
112  /// </summary>
113  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
114  public Dictionary<string, string> Headers { get; set; }
115 
116  /// <summary>
117  /// Send to address:
118  /// </summary>
119  public string Address { get; set; }
120 
121  /// <summary>
122  /// Email subject
123  /// </summary>
124  public string Subject { get; set; }
125 
126  /// <summary>
127  /// Message to send.
128  /// </summary>
129  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
130  public string Message { get; set; }
131 
132  /// <summary>
133  /// Email Data
134  /// </summary>
135  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
136  public string Data { get; set; }
137 
138  /// <summary>
139  /// Default constructor for sending an email notification
140  /// </summary>
141  /// <param name="address">Address to send to. Will throw <see cref="ArgumentException"/> if invalid
142  /// <see cref="Validate.EmailAddress"/></param>
143  /// <param name="subject">Subject of the email. Will set to <see cref="string.Empty"/> if null</param>
144  /// <param name="message">Message body of the email. Will set to <see cref="string.Empty"/> if null</param>
145  /// <param name="data">Data to attach to the email. Will set to <see cref="string.Empty"/> if null</param>
146  /// <param name="headers">Optional email headers to use</param>
147  public NotificationEmail(string address, string subject = "", string message = "", string data = "", Dictionary<string, string> headers = null)
148  {
149  if (!Validate.EmailAddress(address))
150  {
151  throw new ArgumentException(Messages.NotificationEmail.InvalidEmailAddress(address));
152  }
153 
154  Address = address;
155  Data = data ?? string.Empty;
156  Message = message ?? string.Empty;
157  Subject = subject ?? string.Empty;
158  Headers = headers;
159  }
160  }
161 
162  /// <summary>
163  /// Telegram notification data
164  /// </summary>
166  {
167  /// <summary>
168  /// Send a notification message to this user on Telegram
169  /// Can be either a personal ID or Group ID.
170  /// </summary>
171  public string Id { get; set; }
172 
173  /// <summary>
174  /// Message to send. Limited to 4096 characters
175  /// </summary>
176  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
177  public string Message { get; set; }
178 
179  /// <summary>
180  /// Token to use
181  /// </summary>
182  [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
183  public string Token { get; set; }
184 
185  /// <summary>
186  /// Constructor for sending a telegram notification to a specific User ID
187  /// or group ID. Note: The bot must have an open chat with the user or be
188  /// added to the group for messages to deliver.
189  /// </summary>
190  /// <param name="id">User Id or Group Id to send the message too</param>
191  /// <param name="message">Message to send</param>
192  /// <param name="token">Bot token to use, if null defaults to "telegram-token"
193  /// in config on send</param>
194  public NotificationTelegram(string id, string message, string token = null)
195  {
196  Id = id;
197  Message = message;
198  Token = token;
199  }
200  }
201 
202  /// <summary>
203  /// FTP notification data
204  /// </summary>
206  {
207  private static readonly Regex HostnameProtocolRegex = new(@"^[s]?ftp\:\/\/", RegexOptions.IgnoreCase | RegexOptions.Compiled);
208 
209  private const int DefaultPort = 21;
210 
211  /// <summary>
212  /// Whether to use SFTP or FTP.
213  /// </summary>
214  [JsonProperty("secure")]
215  public bool Secure { get; }
216 
217  /// <summary>
218  /// The FTP server hostname.
219  /// </summary>
220  [JsonProperty("host")]
221  public string Hostname { get; }
222 
223  /// <summary>
224  /// The FTP server port.
225  /// </summary>
226  [JsonProperty("port")]
227  public int Port { get; }
228 
229  /// <summary>
230  /// The FTP server username.
231  /// </summary>
232  [JsonProperty("username")]
233  public string Username { get; }
234 
235  /// <summary>
236  /// The FTP server password.
237  /// </summary>
238  [JsonProperty("password", DefaultValueHandling = DefaultValueHandling.Ignore)]
239  public string Password { get; }
240 
241  /// <summary>
242  /// The path to file on the FTP server.
243  /// </summary>
244  [JsonProperty("fileDestinationPath")]
245  public string FilePath { get; }
246 
247  /// <summary>
248  /// The contents of the file to send.
249  /// </summary>
250  [JsonProperty("fileContent")]
251  public string FileContent { get; private set; }
252 
253  /// <summary>
254  /// The private key to use for authentication (optional).
255  /// </summary>
256  [JsonProperty("privateKey", DefaultValueHandling = DefaultValueHandling.Ignore)]
257  public string PrivateKey { get; }
258 
259  /// <summary>
260  /// The passphrase for the private key (optional).
261  /// </summary>
262  [JsonProperty("passphrase", DefaultValueHandling = DefaultValueHandling.Ignore)]
263  public string PrivateKeyPassphrase { get; }
264 
265  private NotificationFtp(string hostname, string username, string filePath, byte[] fileContent, bool secure, int? port)
266  {
267  Hostname = NormalizeHostname(hostname);
268  Port = port ?? DefaultPort;
269  Username = username;
270  FilePath = filePath;
271  FileContent = Convert.ToBase64String(fileContent);
272  Secure = secure;
273  }
274 
275  /// <summary>
276  /// Constructor for a notification to sent as a file to an FTP server using password authentication.
277  /// </summary>
278  /// <param name="hostname">FTP server hostname</param>
279  /// <param name="username">The FTP server username</param>
280  /// <param name="password">The FTP server password</param>
281  /// <param name="filePath">The path to file on the FTP server</param>
282  /// <param name="fileContent">The contents of the file</param>
283  /// <param name="secure">Whether to use SFTP or FTP. Defaults to true</param>
284  /// <param name="port">The FTP server port. Defaults to 21</param>
285  public NotificationFtp(string hostname, string username, string password, string filePath, byte[] fileContent,
286  bool secure = true, int? port = null)
287  : this(hostname, username, filePath, fileContent, secure, port)
288  {
289  if (string.IsNullOrEmpty(password))
290  {
291  throw new ArgumentException(Messages.NotificationFtp.MissingPassword);
292  }
293 
294  Password = password;
295  }
296 
297  /// <summary>
298  /// Constructor for a notification to sent as a file to an FTP server over SFTP using SSH keys.
299  /// </summary>
300  /// <param name="hostname">FTP server hostname</param>
301  /// <param name="username">The FTP server username</param>
302  /// <param name="privateKey">The private SSH key to use for authentication</param>
303  /// <param name="privateKeyPassphrase">The optional passphrase to decrypt the private key.
304  /// This can be empty or null if the private key is not encrypted</param>
305  /// <param name="filePath">The path to file on the FTP server</param>
306  /// <param name="fileContent">The contents of the file</param>
307  /// <param name="port">The FTP server port. Defaults to 21</param>
308  public NotificationFtp(string hostname, string username, string privateKey, string privateKeyPassphrase,
309  string filePath, byte[] fileContent, int? port = null)
310  : this(hostname, username, filePath, fileContent, true, port)
311  {
312  if (string.IsNullOrEmpty(privateKey))
313  {
314  throw new ArgumentException(Messages.NotificationFtp.MissingSSHKey);
315  }
316 
317  PrivateKey = privateKey;
318  PrivateKeyPassphrase = privateKeyPassphrase;
319  }
320 
321  /// <summary>
322  /// Constructor for a notification to sent as a file to an FTP server using password authentication.
323  /// </summary>
324  /// <param name="hostname">FTP server hostname</param>
325  /// <param name="username">The FTP server username</param>
326  /// <param name="password">The FTP server password</param>
327  /// <param name="filePath">The path to file on the FTP server</param>
328  /// <param name="fileContent">The contents of the file</param>
329  /// <param name="secure">Whether to use SFTP or FTP. Defaults to true</param>
330  /// <param name="port">The FTP server port. Defaults to 21</param>
331  public NotificationFtp(string hostname, string username, string password, string filePath, string fileContent,
332  bool secure = true, int? port = null)
333  : this(hostname, username, password, filePath, Encoding.ASCII.GetBytes(fileContent), secure, port)
334  {
335  }
336 
337  /// <summary>
338  /// Constructor for a notification to sent as a file to an FTP server over SFTP using SSH keys.
339  /// </summary>
340  /// <param name="hostname">FTP server hostname</param>
341  /// <param name="username">The FTP server username</param>
342  /// <param name="privateKey">The private SSH key to use for authentication</param>
343  /// <param name="privateKeyPassphrase">The optional passphrase to decrypt the private key.
344  /// This can be empty or null if the private key is not encrypted</param>
345  /// <param name="filePath">The path to file on the FTP server</param>
346  /// <param name="fileContent">The contents of the file</param>
347  /// <param name="port">The FTP server port. Defaults to 21</param>
348  public NotificationFtp(string hostname, string username, string privateKey, string privateKeyPassphrase,
349  string filePath, string fileContent, int? port = null)
350  : this(hostname, username, privateKey, privateKeyPassphrase, filePath, Encoding.ASCII.GetBytes(fileContent), port)
351  {
352  }
353 
354  private static string NormalizeHostname(string hostname)
355  {
356  // Remove trailing slashes
357  hostname = hostname.Trim().TrimEnd('/');
358  // Remove protocol if present
359  return HostnameProtocolRegex.Replace(hostname, "");
360  }
361 
362  /// <summary>
363  /// Factory method for Json deserialization: the file contents are already encoded
364  /// </summary>
365  internal static NotificationFtp FromEncodedData(string hostname, string username, string password, string filePath, string encodedFileContent,
366  bool secure, int? port)
367  {
368  var notification = new NotificationFtp(hostname, username, password, filePath, Array.Empty<byte>(), secure, port);
369  notification.FileContent = encodedFileContent;
370  return notification;
371  }
372 
373  /// <summary>
374  /// Factory method for Json deserialization: the file contents are already encoded
375  /// </summary>
376  internal static NotificationFtp FromEncodedData(string hostname, string username, string privateKey, string privateKeyPassphrase,
377  string filePath, string encodedFileContent, int? port)
378  {
379  var notification = new NotificationFtp(hostname, username, privateKey, privateKeyPassphrase, filePath, Array.Empty<byte>(), port);
380  notification.FileContent = encodedFileContent;
381  return notification;
382  }
383  }
384 
385  /// <summary>
386  /// Extension methods for <see cref="Notification"/>
387  /// </summary>
388  public static class NotificationExtensions
389  {
390  /// <summary>
391  /// Check if the notification can be sent (implements the <see cref="Notification.Send"/> method)
392  /// </summary>
393  /// <param name="notification">The notification</param>
394  /// <returns>Whether the notification can be sent</returns>
395  public static bool CanSend(this Notification notification)
396  {
397  if (notification == null)
398  {
399  return false;
400  }
401 
402  var type = notification.GetType();
403  return type != typeof(NotificationEmail) &&
404  type != typeof(NotificationWeb) &&
405  type != typeof(NotificationSms) &&
406  type != typeof(NotificationTelegram) &&
407  type != typeof(NotificationFtp);
408  }
409  }
410 }