[pLog-svn] r5332 - in plugins/branches/lifetype-1.2: . addcommentnotify addcommentnotify/class addcommentnotify/class/action addcommentnotify/class/phpmsnclass addcommentnotify/class/view addcommentnotify/locale addcommentnotify/templates

mark at devel.lifetype.net mark at devel.lifetype.net
Sun Apr 22 13:59:17 EDT 2007


Author: mark
Date: 2007-04-22 13:59:17 -0400 (Sun, 22 Apr 2007)
New Revision: 5332

Added:
   plugins/branches/lifetype-1.2/addcommentnotify/
   plugins/branches/lifetype-1.2/addcommentnotify/class/
   plugins/branches/lifetype-1.2/addcommentnotify/class/action/
   plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyconfigaction.class.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyupdateconfigaction.class.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/config.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msn.class.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.sh
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnsendmsg.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/sample.php
   plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/test.msn
   plugins/branches/lifetype-1.2/addcommentnotify/class/view/
   plugins/branches/lifetype-1.2/addcommentnotify/class/view/pluginaddcommentnotifyconfigview.class.php
   plugins/branches/lifetype-1.2/addcommentnotify/locale/
   plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_en_UK.php
   plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_zh_TW.php
   plugins/branches/lifetype-1.2/addcommentnotify/pluginaddcommentnotify.class.php
   plugins/branches/lifetype-1.2/addcommentnotify/readme.txt
   plugins/branches/lifetype-1.2/addcommentnotify/templates/
   plugins/branches/lifetype-1.2/addcommentnotify/templates/addcommentnotify.template
Log:
Add addcommentnotify plugin contributed by twu2.

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyconfigaction.class.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyconfigaction.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyconfigaction.class.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,20 @@
+<?php
+lt_include(PLOG_CLASS_PATH.'class/action/admin/adminaction.class.php');
+lt_include(PLOG_CLASS_PATH.'plugins/addcommentnotify/class/view/pluginaddcommentnotifyconfigview.class.php');
+
+class PluginAddCommentNotifyConfigAction extends AdminAction
+{
+    function PluginAddCommentNotifyConfigAction($actionInfo, $request)
+    {
+        $this->AdminAction($actionInfo, $request);
+        return;
+    }
+
+    function perform()
+    {
+        $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+        $this->setCommonData();
+        return true;
+    }
+}
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyupdateconfigaction.class.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyupdateconfigaction.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/action/pluginaddcommentnotifyupdateconfigaction.class.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,136 @@
+<?php
+lt_include(PLOG_CLASS_PATH.'class/action/admin/adminaction.class.php');
+lt_include( PLOG_CLASS_PATH.'plugins/addcommentnotify/class/view/pluginaddcommentnotifyconfigview.class.php');
+
+class PluginAddCommentNotifyUpdateConfigAction extends AdminAction
+{
+    var $_pluginEnabled;
+    var $_useMSNClass;
+    var $_msnClassFile;
+    var $_msnUser;
+    var $_msnPassword;
+    var $_msnbot_spool;
+    var $_tolist;
+
+    function PluginAddCommentNotifyUpdateConfigAction($actionInfo, $request)
+    {
+        $this->AdminAction($actionInfo, $request);
+        return;
+    }
+
+    function validate()
+    {
+        $this->_pluginEnabled = $this->_request->getValue('pluginEnabled');
+        $this->_pluginEnabled = ($this->_pluginEnabled != '');
+
+        $this->_useMSNClass = $this->_request->getValue('useMSNClass');
+        $this->_useMSNClass = ($this->_useMSNClass != '');
+
+        $this->_msnClassFile = trim($this->_request->getValue('msnClassFile'));
+        $this->_msnUser = trim($this->_request->getValue('msnUser'));
+        $this->_msnPassword = trim($this->_request->getValue('msnPassword'));
+        $this->_msnbot_spool = rtrim(trim($this->_request->getValue('msnbotSpool')), '/');
+        $this->_tolist = trim($this->_request->getValue('toList'));
+
+        if (($this->_useMSNClass && ($this->_msnClassFile === '' || $this->_msnUser === '' || $this->_msnPassword === '')) ||
+            (!$this->_useMSNClass && ($this->_msnbot_spool === '')) || 
+            $this->_tolist === '') {
+            $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+            $this->_view->setErrorMessage($this->_locale->tr('error_required_missing'));
+            $this->setCommonData();
+            return false;
+        }
+
+        if ($this->_useMSNClass) {
+            if (!is_file($this->_msnClassFile)) {
+                $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+                $this->_view->setErrorMessage($this->_locale->tr('error_addcommentnotify_msnclass_file'));
+                $this->setCommonData();
+                return false;
+            }
+            @list($name, $domain, $network) = @explode('@', $this->_msnUser);
+            if ($domain == null || $network != null) {
+                $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+                $this->_view->setErrorMessage($this->_locale->tr('error_addcommentnotify_msn_user'));
+                $this->setCommonData();
+                return false;
+            }
+        }
+        else {
+            if (!is_dir($this->_msnbot_spool)) {
+                $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+                $this->_view->setErrorMessage($this->_locale->tr('error_addcommentnotify_msnbot_spool'));
+                $this->setCommonData();
+                return false;
+            }
+        }
+
+        $to_list_error = false;
+        $aList = @explode(',', $this->_tolist);
+        if (count($aList) == 0)
+            $to_list_error = true;
+        else {
+            foreach ($aList as $to) {
+                @list($name, $domain, $network) = @explode('@', $to);
+                if ($network != NULL && $network != 1 && $network != 32) {
+                    $to_list_error = true;
+                    break;
+                }
+                if ($domain == NULL) {
+                    $to_list_error = true;
+                    break;
+                }
+            }
+        }
+
+        if ($to_list_error) {
+            $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+            $this->_view->setErrorMessage($this->_locale->tr('error_addcommentnotify_tolist'));
+            $this->setCommonData();
+            return false;
+        }
+        return true;
+    }
+
+    function perform()
+    {
+        // Get the blog settings
+        $blogSettings = $this->_blogInfo->getSettings();
+
+        // Update the relevant values with the form inputs
+        $blogSettings->setValue('plugin_addcommentnotify_enabled', $this->_pluginEnabled);
+        $blogSettings->setValue('addcommentnotify_usemsnclass', $this->_useMSNClass);
+        $blogSettings->setValue('addcommentnotify_msnclass_file', $this->_msnClassFile);
+        $blogSettings->setValue('addcommentnotify_msn_user', $this->_msnUser);
+        $blogSettings->setValue('addcommentnotify_msn_password', $this->_msnPassword);
+        $blogSettings->setValue('addcommentnotify_msnbot_spool', $this->_msnbot_spool);
+        $blogSettings->setValue('addcommentnotify_tolist', $this->_tolist);
+
+        // Set and save the new blog settings
+        $this->_blogInfo->setSettings($blogSettings);
+        $blogs = new Blogs();
+
+        // Display an error message if there was a problem with the update
+        if( !$blogs->updateBlog($this->_blogInfo)) {
+            $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+            $this->_view->setErrorMessage($this->_locale->tr('error_updating_settings'));
+            $this->setCommonData();
+            return false;
+        }
+
+        // If things went well, save the session...
+        $this->_blogInfo->setSettings($blogSettings);
+        $this->_session->setValue('blogInfo', $this->_blogInfo);
+        $this->saveSession();
+
+        // and show the success message
+        $this->_view = new PluginAddCommentNotifyConfigView($this->_blogInfo);
+        $this->_view->setSuccessMessage($this->_locale->tr('gallery_settings_saved_ok'));
+        $this->setCommonData();
+
+        // clear the cache
+        CacheControl::resetBlogCache($this->_blogInfo->getId());
+        return true;
+    }
+}
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/config.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/config.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/config.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,17 @@
+<?php
+
+// your MSN login account
+$msn_acct = 'YOUR_MSN_ACCOUNT';
+// your MSN password
+$msn_password = 'YOUR_MSN_PASSWORD';
+// your alias name for MSN
+$msn_alias = 'YOUR_MSN_ALIAS';
+// notify list when someone add us to their list or remove us from their list
+// after domain, you can assign @n to specify the network of this email,
+// where @1 is for MSN (yes, if no @1, also for MSN)
+//       @32 is for Yahoo
+$aNotifyUser = array('MSN_ACCOUNT1 at 1',
+                     'MSN_ACCOUNT2',
+                     'YAHOO_ACCOUNT at 32');
+
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msn.class.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msn.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msn.class.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,2735 @@
+<?php
+/*
+
+MSN class ver 1.5 by Tommy Wu
+License: GPL
+
+You can find MSN protocol from this site: http://msnpiki.msnfanatic.com/index.php/Main_Page
+
+This class support both MSNP15 and MSNP9 for send message. The PHP module needed:
+
+  MSNP9: curl pcre
+  MSNP15: curl pcre mhash mcrypt bcmath
+
+Usually, this class will try to use MSNP15 if your system can support it, if your system can't support it,
+it will switch to use MSNP9. But if you use MSNP9, it won't support OIM (Offline Messages).
+
+Sameple Code:
+
+$msn = new MSN;
+
+if (!$msn->connect('YOUR_MSN_ID', 'YOUR_MSN_PASSWORD')) {
+    echo "Error for connect to MSN network\n";
+    echo "$msn->error\n";
+    exit;
+}
+
+$msn->sendMessage('Now: '.strftime('%D %T')."\nTesting\nSecond Line\n\n\n\nand Empty Line",
+                  array(
+                    'somebody1 at hotmail.com',
+                    'somebody2 at hotmail.com'
+                       )
+                 );
+echo "Done!\n";
+exit;
+
+changelog:
+
+1.5 2007/04/06
+ ! change default stream timeout from 10 seconds to 2 seconds (for both NS and SB), can modify it, just change $stream_timeout
+ ! alias should be encoded for PRP command.
+ ! fix the problem for sendMessage() can't send to yahoo user.
+
+1.4 2007/04/02
+ ! change the timeout detect, not for whole session, just for 2 command, reset $start_tm after each command.
+ ! only show header for debug mode
+ ! fix login problem if login.live.com redirect to some URL.
+ ! fix OIM issue, can't send OIM for first email after TO: (one extra space)
+ ! fix OIM problem when msnbot running over 1 day, the ticket will expire, we'll try to get new ticket again.
+ + yes, we can get contact list for MSNP15 now
+ + process ADD/REM and ADL/RML command, allow user define some function for this.
+ + add support for Yahoo! network in MSNP15
+ + try to send OIM if we can't send via SB (timeout? or some error in SB)
+ + also read OIM, and reply it.
+ + move .msn file to backup folder after sent the message
+
+1.3a 2007/03/29
+ ! fix the problem when we need to re-login again.
+ + also wait for $retry_wait seconds if we logout from sever.
+
+1.3 2007/03/29
+ ! fix the return code for switchboard_control(), it should be true for successful.
+ + add doLoop() to work like a sending message bot, get the data from spool directory and never logout.
+ + allow user define a function to process the message.
+ + add login for contacts.msn.com, but even we can login, we still can't send the correct SOAP to get address book.
+
+1.2 2007/03/13
+ ! fix the problem for 32bits platform can't get correct challenge code
+
+1.1 2007/03/13
+ + add timeout detect, make sure we can quit from loop when protocol changed or sometime wrong
+ + allow save the debug information to file
+
+1.0 2007/03/11
+ = public release
+
+*/
+
+class MSN
+{
+	var $server	= 'messenger.hotmail.com';
+	var $port = 1863;
+
+    var $passport_url = 'https://login.live.com/RST.srf';
+    var $protocol = 'MSNP15';
+    var $buildver = '8.1.0178';
+    var $prod_key = 'PK}_A_0N_K%O?A9S';
+    var $prod_id = 'PROD0114ES4Z%Q5W';
+    var $login_method = 'SSO';
+
+    var $oim_send_url = 'https://ows.messenger.msn.com/OimWS/oim.asmx';
+    var $oim_sendsoap = 'http://messenger.live.com/ws/2006/09/oim/Store2';
+    var $oim_maildata_url = 'https://rsi.hotmail.com/rsi/rsi.asmx';
+    var $oim_maildata_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata';
+    var $oim_read_url = 'https://rsi.hotmail.com/rsi/rsi.asmx';
+    var $oim_read_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage';
+    var $oim_del_url = 'https://rsi.hotmail.com/rsi/rsi.asmx';
+    var $oim_del_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages';
+
+    var $membership_url = 'https://contacts.msn.com/abservice/SharingService.asmx';
+    var $membership_soap = 'http://www.msn.com/webservices/AddressBook/FindMembership';
+
+    var $addmember_url = 'https://contacts.msn.com/abservice/SharingService.asmx';
+    var $addmember_soap = 'http://www.msn.com/webservices/AddressBook/AddMember';
+
+    var $delmember_url = 'https://contacts.msn.com/abservice/SharingService.asmx';
+    var $delmember_soap = 'http://www.msn.com/webservices/AddressBook/DeleteMember';
+
+    var $id;
+    var $fp = false;
+    var $error = '';
+
+    var $authed = false;
+    var $user = '';
+    var $password = '';
+
+    var $passport_policy = '';
+    var $oim_try = 3;
+    var $oim_ticket = '';
+    var $contact_ticket = '';
+
+    // FIXME: even we login for following site, but... we don't need that now.
+    var $web_ticket = '';
+    var $space_ticket = '';
+    var $storage_ticket = '';
+
+    var $debug = false;
+    var $log_file = '';
+    var $timeout = 15;
+    var $stream_timeout = 2;
+
+    var $log_path = false;
+
+    var $sb;
+	var $font_fn = 'Arial';
+	var $font_co = '333333';
+	var $font_ef = '';
+
+    var $kill_me = false;
+
+    function MSN($protocol = '', $debug = false, $timeout = 15)
+    {
+        if (is_string($debug) && $debug !== '') {
+            $this->debug = true;
+            $this->log_file = $debug;
+        }
+        else
+            $this->debug = $debug;
+        $this->timeout = $timeout;
+        // check support
+        if (!function_exists('curl_init')) die("We need curl module!\n");
+        if (!function_exists('preg_match')) die("We need pcre module!\n");
+
+        if ($protocol != 'MSNP9' && $protocol != 'MSNP15')
+            $protocol = '';
+
+        if ($protocol != 'MSNP9' && !function_exists('mhash')) {
+            if ($protocol == 'MSNP15') die("We need mhash module for $protocol!\n");
+            $protocol = 'MSNP9';
+        }
+        if ($protocol != 'MSNP9' && !function_exists('mcrypt_cbc')) {
+            if ($protocol == 'MSNP15') die("We need mcrypt module for $protocol!\n");
+            $protocol = 'MSNP9';
+        }
+        if ($protocol != 'MSNP9' && !function_exists('bcmod')) {
+            if ($protocol == 'MSNP15') die("We need bcmath module for $protocol!\n");
+            $protocol = 'MSNP9';
+        }
+        if ($protocol == 'MSNP9') {
+            $this->protocol = 'MSNP9';
+            $this->passport_url = 'https://nexus.passport.com/rdr/pprdr.asp';
+            $this->buildver = '6.0.0602';
+            $this->prod_key = 'Q1P7W2E4J9R8U3S5';
+            $this->prod_id = 'msmsgs at msnmsgr.com';
+            $this->login_method = 'TWN';
+        }
+        else {
+            $this->protocol = 'MSNP15';
+            $this->passport_url = 'https://login.live.com/RST.srf';
+            $this->buildver = '8.1.0178';
+            $this->prod_key = 'PK}_A_0N_K%O?A9S';
+            $this->prod_id = 'PROD0114ES4Z%Q5W';
+            $this->login_method = 'SSO';
+
+            $this->oim_send_url = 'https://ows.messenger.msn.com/OimWS/oim.asmx';
+            $this->oim_send_soap = 'http://messenger.live.com/ws/2006/09/oim/Store2';
+        }
+        return;
+    }
+
+    function get_passport_ticket($url = '')
+    {
+        $user = htmlspecialchars($this->user);
+        $password = htmlspecialchars($this->password);
+
+        if ($url === '')
+            $passport_url = $this->passport_url;
+        else
+            $passport_url = $url;
+
+        $XML = '<?xml version="1.0" encoding="UTF-8"?>
+<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"
+          xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
+          xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
+          xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
+          xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+          xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
+          xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc"
+          xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust">
+<Header>
+  <ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="PPAuthInfo">
+    <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>
+    <ps:BinaryVersion>4</ps:BinaryVersion>
+    <ps:UIVersion>1</ps:UIVersion>
+    <ps:Cookies></ps:Cookies>
+    <ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>
+  </ps:AuthInfo>
+  <wsse:Security>
+    <wsse:UsernameToken Id="user">
+      <wsse:Username>'.$user.'</wsse:Username>
+      <wsse:Password>'.$password.'</wsse:Password>
+    </wsse:UsernameToken>
+  </wsse:Security>
+</Header>
+<Body>
+  <ps:RequestMultipleSecurityTokens xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="RSTS">
+    <wst:RequestSecurityToken Id="RST0">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>http://Passport.NET/tb</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+    </wst:RequestSecurityToken>
+    <wst:RequestSecurityToken Id="RST1">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>messengerclear.live.com</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+      <wsse:PolicyReference URI="'.$this->passport_policy.'"></wsse:PolicyReference>
+    </wst:RequestSecurityToken>
+    <wst:RequestSecurityToken Id="RST2">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>messenger.msn.com</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+      <wsse:PolicyReference URI="?id=507"></wsse:PolicyReference>
+    </wst:RequestSecurityToken>
+    <wst:RequestSecurityToken Id="RST3">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>contacts.msn.com</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+      <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>
+    </wst:RequestSecurityToken>
+    <wst:RequestSecurityToken Id="RST4">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>messengersecure.live.com</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+      <wsse:PolicyReference URI="MBI_SSL"></wsse:PolicyReference>
+    </wst:RequestSecurityToken>
+    <wst:RequestSecurityToken Id="RST5">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>spaces.live.com</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+      <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>
+    </wst:RequestSecurityToken>
+    <wst:RequestSecurityToken Id="RST6">
+      <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+      <wsp:AppliesTo>
+        <wsa:EndpointReference>
+          <wsa:Address>storage.msn.com</wsa:Address>
+        </wsa:EndpointReference>
+      </wsp:AppliesTo>
+      <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>
+    </wst:RequestSecurityToken>
+  </ps:RequestMultipleSecurityTokens>
+</Body>
+</Envelope>';
+
+        $this->debug_message("*** URL: $passport_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $passport_url);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200) {
+            // sometimes, rediret to another URL
+            // MSNP15
+            //<faultcode>psf:Redirect</faultcode>
+            //<psf:redirectUrl>https://msnia.login.live.com/pp450/RST.srf</psf:redirectUrl>
+            //<faultstring>Authentication Failure</faultstring>
+            if (strpos($data, '<faultcode>psf:Redirect</faultcode>') === false) {
+                $this->debug_message("*** Can't get passport ticket! http code = $http_code");
+                return false;
+            }
+            preg_match("#<psf\:redirectUrl>(.*)</psf\:redirectUrl>#", $data, $matches);
+            if (count($matches) == 0) {
+                $this->debug_message("*** redirect, but can't get redirect URL!");
+                return false;
+            }
+            $redirect_url = $matches[1];
+            if ($redirect_url == $passport_url) {
+                $this->debug_message("*** redirect, but redirect to same URL!");
+                return false;
+            }
+            return $this->get_passport_ticket($redirect_url);
+        }
+
+        // we need ticket and secret code
+        // RST1: messengerclear.live.com
+        // <wsse:BinarySecurityToken Id="Compact1">t=tick&p=</wsse:BinarySecurityToken>
+        // <wst:BinarySecret>binary secret</wst:BinarySecret>
+        // RST2: messenger.msn.com
+        // <wsse:BinarySecurityToken Id="PPToken2">t=tick</wsse:BinarySecurityToken>
+        // RST3: contacts.msn.com
+        // <wsse:BinarySecurityToken Id="Compact3">t=tick&p=</wsse:BinarySecurityToken>
+        // RST4: messengersecure.live.com
+        // <wsse:BinarySecurityToken Id="Compact4">t=tick&p=</wsse:BinarySecurityToken>
+        // RST5: spaces.live.com
+        // <wsse:BinarySecurityToken Id="Compact5">t=tick&p=</wsse:BinarySecurityToken>
+        // RST6: storage.msn.com
+        // <wsse:BinarySecurityToken Id="Compact6">t=tick&p=</wsse:BinarySecurityToken>
+        preg_match("#".
+                   "<wsse\:BinarySecurityToken Id=\"Compact1\">(.*)</wsse\:BinarySecurityToken>(.*)".
+                   "<wst\:BinarySecret>(.*)</wst\:BinarySecret>(.*)".
+                   "<wsse\:BinarySecurityToken Id=\"PPToken2\">(.*)</wsse\:BinarySecurityToken>(.*)".
+                   "<wsse\:BinarySecurityToken Id=\"Compact3\">(.*)</wsse\:BinarySecurityToken>(.*)".
+                   "<wsse\:BinarySecurityToken Id=\"Compact4\">(.*)</wsse\:BinarySecurityToken>(.*)".
+                   "<wsse\:BinarySecurityToken Id=\"Compact5\">(.*)</wsse\:BinarySecurityToken>(.*)".
+                   "<wsse\:BinarySecurityToken Id=\"Compact6\">(.*)</wsse\:BinarySecurityToken>(.*)".
+                   "#",
+                   $data, $matches);
+
+        // no ticket found!
+        if (count($matches) == 0) {
+            $this->debug_message("*** Can't get passport ticket!");
+            return false;
+        }
+
+        //$this->debug_message(var_export($matches, true));
+        // matches[0]: all data
+        // matches[1]: RST1 (messengerclear.live.com) ticket
+        // matches[2]: ...
+        // matches[3]: RST1 (messengerclear.live.com) binary secret
+        // matches[4]: ...
+        // matches[5]: RST2 (messenger.msn.com) ticket
+        // matches[6]: ...
+        // matches[7]: RST3 (contacts.msn.com) ticket
+        // matches[8]: ...
+        // matches[9]: RST4 (messengersecure.live.com) ticket
+        // matches[10]: ...
+        // matches[11]: RST5 (spaces.live.com) ticket
+        // matches[12]: ...
+        // matches[13]: RST6 (storage.live.com) ticket
+        // matches[14]: ...
+
+        // so
+        // ticket => $matches[1]
+        // secret => $matches[3]
+        // web_ticket => $matches[5]
+        // contact_ticket => $matches[7]
+        // oim_ticket => $matches[9]
+        // space_ticket => $matches[11]
+        // storage_ticket => $matches[13]
+
+        // yes, we get ticket
+        $aTickets = array(
+                    'ticket' => html_entity_decode($matches[1]),
+                    'secret' => html_entity_decode($matches[3]),
+                    'web_ticket' => html_entity_decode($matches[5]),
+                    'contact_ticket' => html_entity_decode($matches[7]),
+                    'oim_ticket' => html_entity_decode($matches[9]),
+                    'space_ticket' => html_entity_decode($matches[11]),
+                    'storage_ticket' => html_entity_decode($matches[13])
+                    );
+        //$this->debug_message(var_export($aTickets, true));
+        return $aTickets;
+    }
+
+	function get_tweener_passport_ticket($nonce)
+	{
+        $this->debug_message("*** URL: $this->passport_url");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->passport_url);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_NOBODY, 1);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        // we need login URL
+        // DALogin=xxx
+        preg_match('/DALogin=(.*?),/', $data, $matches);
+
+        // no URL found!
+        if (count($matches) == 0) {
+            $this->debug_message("*** Can't get passport's URL! http code = $http_code");
+            return false;
+        }
+
+        $url = 'https://'.$matches[1];
+
+        $this->debug_message("*** URL: $url");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                        'Authorization: Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in='.$this->user.',pwd='.$this->password.','.$nonce,
+                        'Host: login.passport.com'
+                        ));
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_NOBODY, 1);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        // we need ticket
+        // from-PP=xxx
+        preg_match("/from-PP='(.*?)'/", $data, $matches);
+
+        // no URL found!
+        if (count($matches) == 0) {
+            $this->debug_message("*** Can't get passport's ticket! http code = $http_code");
+            return false;
+        }
+        return $matches[1];
+	}
+
+    function delMemberFromList($memberID, $email, $network, $list)
+    {
+        if ($network != 1 && $network != 32) return true;
+        if ($memberID === false) return true;
+        $user = htmlspecialchars($email);
+        $ticket = htmlspecialchars($this->contact_ticket);
+        if ($network == 1)
+            $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+    <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+        <IsMigration>false</IsMigration>
+        <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+    </ABApplicationHeader>
+    <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ManagedGroupRequest>false</ManagedGroupRequest>
+        <TicketToken>'.$ticket.'</TicketToken>
+    </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+    <DeleteMember xmlns="http://www.msn.com/webservices/AddressBook">
+        <serviceHandle>
+            <Id>0</Id>
+            <Type>Messenger</Type>
+            <ForeignId></ForeignId>
+        </serviceHandle>
+        <memberships>
+            <Membership>
+                <MemberRole>'.$list.'</MemberRole>
+                <Members>
+                    <Member xsi:type="PassportMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+                        <Type>Passport</Type>
+                        <MembershipId>'.$memberID.'</MembershipId>
+                        <State>Accepted</State>
+                    </Member>
+                </Members>
+            </Membership>
+        </memberships>
+    </DeleteMember>
+</soap:Body>
+</soap:Envelope>';
+        else
+            $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+    <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+        <IsMigration>false</IsMigration>
+        <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+    </ABApplicationHeader>
+    <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ManagedGroupRequest>false</ManagedGroupRequest>
+        <TicketToken>'.$ticket.'</TicketToken>
+    </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+    <DeleteMember xmlns="http://www.msn.com/webservices/AddressBook">
+        <serviceHandle>
+            <Id>0</Id>
+            <Type>Messenger</Type>
+            <ForeignId></ForeignId>
+        </serviceHandle>
+        <memberships>
+            <Membership>
+                <MemberRole>'.$list.'</MemberRole>
+                <Members>
+                    <Member xsi:type="EmailMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+                        <Type>Email</Type>
+                        <MembershipId>'.$memberID.'</MembershipId>
+                        <State>Accepted</State>
+                    </Member>
+                </Members>
+            </Membership>
+        </memberships>
+    </DeleteMember>
+</soap:Body>
+</soap:Envelope>';
+
+        $header_array = array(
+                        'SOAPAction: '.$this->delmember_soap,
+                        'Content-Type: text/xml; charset=utf-8',
+                        'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
+                    );
+
+        $this->debug_message("*** URL: $this->delmember_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->delmember_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200) {
+            preg_match('#<faultcode>(.*)</faultcode><faultstring>(.*)</faultstring>#', $data, $matches);
+            if (count($matches) == 0) {
+                $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list");
+                return false;
+            }
+            $faultcode = trim($matches[1]);
+            $faultstring = trim($matches[2]);
+            if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) {
+                $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring");
+                return false;
+            }
+            $this->log_message("*** delete member (network: $network) $email ($memberID) from $list, not exist");
+            return true;
+        }
+        $this->log_message("*** delete member (network: $network) $email ($memberID) from $list");
+        return true;
+    }
+
+    function addMemberToList($email, $network, $list)
+    {
+        if ($network != 1 && $network != 32) return true;
+        $ticket = htmlspecialchars($this->contact_ticket);
+        $user = htmlspecialchars($email);
+
+        if ($network == 1)
+            $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+    <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+        <IsMigration>false</IsMigration>
+        <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+    </ABApplicationHeader>
+    <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ManagedGroupRequest>false</ManagedGroupRequest>
+        <TicketToken>'.$ticket.'</TicketToken>
+    </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+    <AddMember xmlns="http://www.msn.com/webservices/AddressBook">
+        <serviceHandle>
+            <Id>0</Id>
+            <Type>Messenger</Type>
+            <ForeignId></ForeignId>
+        </serviceHandle>
+        <memberships>
+            <Membership>
+                <MemberRole>'.$list.'</MemberRole>
+                <Members>
+                    <Member xsi:type="PassportMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+                        <Type>Passport</Type>
+                        <State>Accepted</State>
+                        <PassportName>'.$user.'</PassportName>
+                    </Member>
+                </Members>
+            </Membership>
+        </memberships>
+    </AddMember>
+</soap:Body>
+</soap:Envelope>';
+        else
+            $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+    <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+        <IsMigration>false</IsMigration>
+        <PartnerScenario>ContactMsgrAPI</PartnerScenario>
+    </ABApplicationHeader>
+    <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ManagedGroupRequest>false</ManagedGroupRequest>
+        <TicketToken>'.$ticket.'</TicketToken>
+    </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+    <AddMember xmlns="http://www.msn.com/webservices/AddressBook">
+        <serviceHandle>
+            <Id>0</Id>
+            <Type>Messenger</Type>
+            <ForeignId></ForeignId>
+        </serviceHandle>
+        <memberships>
+            <Membership>
+                <MemberRole>'.$list.'</MemberRole>
+                <Members>
+                    <Member xsi:type="EmailMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+                        <Type>Email</Type>
+                        <State>Accepted</State>
+                        <Email>'.$user.'</Email>
+                        <Annotations>
+                            <Annotation>
+                                <Name>MSN.IM.BuddyType</Name>
+                                <Value>32:YAHOO</Value>
+                            </Annotation>
+                        </Annotations>
+                    </Member>
+                </Members>
+            </Membership>
+        </memberships>
+    </AddMember>
+</soap:Body>
+</soap:Envelope>';
+        $header_array = array(
+                        'SOAPAction: '.$this->addmember_soap,
+                        'Content-Type: text/xml; charset=utf-8',
+                        'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
+                    );
+
+        $this->debug_message("*** URL: $this->addmember_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->addmember_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200) {
+            preg_match('#<faultcode>(.*)</faultcode><faultstring>(.*)</faultstring>#', $data, $matches);
+            if (count($matches) == 0) {
+                $this->log_message("*** can't add member (network: $network) $email to $list");
+                return false;
+            }
+            $faultcode = trim($matches[1]);
+            $faultstring = trim($matches[2]);
+            if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) {
+                $this->log_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring");
+                return false;
+            }
+            $this->log_message("*** add member (network: $network) $email to $list, already exist!");
+            return true;
+        }
+        $this->log_message("*** add member (network: $network) $email to $list");
+        return true;
+    }
+
+    function getMembershipList()
+    {
+        $ticket = htmlspecialchars($this->contact_ticket);
+        $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+<soap:Header>
+    <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>
+        <IsMigration>false</IsMigration>
+        <PartnerScenario>Initial</PartnerScenario>
+    </ABApplicationHeader>
+    <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">
+        <ManagedGroupRequest>false</ManagedGroupRequest>
+        <TicketToken>'.$ticket.'</TicketToken>
+    </ABAuthHeader>
+</soap:Header>
+<soap:Body>
+    <FindMembership xmlns="http://www.msn.com/webservices/AddressBook">
+        <serviceFilter>
+            <Types>
+                <ServiceType>Messenger</ServiceType>
+                <ServiceType>Invitation</ServiceType>
+                <ServiceType>SocialNetwork</ServiceType>
+                <ServiceType>Space</ServiceType>
+                <ServiceType>Profile</ServiceType>
+            </Types>
+        </serviceFilter>
+    </FindMembership>
+</soap:Body>
+</soap:Envelope>';
+        $header_array = array(
+                            'SOAPAction: '.$this->membership_soap,
+                            'Content-Type: text/xml; charset=utf-8',
+                            'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
+                        );
+        $this->debug_message("*** URL: $this->membership_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->membership_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200) return array();
+        $p = $data;
+        $aMemberships = array();
+        while (1) {
+            //$this->debug_message("search p = $p");
+            $start = strpos($p, '<Membership>');
+            $end = strpos($p, '</Membership>');
+            if ($start === false || $end === false || $start > $end) break;
+            //$this->debug_message("start = $start, end = $end");
+            $end += 13;
+            $sMembership = substr($p, $start, $end - $start);
+            $aMemberships[] = $sMembership;
+            //$this->debug_message("add sMembership = $sMembership");
+            $p = substr($p, $end);
+        }
+        //$this->debug_message("aMemberships = ".var_export($aMemberships, true));
+
+        $aContactList = array();
+        foreach ($aMemberships as $sMembership) {
+            //$this->debug_message("sMembership = $sMembership");
+            if (isset($matches)) unset($matches);
+            preg_match('#<MemberRole>(.*)</MemberRole>#', $sMembership, $matches);
+            if (count($matches) == 0) continue;
+            $sMemberRole = $matches[1];
+            //$this->debug_message("MemberRole = $sMemberRole");
+            if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue;
+            $p = $sMembership;
+            if (isset($aMembers)) unset($aMembers);
+            $aMembers = array();
+            while (1) {
+                //$this->debug_message("search p = $p");
+                $start = strpos($p, '<Member xsi:type="');
+                $end = strpos($p, '</Member>');
+                if ($start === false || $end === false || $start > $end) break;
+                //$this->debug_message("start = $start, end = $end");
+                $end += 9;
+                $sMember = substr($p, $start, $end - $start);
+                $aMembers[] = $sMember;
+                //$this->debug_message("add sMember = $sMember");
+                $p = substr($p, $end);
+            }
+            //$this->debug_message("aMembers = ".var_export($aMembers, true));
+            foreach ($aMembers as $sMember) {
+                //$this->debug_message("sMember = $sMember");
+                if (isset($matches)) unset($matches);
+                preg_match('#<Member xsi\:type="([^"]*)">#', $sMember, $matches);
+                if (count($matches) == 0) continue;
+                $sMemberType = $matches[1];
+                //$this->debug_message("MemberType = $sMemberType");
+                $network = -1;
+                preg_match('#<MembershipId>(.*)</MembershipId>#', $sMember, $matches);
+                if (count($matches) == 0) continue;
+                $id = $matches[1];
+                if ($sMemberType == 'PassportMember') {
+                    if (strpos($sMember, '<Type>Passport</Type>') === false) continue;
+                    $network = 1;
+                    preg_match('#<PassportName>(.*)</PassportName>#', $sMember, $matches);
+                }
+                else if ($sMemberType == 'EmailMember') {
+                    if (strpos($sMember, '<Type>Email</Type>') === false) continue;
+                    // Value is 32: or 32:YAHOO
+                    preg_match('#<Annotation><Name>MSN.IM.BuddyType</Name><Value>(.*):(.*)</Value></Annotation>#', $sMember, $matches);
+                    if (count($matches) == 0) continue;
+                    if ($matches[1] != 32) continue;
+                    $network = 32;
+                    preg_match('#<Email>(.*)</Email>#', $sMember, $matches);
+                }
+                if ($network == -1) continue;
+                if (count($matches) > 0) {
+                    $email = $matches[1];
+                    @list($u_name, $u_domain) = @explode('@', $email);
+                    if ($u_domain == NULL) continue;
+                    $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id;
+                    $this->log_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)");
+                }
+            }
+        }
+        return $aContactList;
+    }
+
+	function connect($user, $password)
+	{
+		$this->id = 1;
+        $this->fp = @fsockopen($this->server, $this->port, $errno, $errstr, 5);
+        if (!$this->fp) {
+            $this->error = "Can't connect to $this->server:$this->port, error => $errno, $errstr";
+            return false;
+        }
+
+        stream_set_timeout($this->fp, $this->stream_timeout);
+        $this->authed = false;
+        // MSNP9
+        // NS: >> VER {id} MSNP9 CVR0
+        // MSNP15
+        // NS: >>> VER {id} MSNP15 CVR0
+        $this->writeln("VER $this->id $this->protocol CVR0");
+
+        $start_tm = time();
+        while (!feof($this->fp)) {
+            $data = $this->readln();
+            // no data?
+            if ($data === false) {
+                if ($this->timeout > 0) {
+                    $now_tm = time();
+                    $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm;
+                    if ($used_time > $this->timeout) {
+                        // logout now
+                        // NS: >>> OUT
+                        $this->writeln("OUT");
+                        fclose($this->fp);
+                        $this->error = 'Timeout, maybe protocol changed!';
+                        $this->debug_message("*** $this->error");
+                        return false;
+                    }
+                }
+                continue;
+            }
+            $code = substr($data, 0, 3);
+            $start_tm = time();
+            switch ($code) {
+                case 'VER':
+                    // MSNP9
+                    // NS: <<< VER {id} MSNP9 CVR0
+                    // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 6.0.0602 msmsgs {user}
+                    // MSNP15
+                    // NS: <<< VER {id} MSNP15 CVR0
+                    // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user}
+                    $this->writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS $this->buildver msmsgs $user");
+                    break;
+
+                case 'CVR':
+                    // MSNP9
+                    // NS: <<< CVR {id} {ver_list} {download_serve} ....
+                    // NS: >>> USR {id} TWN I {user}
+                    // MSNP15
+                    // NS: <<< CVR {id} {ver_list} {download_serve} ....
+                    // NS: >>> USR {id} SSO I {user}
+                    $this->writeln("USR $this->id $this->login_method I $user");
+                    break;
+
+                case 'USR':
+                    // already login for passport site, finish the login process now.
+                    // NS: <<< USR {id} OK {user} {verify} 0
+                    if ($this->authed) return true;
+
+                    $this->user = $user;
+                    $this->password = urlencode($password);
+
+                    if ($this->protocol == 'MSNP15') {
+                        // NS: <<< USR {id} SSO S {policy} {nonce}
+                        @list(/* USR */, /* id */, /* SSO */, /* S */, $policy, $nonce,) = @explode(' ', $data);
+
+                        $this->passport_policy = $policy;
+                        $aTickets = $this->get_passport_ticket();
+                        if (!$aTickets || !is_array($aTickets)) {
+                            // logout now
+                            // NS: >>> OUT
+                            $this->writeln("OUT");
+                            fclose($this->fp);
+                            $this->error = 'Passport authenticated fail!';
+                            $this->debug_message("*** $this->error");
+                            return false;
+                        }
+
+                        $ticket = $aTickets['ticket'];
+                        $secret = $aTickets['secret'];
+                        $this->oim_ticket = $aTickets['oim_ticket'];
+                        $this->contact_ticket = $aTickets['contact_ticket'];
+                        $this->web_ticket = $aTickets['web_ticket'];
+                        $this->space_ticket = $aTickets['space_ticket'];
+                        $this->storage_ticket = $aTickets['storage_ticket'];
+
+                        $login_code = $this->generateLoginBLOB($secret, $nonce);
+
+                        // NS: >>> USR {id} SSO S {ticket} {login_code}
+                        $this->writeln("USR $this->id $this->login_method S $ticket $login_code");
+                    }
+                    else {
+                        // NS: <<< USR {id} TWN S {nonce}
+                        @list(/* USR */, /* id */, /* TWN */, /* S */, $nonce,) = @explode(' ', $data);
+
+                        $ticket = $this->get_tweener_passport_ticket($nonce);
+                        if (!$ticket) {
+                            // logout now
+                            // NS: >>> OUT
+                            $this->writeln("OUT");
+                            fclose($this->fp);
+                            $this->error = 'Passport authenticated fail!';
+                            $this->debug_message("*** $this->error");
+                            return false;
+                        }
+
+                        // NS: >>> USR {id} TWN S {ticket}
+                        $this->writeln("USR $this->id $this->login_method S $ticket");
+                    }
+                    $this->authed = true;
+                    break;
+
+                case 'XFR':
+                    // main login server will redirect to anther NS after USR command
+                    // MSNP9
+                    // NS: <<< XFR {id} NS {server} 0 {server}
+                    // MSNP15
+                    // NS: <<< XFR {id} NS {server} U D
+                    @list(/* XFR */, /* id */, /* NS */, $server, /* ... */) = @explode(' ', $data);
+                    @list($ip, $port) = @explode(':', $server);
+                    // this connection will close after XFR
+                    fclose($this->fp);
+
+                    $this->fp = @fsockopen($ip, $port, $errno, $errstr, 5);
+                    if (!$this->fp) {
+                        $this->error = "Can't connect to $ip:$port, error => $errno, $errstr";
+                        $this->debug_message("*** $this->error");
+                        return false;
+                    }
+
+                    stream_set_timeout($this->fp, $this->stream_timeout);
+                    // MSNP9
+                    // NS: >> VER {id} MSNP9 CVR0
+                    // MSNP15
+                    // NS: >>> VER {id} MSNP15 CVR0
+                    $this->writeln("VER $this->id $this->protocol CVR0");
+                    break;
+
+                case 'GCF':
+                    // return some policy data after 'USR {id} SSO I {user}' command
+                    // NS: <<< GCF 0 {size}
+                    @list(/* GCF */, /* 0 */, $size,) = @explode(' ', $data);
+                    // we don't need the data, just read it and drop
+                    if (is_numeric($size) && $size > 0)
+                        $this->readdata($size);
+                    break;
+
+                default:
+                    // we'll quit if got any error
+                    if (is_numeric($code)) {
+                        // logout now
+                        // NS: >>> OUT
+                        $this->writeln("OUT");
+                        fclose($this->fp);
+                        $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+                        $this->debug_message("*** $this->error");
+                        return false;
+                    }
+                    // unknown response from server, just ignore it
+					break;
+            }
+        }
+        // never goto here
+    }
+
+    function derive_key($key, $magic)
+    {
+        $hash1 = mhash(MHASH_SHA1, $magic, $key);
+        $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key);
+        $hash3 = mhash(MHASH_SHA1, $hash1, $key);
+        $hash4 = mhash(MHASH_SHA1, $hash3.$magic, $key);
+        return $hash2.substr($hash4, 0, 4);
+    }
+
+    function generateLoginBLOB($key, $challenge)
+    {
+        $key1 = base64_decode($key);
+        $key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH');
+        $key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION');
+
+        // get hash of challenge using key2
+        $hash = mhash(MHASH_SHA1, $challenge, $key2);
+
+        // get 8 bytes random data
+        $iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8);
+
+        $cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv);
+
+        $blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72);
+        $blob .= $iv;
+        $blob .= $hash;
+        $blob .= $cipher;
+
+        return base64_encode($blob);
+    }
+
+    function getOIM_maildata()
+    {
+        preg_match('#t=(.*)&p=(.*)#', $this->web_ticket, $matches);
+        if (count($matches) == 0) {
+            $this->debug_message('*** no web ticket?');
+            return false;
+        }
+        $t = htmlspecialchars($matches[1]);
+        $p = htmlspecialchars($matches[2]);
+        $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+  <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+    <t>'.$t.'</t>
+    <p>'.$p.'</p>
+  </PassportCookie>
+</soap:Header>
+<soap:Body>
+  <GetMetadata xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi" />
+</soap:Body>
+</soap:Envelope>';
+
+        $header_array = array(
+                                'SOAPAction: '.$this->oim_maildata_soap,
+                                'Content-Type: text/xml; charset=utf-8',
+                                'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
+                            );
+
+        $this->debug_message("*** URL: $this->oim_maildata_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200) {
+            $this->debug_message("*** Can't get OIM maildata! http code: $http_code");
+            return false;
+        }
+
+        // <GetMetadataResponse xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">See #XML_Data</GetMetadataResponse>
+		preg_match('<GetMetadataResponse([^>]*)>(.*)</GetMetadataResponse>#', $data, $matches);
+        if (count($matches) == 0) {
+            $this->debug_message("*** Can't get OIM maildata");
+            return '';
+        }
+        return $matches[2];
+    }
+
+    function getOIM_message($msgid)
+    {
+        preg_match('#t=(.*)&p=(.*)#', $this->web_ticket, $matches);
+        if (count($matches) == 0) {
+            $this->debug_message('*** no web ticket?');
+            return false;
+        }
+        $t = htmlspecialchars($matches[1]);
+        $p = htmlspecialchars($matches[2]);
+
+        // read OIM
+        $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+  <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+    <t>'.$t.'</t>
+    <p>'.$p.'</p>
+  </PassportCookie>
+</soap:Header>
+<soap:Body>
+  <GetMessage xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+    <messageId>'.$msgid.'</messageId>
+    <alsoMarkAsRead>false</alsoMarkAsRead>
+  </GetMessage>
+</soap:Body>
+</soap:Envelope>';
+
+        $header_array = array(
+                                'SOAPAction: '.$this->oim_read_soap,
+                                'Content-Type: text/xml; charset=utf-8',
+                                'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
+                            );
+
+        $this->debug_message("*** URL: $this->oim_read_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->oim_read_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200) {
+            $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code");
+            return false;
+        }
+
+        // why can't use preg_match('#<GetMessageResult>(.*)</GetMessageResult>#', $data, $matches)?
+        // multi-lines?
+        $start = strpos($data, '<GetMessageResult>');
+        $end = strpos($data, '</GetMessageResult>');
+        if ($start === false || $end === false || $start > $end) {
+            $this->debug_message("*** Can't get OIM: $msgid");
+            return false;
+        }
+        $lines = substr($data, $start + 18, $end - $start);
+        $aLines = @explode("\n", $lines);
+        $header = true;
+        $ignore = false;
+        $sOIM = '';
+        foreach ($aLines as $line) {
+            $line = trim($line);
+            if ($header) {
+                if ($line === '') {
+                    $header = false;
+                    continue;
+                }
+                continue;
+            }
+            // stop at empty lines
+            if ($line === '') break;
+            $sOIM .= $line;
+        }
+        $sMsg = base64_decode($sOIM);
+        $this->debug_message("*** we get OIM ($msgid): $sMsg");
+
+        // delete OIM
+        $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+  <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+    <t>'.$t.'</t>
+    <p>'.$p.'</p>
+  </PassportCookie>
+</soap:Header>
+<soap:Body>
+  <DeleteMessages xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">
+    <messageIds>
+      <messageId>'.$msgid.'</messageId>
+    </messageIds>
+  </DeleteMessages>
+</soap:Body>
+</soap:Envelope>';
+
+        $header_array = array(
+                                'SOAPAction: '.$this->oim_del_soap,
+                                'Content-Type: text/xml; charset=utf-8',
+                                'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
+                            );
+
+        $this->debug_message("*** URL: $this->oim_del_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->oim_del_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code != 200)
+            $this->debug_message("*** Can't delete OIM: $msgid, http code = $http_code");
+        else
+            $this->debug_message("*** OIM ($msgid) deleted");
+        return $sMsg;
+    }
+
+    function doLoop($user, $password, $alias = '',
+                    $my_function = false, $my_add_function = false, $my_rem_function = false,
+                    $use_ping = false, $retry_wait = 30, $backup_file = true, $update_pending = true)
+    {
+        if ($this->kill_me) return;
+        $this->log_message("*** startup ***");
+        $process_file = false;
+        $sent = false;
+        $online = false;
+        $aADL = array();
+        $ping_wait = 50;
+        $aContactList = array();
+        $first = true;
+        while (1) {
+            if ($this->kill_me) {
+                if (is_resource($this->fp) && !feof($this->fp)) {
+                    // logout now
+                    // NS: >>> OUT
+                    $this->writeln("OUT");
+                    fclose($this->fp);
+                    $this->fp = false;
+                    $this->log_message("*** logout now!");
+                }
+                $this->log_message("*** Okay, kill me now!");
+                break;
+            }
+            if (!is_resource($this->fp) || feof($this->fp)) {
+                if ($first)
+                    $first = false;
+                else {
+                    $this->log_message("*** wait for $retry_wait seconds");
+                    sleep($retry_wait);
+                }
+                if ($this->kill_me) continue;
+                $this->log_message("*** try to connect to MSN network");
+                if (!$this->connect($user, $password)) {
+                    $this->log_message("!!! Can't connect to server: $this->error");
+                    continue;
+                }
+                if ($this->protocol == 'MSNP9') {
+                    // we need to send SYN command for MSNP9
+                    // NS: >>> SYN {id} 0
+                    $this->writeln("SYN $this->id 0");
+                }
+                $this->log_message("*** connected, wait for command");
+                $start_tm = time();
+                $ping_tm = time();
+                stream_set_timeout($this->fp, $this->stream_timeout);
+            }
+            $data = $this->readln();
+            if ($data === false) {
+                if ($this->kill_me) continue;
+                if ($process_file !== false && $sent !== true) {
+                    // some error for sending message
+                    $precess_file = false;
+                }
+                // check here, do we have any message need to send?
+                $aFiles = glob(dirname($_SERVER['argv'][0]).'/spool/*.msn');
+                if (!is_array($aFiles)) continue;
+                clearstatcache();
+                foreach ($aFiles as $filename) {
+                    if (fileperms($filename) != (0x8000 | 00666)) continue;
+                    $fp = fopen($filename, 'rt');
+                    if (!$fp) continue;
+                    $aTo = array();
+                    $sMessage = '';
+                    $buf = trim(fgets($fp));
+                    if (substr($buf, 0, 3) == 'TO:') {
+                        $to_str = trim(substr($buf, 3));
+                        $aTo = @explode(',', $to_str);
+                        while (!feof($fp)) {
+                            $buf = trim(fgets($fp));
+                            $sMessage .= $buf;
+                            $sMessage .= "\r\n";
+                        }
+                    }
+                    fclose($fp);
+                    if (!is_array($aTo) || count($aTo) == 0 || $sMessage == '') {
+                        $this->log_message("!!! message format error? delete $filename");
+                        if ($backup_file) {
+                            $backup_dir = dirname($_SERVER['argv'][0]).'/backup';
+                            if (!file_exists($backup_dir))
+                                @mkdir($backup_dir);
+                            $backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($filename);
+                            if (@rename($filename, $backup_name))
+                                $this->log_message("*** move file to $backup_name");
+                        }
+                        @unlink($filename);
+                        continue;
+                    }
+                    // assign process_file
+                    $process_file = $filename;
+                    break;
+                }
+
+                if ($process_file === false) {
+                    if ($online && $use_ping) {
+                        $now = time();
+                        if ($now < $ping_tm)
+                            $len =  $now;
+                        else
+                            $len = $now - $ping_tm;
+                        if ($len > $ping_wait) {
+                            // NS: >>> PNG
+                            $this->writeln("PNG");
+                            $ping_tm = time();
+                        }
+                    }
+                    continue;
+                }
+                $sent = false;
+
+                $this->log_message("*** try to send message from $process_file");
+                $this->log_message("*** TO: $to_str");
+                $this->log_message("*** MSG: $sMessage");
+
+                // okay, try to ask a switchboard (SB) for sending message
+                // NS: >>> XFR {id} SB
+                $this->writeln("XFR $this->id SB");
+                continue;
+            }
+            $code = substr($data, 0, 3);
+
+            switch ($code) {
+                case 'SBS':
+                    // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us
+                    // NS: <<< SBS 0 null
+                    $aContactList = $this->getMembershipList();
+                    if ($update_pending) {
+                        if (is_array($aContactList)) {
+                            $pending = 'Pending';
+                            foreach ($aContactList as $u_domain => $aUserList) {
+                                foreach ($aUserList as $u_name => $aNetworks) {
+                                    foreach ($aNetworks as $network => $aData) {
+                                        if (isset($aData[$pending])) {
+                                            // pending list
+                                            $cnt = 0;
+                                            foreach (array('Allow', 'Reverse') as $list) {
+                                                if (isset($aData[$list]))
+                                                    $cnt++;
+                                                else {
+                                                    if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
+                                                        $aContactList[$u_domain][$u_name][$network][$list] = false;
+                                                        $cnt++;
+                                                    }
+                                                }
+                                            }
+                                            if ($cnt >= 2) {
+                                                $id = $aData[$pending];
+                                                // we can delete it from pending now
+                                                if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending))
+                                                    unset($aContactList[$u_domain][$u_name][$network][$pending]);
+                                            }
+                                        }
+                                        else {
+                                            // sync list
+                                            foreach (array('Allow', 'Reverse') as $list) {
+                                                if (!isset($aData[$list])) {
+                                                    if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
+                                                        $aContactList[$u_domain][$u_name][$network][$list] = false;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    $n = 0;
+                    $sList = '';
+                    $len = 0;
+                    if (is_array($aContactList)) {
+                        foreach ($aContactList as $u_domain => $aUserList) {
+                            $str = '<d n="'.$u_domain.'">';
+                            $len += strlen($str);
+                            if ($len > 7400) {
+                                $aADL[$n] = '<ml l="1">'.$sList.'</ml>';
+                                $n++;
+                                $sList = '';
+                                $len = strlen($str);
+                            }
+                            $sList .= $str;
+                            foreach ($aUserList as $u_name => $aNetworks) {
+                                foreach ($aNetworks as $network => $status) {
+                                    $str = '<c n="'.$u_name.'" l="3" t="'.$network.'" />';
+                                    $len += strlen($str);
+                                    // max: 7500, but <ml l="1"></d></ml> is 19,
+                                    // so we use 7475
+                                    if ($len > 7475) {
+                                        $sList .= '</d>';
+                                        $aADL[$n] = '<ml l="1">'.$sList.'</ml>';
+                                        $n++;
+                                        $sList = '<d n="'.$u_domain.'">'.$str;
+                                        $len = strlen($sList);
+                                    }
+                                    else
+                                        $sList .= $str;
+                                }
+                            }
+                            $sList .= '</d>';
+                        }
+                    }
+                    $aADL[$n] = '<ml l="1">'.$sList.'</ml>';
+                    // NS: >>> BLP {id} BL
+                    $this->writeln("BLP $this->id BL");
+                    foreach ($aADL as $str) {
+                        $len = strlen($str);
+                        // NS: >>> ADL {id} {size}
+                        $this->writeln("ADL $this->id $len");
+                        $this->writedata($str);
+                    }
+                    // NS: >>> PRP {id} MFN name
+                    if ($alias == '') $alias = $user;
+                    $aliasname = rawurlencode($alias);
+                    $this->writeln("PRP $this->id MFN $aliasname");
+                    // NS: >>> CHG {id} {status} {clientid} {msnobj}
+                    $this->writeln("CHG $this->id NLN");
+                    $online = true;
+                    break;
+
+                case 'RFS':
+                    // FIXME:
+                    // NS: <<< RFS ???
+                    // refresh ADL, so we re-send it again
+                    if (is_array($aADL)) {
+                        foreach ($aADL as $str) {
+                            $len = strlen($str);
+                            // NS: >>> ADL {id} {size}
+                            $this->writeln("ADL $this->id $len");
+                            $this->writedata($str);
+                        }
+                    }
+                    break;
+
+                case 'LST':
+                    // NS: <<< LST {email} {alias} 11 0
+                    @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data);
+                    @list($u_name, $u_domain) = @explode('@', $email);
+                    if (!isset($aContactList[$u_domain][$u_name][1])) {
+                        $aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';
+                        $this->log_message("*** add to our contact list: $u_name@$u_domain");
+                    }
+                    break;
+
+                case 'ADD':
+                    // randomly, we get ADD command, someome add us to their contact list for MSNP9
+                    // NS: <<< ADD 0 {list} {0} {email} {alias}
+                    @list(/* ADD */, /* 0 */, $u_list, /* 0 */, $u_email, /* alias */) = @explode(' ', $data);
+                    @list($u_name, $u_domain) = @explode('@', $u_email);
+                    if (isset($aContactList[$u_domain][$u_name][1]['Allow']))
+                        $this->log_message("*** someone add us to their list (but already in our list): $u_name@$u_domain");
+                    else {
+                        $aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';
+                        $this->log_message("*** someone add us to their list: $u_name@$u_domain");
+                    }
+                    if ($my_add_function && function_exists($my_add_function) && is_callable($my_add_function))
+                        $my_add_function($u_email);
+                    break;
+
+                case 'REM':
+                    // randomly, we get REM command, someome remove us from their contact list for MSNP9
+                    // NS: <<< REM 0 {list} {0} {email}
+                    @list(/* REM */, /* 0 */, $u_list, /* 0 */, $u_email,) = @explode(' ', $data);
+                    @list($u_name, $u_domain) = @explode('@', $u_email);
+                    if (isset($aContactList[$u_domain][$u_name][1])) {
+                        unset($aContactList[$u_domain][$u_name][1]);
+                        $this->log_message("*** someone remove us from their list: $u_name@$u_domain");
+                    }
+                    else
+                        $this->log_message("*** someone remove us from their list (but not in our list): $u_name@$u_domain");
+                    if ($my_rem_function && function_exists($my_rem_function) && is_callable($my_rem_function))
+                        $my_rem_function($u_email);
+                    break;
+
+                case 'ADL':
+                    // randomly, we get ADL command, someome add us to their contact list for MSNP15
+                    // NS: <<< ADL 0 {size}
+                    @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data);
+                    if (is_numeric($size) && $size > 0) {
+                        $data = $this->readdata($size);
+                        preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);
+                        if (is_array($matches) && count($matches) > 0) {
+                            $u_domain = $matches[1];
+                            $u_name = $matches[2];
+                            $network = $matches[4];
+                            if (isset($aContactList[$u_domain][$u_name][$network]))
+                                $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain");
+                            else {
+                                $re_login = false;
+                                $cnt = 0;
+                                foreach (array('Allow', 'Reverse') as $list) {
+                                    if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
+                                        if ($re_login) {
+                                            $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
+                                            continue;
+                                        }
+                                        $aTickets = $this->get_passport_ticket();
+                                        if (!$aTickets || !is_array($aTickets)) {
+                                            // failed to login? ignore it
+                                            $this->log_message("*** can't re-login, something wrong here");
+                                            $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
+                                            continue;
+                                        }
+                                        $re_login = true;
+                                        $this->oim_ticket = $aTickets['oim_ticket'];
+                                        $this->contact_ticket = $aTickets['contact_ticket'];
+                                        $this->web_ticket = $aTickets['web_ticket'];
+                                        $this->space_ticket = $aTickets['space_ticket'];
+                                        $this->storage_ticket = $aTickets['storage_ticket'];
+                                        $this->log_message("**** get new ticket, try it again");
+                                        if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
+                                            $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
+                                            continue;
+                                        }
+                                    }
+                                    $aContactList[$u_domain][$u_name][$network][$list] = false;
+                                    $cnt++;
+                                }
+                                $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain");
+                            }
+                            $str = '<ml l="1"><d n="'.$u_domain.'"><c n="'.$u_name.'" l="3" t="'.$network.'" /></d></ml>';
+                            $len = strlen($str);
+                            // NS: >>> ADL {id} {size}
+                            $this->writeln("ADL $this->id $len");
+                            $this->writedata($str);
+                            if ($my_add_function && function_exists($my_add_function) && is_callable($my_add_function))
+                                $my_add_function($u_name.'@'.$u_domain, $network);
+                        }
+                        else {
+                            $this->log_message("*** someone add us to their list: $data");
+                        }
+                    }
+                    break;
+
+                case 'RML':
+                    // randomly, we get RML command, someome remove us to their contact list for MSNP15
+                    // NS: <<< RML 0 {size}
+                    @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data);
+                    if (is_numeric($size) && $size > 0) {
+                        $data = $this->readdata($size);
+                        preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);
+                        if (is_array($matches) && count($matches) > 0) {
+                            $u_domain = $matches[1];
+                            $u_name = $matches[2];
+                            $network = $matches[4];
+                            if (isset($aContactList[$u_domain][$u_name][$network])) {
+                                unset($aContactList[$u_domain][$u_name][$network]);
+                                $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain");
+                            }
+                            else
+                                $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain");
+                            if ($my_rem_function && function_exists($my_rem_function) && is_callable($my_rem_function))
+                                $my_rem_function($u_name.'@'.$u_domain, $network);
+                        }
+                        else {
+                            $this->log_message("*** someone remove us from their list: $data");
+                        }
+                    }
+                    break;
+
+                case 'MSG':
+                    // randomly, we get MSG notification from server
+                    // NS: <<< MSG Hotmail Hotmail {size}
+                    @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data);
+                    if (is_numeric($size) && $size > 0) {
+                        $data = $this->readdata($size);
+                        $aLines = @explode("\n", $data);
+                        $header = true;
+                        $ignore = false;
+                        $maildata = '';
+                        foreach ($aLines as $line) {
+                            $line = trim($line);
+                            if ($header) {
+                                if ($line === '') {
+                                    $header = false;
+                                    continue;
+                                }
+                                if (strncasecmp($line, 'Content-Type:', 13) == 0) {
+                                    if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false &&
+                                        strpos($line, 'text/x-msmsgsoimnotification') === false) {
+                                        // we just need text/x-msmsgsinitialmdatanotification
+                                        // or text/x-msmsgsoimnotification
+                                        $ignore = true;
+                                        break;
+                                    }
+                                }
+                                continue;
+                            }
+                            if (strncasecmp($line, 'Mail-Data:', 10) == 0) {
+                                $maildata = trim(substr($line, 10));
+                                break;
+                            }
+                        }
+                        if ($ignore) {
+                            $this->log_message("*** ingnore MSG for: $line");
+                            break;
+                        }
+                        if ($maildata == '') {
+                            $this->log_message("*** ingnore MSG not for OIM");
+                            break;
+                        }
+                        $re_login = false;
+                        if (strcasecmp($maildata, 'too-large') == 0) {
+                            $this->log_message("*** large mail-data, need to get the data via SOAP");
+                            $maildata = $this->getOIM_maildata();
+                            if ($maildata === false) {
+                                $this->log_message("*** can't get mail-data via SOAP");
+                                // maybe we need to re-login again
+                                $aTickets = $this->get_passport_ticket();
+                                if (!$aTickets || !is_array($aTickets)) {
+                                    // failed to login? ignore it
+                                    $this->log_message("*** can't re-login, something wrong here, ignore this OIM");
+                                    break;
+                                }
+                                $re_login = true;
+                                $this->oim_ticket = $aTickets['oim_ticket'];
+                                $this->contact_ticket = $aTickets['contact_ticket'];
+                                $this->web_ticket = $aTickets['web_ticket'];
+                                $this->space_ticket = $aTickets['space_ticket'];
+                                $this->storage_ticket = $aTickets['storage_ticket'];
+                                $this->log_message("**** get new ticket, try it again");
+                                $maildata = $this->getOIM_maildata();
+                                if ($maildata === false) {
+                                    $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM");
+                                    break;
+                                }
+                            }
+                        }
+                        // could be a lots of <M>...</M>, so we can't use preg_match here
+                        $p = $maildata;
+                        $aOIMs = array();
+                        while (1) {
+                            $start = strpos($p, '<M>');
+                            $end = strpos($p, '</M>');
+                            if ($start === false || $end === false || $start > $end) break;
+                            $end += 4;
+                            $sOIM = substr($p, $start, $end - $start);
+                            $aOIMs[] = $sOIM;
+                            $p = substr($p, $end);
+                        }
+                        if (count($aOIMs) == 0) {
+                            $this->log_message("*** ingnore empty OIM");
+                            break;
+                        }
+                        foreach ($aOIMs as $maildata) {
+                            // T: 11 for MSN, 13 for Yahoo
+                            // S: 6 for MSN, 7 for Yahoo
+                            // RT: the datetime received by server
+                            // RS: already read or not
+                            // SZ: size of message
+                            // E: sender
+                            // I: msgid
+                            // F: always 00000000-0000-0000-0000-000000000009
+                            // N: sender alias
+                            preg_match('#<T>(.*)</T>#', $maildata, $matches);
+                            if (count($matches) == 0) {
+                                $this->log_message("*** ingnore OIM maildata without <T>type</T>");
+                                continue;
+                            }
+                            $oim_type = $matches[1];
+                            if ($oim_type = 13)
+                                $network = 32;
+                            else
+                                $network = 1;
+                            preg_match('#<E>(.*)</E>#', $maildata, $matches);
+                            if (count($matches) == 0) {
+                                $this->log_message("*** ingnore OIM maildata without <E>sender</E>");
+                                continue;
+                            }
+                            $oim_sender = $matches[1];
+                            preg_match('#<I>(.*)</I>#', $maildata, $matches);
+                            if (count($matches) == 0) {
+                                $this->log_message("*** ingnore OIM maildata without <I>msgid</I>");
+                                continue;
+                            }
+                            $oim_msgid = $matches[1];
+                            preg_match('#<SZ>(.*)</SZ>#', $maildata, $matches);
+                            $oim_size = (count($matches) == 0) ? 0 : $matches[1];
+                            preg_match('#<RT>(.*)</RT>#', $maildata, $matches);
+                            $oim_time = (count($matches) == 0) ? 0 : $matches[1];
+                            $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size");
+                            $sMsg = $this->getOIM_message($oim_msgid);
+                            if ($sMsg === false) {
+                                $this->log_message("*** can't get OIM, msgid = $oim_msgid");
+                                if ($re_login) {
+                                    $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM");
+                                    continue;
+                                }
+                                $aTickets = $this->get_passport_ticket();
+                                if (!$aTickets || !is_array($aTickets)) {
+                                    // failed to login? ignore it
+                                    $this->log_message("*** can't re-login, something wrong here, ignore this OIM");
+                                    continue;
+                                }
+                                $re_login = true;
+                                $this->oim_ticket = $aTickets['oim_ticket'];
+                                $this->contact_ticket = $aTickets['contact_ticket'];
+                                $this->web_ticket = $aTickets['web_ticket'];
+                                $this->space_ticket = $aTickets['space_ticket'];
+                                $this->storage_ticket = $aTickets['storage_ticket'];
+                                $this->log_message("**** get new ticket, try it again");
+                                $sMsg = $this->getOIM_message($oim_msgid);
+                                if ($sMsg === false) {
+                                    $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM");
+                                    continue;
+                                }
+                            }
+                            $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg");
+                            if ($my_function && function_exists($my_function) && is_callable($my_function)) {
+                                $sMessage = $my_function($oim_sender, $sMsg, $network);
+                                if ($sMessage !== '') {
+                                    $now = strftime('%D %T');
+                                    $fname = dirname($_SERVER['argv'][0]).'/spool/msn_'.posix_getpid().'_'.md5('offline'.rand(1,1000).$now).'.msn';
+                                    $fp = fopen($fname, 'wt');
+                                    if ($fp) {
+                                        fputs($fp, "TO: $oim_sender@$network\n");
+                                        fputs($fp, $sMessage);
+                                        fclose($fp);
+                                        chmod($fname, 0666);
+                                        $this->log_message("Response to $oim_sender (Offline, network: $network): $fname");
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    break;
+
+                case 'UBM':
+                    // randomly, we get UBM, this is the message from other network, like Yahoo!
+                    // NS: <<< UBM {email} $network $type {size}
+                    @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data);
+                    if (is_numeric($size) && $size > 0) {
+                        $data = $this->readdata($size);
+                        $aLines = @explode("\n", $data);
+                        $header = true;
+                        $ignore = false;
+                        $sMsg = '';
+                        foreach ($aLines as $line) {
+                            $line = trim($line);
+                            if ($header) {
+                                if ($line === '') {
+                                    $header = false;
+                                    continue;
+                                }
+                                if (strncasecmp($line, 'TypingUser:', 11) == 0) {
+                                    $ignore = true;
+                                    break;
+                                }
+                                continue;
+                            }
+                            if ($sMsg !== '')
+                                $sMsg .= "\r\n";
+                            $sMsg .= $line;
+                        }
+                        if ($ignore) {
+                            $this->log_message("*** ingnore from $from_email: $line");
+                            break;
+                        }
+                        $this->log_message("*** MSG from $from_email (network: $network): $sMsg");
+                        if ($my_function && function_exists($my_function) && is_callable($my_function)) {
+                            $msg = $my_function($from_email, $sMsg, $network);
+                            if ($msg !== '') {
+                                /* typing?
+                                $message = "MIME-Version: 1.0\r\nContent-Type: text/x-msmsgscontrol\r\nTypingUser: $user\r\n\r\n\r\n";
+                                $len = strlen($message);
+                                $this->writeln("UUM $this->id $from_email $network 2 $len");
+                                $this->writedata($message);
+                                */
+                                $message = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n$msg";
+                                $len = strlen($message);
+                                $this->writeln("UUM $this->id $from_email $network 1 $len");
+                                $this->writedata($message);
+                                $this->log_message("Response to $from_email (network: $network):\n$msg");
+                            }
+                        }
+                    }
+                    break;
+
+                case 'UBX':
+                    // randomly, we get UBX notification from server
+                    // NS: <<< UBX email {network} {size}
+                    @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data);
+                    // we don't need the notification data, so just ignore it
+                    if (is_numeric($size) && $size > 0)
+                        $this->readdata($size);
+                    break;
+
+                case 'CHL':
+                    // randomly, we'll get challenge from server
+                    // NS: <<< CHL 0 {code}
+                    @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data);
+                    $fingerprint = $this->getChallenge($chl_code);
+                    // NS: >>> QRY {id} {product_id} 32
+                    // NS: >>> fingerprint
+                    $this->writeln("QRY $this->id $this->prod_id 32");
+                    $this->writedata($fingerprint);
+                    break;
+
+                case 'SYN':
+                    if ($this->protocol != 'MSNP9') break;
+                    // NS: <<< SYN 8 1 2 1
+                    // ignore it
+                    // change our status to online first
+                    // NS: >>> CHG {id} {status}
+                    $this->writeln("CHG $this->id NLN");
+                    $online = true;
+                    break;
+
+                case 'CHG':
+                    // NS: <<< CHG {id} {status} {code}
+                    // ignore it
+                    // change our status to online first
+                    break;
+
+                case 'XFR':
+                    // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0
+                    @list(/* XFR */, /* {id} */, /* SB */, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data);
+                    @list($ip, $port) = @explode(':', $server);
+                    $this->error = '';
+                    $aSBresult = $this->switchboard_control($ip, $port, $cki_code, $aTo, $sMessage);
+                    if ($aSBresult === false) {
+                        // error for switchboard, quit
+                        $this->log_message("!!! error for sending message: $this->error");
+                        $process_file = false;
+                        $sent = false;
+                        break;
+                    }
+                    if ($aSBresult === true) {
+                        $this->log_message("*** already send message from $process_file");
+                        if ($backup_file) {
+                            $backup_dir = dirname($_SERVER['argv'][0]).'/backup';
+                            if (!file_exists($backup_dir))
+                                @mkdir($backup_dir);
+                            $backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($process_file);
+                            if (@rename($process_file, $backup_name))
+                                $this->log_message("*** move file to $backup_name");
+                        }
+                        @unlink($process_file);
+                        $process_file = false;
+                        $sent = true;
+                        break;
+                    }
+                    // okay, process offline user or other networks
+                    if ($this->protocol == 'MSNP9') {
+                        // MSNP9 don't support OIM
+                        $this->debug_message("*** MSNP9 don't support OIM, so we ignore offline user and other networks!");
+                        $this->log_message("*** already send message from $process_file");
+                        if ($backup_file) {
+                            $backup_dir = dirname($_SERVER['argv'][0]).'/backup';
+                            if (!file_exists($backup_dir))
+                                @mkdir($backup_dir);
+                            $backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($process_file);
+                            if (@rename($process_file, $backup_name))
+                                $this->log_message("*** move file to $backup_name");
+                        }
+                        @unlink($process_file);
+                        $process_file = false;
+                        $sent = true;
+                        break;
+                    }
+                    // offline user first
+                    $lockkey = '';
+                    $re_login = false;
+                    foreach ($aSBresult['offline'] as $to) {
+                        for ($i = 0; $i < $this->oim_try; $i++) {
+                            $chl_code = $this->sendOIM($to, $sMessage, $lockkey);
+                            if ($chl_code === true) {
+                                // finished
+                                break;
+                            }
+                            if ($chl_code === false) {
+                                if ($re_login) {
+                                    $this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM");
+                                    break;
+                                }
+                                $this->log_message("*** can't send OIM, maybe ticket expired, try to login again");
+                                // maybe we need to re-login again
+                                $aTickets = $this->get_passport_ticket();
+                                if (!$aTickets || !is_array($aTickets)) {
+                                    // failed to login? ignore it
+                                    $this->log_message("*** can't re-login, something wrong here, ignore this OIM");
+                                    break;
+                                }
+                                $re_login = true;
+                                $this->oim_ticket = $aTickets['oim_ticket'];
+                                $this->contact_ticket = $aTickets['contact_ticket'];
+                                $this->web_ticket = $aTickets['web_ticket'];
+                                $this->space_ticket = $aTickets['space_ticket'];
+                                $this->storage_ticket = $aTickets['storage_ticket'];
+                                $this->log_message("**** get new ticket, try it again");
+                                $i--;
+                                continue;
+                            }
+                            // need challenge lockkey
+                            $this->log_message("*** we need a new challenge code for $chl_code");
+                            $lockkey = $this->getChallenge($chl_code);
+                        }
+                    }
+                    // other networks
+                    foreach ($aSBresult['others'] as $network => $aNetUsers) {
+                        foreach ($aNetUsers as $to) {
+                            $message = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n$sMessage";
+                            $len = strlen($message);
+                            $this->writeln("UUM $this->id $to $network 1 $len");
+                            $this->writedata($message);
+                            $this->log_message("*** sent to $to (network: $network):\n$sMessage");
+                        }
+                    }
+                    $this->log_message("*** already send message from $process_file");
+                    if ($backup_file) {
+                        $backup_dir = dirname($_SERVER['argv'][0]).'/backup';
+                        if (!file_exists($backup_dir))
+                            @mkdir($backup_dir);
+                        $backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($process_file);
+                        if (@rename($process_file, $backup_name))
+                            $this->log_message("*** move file to $backup_name");
+                    }
+                    @unlink($process_file);
+                    $process_file = false;
+                    $sent = true;
+                    break;
+
+                case 'QNG':
+                    // NS: <<< QNG {time}
+                    @list(/* QNG */, $ping_wait) = @explode(' ', $data);
+                    if ($ping_wait == 0) $ping_wait = 50;
+                    break;
+
+                case 'RNG':
+                    // someone is trying to talk to us
+                    // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0
+                    @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data);
+                    @list($sb_ip, $sb_port) = @explode(':', $server);
+                    $this->log_message("*** RING from $email, $sb_ip:$sb_port");
+                    $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket, $my_function);
+					break;
+
+                case 'OUT':
+                    // force logout from NS
+                    // NS: <<< OUT xxx
+                    fclose($this->fp);
+                    $this->log_message("*** LOGOUT from NS");
+                    break;
+
+                default:
+                    if (is_numeric($code)) {
+                        $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+                        $this->debug_message("*** NS: $this->error");
+                        if ($process_file !== false) {
+                            // try login again...
+                            // logout now
+                            // NS: >>> OUT
+                            $this->writeln("OUT");
+                            fclose($this->fp);
+                            $this->fp = false;
+                            $this->log_message("!!! logout");
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+
+	function sendMessage($sMessage, $aTo)
+	{
+        if ($this->protocol == 'MSNP9') {
+            // we need to send SYN command for MSNP9
+            // NS: >>> SYN {id} 0
+            $this->writeln("SYN $this->id 0");
+        }
+        stream_set_timeout($this->fp, $this->stream_timeout);
+        $quit = false;
+        $online_cnt = 0;
+        $offline_cnt = 0;
+        $other_cnt = 0;
+        $start_tm = time();
+		while (!feof($this->fp)) {
+            if ($quit) break;
+			$data = $this->readln();
+            // no data ?
+            if ($data === false) {
+                if ($this->timeout > 0) {
+                    $now_tm = time();
+                    $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm;
+                    if ($used_time > $this->timeout) {
+                        $this->error = 'Timeout, maybe protocol changed!';
+                        $this->debug_message("*** $this->error");
+                        break;
+                    }
+                }
+                continue;
+            }
+            $code = substr($data, 0, 3);
+            $start_tm = time();
+            switch ($code) {
+                case 'SBS':
+                    // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us
+                    // NS: <<< SBS 0 null
+                    // we don't need profile data, so just ignore it
+                    // change our status to online first
+                    // NS: >>> CHG {id} {status}
+                    $this->writeln("CHG $this->id NLN");
+                    break;
+
+                case 'MSG':
+                    // randomly, we get MSG notification from server
+                    // NS: <<< MSG Hotmail Hotmail {size}
+                    @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data);
+                    // we don't need the notification data, so just ignore it
+                    if (is_numeric($size) && $size > 0)
+                        $this->readdata($size);
+                    break;
+
+                case 'CHL':
+                    // randomly, we'll get challenge from server
+                    // NS: <<< CHL 0 {code}
+                    @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data);
+                    $fingerprint = $this->getChallenge($chl_code);
+                    // NS: >>> QRY {id} {product_id} 32
+                    // NS: >>> fingerprint
+                    $this->writeln("QRY $this->id $this->prod_id 32");
+                    $this->writedata($fingerprint);
+                    break;
+
+                case 'SYN':
+                    if ($this->protocol != 'MSNP9') break;
+                    // NS: <<< SYN 8 1 2 1
+                    // ignore it
+                    // change our status to online first
+                    // NS: >>> CHG {id} {status}
+                    $this->writeln("CHG $this->id NLN");
+                    break;
+
+                case 'CHG':
+                    // NS: <<< CHG {id} {status} {code}
+                    // ignore it
+
+                    // if message is empty or To list is empty, just quit
+                    if (count($aTo) == 0 || $sMessage === '') {
+                        $quit = true;
+                        break;
+                    }
+                    // okay, try to ask a switchboard (SB) for sending message
+                    // NS: >>> XFR {id} SB
+                    $this->writeln("XFR $this->id SB");
+                    break;
+
+                case 'XFR':
+                    // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0
+                    @list(/* XFR */, /* {id} */, /* SB */, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data);
+                    @list($ip, $port) = @explode(':', $server);
+                    $aSBresult = $this->switchboard_control($ip, $port, $cki_code, $aTo, $sMessage);
+                    if ($aSBresult === false) {
+                        // error for switchboard, quit
+                        $quit = true;
+                        break;
+                    }
+                    if ($aSBresult === true) {
+                        // we're done switchboard, quit
+                        $quit = true;
+                        break;
+                    }
+                    // okay, process offline user
+                    if ($this->protocol == 'MSNP9') {
+                        // MSNP9 don't support OIM
+                        $this->debug_message("*** MSNP9 don't support OIM, so we ignore offline user!");
+                        $quit = true;
+                        break;
+                    }
+                    $lockkey = '';
+                    foreach ($aSBresult['offline'] as $to) {
+                        for ($i = 0; $i < $this->oim_try; $i++) {
+                            $chl_code = $this->sendOIM($to, $sMessage, $lockkey);
+                            if ($chl_code === true) {
+                                // finished
+                                break;
+                            }
+                            if ($chl_code === false) {
+                                if ($re_login) {
+                                    $this->debug_message("*** can't send OIM, but we already re-login again, so ignore this OIM");
+                                    break;
+                                }
+                                $this->debug_message("*** can't send OIM, maybe ticket expired, try to login again");
+                                // maybe we need to re-login again
+                                $aTickets = $this->get_passport_ticket();
+                                if (!$aTickets || !is_array($aTickets)) {
+                                    // failed to login? ignore it
+                                    $this->debug_message("*** can't re-login, something wrong here, ignore this OIM");
+                                    break;
+                                }
+                                $re_login = true;
+                                $this->oim_ticket = $aTickets['oim_ticket'];
+                                $this->contact_ticket = $aTickets['contact_ticket'];
+                                $this->web_ticket = $aTickets['web_ticket'];
+                                $this->space_ticket = $aTickets['space_ticket'];
+                                $this->storage_ticket = $aTickets['storage_ticket'];
+                                $this->debug_message("**** get new ticket, try it again");
+                                $i--;
+                                continue;
+                            }
+                            // need challenge lockkey
+                            $this->debug_message("*** we need a new challenge code for $chl_code");
+                            $lockkey = $this->getChallenge($chl_code);
+                        }
+                    }
+                    // other networks
+                    foreach ($aSBresult['others'] as $network => $aNetUsers) {
+                        foreach ($aNetUsers as $to) {
+                            $other_cnt++;
+                            $message = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n$sMessage";
+                            $len = strlen($message);
+                            $this->writeln("UUM $this->id $to $network 1 $len");
+                            $this->writedata($message);
+                            $this->debug_message("*** sent to $to (network: $network):\n$sMessage");
+                        }
+                    }
+                    $quit = true;
+                    break;
+
+                default:
+                    if (is_numeric($code)) {
+                        $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+                        $this->debug_message("*** NS: $this->error");
+                    }
+                    break;
+            }
+		}
+        // logout now
+        // NS: >>> OUT
+        $this->writeln("OUT");
+        fclose($this->fp);
+        return array(
+                        'online' => $online_cnt,
+                        'offline' => $offline_cnt,
+                        'others' => $other_cnt
+                    );
+	}
+
+    function getChallenge($code)
+    {
+        if ($this->protocol == 'MSNP9') {
+            // simple challenge for MSNP9
+            return md5($code.$this->prod_key);
+        }
+        // MSNP15
+        // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges
+        // Step 1: The MD5 Hash
+        $md5Hash = md5($code.$this->prod_key);
+        $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0"));
+        for ($i = 0; $i < 4; $i++) {
+            $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0"))));
+            $aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF;
+        }
+
+        // Step 2: A new string
+        $chl_id = $code.$this->prod_id;
+        $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8));
+
+        $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1));
+        for ($i = 0; $i < count($aID); $i++) {
+            $aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0"))));
+            $aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10);
+        }
+
+        // Step 3: The 64 bit key
+        $magic_num = 0x0E79A9C1;
+        $str7f = 0x7FFFFFFF;
+        $high = 0;
+        $low = 0;
+        for ($i = 0; $i < count($aID); $i += 2) {
+            $temp = $aID[$i];
+            $temp = bcmod(bcmul($magic_num, $temp), $str7f);
+            $temp = bcadd($temp, $high);
+            $temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]);
+            $temp = bcmod($temp, $str7f);
+
+            $high = $aID[$i+1];
+            $high = bcmod(bcadd($high, $temp), $str7f);
+            $high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]);
+            $high = bcmod($high, $str7f);
+
+            $low = bcadd(bcadd($low, $high), $temp);
+        }
+
+        $high = bcmod(bcadd($high, $aMD5[1]), $str7f);
+        $low = bcmod(bcadd($low, $aMD5[3]), $str7f);
+
+        $new_high = bcmul($high & 0xFF, 0x1000000);
+        $new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100));
+        $new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100));
+        $new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000));
+        // we need integer here
+        $high = 0+$new_high;
+
+        $new_low = bcmul($low & 0xFF, 0x1000000);
+        $new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100));
+        $new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100));
+        $new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000));
+        // we need integer here
+        $low = 0+$new_low;
+
+        // we just use 32 bits integer, don't need the key, just high/low
+        // $key = bcadd(bcmul($high, 0x100000000), $low);
+
+        // Step 4: Using the key
+        $md5Hash = md5($code.$this->prod_key);
+        $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0"));
+
+        $hash = '';
+        $hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high);
+        $hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low);
+        $hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high);
+        $hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low);
+
+        return $hash;
+    }
+
+    function switchboard_control($ip, $port, $cki_code, $aTo, $sMessage)
+    {
+        if (is_array($aTo))
+            $aUsers = $aTo;
+        else
+            $aUsers[0] = $aTo;
+
+        $aMSNUsers = array();
+        $aOtherUsers = array();
+        foreach ($aUsers as $user) {
+            @list($u_user, $u_domain, $u_network) = @explode('@', $user);
+            if ($u_network === '' || $u_network == NULL)
+                $u_network = 1;
+            $to_email = trim($u_user.'@'.$u_domain);
+            if ($u_network == 1)
+                $aMSNUsers[] = $to_email;
+            else
+                $aOtherUsers[$u_network][] = $to_email;
+        }
+
+        $aOfflineUsers = array();
+        $total_cnt = count($aMSNUsers);
+
+        if ($total_cnt == 0) {
+            $this->sb_writeln("OUT");
+            @fclose($this->sb);
+            if (count($aOtherUsers) > 0) {
+                return array('msn' => true,
+                             'offline' => $aOfflineUsers,
+                             'others' => $aOtherUsers);
+            }
+            return true;
+        }
+
+        $this->debug_message("*** SB: try to connect to switchboard server $ip:$port");
+        $this->sb = @fsockopen($ip, $port, $errno, $errstr, 5);
+        if (!$this->sb) {
+            $this->error = "SB: Can't connect to $ip:$port, error => $errno, $errstr";
+            $this->debug_message("*** $this->error");
+            return false;
+        }
+
+        stream_set_timeout($this->sb, $this->stream_timeout);
+        // SB: >>> USR {id} {user} {cki}
+        $this->sb_writeln("USR $this->id $this->user $cki_code");
+
+        $sent = false;
+        $cal_cnt = 0;
+        $other_cnt = 0;
+        $offline_cnt = 0;
+        $start_tm = time();
+        $got_error = false;
+		while (!feof($this->sb)) {
+            if ($sent) break;
+			$data = $this->sb_readln();
+            if ($data === false) {
+                if ($this->timeout > 0) {
+                    $now_tm = time();
+                    $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm;
+                    if ($used_time > $this->timeout) {
+                        $this->error = 'Timeout, maybe protocol changed!';
+                        $this->debug_message("*** $this->error");
+                        break;
+                    }
+                }
+                continue;
+            }
+            $code = substr($data, 0, 3);
+            $start_tm = time();
+            switch($code) {
+                case 'USR':
+                    // SB: <<< USR {id} OK {user} {alias}
+                    // we don't need the data, just ignore it
+                    $user = $aMSNUsers[$cal_cnt++];
+                    // request user to join this switchboard
+                    // SB: >>> CAL {id} {user}
+                    $this->sb_writeln("CAL $this->id $user");
+                    break;
+
+                case 'CAL':
+                    // SB: <<< CAL {id} RINGING {?}
+                    // we don't need this, just ignore, and wait for other response
+                    break;
+
+                case '217':
+                    // SB: <<< 217 {id}
+                    // if user isn't online or no such user, we will get 217.
+                    // switchboard can't send message if he isn't online
+                    $aOfflineUsers[$offline_cnt++] = $user;
+                    // continue process with JOI here
+                case 'JOI':
+                    // SB: <<< JOI {user} {alias} {clientid?}
+                    // someone join us
+                    // we don't need the data, just ignore it
+                    // try to call others if we've
+                    if ($cal_cnt < $total_cnt) {
+                        $user = $aMSNUsers[$cal_cnt++];
+                        // request user to join this switchboard
+                        // SB: >>> CAL {id} {user}
+                        $this->sb_writeln("CAL $this->id $user");
+                        break;
+                    }
+                    // no more user here
+                    // check, all users are offline
+                    if ($cal_cnt == $offline_cnt) {
+                        $this->debug_message("*** SB: no any use online! skip to send message!");
+                        $sent = true;
+                        break;
+                    }
+                    $message = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n$sMessage";
+                    $len = strlen($message);
+                    $this->sb_writeln("MSG 20 N $len");
+                    $this->sb_writedata($message);
+                    $sent = true;
+                    break;
+
+                default:
+                    if (is_numeric($code)) {
+                        $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+                        $this->debug_message("*** SB: $this->error");
+                        $got_error = true;
+                    }
+                    break;
+            }
+        }
+        $this->sb_writeln("OUT");
+        @fclose($this->sb);
+        if ($sent) {
+            if (count($aOfflineUsers) > 0 || count($aOtherUsers) > 0) {
+                return array('msn' => !$got_error,
+                             'offline' => $aOfflineUsers,
+                             'others' => $aOtherUsers);
+            }
+            return !$got_error;
+        }
+        // if not sent, try to sending via OIM
+        return array('msn' => !$got_error,
+                     'offline' => $aMSNUsers,
+                     'others' => $aOtherUsers);
+    }
+
+    function switchboard_ring($ip, $port, $sid, $ticket, $my_function)
+    {
+        $this->debug_message("*** SB: try to connect to switchboard server $ip:$port");
+        $this->sb = @fsockopen($ip, $port, $errno, $errstr, 5);
+        if (!$this->sb) {
+            $this->error = "SB: Can't connect to $ip:$port, error => $errno, $errstr";
+            $this->debug_message("*** $this->error");
+            $this->log_message("!!! $this->error");
+            return false;
+        }
+
+        stream_set_timeout($this->sb, $this->stream_timeout);
+        // SB: >>> ANS {id} {user} {ticket} {session_id}
+        $this->sb_writeln("ANS $this->id $this->user $ticket $sid");
+
+        $start_tm = time();
+		while (!feof($this->sb)) {
+			$data = $this->sb_readln();
+            if ($data === false) {
+                if ($this->timeout > 0) {
+                    $now_tm = time();
+                    $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm;
+                    if ($used_time > $this->timeout) {
+                        $this->error = 'Timeout, maybe protocol changed!';
+                        $this->debug_message("*** $this->error");
+                        $this->log_message("!!! $this->error");
+                        $this->sb_writeln("OUT");
+                        fclose($this->sb);
+                        $this->sb = false;
+                        return false;
+                    }
+                }
+                continue;
+            }
+            $code = substr($data, 0, 3);
+            $start_tm = time();
+            switch($code) {
+                case 'IRO':
+                    // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid}
+                    @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data);
+                    $this->log_message("*** $email join us");
+                    break;
+
+                case 'ANS':
+                    // SB: <<< ANS {id} OK
+                    // ignore this
+                    break;
+
+                case 'BYE':
+                    $this->log_message("*** Quit for BYE");
+                    $this->sb_writeln("OUT");
+                    fclose($this->sb);
+                    $this->sb = false;
+                    return true;
+
+                case 'MSG':
+                    // SB: <<< MSG {email} {alias} {len}
+                    @list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data);
+                    $len = trim($len);
+                    $data = $this->sb_readdata($len);
+                    $aLines = @explode("\n", $data);
+                    $header = true;
+                    $ignore = false;
+                    $sMsg = '';
+                    foreach ($aLines as $line) {
+                        $line = trim($line);
+                        if ($header) {
+                            if ($line === '') {
+                                $header = false;
+                                continue;
+                            }
+                            if (strncasecmp($line, 'TypingUser:', 11) == 0) {
+                                $ignore = true;
+                                break;
+                            }
+                            continue;
+                        }
+                        if ($sMsg !== '')
+                            $sMsg .= "\r\n";
+                        $sMsg .= $line;
+                    }
+                    if ($ignore) {
+                        $this->log_message("*** ingnore from $from_email: $line");
+                        break;
+                    }
+                    $this->log_message("*** MSG from $from_email: $sMsg");
+                    if ($my_function && function_exists($my_function) && is_callable($my_function)) {
+                        $msg = $my_function($from_email, $sMsg);
+                        if ($msg !== '') {
+                            /* typing
+                            $message = "MIME-Version: 1.0\r\nContent-Type: text/x-msmsgscontrol\r\nTypingUser: $this->user\r\n\r\n\r\n";
+                            $len = strlen($message);
+                            $this->sb_writeln("MSG 20 N $len");
+                            $this->sb_writedata($message);
+                            */
+                            $message = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n$msg";
+                            $len = strlen($message);
+                            $this->sb_writeln("MSG 20 N $len");
+                            $this->sb_writedata($message);
+                            $this->log_message("Response to $from_email:\n$msg");
+                        }
+                    }
+                    $this->sb_writeln("OUT");
+                    fclose($this->sb);
+                    $this->sb = false;
+                    return true;
+
+                default:
+                    if (is_numeric($code)) {
+                        $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
+                        $this->debug_message("*** SB: $this->error");
+                    }
+                    break;
+            }
+        }
+        return true;
+    }
+
+    function sendOIM($to, $sMessage, $lockkey)
+    {
+        $XML = '<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+<soap:Header>
+  <From memberName="'.$this->user.'"
+        friendlyName="=?utf-8?B?'.base64_encode($this->user).'?="
+        xml:lang="zh-TW"
+        proxy="MSNMSGR"
+        xmlns="http://messenger.msn.com/ws/2004/09/oim/"
+        msnpVer="'.$this->protocol.'"
+        buildVer="'.$this->buildver.'"/>
+  <To memberName="'.$to.'" xmlns="http://messenger.msn.com/ws/2004/09/oim/"/>
+  <Ticket passport="'.htmlspecialchars($this->oim_ticket).'"
+          appid="'.$this->prod_id.'"
+          lockkey="'.$lockkey.'"
+          xmlns="http://messenger.msn.com/ws/2004/09/oim/"/>
+  <Sequence xmlns="http://schemas.xmlsoap.org/ws/2003/03/rm">
+    <Identifier xmlns="http://schemas.xmlsoap.org/ws/2002/07/utility">http://messenger.msn.com</Identifier>
+    <MessageNumber>1</MessageNumber>
+  </Sequence>
+</soap:Header>
+<soap:Body>
+  <MessageType xmlns="http://messenger.msn.com/ws/2004/09/oim/">text</MessageType>
+  <Content xmlns="http://messenger.msn.com/ws/2004/09/oim/">MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: base64
+X-OIM-Message-Type: OfflineMessage
+X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7}
+X-OIM-Sequence-Num: 1
+
+'.chunk_split(base64_encode($sMessage)).'
+  </Content>
+</soap:Body>
+</soap:Envelope>';
+
+        $header_array = array(
+                                'SOAPAction: '.$this->oim_send_soap,
+                                'Content-Type: text/xml',
+                                'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
+                            );
+
+        $this->debug_message("*** URL: $this->oim_send_url");
+        $this->debug_message("*** Sending SOAP:\n$XML");
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $this->oim_send_url);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
+        if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
+        curl_setopt($curl, CURLOPT_POST, 1);
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
+        $data = curl_exec($curl);
+        $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        $this->debug_message("*** Get Result:\n$data");
+
+        if ($http_code == 200) {
+            $this->debug_message("*** OIM sent for $to");
+            return true;
+        }
+
+        // the lockkey is invalid, authenticated fail, we need challenge it again
+        // <LockKeyChallenge xmlns="http://messenger.msn.com/ws/2004/09/oim/">364763969</LockKeyChallenge>
+		preg_match("#<LockKeyChallenge (.*)>(.*)</LockKeyChallenge>#", $data, $matches);
+        // no challenge, assume it work
+        if (count($matches) == 0) {
+            //<faultcode xmlns:q0="http://messenger.msn.com/ws/2004/09/oim/">q0:AuthenticationFailed</faultcode>
+            preg_match("#<faultcode (.*)>(.*)</faultcode>#", $data, $matches);
+            if (count($matches) == 0) {
+                // no error, we assume the OIM is sent
+                $this->debug_message("*** OIM sent for $to");
+                return true;
+            }
+            $err_code = $matches[2];
+            //<faultstring>Exception of type 'System.Web.Services.Protocols.SoapException' was thrown.</faultstring>
+            preg_match("#<faultstring>(.*)</faultstring>#", $data, $matches);
+            if (count($matches) > 0)
+                $err_msg = $matches[1];
+            else
+                $err_msg = '';
+            $this->debug_message("*** OIM failed for $to");
+            $this->debug_message("*** OIM Error code: $err_code");
+            $this->debug_message("*** OIM Error Message: $err_msg");
+            return false;
+        }
+        // yes, we get new LockKeyChallenge
+        $challenge = $matches[2];
+        $this->debug_message("*** OIM need new challenge ($challenge) for $to");
+        return $challenge;
+    }
+
+    // read data for specified size
+    function readdata($size)
+    {
+        $data = '';
+        $count = 0;
+        while (!feof($this->fp)) {
+            $buf = @fread($this->fp, $size - $count);
+            $data .= $buf;
+            $count += strlen($buf);
+            if ($count >= $size) break;
+        }
+        $this->debug_message("NS: data ($size/$count) <<<\n$data");
+        return $data;
+    }
+
+    // read one line
+	function readln()
+	{
+        $data = @fgets($this->fp, 4096);
+        if ($data !== false) {
+            $data = trim($data);
+            $this->debug_message("NS: <<< $data");
+        }
+        return $data;
+	}
+
+    // write to server, append \r\n, also increase id
+	function writeln($data)
+	{
+		@fwrite($this->fp, $data."\r\n");
+        $this->debug_message("NS: >>> $data");
+        $this->id++;
+        return;
+	}
+
+    // write data to server
+	function writedata($data)
+	{
+		@fwrite($this->fp, $data);
+        $this->debug_message("NS: >>> $data");
+        return;
+	}
+
+    // read data for specified size for SB
+    function sb_readdata($size)
+    {
+        $data = '';
+        $count = 0;
+        while (!feof($this->sb)) {
+            $buf = @fread($this->sb, $size - $count);
+            $data .= $buf;
+            $count += strlen($buf);
+            if ($count >= $size) break;
+        }
+        $this->debug_message("SB: data ($size/$count) <<<\n$data");
+        return $data;
+    }
+
+    // read one line for SB
+	function sb_readln()
+	{
+        $data = @fgets($this->sb, 4096);
+        if ($data !== false) {
+            $data = trim($data);
+            $this->debug_message("SB: <<< $data");
+        }
+        return $data;
+	}
+
+    // write to server for SB, append \r\n, also increase id
+    // switchboard server only accept \r\n, it will lost connection if just \n only
+	function sb_writeln($data)
+	{
+		@fwrite($this->sb, $data."\r\n");
+        $this->debug_message("SB: >>> $data");
+        $this->id++;
+        return;
+	}
+
+    // write data to server
+	function sb_writedata($data)
+	{
+		@fwrite($this->sb, $data);
+        $this->debug_message("SB: >>> $data");
+        return;
+	}
+
+    // show debug information
+    function debug_message($str)
+    {
+        if (!$this->debug) return;
+        if ($this->log_file !== '') {
+            $fp = fopen($this->log_file, 'at');
+            if ($fp) {
+                fputs($fp, strftime('%D %T').' ['.posix_getpid().'] '.$str."\n");
+                fclose($fp);
+                return;
+            }
+            // still show debug information, if we can't open log_file
+        }
+        echo $str."\n";
+        return;
+    }
+
+    // write log
+    function log_message($str)
+    {
+        if (!$this->log_path)
+            $this->log_path = dirname($_SERVER['argv'][0]);
+        $fname = $this->log_path.'/log/msn_'.strftime('%Y%m%d').'.log';
+        $fp = fopen($fname, 'at');
+        if ($fp) {
+            fputs($fp, strftime('%D %T').' ['.posix_getpid().'] '.$str."\n");
+            fclose($fp);
+        }
+        $this->debug_message($str);
+        return;
+    }
+}
+
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,181 @@
+#!/usr/bin/php -Cq
+<?php
+/*
+INSTALL:
+
+1. Create some folders like:
+   mkdir /var/spool/msnbot
+   mkdir /var/spool/msnbot/log
+   mkdir /var/spool/msnbot/spool
+
+2. Change the attribute for spool folder:
+   chmod 777 /var/spool/msnbot/spool
+   chmod o+t /var/spool/msnbot/spool
+
+3. Put msnbot.php and msn.class.php to /var/spool/msnbot/, and make msnbot.php executable:
+   chmod +x /var/spool/msnbot/msnbot.php
+
+4. Use msnbot.sh as your startup script to execute msnbot after system boot.
+
+5. Change processMsg function in msnbot.php to do whatever you want.
+
+6. If you need to send message to someone, just create a file under /var/spool/msnbot/spool, the filename like '*.msn',
+   and the format like test.msn, first line is TO: email1,email2, and the other lines is the message.
+   After create the file, just change the attribute to 0666, then msnbot will try to send it.
+
+*/
+
+function sig_handler($signal)
+{
+    global $msn;
+
+    if (is_object($msn)) {
+        $msn->log_message("*** someone kill me ***");
+        $msn->kill_me = true;
+    }
+    return;
+}
+
+// network:
+//      1: WLM/MSN
+//      2: LCS
+//      4: Mobile Phones
+//     32: Yahoo!
+function getNetworkName($network)
+{
+    switch ($network) {
+        case 1:
+            return 'WLM/MSN';
+        case 2:
+            return 'LCS';
+        case 4:
+            return 'Mobile Phones';
+        case 32:
+            return 'Yahoo!';
+    }
+    return "Unknown ($network)";
+}
+
+// your function to process message from someone
+function processMsg($from, $msg, $network = 1)
+{
+    global $msn_acct;
+
+    // from myself? ignore it
+    if ($from == $msn_acct) return '';
+    // also ignore other bot
+    if ($from == 'msnbot at hotmail.com') return '';
+    $nw_name = getNetworkName($network);
+    return "message from $from (network: $nw_name):\r\n$msg";
+}
+
+// your function when someone add us to his contact list
+function addContact($from, $network = 1)
+{
+    global $aNotifyUser;
+
+    if (is_array($aNotifyUser) && count($aNotifyUser) > 0) {
+        $nw_name = getNetworkName($network);
+        $now = strftime('%D %T');
+        $fname = dirname($_SERVER['argv'][0]).'/spool/msn_'.posix_getpid().'_'.md5('add'.rand(1,1000).$now).'.msn';
+        $fp = fopen($fname, 'wt');
+        if ($fp) {
+            $list = '';
+            foreach ($aNotifyUser as $user) {
+                if ($list === '')
+                    $list = $user;
+                else
+                    $list .= ','.$user;
+            }
+            fputs($fp, "TO: $list\n");
+            fputs($fp, "Now: $now\n$from (network: $nw_name) add me to his contact list!");
+            fclose($fp);
+            chmod($fname, 0666);
+        }
+    }
+    return;
+}
+
+// your function when someone remove us from his contact list
+function removeContact($from, $network = 1)
+{
+    global $aNotifyUser;
+
+    if (is_array($aNotifyUser) && count($aNotifyUser) > 0) {
+        $nw_name = getNetworkName($network);
+        $now = strftime('%D %T');
+        $fname = dirname($_SERVER['argv'][0]).'/spool/msn_'.posix_getpid().'_'.md5('delete'.rand(1,1000).$now).'.msn';
+        $fp = fopen($fname, 'wt');
+        if ($fp) {
+            $list = '';
+            foreach ($aNotifyUser as $user) {
+                if ($list === '')
+                    $list = $user;
+                else
+                    $list .= ','.$user;
+            }
+            fputs($fp, "TO: $list\n");
+            fputs($fp, "Now: $now\n$from (network: $nw_name) remove me from his contact list!");
+            fclose($fp);
+            chmod($fname, 0666);
+        }
+    }
+    return;
+}
+
+// tick use required as of PHP 4.3.0
+declare (ticks = 1);
+
+error_reporting(E_ALL);
+
+$pid = pcntl_fork();
+if ($pid) exit;
+
+require('config.php');
+include_once('msn.class.php');
+
+$aContact = false;
+$msn = new MSN('', dirname($_SERVER['argv'][0]).'/log/debug.log');
+
+pcntl_signal(SIGTERM, 'sig_handler');
+pcntl_signal(SIGHUP, 'sig_handler');
+
+$fp = fopen(dirname($_SERVER['argv'][0]).'/log/msnbot.pid', 'wt');
+if ($fp) {
+    fputs($fp, posix_getpid());
+    fclose($fp);
+}
+/*
+
+function doLoop($user,
+                $password,
+                $alias = '',
+                $my_function = false,
+                $my_add_function = false,
+                $my_rem_function = false,
+                $use_ping = false,
+                $retry_wait = 30,
+                $backup_file = true,
+                $update_pending = true
+                );
+
+$user: the MSN account
+$password: password of the MSN account
+$alias: the MSN alias, if empty, the program will use $user
+$my_function: your message process function, if empty, msnbot will ignore any message. like: processMsg($from, $msg, $network = 1)
+$my_add_function: your function when someone add us to his contact list. like: addContact($from, $network = 1)
+$my_rem_function: your function when someone remove us from his contact list. like removeContact($from, $network = 1)
+$use_ping: if true, msnbot will send PNG to server (0-50 seconds) to keep alive, but... even without this, we're still online.
+$retry_wait: if we lost connection, how long we should wait before we try again.
+$backup_file: move .msn file to backup folder after processed
+$update_pending: try to add pending list member to avail/reverse list, and delete it from pending list
+
+*/
+$msn->doLoop($msn_acct, $msn_password, $msn_alias, 'processMsg', 'addContact', 'removeContact');
+
+$msn->log_message("done!");
+ at unlink(dirname($_SERVER['argv'][0]).'/log/msnbot.pid');
+
+exit;
+
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.sh
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.sh	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnbot.sh	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,35 @@
+#! /bin/sh
+#
+# MSN bot
+#
+
+NAME=msnbot
+DESC="MSN bot"
+
+set -e
+
+case "$1" in
+  start)
+	echo -n "Starting $DESC: $NAME"
+	/var/spool/msnbot/msnbot.php
+	echo "."
+	;;
+  stop)
+	echo -n "Stopping $DESC: $NAME"
+    MSNPID=`cat /var/spool/msnbot/log/msnbot.pid`
+	kill $MSNPID
+	echo "."
+	;;
+  restart|force-reload)
+	$0 stop
+	sleep 1s
+	$0 start
+	;;
+  *)
+	N=/etc/init.d/$NAME
+	echo "Usage: $N {start|stop|restart|force-reload}" >&2
+	exit 1
+	;;
+esac
+
+exit 0

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnsendmsg.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnsendmsg.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/msnsendmsg.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,56 @@
+#!/usr/bin/php -Cq
+<?php
+
+error_reporting(0);
+
+if (!isset($argc)) $argc = $_SERVER['argc'];
+if (!isset($argv)) $argv = $_SERVER['argv'];
+
+if ($argc != 3) {
+    echo "Syntax: $argv[0] to msg\n";
+    exit;
+}
+/*
+// send it via MSN object directly.
+$aTo = explode(',', $argv[1]);
+$sMsg = $argv[2];
+
+$msn_acct = 'YOUR_MSN_ACCOUNT';
+$msn_password = 'YOUR_MSN_PASSWORD';
+
+include_once('msn.class.php');
+
+$msn = new MSN();
+
+if (!$msn->connect($msn_acct, $msn_password)) {
+    echo "Error for connect to MSN network\n";
+    echo "$msn->error\n";
+    exit;
+}
+
+$msn->sendMessage($sMsg, $aTo);
+if ($msn->error != '')
+    echo "Error: $msn->error\n";
+
+*/
+
+// write the file to use msnbot
+$sTo = $argv[1];
+$sMsg = $argv[2];
+
+$fname = '/var/spool/msnbot/spool/msn_'.posix_getpid().'_'.md5(strftime('%D %T')).'.msn';
+
+$fp = fopen($fname, 'wt');
+if (!$fp) {
+    echo "Can't write to $fname\n";
+    exit;
+}
+
+fputs($fp, "TO: $sTo\n$sMsg");
+fclose($fp);
+chmod($fname, 0666);
+
+exit;
+
+?>
+

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/sample.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/sample.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/sample.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,41 @@
+#!/usr/bin/php -Cq
+<?php
+
+error_reporting(E_ALL);
+include_once('msn.class.php');
+
+// force to use MSNP9, without debug information
+// $msn = new MSN('MSNP9');
+
+// force to use MSNP9, with debug information
+// $msn = new MSN('MSNP9', true);
+
+// force to use MSNP15, without debug information
+// $msn = new MSN('MSNP15');
+
+// force to use MSNP15, with debug information
+// $msn = new MSN('MSNP15', true);
+
+// auto detect MSN protocol, without debug information
+// $msn = new MSN;
+
+// auto detect MSN protocol, with debug information
+$msn = new MSN('', true);
+
+if (!$msn->connect('YOUR_ID', 'YOUR_PASSWORD')) {
+    echo "Error for connect to MSN network\n";
+    echo "$msn->error\n";
+    exit;
+}
+
+$msn->sendMessage('Now: '.strftime('%D %T')."\nTesting\nSecond Line\n\n\n\nand Empty Line",
+                  array(
+                    'somebody1 at hotmail.com',
+                    'somebody2 at hotmail.com'
+                       )
+                 );
+echo "Done!\n";
+exit;
+
+?>
+

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/test.msn
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/test.msn	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/phpmsnclass/test.msn	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,5 @@
+TO: sombody1 at hotmail.com,sombody2 at hotmail.com@1,yahoo_acct at yahoo.com@32
+test測試中文
+123
+
+345

Added: plugins/branches/lifetype-1.2/addcommentnotify/class/view/pluginaddcommentnotifyconfigview.class.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/class/view/pluginaddcommentnotifyconfigview.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/class/view/pluginaddcommentnotifyconfigview.class.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,36 @@
+<?php
+lt_include(PLOG_CLASS_PATH.'class/view/admin/adminplugintemplatedview.class.php');
+
+class PluginAddCommentNotifyConfigView extends AdminPluginTemplatedView
+{
+    function PluginAddCommentNotifyConfigView($blogInfo)
+    {
+        $this->AdminPluginTemplatedView($blogInfo, 'addcommentnotify', 'addcommentnotify');
+        return;
+    }
+
+    function render()
+    {
+        $blogSettings = $this->_blogInfo->getSettings();
+        $pluginEnabled = $blogSettings->getValue('plugin_addcommentnotify_enabled');
+        $usemsnclass = $blogSettings->getValue('addcommentnotify_usemsnclass');
+        $msnclass_file = $blogSettings->getValue('addcommentnotify_msnclass_file');
+        $msn_user = $blogSettings->getValue('addcommentnotify_msn_user');
+        $msn_password = $blogSettings->getValue('addcommentnotify_msn_password');
+        $msnbot_spool = $blogSettings->getValue('addcommentnotify_msnbot_spool');
+        $tolist = $blogSettings->getValue('addcommentnotify_tolist');
+
+        // Export the settings to the template
+        $this->setValue('pluginEnabled', $pluginEnabled);
+        $this->setValue('useMSNClass', $usemsnclass);
+        $this->setValue('msnClassFile', $msnclass_file);
+        $this->setValue('msnUser', $msn_user);
+        $this->setValue('msnPassword', $msn_password);
+        $this->setValue('msnbotSpool', $msnbot_spool);
+        $this->setValue('toList', $tolist);
+        parent::render();
+
+        return true;
+    }
+}
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_en_UK.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_en_UK.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_en_UK.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,28 @@
+<?php
+$messages['AddCommentNotify'] = 'Add Comment Notify';
+$messages['addcommentnotify_plugin'] = 'Add Comment Notify Plugin';
+
+$messages['error_required_missing'] = 'Please fill in all required values.';
+$messages['error_addcommentnotify_msnclass_file'] = 'Cannot find msn.class.php.';
+$messages['error_addcommentnotify_msn_user'] = 'MSN login account not correct.';
+$messages['error_addcommentnotify_msnbot_spool'] = 'msnbot spool folder does not seem to be correct.';
+$messages['error_addcommentnotify_tolist'] = 'MSN/Yahoo list is not correct.';
+$messages['addcommentnotify_settings_saved_ok'] = 'Add Comment Notify settings saved successfully!';
+
+$messages['label_configuration'] = 'Configuration';
+$messages['label_addcommentnotify_enable'] = 'Enable this plugin';
+$messages['label_addcommentnotify_usemsnclass'] = 'Using msn.class.php';
+$messages['label_addcommentnotify_msnclass_file'] = 'msn.class.php location';
+$messages['label_addcommentnotify_msn_user'] = 'MSN login account';
+$messages['label_addcommentnotify_msn_password'] = 'MSN login password';
+$messages['label_addcommentnotify_msnbot_spool'] = 'msnbot spool folder';
+$messages['label_addcommentnotify_tolist'] = 'MSN/Yahoo list';
+
+$messages['help_addcommentnotify_enable'] = 'Enable Add Comment Notify plugin';
+$messages['help_addcommentnotify_usemsnclass'] = 'Using msn.class.php';
+$messages['help_addcommentnotify_msnclass_file'] = 'msn.class.php location (If you choose to use msn.class.php)';
+$messages['help_addcommentnotify_msn_user'] = 'MSN login account (If you choose to use msn.class.php)';
+$messages['help_addcommentnotify_msn_password'] = 'MSN login password (If you choose to use msn.class.php)';
+$messages['help_addcommentnotify_msnbot_spool'] = 'msnbot spool folder';
+$messages['help_addcommentnotify_tolist'] = 'MSN/Yahoo list.';
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_zh_TW.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_zh_TW.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/locale/locale_zh_TW.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,28 @@
+<?php
+$messages['AddCommentNotify'] = '新增迴響通知';
+$messages['addcommentnotify_plugin'] = '新增迴響通知 外掛設定';
+
+$messages['error_required_missing'] = '請輸入所有必要欄位.';
+$messages['error_addcommentnotify_msnclass_file'] = 'msn.class.php 的檔案路徑不正確.';
+$messages['error_addcommentnotify_msn_user'] = 'MSN 登入帳號不正確.';
+$messages['error_addcommentnotify_msnbot_spool'] = 'msnbot spool 的路徑不正確.';
+$messages['error_addcommentnotify_tolist'] = '收件人名單不正確.';
+$messages['addcommentnotify_settings_saved_ok'] = 'Add Comment Notify 外掛模組設定成功儲存!';
+
+$messages['label_configuration'] = '設定';
+$messages['label_addcommentnotify_enable'] = '啟動這個外掛模組';
+$messages['label_addcommentnotify_usemsnclass'] = '使用 msn.class.php 來傳送';
+$messages['label_addcommentnotify_msnclass_file'] = 'msn.class.php 的檔案路徑';
+$messages['label_addcommentnotify_msn_user'] = 'MSN 的登入帳號';
+$messages['label_addcommentnotify_msn_password'] = 'MSN 的登入密碼';
+$messages['label_addcommentnotify_msnbot_spool'] = 'msnbot spool 所在路徑';
+$messages['label_addcommentnotify_tolist'] = '收件人名單';
+
+$messages['help_addcommentnotify_enable'] = '啟動 Add Comment Notify 外掛模組';
+$messages['help_addcommentnotify_usemsnclass'] = '使用 msn.class.php 來傳送訊息';
+$messages['help_addcommentnotify_msnclass_file'] = 'msn.class.php 的檔案路徑 (使用 msn.class.php 時必須設定)';
+$messages['help_addcommentnotify_msn_user'] = 'MSN 的登入帳號 (使用 msn.class.php 時必須設定)';
+$messages['help_addcommentnotify_msn_password'] = 'MSN 的登入密碼 (使用 msn.class.php 時必須設定)';
+$messages['help_addcommentnotify_msnbot_spool'] = 'msnbot 的 spool 路徑 (不使用 msn.class.php 時必須設定)';
+$messages['help_addcommentnotify_tolist'] = 'MSN/Yahoo 收件人名單, 以逗號分隔.';
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/pluginaddcommentnotify.class.php
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/pluginaddcommentnotify.class.php	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/pluginaddcommentnotify.class.php	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,119 @@
+<?php
+lt_include(PLOG_CLASS_PATH.'class/plugin/pluginbase.class.php');
+
+class PluginAddCommentNotify extends PluginBase
+{
+    var $_pluginEnabled;
+    var $_useMSNClass;
+    var $_msnClassFile;
+    var $_msnUser;
+    var $_msnPassword;
+    var $_msnbot_spool;
+    var $_tolist;
+
+    function PluginAddCommentNotify($source = "")
+    {
+        $this->PluginBase($source);
+
+        // Setup the plugin information
+        $this->id = 'addcommentnotify';
+        $this->author = 'twu2';
+        $this->desc = 'send MSN via msnbot after add comment.';
+        $this->version = '20070407';
+
+        // Setup the locale
+        $this->locales = array('en_UK', 'zh_TW');
+
+        if ($source == 'admin')
+            $this->initAdmin();
+        $this->registerNotification(EVENT_POST_COMMENT_ADD);
+    }
+
+    function initAdmin()
+    {
+        // Register the actions
+        $this->registerAdminAction('addcommentnotify', 'PluginAddCommentNotifyConfigAction');
+        $this->registerAdminAction('updateaddcommentnotify', 'PluginAddCommentNotifyUpdateConfigAction');
+
+        // Set up the Admin menu options
+        $this->addMenuEntry('/menu/controlCenter/manageSettings', 'AddCommentNotify', '?op=addcommentnotify');
+    }
+
+    function register()
+    {
+        $blogSettings = $this->blogInfo->getSettings();
+        $this->_pluginEnabled = $blogSettings->getValue('plugin_addcommentnotify_enabled');
+        $this->_useMSNClass = $blogSettings->getValue('addcommentnotify_usemsnclass');
+        $this->_msnClassFile = $blogSettings->getValue('addcommentnotify_msnclass_file');
+        $this->_msnUser = $blogSettings->getValue('addcommentnotify_msn_user');
+        $this->_msnPassword = $blogSettings->getValue('addcommentnotify_msn_password');
+        $this->_msnbot_spool = $blogSettings->getValue('addcommentnotify_msnbot_spool');
+        $this->_tolist = $blogSettings->getValue('addcommentnotify_tolist');
+        return true;
+    }
+
+    function isEnabled()
+    {
+        return $this->_pluginEnabled;
+    }
+
+    function process($eventType, $params)
+    {
+        if (!isset($params['comment'])) return true;
+        $comment = $params['comment'];
+        $this->register();
+        if (!$this->_pluginEnabled) return true;
+        if (!$this->_useMSNClass)
+        	if (!is_dir($this->_msnbot_spool)) return true;
+        $aTo = @explode(',', $this->_tolist);
+        if (count($aTo) == 0) return true;
+        $sTo = '';
+        foreach ($aTo as $to) {
+            @list($name, $domain, $network) = @explode('@', $to);
+            if ($domain == null) continue;
+            if ($network == null) $network = 1;
+            if ($network != 1 && $network != 32) continue;
+            $name = trim($name);
+            $domain = trim($domain);
+            if ($name === '' || $domain === '') continue;
+            $user = $name.'@'.$domain.'@'.$network;
+            if ($sTo === '')
+                $sTo = $user;
+            else
+                $sTo .= ','.$user;
+        }
+        if ($sTo === '') return true;
+        $url = $this->blogInfo->getBlogRequestGenerator();
+        $permalink = $url->postPermalink($comment->getArticle()).'#'.$comment->getId();
+        $sText = "Date: ".$comment->getDate()."\n".
+                 "Article: $permalink\n".
+                 "IP: ".$comment->getClientIp()."\n".
+                 "Name: ".$comment->getUserName()."\n".
+                 "Email: ".$comment->getUserEmail()."\n".
+                 "URL: ".$comment->getUserUrl()."\n".
+                 "Topic: ".$comment->getNormalizedTopic()."\n".
+                 "----------------------------------------\n".
+                 $comment->getNormalizedText();
+        if ($this->_useMSNClass) {
+            require_once($this->_msnClassFile);
+            $msn = new MSN;
+            echo "$$$$";
+            if ($msn->connect($this->_msnUser, $this->_msnPassword)) {
+                $aTo = @explode(',', $sTo);
+                $msn->sendMessage($sText, $aTo);
+            }
+            unset($msn);
+        }
+        else {
+            $fname = $this->_msnbot_spool.'/addcomment_'.md5(strftime('%D $T').rand(1,100)).'_'.posix_getpid().'.msn';
+            $fp = @fopen($fname, 'wt');
+            if ($fp) {
+                fputs($fp, "TO: $sTo\n$sText");
+                fclose($fp);
+                chmod($fname, 0666);
+            }
+        }
+        return true;
+    }
+}
+?>

Added: plugins/branches/lifetype-1.2/addcommentnotify/readme.txt
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/readme.txt	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/readme.txt	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,10 @@
+AddCommentNotify Plugin v20070406
+
+You need msnbot or msn.class.php: 
+http://blog.teatime.com.tw/1/post/227
+http://blog.teatime.com.tw/1/post/220
+http://blog.teatime.com.tw/1/post/200
+
+When use msn.class.php, it will try to login to MSN and send the message.
+If not use msn.class.php, it will write the file to msnbot's spool folder, then msnbot will send the message to you MSN/Yahoo list.
+

Added: plugins/branches/lifetype-1.2/addcommentnotify/templates/addcommentnotify.template
===================================================================
--- plugins/branches/lifetype-1.2/addcommentnotify/templates/addcommentnotify.template	                        (rev 0)
+++ plugins/branches/lifetype-1.2/addcommentnotify/templates/addcommentnotify.template	2007-04-22 17:59:17 UTC (rev 5332)
@@ -0,0 +1,60 @@
+{include file="$admintemplatepath/header.template"}
+{include file="$admintemplatepath/navigation.template" showOpt=AddCommentNotify title=$locale->tr("addcommentnotify_plugin")}
+<form name="addcommentnotifyPluginConfig" method="post">
+ <fieldset class="inputField">
+ <legend>{$locale->tr("label_configuration")}</legend>
+  {include file="$admintemplatepath/successmessage.template"}
+  {include file="$admintemplatepath/errormessage.template"}
+  <div class="field">
+   <label for="pluginEnabled">{$locale->tr("label_addcommentnotify_enable")}</label>
+   <span class="required"></span>
+   <div class="formHelp">
+    <input class="checkbox" type="checkbox" name="pluginEnabled" id="pluginEnabled" {if $pluginEnabled} checked="checked" {/if} value="1" />{$locale->tr("help_addcommentnotify_enable")}
+   </div>
+  </div>
+  <div class="field">
+   <label for="useMSNClass">{$locale->tr("label_addcommentnotify_usemsnclass")}</label>
+   <span class="required"></span>
+   <div class="formHelp">
+    <input class="checkbox" type="checkbox" name="useMSNClass" id="useMSNClass" {if $useMSNClass} checked="checked" {/if} value="1" />{$locale->tr("help_addcommentnotify_usemsnclass")}
+   </div>
+  </div>
+  <div class="field">
+   <label for="msnClassFile">{$locale->tr("label_addcommentnotify_msnclass_file")}</label>
+   <span class="required">*</span>
+   <div class="formHelp">{$locale->tr("help_addcommentnotify_msnclass_file")}</div>
+   <input class="text" type="text" name="msnClassFile" id="msnClassFile" value="{$msnClassFile}" width="40" />
+  </div>
+  <div class="field">
+   <label for="msnUser">{$locale->tr("label_addcommentnotify_msn_user")}</label>
+   <span class="required">*</span>
+   <div class="formHelp">{$locale->tr("help_addcommentnotify_msn_user")}</div>
+   <input class="text" type="text" name="msnUser" id="msnUser" value="{$msnUser}" width="40" />
+  </div>
+  <div class="field">
+   <label for="msnPassword">{$locale->tr("label_addcommentnotify_msn_password")}</label>
+   <span class="required">*</span>
+   <div class="formHelp">{$locale->tr("help_addcommentnotify_msn_password")}</div>
+   <input class="text" type="password" name="msnPassword" id="msnPassword" value="{$msnPassword}" width="40" />
+  </div>
+  <div class="field">
+   <label for="msnbotSpool">{$locale->tr("label_addcommentnotify_msnbot_spool")}</label>
+   <span class="required">*</span>
+   <div class="formHelp">{$locale->tr("help_addcommentnotify_msnbot_spool")}</div>
+   <input class="text" type="text" name="msnbotSpool" id="msnbotSpool" value="{$msnbotSpool}" width="40" />
+  </div>
+  <div class="field">
+   <label for="toList">{$locale->tr("label_addcommentnotify_tolist")}</label>
+   <span class="required">*</span>
+   <div class="formHelp">{$locale->tr("help_addcommentnotify_tolist")}</div>
+   <input class="text" type="text" name="toList" id="toList" value="{$toList}" width="40" />
+  </div>
+ </div>
+ <div class="buttons">
+  <input type="hidden" name="op" value="updateaddcommentnotify" />
+  <input type="reset" name="{$locale->tr("reset")}" />
+  <input type="submit" name="{$locale->tr("update_settings")}" value="{$locale->tr("update")}" />
+ </div>
+</form>
+{include file="$admintemplatepath/footernavigation.template"}
+{include file="$admintemplatepath/footer.template"}



More information about the pLog-svn mailing list