using System; using System.Collections; using System.IO; using System.Threading; using System.Windows.Forms; using DotMSN; using Timer = System.Windows.Forms.Timer; namespace Icarus.MsnBot { public class MsnBotForm : System.Windows.Forms.Form { #region Controls private System.Windows.Forms.TextBox txtEmail; private System.Windows.Forms.TextBox txtPassword; private System.Windows.Forms.Label lblEmail; private System.Windows.Forms.Label lblPassword; private System.Windows.Forms.Button btnSignIn; private System.Windows.Forms.NotifyIcon trayIcon; private System.ComponentModel.IContainer components; private System.Windows.Forms.ContextMenu trayIconMenu; private System.Windows.Forms.MenuItem mnuViewLog; private System.Windows.Forms.MenuItem mnuViewMsgLog; private System.Windows.Forms.MenuItem mnuShutdown; private System.Windows.Forms.MenuItem mnuSeperator; #endregion #region Constants private const string BOT_FILE = "MsnBot.bot"; private const int MAX_LOGON_ATTEMPTS = 3; private const int CONNECT_ATTEMPT_INTERVAL = 10 * 1000; private const int LOGON_ATTEMPT_INTERVAL = 10 * 1000; private const int CLEANUP_INTERVAL = 10 * 60 * 1000; private const string VERSION = "MsnBot v0.2"; #endregion #region Member variables //Msn connection and conversation information private Messenger msn; private Hashtable conversationsInfo; //Loggers and their windows private Logger cmdLog; private Logger msgLog; private LogForm cmdLogWnd; private LogForm msgLogWnd; //Timers for cleanup and reconnect attempts private Timer cleanupTimer; private Timer connectTimer; private Timer logonTimer; //Variables for reconnect logic private bool loggedOn = false; private bool gotCredentials = false; private bool connected = false; private int failedLogons = 0; private bool hideMainWindow = false; //The AI of the system, answers messages: private MsnBot bot; #endregion #region Startup and constructor [STAThread] static void Main(string[] args) { Application.Run(new MsnBotForm(args)); } public MsnBotForm(string[] args) { InitializeComponent(); //Timer to clean up old conversations cleanupTimer = new Timer(this.components); cleanupTimer.Interval = CLEANUP_INTERVAL; cleanupTimer.Tick += new EventHandler(CleanUpConversations); cleanupTimer.Start(); //Timer to reconnect if connection breaks connectTimer = new Timer(this.components); connectTimer.Interval = CONNECT_ATTEMPT_INTERVAL; connectTimer.Tick += new EventHandler(Connect); connectTimer.Start(); //Timer to try to logon again if logon fails logonTimer = new Timer(this.components); logonTimer.Interval = LOGON_ATTEMPT_INTERVAL; logonTimer.Tick += new EventHandler(LogOn); logonTimer.Start(); trayIcon.Text = VERSION + " - Offline"; //Read AI Bot from file bot = new MsnBot(BOT_FILE); conversationsInfo = new Hashtable(); //Initialize loggers cmdLog = Logger.GetLogger("cmd", "Logs"); msgLog = Logger.GetLogger("msg", "Logs"); //Silent startup if (args.Length == 2) { txtEmail.Text = args[0]; txtPassword.Text = args[1]; } else if (args.Length == 1 || args.Length > 2) cmdLog.Warn("Invalid argument count: " + args.Length); //bot.TestCategories(); Connect(null, null); } #endregion #region Timer operations, connect, logon and cleanup private void Connect(Object sender, EventArgs e) { //cmdLog.Info("Timer check on Connect"); if (msn != null && !msn.Connected) { connected = false; loggedOn = false; } if (!connected) { try { msn = new Messenger(); connected = true; cmdLog.Info("Attempting to connect to MSN..."); msn.ContactOnline += new Messenger.ContactOnlineHandler(OnContactOnline); msn.ContactStatusChange += new Messenger.ContactStatusChangeHandler(OnContactStatusChange); msn.ConversationCreated += new Messenger.ConversationCreatedHandler(OnConversationCreated); msn.SynchronizationCompleted += new Messenger.SynchronizationCompletedHandler(OnSynchronizationCompleted); msn.ReverseAdded += new Messenger.ReverseAddedHandler(OnReverseAdded); msn.ConnectionFailure += new DotMSN.Messenger.ConnectionFailureHandler(OnConnectionFailure); cmdLog.Info("Connected to MSN"); failedLogons = 0; } catch(Exception ex) { cmdLog.Error("Failed to connect: " + ex.Message + "\r\nTrying again in 10 seconds..."); } } } private void LogOn(Object sender, EventArgs e) { if (connected && gotCredentials && !loggedOn) { if (failedLogons < MAX_LOGON_ATTEMPTS) { try { cmdLog.Info("Attempting to log on as " + txtEmail.Text); trayIcon.Text = VERSION + " - Signing in"; msn.Connect(txtEmail.Text, txtPassword.Text); cmdLog.Info("Logged on as " + txtEmail.Text); trayIcon.Text = VERSION + " - Synchronizing contact list"; msn.SynchronizeList(); trayIcon.Text = VERSION + " - Online"; loggedOn = true; failedLogons = 0; } catch(Exception ex) { cmdLog.Error("Failed to log on: " + ex.Message + "\r\nTrying again in 10 seconds..."); failedLogons++; } } else { cmdLog.Warn("3 logon attempts have failed, will not try again."); } } } private void CleanUpConversations(Object sender, EventArgs e) { try { ArrayList toBeRemoved = new ArrayList(); foreach (ConversationInfo info in conversationsInfo.Values) { TimeSpan diff = DateTime.Now.Subtract(info.LastMessageTime); if ( diff.Minutes > 30) { info.Close(); toBeRemoved.Add(info); cmdLog.Info("Cleaning up conversation with " + info.Email); } } foreach (ConversationInfo info in toBeRemoved) { conversationsInfo.Remove(info.Email); } } catch(Exception ex) { cmdLog.Error("Cleanup conversation error: " + ex.Message); } } #endregion #region Windows Form Designer generated code protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(MsnBotForm)); this.txtEmail = new System.Windows.Forms.TextBox(); this.txtPassword = new System.Windows.Forms.TextBox(); this.lblEmail = new System.Windows.Forms.Label(); this.lblPassword = new System.Windows.Forms.Label(); this.btnSignIn = new System.Windows.Forms.Button(); this.trayIcon = new System.Windows.Forms.NotifyIcon(this.components); this.trayIconMenu = new System.Windows.Forms.ContextMenu(); this.mnuViewLog = new System.Windows.Forms.MenuItem(); this.mnuViewMsgLog = new System.Windows.Forms.MenuItem(); this.mnuSeperator = new System.Windows.Forms.MenuItem(); this.mnuShutdown = new System.Windows.Forms.MenuItem(); this.SuspendLayout(); // // txtEmail // this.txtEmail.Location = new System.Drawing.Point(72, 20); this.txtEmail.Name = "txtEmail"; this.txtEmail.Size = new System.Drawing.Size(144, 20); this.txtEmail.TabIndex = 0; this.txtEmail.Text = ""; // // txtPassword // this.txtPassword.Location = new System.Drawing.Point(72, 52); this.txtPassword.Name = "txtPassword"; this.txtPassword.PasswordChar = '*'; this.txtPassword.Size = new System.Drawing.Size(144, 20); this.txtPassword.TabIndex = 1; this.txtPassword.Text = ""; // // lblEmail // this.lblEmail.Location = new System.Drawing.Point(32, 24); this.lblEmail.Name = "lblEmail"; this.lblEmail.Size = new System.Drawing.Size(40, 23); this.lblEmail.TabIndex = 2; this.lblEmail.Text = "Email:"; this.lblEmail.TextAlign = System.Drawing.ContentAlignment.TopRight; // // lblPassword // this.lblPassword.Location = new System.Drawing.Point(8, 56); this.lblPassword.Name = "lblPassword"; this.lblPassword.Size = new System.Drawing.Size(64, 23); this.lblPassword.TabIndex = 3; this.lblPassword.Text = "Password:"; this.lblPassword.TextAlign = System.Drawing.ContentAlignment.TopRight; // // btnSignIn // this.btnSignIn.Location = new System.Drawing.Point(88, 80); this.btnSignIn.Name = "btnSignIn"; this.btnSignIn.Size = new System.Drawing.Size(112, 23); this.btnSignIn.TabIndex = 4; this.btnSignIn.Text = "Sign in"; this.btnSignIn.Click += new System.EventHandler(this.btnSignIn_Click); // // trayIcon // this.trayIcon.ContextMenu = this.trayIconMenu; this.trayIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("trayIcon.Icon"))); this.trayIcon.Text = "MsnBot v0.1 - Running"; this.trayIcon.Visible = true; // // trayIconMenu // this.trayIconMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.mnuViewLog, this.mnuViewMsgLog, this.mnuSeperator, this.mnuShutdown}); // // mnuViewLog // this.mnuViewLog.Index = 0; this.mnuViewLog.Text = "View log"; this.mnuViewLog.Click += new System.EventHandler(this.mnuViewLog_Click); // // mnuViewMsgLog // this.mnuViewMsgLog.Index = 1; this.mnuViewMsgLog.Text = "View message log"; this.mnuViewMsgLog.Click += new System.EventHandler(this.mnuViewMsgLog_Click); // // mnuSeperator // this.mnuSeperator.Index = 2; this.mnuSeperator.Text = "-"; // // mnuShutdown // this.mnuShutdown.DefaultItem = true; this.mnuShutdown.Index = 3; this.mnuShutdown.Text = "Shutdown MsnBot"; this.mnuShutdown.Click += new System.EventHandler(this.mnuShutdown_Click); // // MsnBotForm // this.AccessibleRole = System.Windows.Forms.AccessibleRole.None; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(272, 122); this.Controls.Add(this.btnSignIn); this.Controls.Add(this.lblPassword); this.Controls.Add(this.lblEmail); this.Controls.Add(this.txtPassword); this.Controls.Add(this.txtEmail); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "MsnBotForm"; this.Text = "Msn Bot v0.1"; this.Load += new System.EventHandler(this.MsnBotForm_Load); this.VisibleChanged += new System.EventHandler(this.MsnBotForm_VisibleChanged); this.ResumeLayout(false); } #endregion #region DOTMsn event handlers public void OnConnectionFailure(Messenger sender, ConnectionErrorEventArgs e) { cmdLog.Error("Connection failed: " + e.Error.Message); cmdLog.Info("New connection attempt will be made within 10 seconds..."); connected = false; loggedOn = false; } private void OnContactOnline(Messenger sender, ContactEventArgs e) { //cmdLog.Info(e.Contact.Name + " went online"); } private void OnContactStatusChange(Messenger sender, ContactStatusChangeEventArgs e) { //cmdLog.Info(e.Contact.Mail + " changed status to " + e.Contact.Status.ToString()); } private void OnConversationCreated(Messenger sender, ConversationEventArgs e) { cmdLog.Info("Conversation object created."); // remember there are not yet users in the conversation (except ourselves) // they will join _after_ this event. We create another callback to handle this. // When user(s) have joined we can start sending messages. e.Conversation.ContactJoin += new Conversation.ContactJoinHandler(OnContactJoined); e.Conversation.MessageReceived += new Conversation.MessageReceivedHandler( OnMessageReceived ); } private void OnSynchronizationCompleted(Messenger sender, EventArgs e) { try { msn.SetStatus(MSNStatus.Online); msn.Owner.Name = bot.Name; cmdLog.Info("Signed in as " + msn.Owner.Mail); foreach(Contact contact in msn.ReverseList) { msn.AddContact(contact.Mail); } } catch(Exception ex) { cmdLog.Error("Synchronization error: " + ex.Message); } } private void OnContactJoined(Conversation sender, ContactEventArgs e) { cmdLog.Info(e.Contact.Name + " (" + e.Contact.Mail + ") joined our conversation."); } private void OnMessageReceived(Conversation sender, MessageEventArgs e ) { string msg = e.Message.Text; while (msg.EndsWith("\0")) msg = msg.Substring(0, msg.Length-1); try { if (e.Sender.Mail.ToLower() == bot.AuthorEmail.ToLower() && msg.StartsWith("$")) { SystemCommand(msg, sender); return; } cmdLog.Info("Received message from " + e.Sender.Name + " (" + e.Sender.Mail + ")"); msgLog.Text(e.Sender.Name + " (" + e.Sender.Mail + ") says:\r\n\t" + msg + "\r\n"); string reply; string[] replyParts; ConversationInfo info = (ConversationInfo) conversationsInfo[e.Sender.Mail]; if (info == null) { info = new ConversationInfo(e.Sender.Mail, new ArrayList(), DateTime.Now, bot.Clone() ); conversationsInfo.Add(e.Sender.Mail, info); reply = info.Bot.GetStartupReply(msg, e.Sender.Name); } else { reply = info.GetPendingReply(); } info.LastMessageTime = DateTime.Now; info.WriteMessage(msg, e.Sender.Name, DateTime.Now); if (reply != null) replyParts = reply.Split(Category.SINGLE_REPLY_SPLITTER); else { reply = info.Bot.GetReply(msg, e.Sender.Name); string[] pending = reply.Split(Category.REPLY_SPLITTER); replyParts = pending[0].Split(Category.SINGLE_REPLY_SPLITTER); if (pending.Length > 1) { for (int i = 1; i < pending.Length; i++) info.AddPendingReply(pending[i]); } } //And now send the reply parts: int charTime = 70; int maxDelay = 6500; long before, after, duration; for (int i = 0; i < replyParts.Length; i++) { int delay = replyParts[i].Length * charTime; if (delay > maxDelay) delay = maxDelay; before = DateTime.Now.Ticks; SendTypingMsg(sender); after = DateTime.Now.Ticks; duration = after - before; if (duration < delay) Thread.Sleep((int) (delay - duration)); sender.SendMessage(replyParts[i]); msgLog.Text(bot.Name + " says to " + e.Sender.Mail + ":\r\n\t" + replyParts[i] + "\r\n"); cmdLog.Info("Sent message to " + e.Sender.Mail); info.WriteMessage(replyParts[i], bot.Name, DateTime.Now); } } catch(Exception ex) { cmdLog.Error("OnMessageReceived error: " + ex.Message); } } private void SendTypingMsg(Conversation conv) { try { string msg = "MSG " + msn.Owner.Mail + " " + msn.Owner.Name + "[LENGTH]" + "\r\n"; msg += "MIME-Version: 1.0\r\n"; msg += "Content-Type: text/x-msmsgscontrol\r\n"; msg += "TypingUser: " + msn.Owner.Mail + "\r\n"; int len = msg.Length - "[Length]".Length; int oldlen = len; len += len.ToString().Length; if ((oldlen < 10 && len >= 10) || (oldlen < 100 && len >=100)) len++; msg = msg.Replace("[LENGTH]", len.ToString()); conv.SendMessage("", msg); } catch(Exception ex) { cmdLog.Error("TypingMsg error: " + ex.Message); } } private void OnReverseAdded(Messenger sender, ContactEventArgs e) { try { cmdLog.Info("Reverse add from " + e.Contact.Name + " (" + e.Contact.Mail + ")"); msn.AddContact(e.Contact.Mail); } catch(Exception ex) { cmdLog.Error("ReverseAdded error: " + ex.Message); } } #endregion #region Control Event Handlers private void MsnBotForm_Load(object sender, System.EventArgs e) { if (txtEmail.Text != "" && txtPassword.Text != "") { cmdLog.Info("Quiet startup mode: sign-in email = " + txtEmail.Text); gotCredentials = true; btnSignIn_Click(null, null); hideMainWindow = true; } } private void MsnBotForm_VisibleChanged(object sender, System.EventArgs e) { if (hideMainWindow) Hide(); } private void btnSignIn_Click(object sender, System.EventArgs e) { gotCredentials = true; Hide(); LogOn(null, null); } private void mnuViewMsgLog_Click(object sender, System.EventArgs e) { if (msgLogWnd == null || msgLogWnd.IsDisposed) { cmdLog.Info("Showing new message log form."); msgLogWnd = new LogForm(msgLog, "MsnBot - Message Log"); msgLogWnd.Show(); } else msgLogWnd.Focus(); } private void mnuViewLog_Click(object sender, System.EventArgs e) { if (cmdLogWnd == null || cmdLogWnd.IsDisposed) { cmdLog.Info("Showing new command log form."); cmdLogWnd = new LogForm(cmdLog, "MsnBot - Log"); cmdLogWnd.Show(); } else cmdLogWnd.Focus(); } private void mnuShutdown_Click(object sender, System.EventArgs e) { trayIcon.Text = "MsnBot v0.1 - Shutting down"; cmdLog.Info("Shutting down program."); msn.CloseConnection(); foreach (ConversationInfo info in conversationsInfo.Values) { info.Close(); } cmdLog.Close(); msgLog.Close(); this.Close(); } #endregion public void SystemCommand(string command, Conversation conv) { try { if (command.ToUpper().StartsWith("$CONTACTS")) { cmdLog.Info("Sending contact list to owner"); foreach (Contact c in msn.ForwardList) { conv.SendMessage("CONTACT: " + c.Mail + " '" + c.Name + "'"); } } else if (command.ToUpper().StartsWith("$CMDLOG")) { cmdLog.Info("Sending command log to owner"); string file = cmdLog.CurrentLogFile.Substring("Logs/".Length); string copyTo = "Temp/" + file; File.Copy(msgLog.CurrentLogFile, copyTo , true); conv.FileTransferHandler.TransferFile(bot.AuthorEmail, copyTo); } else if (command.ToUpper().StartsWith("$MSGLOG")) { msgLog.Info("Owner requesting message log"); cmdLog.Info("Sending message log to owner"); string file = msgLog.CurrentLogFile.Substring("Logs/".Length); string copyTo = "Temp/" + file; File.Copy(msgLog.CurrentLogFile, copyTo , true); conv.FileTransferHandler.TransferFile(bot.AuthorEmail, copyTo); } else if (command.ToUpper().StartsWith("$CONVINFO")) { int count = 0; foreach (ConversationInfo info in conversationsInfo.Values) { conv.SendMessage(info.Email + ", last message at " + info.LastMessageTime); count++; } if (count == 0) conv.SendMessage("No active conversations"); } else if (command.ToUpper().StartsWith("$MSGHISTORY")) { if (command.Trim().Length > "$MSGHISTORY".Length + 1) { string email = command.Substring("$MSGHISTORY".Length+1); cmdLog.Info("Message history request for user " + email); string filename = "MessageLogs/" + email + ".txt"; if (File.Exists(filename)) { string copyTo = "Temp/" + email + ".txt"; File.Copy(filename, copyTo, true); conv.FileTransferHandler.TransferFile(bot.AuthorEmail, copyTo); } else conv.SendMessage("No message history for '" + email + "'"); } else { conv.SendMessage("Usage: $MSGHISTORY "); } } else if (command.ToUpper().StartsWith("$SHUTDOWN")) { cmdLog.Info("Shutdown command from user " + bot.AuthorEmail); mnuShutdown_Click(null, null); } else if (command.ToUpper().StartsWith("$DIR")) { if (command.Trim().Length == "$DIR".Length) conv.SendMessage("Usage: $DIR "); else { string dirname = command.Substring("$DIR".Length).Trim(); if (!Directory.Exists(dirname)) conv.SendMessage("Directory '" + dirname + "' does not exist!"); else { string[] files = Directory.GetFiles(dirname); string reply = ""; for (int i = 0; i < files.Length; i++) { reply += files[i] + "\r\n"; if (i % 5 == 0) { conv.SendMessage(reply); reply = ""; } } } } } else if (command.ToUpper().StartsWith("$GETFILE")) { if (command.Trim().Length == "$GETFILE".Length) conv.SendMessage("Usage: $GETFILE "); else { string filename = command.Substring("$GETFILE".Length).Trim(); if (!File.Exists(filename)) conv.SendMessage("File '" + filename + "' does not exist!"); else { conv.FileTransferHandler.TransferFile(bot.AuthorEmail, filename); } } } else { cmdLog.Warn("Unrecognized system command: " + command); conv.SendMessage("Unrecognized system command!"); } } catch(Exception e) { cmdLog.Error("Error in system command: " + e.Message); conv.SendMessage("Error: " + e.Message); } } } }