网上搜一搜,发现MSN的开发库还不少。比如有名的Pidgin(可以让你同时登录MSN,QQ,Yahoo通,Gtallk等)使用的IM互通的库叫libpurple, 完全是Pidgin自己使用C和GTK开发的,有其它的一些IM也是基于它开发的。但本人看了libpurple的代码后,觉得不敢恭维,像我们通常说:“业务逻辑跟GUI界面揉在一团”,很难使用。所以搜索了一下其它MSN的库,有python写的, 有Java写的, 还有.Net和C#实现的。通过对它们的了解,最终使用了MSNP-Sharp这个 库,它是.Net平台,使用C#开发。我一向偏重首选Linux平台的源码库,但无奈其它的MSN开发库太老,N久或N年没更新过,唯独这个MSNP-Sharp最活跃,而且最近都有版本更新。
基本上这些第三方的MSN开发库都没有正式的文档,所以只能看源码,还好MSNP-Sharp源码中包括了一个Example
要使用MSNPSharp向你的好友发送消息,首先必须建立一个Conversation对象,这个对象可以通过调用Messenger类的CreateConversation函数获得。
建立了Conversation就可以通话了么?答案是否定的,因为这个Conversation这时候只有你一个人,就好比你要向一个朋友通电话,你直接拿起话筒就能和对方说话么?
于是下一步我们该干什么呢?让我们回到通电话的那个例子上来。我们拿起话筒,理所当然做的第二件事情就是——拨号。MSNPSharp也一样,你得“拨号”把对方邀请到你的Conversation里面来,这很简单,调用你先前得到的那个Conversation对象的Invite函数,那个函数只有一个参数,就是目标好友的Windows Live 帐号(前提是他没有Block你),或者干脆就是你目标好友的Contact对象。
完事了么?很明显没有。你拨号了之后还得等人接了才能通话。当然在现实生活中完成这个动作很简单:你只需要拿着话筒等待就可以了,因为拨号的这个过程是线性的(或者说是同步的),动作一个接一个地发生。但是写程序就没有那么走运了,要知道Invite对方直到对方响应可能是一个很长的过程,如果是同步的话,那么可能需要等待很长的时间,以至于你不得不建立多条线程来处理每一个Invite。所以MSNPSharp采用的是异步+事件通知的设计:Invite后Invite函数立即返回,当对方“接听”后,会在Conversation.SwitchBrard触发一个事件:ContactJoined ,这个时候你才可以用 Conversation.SwitchBoard.SendTextMessage向对方发送消息。
通常很多人在ContactJoined事件之前就向对方调用SendTextMessage,那结果只有一个:发送失败。如果在ContactJoined事件之前UI又有几条消息要被指定发送,那么建议先用个队列或数组之类保存这些消息,等ContactJoined事件触发了之后再发送。
事情看上去大功告成了。但是不得不告诉各位一个坏消息,提供通话服务的MSN“电信局”很吝啬,如果你俩通话过程中超过30秒到一分钟一言不发,它会把你们的通话掐断(事实上应该是如果对方使用的是官方的MSN客户端,对方主动退出,据测试)。这个时候 Conversation.SwitchBard 的 ContactLeft 事件会被触发,通知你对方用户退出Conversation,Conversation.SwitchBoard.Contacts.Count属性会自减1,表示在此Conversation里面的Contact少了一个。(如果一个Conversation里面所有对方用户都退出了,会触发AllContactsLeft事件,这时可以直接调用Conversation.Switchboard.Close()关闭Conversation)
这时问题就来了,如果我想向一个因为长时间不发送消息而退出的用户重新发送消息该怎么办呢?恩,是的,重新调用Messanger. CreateConversation再建立一个Conversation Invite他们一次。但是请记住就是,在他们Left之前把退出的用户记录下来,要不然你不知道该重新邀请谁,还有,千万别忘记,在ContactJoin之前把要发送的消息缓冲,join之后才能发送。
今天把与实现IM机器人相关的代码抽取出来,做了一个测试,可以跑起来了。这个机器人实现的功能就是:你对它发送什么,他就发什么给你。(echo消息啦,还没去做到建立数据库这块)。代码如下:
using System;
using System.Collections.Generic;
using System.Text;
//using System.Drawing.Color;
namespace MSNRobot
{
using MSNPSharp;
using MSNPSharp.Core;
using MSNPSharp.DataTransfer;
class RobotConversation
{
private Conversation _conversation = null;
private RobotMain _robotmain = null;
public RobotConversation(Conversation conv, RobotMain robotmain)
{
Console.WriteLine("==> Struct a conversation");
_conversation = conv;
_conversation.Switchboard.TextMessageReceived += new EventHandler<TextMessageEventArgs>(Switchboard_TextMessageReceived);
_conversation.Switchboard.SessionClosed += new EventHandler<EventArgs>(Switchboard_SessionClosed);
_conversation.Switchboard.ContactLeft += new EventHandler<ContactEventArgs>(Switchboard_ContactLeft);
_robotmain = robotmain;
}
//online status
private void Switchboard_TextMessageReceived(object sender, TextMessageEventArgs e)
{
Console.WriteLine("==>Received Msg From " + e.Sender.Mail + " Content:\n" + e.Message.Text);
//echo back ///////////// TODO /////////////////
_conversation.Switchboard.SendTextMessage(e.Message);
}
private void Switchboard_SessionClosed(object sender, EventArgs e)
{
Console.WriteLine("==>Session Closed, Remove conversation");
_conversation.Switchboard.Close();
_conversation = null;
_robotmain.RobotConvlist.Remove(this);
}
private void Switchboard_ContactLeft(object sender, ContactEventArgs e)
{
Console.WriteLine("==>Contact Left.");
}
}
class RobotMain
{
private Messenger messenger = new Messenger();
private List<RobotConversation> _convs = new List<RobotConversation>(0);
public RobotMain()
{
messenger.NameserverProcessor.ConnectionEstablished += new EventHandler<EventArgs>(NameserverProcessor_ConnectionEstablished);
messenger.Nameserver.SignedIn += new EventHandler<EventArgs>(Nameserver_SignedIn);
messenger.Nameserver.SignedOff += new EventHandler<SignedOffEventArgs>(Nameserver_SignedOff);
messenger.NameserverProcessor.ConnectingException += new EventHandler<ExceptionEventArgs>(NameserverProcessor_ConnectingException);
messenger.Nameserver.ExceptionOccurred += new EventHandler<ExceptionEventArgs>(Nameserver_ExceptionOccurred);
messenger.Nameserver.AuthenticationError += new EventHandler<ExceptionEventArgs>(Nameserver_AuthenticationError);
messenger.Nameserver.ServerErrorReceived += new EventHandler<MSNErrorEventArgs>(Nameserver_ServerErrorReceived);
messenger.Nameserver.ContactService.ReverseAdded += new EventHandler<ContactEventArgs>(Nameserver_ReverseAdded);
messenger.ConversationCreated += new EventHandler<ConversationCreatedEventArgs>(messenger_ConversationCreated);
messenger.Nameserver.OIMService.OIMReceived += new EventHandler<OIMReceivedEventArgs>(Nameserver_OIMReceived);
messenger.Nameserver.OIMService.OIMSendCompleted += new EventHandler<OIMSendCompletedEventArgs>(OIMService_OIMSendCompleted);
}
public List<RobotConversation> RobotConvlist
{
get
{
return _convs;
}
}
private void NameserverProcessor_ConnectionEstablished(object sender, EventArgs e)
{
//messenger.Nameserver.AutoSynchronize = true;
Console.WriteLine("==>Connection established!");
}
private void Nameserver_SignedIn(object sender, EventArgs e)
{
messenger.Owner.Status = PresenceStatus.Online;
Console.WriteLine("==>Signed into the messenger network as " + messenger.Owner.Name);
}
private void Nameserver_SignedOff(object sender, SignedOffEventArgs e)
{
Console.WriteLine("==>Signed off from the messenger network");
}
private void NameserverProcessor_ConnectingException(object sender, ExceptionEventArgs e)
{
//MessageBox.Show(e.Exception.ToString(), "Connecting exception");
Console.WriteLine("==>Connecting failed");
}
private void Nameserver_ExceptionOccurred(object sender, ExceptionEventArgs e)
{
// ignore the unauthorized exception, since we’re handling that error in another method.
if (e.Exception is UnauthorizedException)
return;
Console.WriteLine("==>Nameserver exception:" + e.Exception.ToString());
}
private void Nameserver_AuthenticationError(object sender, ExceptionEventArgs e)
{
Console.WriteLine("==>Authentication failed:" + e.Exception.InnerException.Message);
}
private void Nameserver_ServerErrorReceived(object sender, MSNErrorEventArgs e)
{
// when the MSN server sends an error code we want to be notified.
Console.WriteLine("==>Server error received:" + e.MSNError.ToString());
}
void Nameserver_ReverseAdded(object sender, ContactEventArgs e)
{
//Contact contact = e.Contact;
//contact.OnAllowedList = true;
//contact.OnPendingList = false;
//messenger.Nameserver.ContactService.AddNewContact(contact.Mail);
Console.WriteLine("==>ReverseAdded contact mail:" + e.Contact.Mail);
//messenger.Nameserver.AddNewContact(
e.Contact.OnAllowedList = true;
e.Contact.OnForwardList = true;
}
private void messenger_ConversationCreated(object sender, ConversationCreatedEventArgs e)
{
Console.WriteLine("==>Conversation created");
_convs.Add(new RobotConversation(e.Conversation, this));
}
//offline status
void Nameserver_OIMReceived(object sender, OIMReceivedEventArgs e)
{
Console.WriteLine("==>OIM received at : " + e.ReceivedTime + " From : " +
e.NickName + " (" + e.Email + ") " + e.Message);
TextMessage message = new TextMessage(e.Message);
message.Font = "Trebuchet MS";
//message.Color = Color.Brown;
message.Decorations = TextDecorations.Bold;
Console.WriteLine("==>Echo back");
messenger.OIMService.SendOIMMessage(e.Email, message.Text);
}
void OIMService_OIMSendCompleted(object sender, OIMSendCompletedEventArgs e)
{
if (e.Error != null)
{
Console.WriteLine("OIM Send Error:" + e.Error.Message);
}
}
public void BeginLogin(string account, string password)
{
if (messenger.Connected)
{
Console.WriteLine("==>Disconnecting from server");
messenger.Disconnect();
}
// set the credentials, this is ofcourse something every MSNPSharp program will need to implement.
messenger.Credentials = new Credentials(account, password, MsnProtocol.MSNP16);
// inform the user what is happening and try to connecto to the messenger network.
Console.WriteLine("==>Connecting to server...");
messenger.Connect();
//displayImageBox.Image = global::MSNPSharpClient.Properties.Resources.loading;
//loginButton.Tag = 1;
//loginButton.Text = "Cancel";
// note that Messenger.Connect() will run in a seperate thread and return immediately.
// it will fire events that informs you about the status of the connection attempt.
// these events are registered in the constructor.
}
/// <summary>
/// main()
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
string robot_acc = "";
string robot_passwd = "";
if (args.Length == 0)
{
Console.WriteLine("USAGE:MSNRobot.exe <msn_account> [password]");
return;
}
robot_acc = args[0];
if (args.Length == 2)
robot_passwd = args[1];
else
{
Console.WriteLine("Password for " + robot_acc + ":");
robot_passwd = Console.ReadLine();
}
RobotMain app = new RobotMain();
app.BeginLogin(robot_acc, robot_passwd);
while (true)
{
Console.WriteLine("I am a MSN robot:" + robot_acc);
Console.ReadLine();
}
}
}
}
源码下载:http://msnp-sharp.googlecode.com/svn/branches/MSNPSHARP_25_STABLE/