65.9K
CodeProject 正在变化。 阅读更多。
Home

用于构建通信类应用程序的 MSN Messenger 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (13投票s)

2008年3月24日

CPOL

14分钟阅读

viewsIcon

110311

downloadIcon

2269

本文介绍了一个用于利用 MSN Messenger 服务和协议生成通信应用程序的库。

msnlibrary2.jpg

引言

本文介绍了一个通用的 MSN Messenger 框架,用于生成 MSN Messenger 风格的应用程序(既可以作为独立应用程序,也可以集成到大型应用程序中(例如 Microsoft Groove)。通信是现代软件基础设施的重要组成部分,而使用此库可以避免复杂的服务器/客户端设计编程,而是利用现有技术,该技术得到了广泛支持。

本文无意提供 MSN 客户端或服务器的替代品,而是说明如何使用包含的库来快速轻松地创建它们。演示应用程序显示了生成通信应用程序所需的最低限度,但是 CIRIP 应用程序(也已链接到上方)展示了更复杂和精密的实现。

该库支持 MSNP8 和 MSNP9 版本协议,并且可以用于从 .NET 1.0 及更高版本(包括 .NET 3.5 和 Windows Presentation Foundation,如演示中所用)的任何 .NET 语言中生成 MSN 客户端。

该库目前支持以下功能

  • 登录/退出 MSN 网络
  • 用户状态
  • 用户友好名称
  • 联系人列表同步(只读,尚不支持添加新联系人或在组之间移动联系人)
  • 联系人友好名称、电话号码和状态
  • 对话、发送和接收消息(包括字体信息和正在输入的文字)
  • 邀请其他用户加入对话
  • 对话插件

该库目前不支持以下功能*

  • 文件传输
  • 联系人图片(以及本地图片)
  • 戳(Nudges)
  • 表情符号(但可以通过插件轻松添加)
  • 活动
  • 代理支持(除非通过 Internet Explorer 设置)

* 这些功能可能会在后续版本中添加,请请求其他功能,我将尽力优先处理。

背景

"MSN Messenger 是由微软在 1999 年至 2005 年开发和分发的免费即时通讯客户端,并在 2007 年为运行 Microsoft Windows 操作系统的计算机(Windows Vista 除外)发布,主要面向家庭用户。2006 年 2 月,作为微软 Windows Live 系列在线服务和软件的一部分,它被重命名为 Windows Live Messenger。

MSN Messenger 通常指的是 .NET Messenger Service(允许系统运行的协议和服务器),而不是任何特定的客户端。

MSN Messenger 使用 Microsoft Notification Protocol (MSNP) 通过 TCP(以及可选的 HTTP 来处理代理)连接到 .NET Messenger Service — 这是在 messenger.hotmail.com 的 1863 端口上提供的服务。其当前版本为 13 (MSNP13),由 MSN Messenger 7.5 及其他第三方客户端使用。该协议并非完全保密;微软于 1999 年在 Internet Draft 中向开发者公开了版本 2 (MSNP2),但从未向公众发布版本 8、9、10、11 或 12。.NET Messenger Service 服务器目前仅接受 8 及更高版本的协议,因此 8、9、10、11 和 12 版本的新命令的语法仅通过 Wireshark 等嗅探器得知。MSNP13 将是在 Windows Live Messenger 中使用的协议。截至目前,该程序仍不兼容 Mac OS X 的浏览器。" 维基百科 --- MSN Messenger

有关 MSN 协议的更深入介绍,请参阅 http://www.hypothetic.org/docs/msn/index.php;本文不涵盖 MSN 协议,因为它旨在消除对该主题的任何先验知识的需求。

代码结构

所有代码都围绕 MSNController(主要)、MSNSwitchboardController 和 MSNSwitchboard 类。

MSNController

创建无需输入参数。

MSNController controller = new MSNController();

MSNController 类具有以下属性;

  • String Username {get; set;} - 获取或设置本地客户端的用户名;必须在登录前设置,否则身份验证将失败。
  • String Password {get; set;} - 获取或设置本地客户端的密码;必须在登录前设置,否则身份验证将失败。此外,get 属性只返回星号,即如果 .Password 设置为 thePassword,则 get 调用将返回 ***********。
  • String FriendlyName {get; set;} - 获取或设置本地客户端的友好名称。必须在控制器连接后才能设置。
  • MSNEnumerations.UserStatus Status {get; set;} - 获取或设置本地客户端的状态(例如,在线、忙碌、显示为离线等)。set 属性只能在登录后调用,否则可能会抛出异常(不要依赖此行为)。
  • MSNEnumerations.LoginStatus LoginStatus {get; set;} - 获取或设置连接状态,set 属性只接受有效组合(例如,请求登录状态在控制器已登录时会抛出异常)。

MSNController 类还通过以下属性提供对其他关键对象的访问(主要在连接时可用);

  • MSNContactsList ContactsList {get;} - 获取 MSNContactsList 对象,该对象包含联系人列表的同步版本。
  • MSNSwitchboardController SwitchboardController {get;} - 获取处理对话创建(和重新创建)的 MSNSwitchboardController。

MSNController 类具有以下方法;

  • void loginMSNClient() - 已弃用,用于登录控制器。
  • void logoutMSNClient() - 已弃用,用于退出控制器。
  • void startConversation(List<String> initialUsers) - 开始与以下用户名的对话。指定的用户名必须已在联系人列表中,否则对话将不包含该联系人。

MSNController 的主要连接点是通过以下事件(尽管轮询也能正常工作,但强烈建议使用事件,并且通过属性设置变量然后在相关事件上更新用户界面 (UI) 是更好的做法);

  • event MSNEventDelegates.StatusChangedEventDelegate LocalClientStatusChanged - 本地客户端状态更改时触发(在线、忙碌、离开等)。**
  • event MSNEventDelegates.FriendlyNameChangedEventDelegate LocalFriendlyNameChanged - 本地客户端友好名称更改时触发;登录后也会触发一次,以指示初始友好名称。**
  • event MSNEventDelegates.LoginStatusChangedEventDelegate LoginStatusChanged - 控制器连接、断开或正在连接时触发。从 logged_out 到 logging_in 再到 logged_out 的状态更改表示身份验证失败。
  • event MSNEventDelegates.LoginSettingsChangedEventDelegate LoginSettingsChanged - 用户名或密码属性更改时触发。
  • event MSNEventDelegates.FriendlyNameChangedEventDelegate FriendlyNameChanged - 在线联系人的友好名称更改时触发。连接初始时也会触发,以指定初始友好名称。**
  • event MSNEventDelegates.PhoneNumberChangedEventDelegate PhoneNumberChanged - 在线联系人的电话号码更改时触发。在初始联系人列表同步期间也会触发。**
  • event MSNEventDelegates.GroupModifiedEventDelegate GroupModified - 在添加或删除组时触发。在初始联系人列表同步期间也会触发。**
  • event MSNEventDelegates.GroupMemberChangedEventDelegate GroupMemberChanged - 在联系人被添加到组或从组中移除时触发。在初始联系人列表同步期间也会触发。**
  • event MSNEventDelegates.ContactStatusChangedEventDelegate ContactStatusChanged - 联系人状态更改时触发。**
  • event MSNEventDelegates.ContactAddedEventDelegate ContactAdded - 联系人被添加到联系人列表或从联系人列表中移除时触发。在初始联系人列表同步期间也会触发。**
  • event MSNEventDelegates.MasterListContactAdded MasterListContactAdded - 联系人被添加到正向或反向列表(即被阻止的用户、已请求将您添加为联系人的用户)或从中移除时触发。**

** 仅在控制器连接时触发。

MSNAuthentication

库外无访问权限;用于与 Microsoft 密码进行身份验证,以及处理身份验证挑战。

MSNContactsList

在连接时维护联系人列表的同步版本。组数据通过 MSNGroup 类包装,联系人数据(不包括本地用户)通过 MSNContact 类包装。

  • Dictionary<String, MSNContact> Contacts {get;} - 通过用户名返回联系人字典的引用。
  • Dictionary<String, MSNGroup> Groups {get;} - 通过组名返回组字典的引用。

MSNContact

具有以下属性和方法;

  • String Username {get;} - 获取联系人的用户名。
  • String FriendlyName {get; set;} - 获取或设置联系人的友好名称。***
  • MSNEnumerations.UserStatus Status {get; set;} - 获取或设置联系人的状态。***
  • MSNListenableList<MSNGroup> Groups {get;} - 获取联系人所属的组列表。***
  • void setListMember(MSNEnumbers.ContactLists list, bool member) - 将用户添加到联系人列表(例如,阻止列表)或从中移除。***
  • bool getListMember(MSNEnumerations.ContactLists list) - 如果联系人在指定列表中,则返回 true,否则返回 false。
  • void setPhone(MSNEnumerations.PhoneTypes phoneType, String value) - 设置指定的电话号码。***
  • String getPhone(MSNEnumerations.PhoneTypes phoneType) - 获取联系人的指定电话号码,如果未设置则返回 ""。

*** 仅修改内部数据结构,不修改在线版本。

MSNGroup

具有以下属性和方法;

  • String GroupName {get;} - 获取组的友好名称。
  • int GroupNumber {get;} - 获取组的标识号。
  • MSNListenableList<MsnContact> Contacts {get;} - 获取组内联系人列表。

*** 仅修改内部数据结构,不修改在线版本。

MSNSwitchboardController

此类用于发起新对话(以及由此产生的对话窗口)。该类只有两个事件,但如果需要允许对话,则必须处理这两个事件;

  • event MSNEventDelegates.SwitchboardCreated SwitchboardCreated - 新对话开始时生成(由本地用户或联系人发起)。请注意,当联系人打开对话窗口时会生成此事件,而不是在发送第一条消息时生成,因此如果在此事件中创建对话窗口,则可能收不到任何消息,如果联系人打开对话窗口然后立即关闭而未发送消息。
  • event MSNEventDelegates.SwitchboardReCreated SwitchboardReCreated - 当对话关闭后重新打开时生成;例如,联系人开始对话,关闭其对话窗口,然后重新打开对话窗口。与 SwitchboardCreated 事件类似,当移除窗口创建时会调用事件,而不是发送消息。

MSNSwitchboard

此类处理单个对话,并通过 IMSNSwitchboardPlugin 接口支持对话插件。

  • event MSNEventDelegates.SwitchboardUserConnected UserConnected - 联系人加入对话时触发,包括初始对话联系人和任何其他用户。
  • event MSNEventDelegates.MessageRecieved MessageRecieved - 消息已由插件处理(且消息设置为显示,默认为 true)后触发。
  • event MSNEventDelegates.MessageSent MessageSent - 消息已由插件处理然后发送(且消息设置为显示,默认为 true)后触发。
  • void sendMessage(MSNUserMessage message) - 发送指定的消息(经过插件处理后)。
  • void invite(String username) - 将指定用户名的用户邀请到对话中(用户名必须在联系人列表中)。
  • void closeConversation() - 关闭对话(尽管对话可以通过 MSNSwitchboardController 中的 SwitchboardReCreated 事件重新启动)。
  • List<String> getConnectedUsers() - 返回当前在对话中的用户名列表(不包括已受邀但尚未加入的用户)。
  • MSNListenableList<IMSNSwitchboardPlugin> Plugins {get;} - 返回当前插件列表,应将插件添加到此列表中。

请注意,当 MSNController 断开连接时,对话不会自动终止;因此,可以退出登录但仍与现有对话成员聊天。

演练

连接到 MSN 网络

  1. 创建 MSNController 对象(controller)
  2. 通过 controller.Username 设置用户名
  3. LoginSettingsChanged 事件触发
  4. 通过 controller.Password 设置密码
  5. LoginSettingsChanged 事件触发
  6. 调用 controller.LoginStatus = MSNEnumerations.LoggedIn
  7. 库尝试连接
  8. LoginStatusChanged 触发,显示 MSNEnumerations.LoggingIn
  9. LoginStatusChanged 触发,显示 MSNEnumerations.LoggedIn
  10. LoggedIn 已处理,客户端通过 controller.UserStatus 发送初始用户状态
  11. LocalClientStatusChanged 事件触发
  12. 联系人列表开始同步,通过 ContactAdded、GroupModified、GroupMemberChanged、ContactStatusChanged 和 FriendlyNameChanged 事件

开始对话

  1. 调用 controller.startConversation,并提供要初始加入对话的用户列表
  2. SwitchboardCreated 事件被调用,并带有一个新的 switchboard
  3. 客户端代码从事件 switchboard 创建一个新的对话窗口
  4. UserConnected 事件对每个对话成员调用一次

发送消息(在已有对话的情况下)

  1. 用户输入新消息,客户端将其转换为 MSNUserOutgoingMessage
  2. 客户端通过 switchboard.sendMessage(MSNUserMessage message) 发送消息
  3. 库依次将消息传递给每个插件
  4. 如果 message.getSend() 为 true,则消息发送到 MSN 网络
  5. 如果 message.getDisplay() 为 true,则触发 MessageSent 事件

接收消息(在已有对话的情况下)

  1. 联系人通过对话 switchboard 发送消息
  2. 库将其转换为 MSNUserIncommingMessage
  3. 库依次将消息传递给每个插件
  4. 如果 message.getDisplay() 为 true,则触发 MessageRecieved 事件

Using the Code

主应用程序窗口

界面组件

  • TextBox usernameTextBox - 本地用户的用户名
  • PasswordBox passwordBox - 本地用户的密码
  • Button connectionButton - 用于连接/断开 MSN 网络
  • DropDownBox statusBox - 用于设置本地用户状态(例如,在线、忙碌、离开等)
  • TreeView contactsTreeView - 用于显示联系人/组
  • TreeViewItem contactsTreeViewItem - contactsTreeView 的基节点,在 Header 中显示“联系人”

连接

在 connectionButton 的点击处理程序中,将用户名和密码设置到控制器并设置连接状态。

private void connectionButton_Click(object sender, RoutedEventArgs e)
{
    if (controller.LoginStatus == MSNEnumerations.LoginStatus.LOGGED_OUT) {
        controller.LoginStatus = MSNEnumerations.LoginStatus.LOGGED_IN;
    }
    else if (controller.LoginStatus == MSNEnumerations.LoginStatus.LOGGED_IN) {
        controller.Username = usernameTextBox.Text;
        controller.Password= passwordBox.Password;
        controller.LoginStatus = MSNEnumerations.LoginStatus.LOGGED_OUT;
    }
}

处理 LoginStatusChanged;更新连接按钮文本,断开连接时清除数据结构,连接时设置新状态。此外,建议在连接时禁用连接按钮,以防止用户在正在连接时尝试连接(尤其是在互联网连接缓慢的情况下)。请注意,事件处理程序不在事件分发线程上,因此需要更新 UI 组件。

 
private void controller_LoginStatusChanged(MSNEnumerations.LoginStatus newStatus)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                            new RefreshDelegate(delegate()
        {
            if (controller.LoginStatus == MSNEnumerations.LoginStatus.LOGGED_OUT) {
                connectionButton.Content = "Connect";
                connectionButton.IsEnabled = true;
                statusComboBox_SelectionChanged(this, null);

                baseNode.Items.Clear();
                contactsTreeNodes.Clear();
                groupsTreeNodes.Clear();
                conversationWindows.Clear();
            }
            else if (controller.LoginStatus == MSNEnumerations.LoginStatus.LOGGED_IN) {
                connectionButton.Content = "Disconnect";
                connectionButton.IsEnabled = true;

                //Fire a status changed to start contact list synchronisation
                statusComboBox_SelectionChanged(this, null);
            }
            else {
                connectionButton.Content = "Connecting...";
                connectionButton.IsEnabled = false;
            }
        }));
}

用户状态

调用 controller.Status 并传入一个有效的状态枚举对象。请注意,此事件处理不是理想的,因为如果用户状态由插件更改,则更改不会反映在 UI 中。UI 应通过相应的事件处理程序更新。

        
private void statusComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (statusComboBox.SelectedItem != null && 
            controller != null && 
            controller.LoginStatus == MSNEnumerations.LoginStatus.LOGGED_IN) {
        String status = ((ComboBoxItem)(statusComboBox.SelectedItem)).Content.ToString().Trim();
        MSNEnumerations.UserStatus userStatus = MSNEnumerations.UserStatus.online;

        if (status.Equals("Away")) {
            userStatus = MSNEnumerations.UserStatus.away;
        }
        else if (status.Equals("Busy")) {
            userStatus = MSNEnumerations.UserStatus.busy;
        }
        else if (status.Equals("Appear Offline")) {
            userStatus = MSNEnumerations.UserStatus.offline;
        }

        if (controller != null) {
            controller.Status = userStatus;
        }
    }
}

联系人列表同步

联系人信息同时存储在 UI 和两个字典中(显著提高性能),如下所示。

        
private Dictionary<String, TreeViewItem> contactsTreeNodes = 
    new Dictionary<string, TreeViewItem>(); //username, treenode
private Dictionary<String, TreeViewItem> groupsTreeNodes = 
    new Dictionary<string, TreeViewItem>(); //group name, treenode

处理联系人添加或移除;如果添加,则创建一个新的 TreeViewItem 并附加到 contactsTreeViewItem。Header 可以包含用户名,直到友好名称已知,但用户名也应存储在 tag 中,以便根据节点进行简单的用户名提取(方便以后启动对话)。移除涉及从其父节点和数据结构中移除节点。

        
private void controller_ContactAdded(string username, bool added)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
        {
            if (added) {
                TreeViewItem tvi = new TreeViewItem();
                tvi.Header = username;
                tvi.Tag = username;
                tvi.ToolTip = new ToolTip() { Content = username };
                tvi.MouseDoubleClick += new MouseButtonEventHandler(tvi_MouseDoubleClick);
                contactsTreeNodes.Add(username, tvi);

                baseNode.Items.Add(tvi);
            }
            else {
                TreeViewItem tvi = contactsTreeNodes[username];
                ((TreeViewItem)tvi.Parent).Items.Remove(tvi);
                contactsTreeNodes.Remove(username);
            }
       }));
}

请注意,已为启动对话的事件处理程序添加了双击处理程序(见下文)。

通过查找联系人字典中的相关节点并更新其 Header 来处理联系人友好名称的更改。

        
private void controller_FriendlyNameChanged(string username, string friendlyName)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
        {
            TreeViewItem tvi = contactsTreeNodes[username];
            tvi.Header = friendlyName;
        }));
}

组的创建和修改方式类似。

        
private void controller_GroupModified(string groupName, bool added)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
        {
            if (added) {
                TreeViewItem tvi = new TreeViewItem();
                tvi.Header = groupName;
                tvi.IsExpanded = true;
                groupsTreeNodes.Add(groupName, tvi);

                baseNode.Items.Add(tvi);
            }
            else {
                TreeViewItem tvi = contactsTreeNodes[groupName];
                ((TreeViewItem)tvi.Parent).Items.Remove(tvi);
                groupsTreeNodes.Remove(groupName);
            }
       }));
}

private void controller_GroupMemberChanged(string username, string groupName, bool added)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
        {
            if (added) {
                TreeViewItem contactTvi = contactsTreeNodes[username];
                TreeViewItem groupTvi = groupsTreeNodes[groupName];

                //remove from old parent
                ((TreeViewItem)contactTvi.Parent).Items.Remove(contactTvi);

                //add to new parent
                groupTvi.Items.Add(contactTvi);
            }
    }));
}

开始对话

对话的启动由联系人节点的双击处理程序处理;用户名在节点的 Tag 属性中。

        
private void tvi_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (controller != null && 
            controller.LoginStatus == MSNEnumerations.LoginStatus.LOGGED_IN) {
        TreeViewItem tvi = (TreeViewItem)sender;
        String name = null;

        if (tvi.Tag != null) {
            name = tvi.Tag.ToString();
        }
        
        List<String> users = new List<string>();
        users.Add(name);

        controller.startConversation(users);
    }
}

对话窗口(ConversationWindow)在 SwitchboardCreated 事件中创建,并在 SwitchboardReCreated 事件处理程序中重新打开。为了方便 SwitchboardReCreated 事件处理程序的查找,添加了一个 MSNSwitchboard、ConversationWindow 的字典。

        
private Dictionary<MSNSwitchboard, ConversationWindow> conversationWindows = 
    new Dictionary<MSNSwitchboard, ConversationWindow>(); //switchboard, associated ui element

private void SwitchboardController_SwitchboardReCreated(MSNSwitchboard switchboard)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
    {
        ConversationWindow window = conversationWindows[switchboard];
        window.Show();
    }));
}

private void SwitchboardController_SwitchboardCreated(MSNSwitchboard switchboard)
{
    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
        {
            ConversationWindow window = new ConversationWindow(controller, switchboard);
            conversationWindows.Add(switchboard, window);
            window.Show();
        }));
}

对话窗口

用户界面组件

  • System.Windows.WebBrowser conversationFrame - 用于显示消息。未使用 Frame,因为它目前不支持内存中的 HTML,而只支持 Uri(Web 或本地)。
  • TextBox sendTextBox - 用于用户输入要发送的消息。
  • Button sendButton - 用于发送 sendTextBox 中的内容;应限制为最大消息长度(100 个字符)。

在线用户

加入对话的用户应在 conversationFrame 中指示,除非是初始用户加入。窗口标题也会更新。ConnectedUsers 是在线用户列表。conversationHTML 是一个包含完整消息历史记录的字符串,然后通过 conversationFrame.DocumentText 更新到显示屏。

        
private void switchboard_UserConnected(string username, bool joined)
{
    Console.WriteLine("CONNECTED " + username);

    this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                               new RefreshDelegate(delegate()
        {
            //recievedBox.AppendText(message.getUserPayload() + "\r\n");
            if (joined) {
                if (connectedUsers.Count > 0) {
                    conversationHTML += username + " connected...
";
                    conversationFrame.DocumentText = conversationHTML;
                }
                
                connectedUsers.Add(username);

                this.Title += " - " + username;
           }
           else {
               if (connectedUsers.Count > 1) {
                    conversationHTML += username + " disconnected...
";
                    conversationFrame.DocumentText = conversationHTML;
               }

                connectedUsers.Remove(username);

                this.Title.Replace(" - " + username, "");
            }

        }));
}

发送消息

消息的发送通过 sendButton 的点击处理程序执行。此外,当用户在 sendTextBox 中输入时,应发送 MSNUserTypingMessages,以便联系人的 UI 可以显示“username”正在输入。同样,MessageRecieved 处理程序也应处理这些消息。

        
private void sendButton_Click(object sender, RoutedEventArgs e)
{
    //do NOT update gui from this
    switchboard.sendMessage(new MSNUserOutgoingMessage("Times New Roman", sendTextBox.Text));
    sendTextBox.Text = "";
}

消息显示

消息显示仅由 MessageSent 和 MessageRecieved 事件处理程序更新(而不是 sendButton 的点击事件处理程序),以便任何内容都可以先由任何插件处理。

        
private void switchboard_MessageSent(MSNUserMessage message)
{
    if (message.getMessageType() == MSNEnumerations.UserMessageType.outgoing_text_message) {
        Console.WriteLine("MESSAGE>>> " + message.getUserPayload());

        this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                 new RefreshDelegate(delegate()
        {
            //recievedBox.AppendText(message.getUserPayload() + "\r\n");
            conversationHTML += controller.FriendlyName + " says: "
                               + "<font color=\"Gray\">" + message.getUserPayload() + "</font><br />";

            conversationFrame.DocumentText = conversationHTML;

        }));
    }
}

private void switchboard_MessageRecieved(MSNUserMessage message)
{
    if (message.getMessageType() == MSNEnumerations.UserMessageType.incomming_text_message) {
        Console.WriteLine("MESSAGE<<< " + message.getUserPayload());
        
        this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, 
                                   new RefreshDelegate(delegate()
            {
                conversationHTML += controller.ContactsList.Contacts[message.getUsername()].FriendlyName + " says: "
                        + "<font color=\"Gray\">" + message.getUserPayload() + "</font><br />";

                conversationFrame.DocumentText = conversationHTML;

           }));
    }
}

编写插件

插件应实现 IMSNSwitchboardPlugin 接口,该接口具有以下方法;

  • void processOutgoingMessage(MSNUserMessage message) - 在 UI 调用 sendMessage 后,但在消息实际发送到 MSN 网络之前调用。
  • void processIngoingMessage(MSNUserMessage message) - 在库从 MSN 网络接收消息后,但在通过 MessageRecieved 事件发送到 UI 之前调用。
  • MSNSwitchboard Switchboard {get; set;} - 用于设置与对话对应的 MSNSwitchboard。

示例插件

此插件会将任何等于 @website 的传出消息替换为 http://www.derek-bartram.co.uk,并在本地客户端上显示已发送的网站地址。

    
public class WebsitePlugin : IMSNSwitchboardPlugin
{
    private MSNSwitchboard switchboard = null;

    #region IMSNSwitchboardPlugin Members

    public void processOutgoingMessage(MSNUserMessage message)
    {
        if (switchboard != null &&
                message.getMessageType() == MSNEnumerations.UserMessageType.outgoing_text_message &&
                message.getUserPayload().Equals("@website"))
        {
            message.setDisplay(false); //don't display the message locally
            message.setUserPayload("http://www.derek-bartram.co.uk"); //update send text

            #region create local content
            MSNUserOutgoingMessage localMessage = new MSNUserOutgoingMessage("Times New Roman", "Sent Website");
            localMessage.setSend(false); //don't send it to remote user
            localMessage.ProcessByPlugins = false; //don't process it through plugins as it has already been processed
            switchboard.sendMessage(localMessage); //send it
            #endregion
        }
    }

    public void processIngoingMessage(MSNUserMessage message)
    {
        //do nothing, this plugin only affects outgoing messages
    }

    public MSNSwitchboard Switchboard
    {
        get
        {
            return switchboard;
        }
        set
        {
            switchboard = value;
        }
    }

    #endregion
}

历史

版本 1.0.0.0 - 初始构建

其他许可说明

欢迎在您的工作中随意使用此代码,但请注意,我们采用修改后的 The Code Project Open License (CPOL);基本上它与标准许可证相同,但未经事先授权,此代码 **不得** 用于商业或非营利性商业用途。请参阅随附的源代码和演示文件中的 license.txt 或 license.pdf。

© . All rights reserved.