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);
}
}
}
}