/*
msnswitchboardsocket . cpp - switch board connection socket
Copyright ( c ) 2002 by Martijn Klingens < klingens @ kde . org >
Copyright ( c ) 2002 - 2006 by Olivier Goffart < ogoffart @ kde . org >
Kopete ( c ) 2002 - 2005 by the Kopete developers < kopete - devel @ kde . org >
Portions of this code are taken from KMerlin ,
( c ) 2001 by Olaf Lueg < olueg @ olsd . de >
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* This program is free software ; you can redistribute it and / or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation ; either version 2 of the License , or *
* ( at your option ) any later version . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
# include "msnswitchboardsocket.h"
# include <stdlib.h>
# include <time.h>
# include <cmath>
// qt
# include <tqstylesheet.h>
# include <tqregexp.h>
# include <tqimage.h>
# include <tqtimer.h>
# include <tqfile.h>
# include <tqfileinfo.h>
// kde
# include <kdebug.h>
# include <tdemessagebox.h>
# include <tdeapplication.h>
# include <tdeaboutdata.h>
# include <tdetempfile.h>
# include <tdeconfig.h>
# include <kmdcodec.h>
# include <kstandarddirs.h>
# include <tdetempfile.h>
// for the display picture
# include <msncontact.h>
# include "msnnotifysocket.h"
//kopete
# include "msnaccount.h"
# include "msnprotocol.h"
# include "kopetemessage.h"
# include "kopetecontact.h"
# include "kopeteuiglobal.h"
# include "kopeteemoticons.h"
//#include "kopeteaccountmanager.h"
//#include "kopeteprotocol.h"
# include "sha1.h"
# include "dispatcher.h"
using P2P : : Dispatcher ;
MSNSwitchBoardSocket : : MSNSwitchBoardSocket ( MSNAccount * account , TQObject * parent )
: MSNSocket ( parent )
{
m_account = account ;
m_recvIcons = 0 ;
m_emoticonTimer = 0L ;
m_chunks = 0 ;
m_clientcapsSent = false ;
m_dispatcher = 0l ;
m_keepAlive = 0l ;
m_keepAliveNb = 0 ;
}
MSNSwitchBoardSocket : : ~ MSNSwitchBoardSocket ( )
{
kdDebug ( 14140 ) < < k_funcinfo < < endl ;
TQMap < TQString , TQPair < TQString , KTempFile * > > : : Iterator it ;
for ( it = m_emoticons . begin ( ) ; it ! = m_emoticons . end ( ) ; + + it )
{
delete it . data ( ) . second ;
}
}
void MSNSwitchBoardSocket : : connectToSwitchBoard ( TQString ID , TQString address , TQString auth )
{
// we need these for the handshake later on (when we're connected)
m_ID = ID ;
m_auth = auth ;
TQString server = address . left ( address . find ( " : " ) ) ;
uint port = address . right ( address . length ( ) - address . findRev ( " : " ) - 1 ) . toUInt ( ) ;
TQObject : : connect ( this , TQT_SIGNAL ( blockRead ( const TQByteArray & ) ) ,
this , TQT_SLOT ( slotReadMessage ( const TQByteArray & ) ) ) ;
TQObject : : connect ( this , TQT_SIGNAL ( onlineStatusChanged ( MSNSocket : : OnlineStatus ) ) ,
this , TQT_SLOT ( slotOnlineStatusChanged ( MSNSocket : : OnlineStatus ) ) ) ;
TQObject : : connect ( this , TQT_SIGNAL ( socketClosed ( ) ) ,
this , TQT_SLOT ( slotSocketClosed ( ) ) ) ;
connect ( server , port ) ;
}
void MSNSwitchBoardSocket : : handleError ( uint code , uint id )
{
kdDebug ( 14140 ) < < k_funcinfo < < endl ;
TQString msg ;
MSNSocket : : ErrorType type ;
switch ( code )
{
case 208 :
{
msg = i18n ( " Invalid user: \n "
" this MSN user does not exist; please check the MSN ID. " ) ;
type = MSNSocket : : ErrorServerError ;
userLeftChat ( m_msgHandle , i18n ( " user never joined " ) ) ;
break ;
}
case 215 :
{
msg = i18n ( " The user %1 is already in this chat. " ) . arg ( m_msgHandle ) ;
type = MSNSocket : : ErrorServerError ;
//userLeftChat(m_msgHandle , i18n("user was twice in this chat") ); //(the user shouln't join there
break ;
}
case 216 :
{
msg = i18n ( " The user %1 is online but has blocked you: \n you can not talk to this user. " ) . arg ( m_msgHandle ) ;
type = MSNSocket : : ErrorInformation ;
userLeftChat ( m_msgHandle , i18n ( " user blocked you " ) ) ;
break ;
}
case 217 :
{
// TODO: we need to know the nickname instead of the handle.
msg = i18n ( " The user %1 is currently not signed in. \n " " Messages will not be delivered. " ) . arg ( m_msgHandle ) ;
type = MSNSocket : : ErrorServerError ;
userLeftChat ( m_msgHandle , i18n ( " user disconnected " ) ) ;
break ;
}
case 713 :
{
TQString msg = i18n ( " You are trying to invite too many contacts to this chat at the same time " ) . arg ( m_msgHandle ) ;
type = MSNSocket : : ErrorInformation ;
userLeftChat ( m_msgHandle , i18n ( " user blocked you " ) ) ;
break ;
}
case 911 :
{
msg = i18n ( " Kopete MSN plugin has trouble authenticating with switchboard server. " ) ;
type = MSNSocket : : ErrorServerError ;
break ;
}
default :
MSNSocket : : handleError ( code , id ) ;
break ;
}
if ( ! msg . isEmpty ( ) )
emit errorMessage ( type , msg ) ;
}
void MSNSwitchBoardSocket : : parseCommand ( const TQString & cmd , uint id ,
const TQString & data )
{
if ( cmd = = " NAK " )
{
emit msgAcknowledgement ( id , false ) ; // msg was not accepted
}
else if ( cmd = = " ACK " )
{
emit msgAcknowledgement ( id , true ) ; // msg has received
}
else if ( cmd = = " JOI " )
{
// new user joins the chat, update user in chat list
TQString handle = data . section ( ' ' , 0 , 0 ) ;
TQString screenname = unescape ( data . section ( ' ' , 1 , 1 ) ) ;
if ( ! m_chatMembers . contains ( handle ) )
m_chatMembers . append ( handle ) ;
emit userJoined ( handle , screenname , false ) ;
}
else if ( cmd = = " IRO " )
{
// we have joined a multi chat session- this are the users in this chat
TQString handle = data . section ( ' ' , 2 , 2 ) ;
if ( ! m_chatMembers . contains ( handle ) )
m_chatMembers . append ( handle ) ;
TQString screenname = unescape ( data . section ( ' ' , 3 , 3 ) ) ;
emit userJoined ( handle , screenname , true ) ;
}
else if ( cmd = = " USR " )
{
slotInviteContact ( m_msgHandle ) ;
}
else if ( cmd = = " BYE " )
{
// some has disconnect from chat, update user in chat list
cleanQueue ( ) ; //in case some message are waiting their emoticons, never mind, send them
TQString handle = data . section ( ' ' , 0 , 0 ) . replace ( " \r \n " , " " ) ;
userLeftChat ( handle , ( data . section ( ' ' , 1 , 1 ) = = " 1 " ) ? i18n ( " timeout " ) : TQString ( ) ) ;
}
else if ( cmd = = " MSG " )
{
TQString len = data . section ( ' ' , 2 , 2 ) ;
// we need to know who's sending is the block...
m_msgHandle = data . section ( ' ' , 0 , 0 ) ;
/*//This is WRONG! the displayName is never updated on the switchboeardsocket
//so we can't trust it.
//that's why the official client does not uptade alaws the nickname immediately.
if ( m_account - > contacts ( ) [ m_msgHandle ] )
{
TQString displayName = data . section ( ' ' , 1 , 1 ) ;
if ( m_account - > contacts ( ) [ m_msgHandle ] - > displayName ( ) ! = displayName )
m_account - > contacts ( ) [ m_msgHandle ] - > rename ( displayName ) ;
} */
readBlock ( len . toUInt ( ) ) ;
}
}
void MSNSwitchBoardSocket : : slotReadMessage ( const TQByteArray & bytes )
{
TQString msg = TQString : : fromUtf8 ( bytes , bytes . size ( ) ) ;
TQRegExp rx ( " Content-Type: ([A-Za-z0-9/ \\ -]*) " ) ;
rx . search ( msg ) ;
TQString type = rx . cap ( 1 ) ;
rx = TQRegExp ( " User-Agent: ([A-Za-z0-9./ \\ -]*) " ) ;
rx . search ( msg ) ;
TQString clientStr = rx . cap ( 1 ) ;
if ( ! clientStr . isNull ( ) & & ! m_msgHandle . isNull ( ) )
{
Kopete : : Contact * c = m_account - > contacts ( ) [ m_msgHandle ] ;
if ( c )
c - > setProperty ( MSNProtocol : : protocol ( ) - > propClient , clientStr ) ;
}
// incoming message for File-transfer
if ( type = = " text/x-msmsgsinvite " )
{
emit invitation ( m_msgHandle , msg ) ;
}
else if ( type = = " text/x-msmsgscontrol " )
{
TQString message ;
message = msg . right ( msg . length ( ) - msg . findRev ( " " ) - 1 ) ;
message = message . replace ( " \r \n " , " " ) ;
emit receivedTypingMsg ( message . lower ( ) , true ) ;
}
else if ( type = = " text/x-msnmsgr-datacast " )
{
if ( msg . contains ( " ID: " ) )
{
TQRegExp rx ( " ID: ([0-9]*) " ) ;
rx . search ( msg ) ;
uint dataCastId = rx . cap ( 1 ) . toUInt ( ) ;
if ( dataCastId = = 1 )
{
kdDebug ( 14140 ) < < k_funcinfo < < " Received a nudge ! " < < endl ;
emit nudgeReceived ( m_msgHandle ) ;
}
}
}
else if ( type = = " text/plain " /* || type.isEmpty()*/ )
{
// Some MSN Clients (like CCMSN) don't like to stick to the rules.
// In case of CCMSN, it doesn't send what the content type is when
// sending a text message. So if it's not supplied, we'll just
// assume its that.
TQColor fontColor ;
TQFont font ;
if ( msg . contains ( " X-MMS-IM-Format " ) )
{
TQString fontName ;
TQString fontInfo ;
TQString color ;
rx = TQRegExp ( " X-MMS-IM-Format: ([^ \r \n ]*) " ) ;
rx . search ( msg ) ;
fontInfo = rx . cap ( 1 ) ;
color = parseFontAttr ( fontInfo , " CO " ) ;
// FIXME: this is so BAAAAAAAAAAAAD :(
if ( ! color . isEmpty ( ) & & color . toInt ( 0 , 16 ) ! = 0 )
{
if ( color . length ( ) = = 2 ) // only #RR (red color) given
fontColor . setRgb (
color . mid ( 0 , 2 ) . toInt ( 0 , 16 ) ,
0 ,
0 ) ;
else if ( color . length ( ) = = 4 ) // #GGRR (green, red) given.
{
fontColor . setRgb (
color . mid ( 2 , 2 ) . toInt ( 0 , 16 ) ,
color . mid ( 0 , 2 ) . toInt ( 0 , 16 ) ,
0 ) ;
}
else if ( color . length ( ) = = 6 ) // full #BBGGRR given
{
fontColor . setRgb (
color . mid ( 4 , 2 ) . toInt ( 0 , 16 ) ,
color . mid ( 2 , 2 ) . toInt ( 0 , 16 ) ,
color . mid ( 0 , 2 ) . toInt ( 0 , 16 ) ) ;
}
}
fontName = parseFontAttr ( fontInfo , " FN " ) . replace ( " %20 " , " " ) ;
// Some clients like Trillian and Kopete itself send a font
// name of 'MS Serif' since MS changed the server to
// _require_ a font name specified in june 2002.
// MSN's own client defaults to 'MS Sans Serif', which also
// has issues.
// Handle 'MS Serif' and 'MS Sans Serif' as an empty font name
if ( ! fontName . isEmpty ( ) & & fontName ! = " MS Serif " & & fontName ! = " MS Sans Serif " )
{
TQString ef = parseFontAttr ( fontInfo , " EF " ) ;
font = TQFont ( fontName ,
parseFontAttr ( fontInfo , " PF " ) . toInt ( ) , // font size
ef . contains ( ' B ' ) ? TQFont : : Bold : TQFont : : Normal ,
ef . contains ( ' I ' ) ) ;
font . setUnderline ( ef . contains ( ' U ' ) ) ;
font . setStrikeOut ( ef . contains ( ' S ' ) ) ;
}
}
TQPtrList < Kopete : : Contact > others ;
others . append ( m_account - > myself ( ) ) ;
TQStringList : : iterator it2 ;
for ( it2 = m_chatMembers . begin ( ) ; it2 ! = m_chatMembers . end ( ) ; + + it2 )
{
if ( * it2 ! = m_msgHandle )
others . append ( m_account - > contacts ( ) [ * it2 ] ) ;
}
TQString message = msg . right ( msg . length ( ) - msg . find ( " \r \n \r \n " ) - 4 ) ;
//Stupid MSN PLUS colors code. message with incorrect charactere are not showed correctly in the chatwindow.
//TODO: parse theses one to show the color too in Kopete
message . replace ( " \3 " , " " ) . replace ( " \4 " , " " ) . replace ( " \2 " , " " ) . replace ( " \5 " , " " ) . replace ( " \6 " , " " ) . replace ( " \7 " , " " ) ;
if ( ! m_account - > contacts ( ) [ m_msgHandle ] )
{
//this may happens if the contact has been deleted.
kdDebug ( 14140 ) < < k_funcinfo < < " WARNING: contact is null, adding it " < < endl ;
if ( ! m_chatMembers . contains ( m_msgHandle ) )
m_chatMembers . append ( m_msgHandle ) ;
emit userJoined ( m_msgHandle , m_msgHandle , false ) ;
}
Kopete : : Message kmsg ( m_account - > contacts ( ) [ m_msgHandle ] , others ,
message ,
Kopete : : Message : : Inbound , Kopete : : Message : : PlainText ) ;
kmsg . setFg ( fontColor ) ;
kmsg . setFont ( font ) ;
rx = TQRegExp ( " Chunks: ([0-9]*) " ) ;
rx . search ( msg ) ;
unsigned int chunks = rx . cap ( 1 ) . toUInt ( ) ;
rx = TQRegExp ( " Chunk: ([0-9]*) " ) ;
rx . search ( msg ) ;
unsigned int chunk = rx . cap ( 1 ) . toUInt ( ) ;
if ( chunk ! = 0 & & ! m_msgQueue . isEmpty ( ) )
{
TQString msg = m_msgQueue . last ( ) . plainBody ( ) ;
m_msgQueue . pop_back ( ) ; //removes the last item
kmsg . setBody ( msg + message , Kopete : : Message : : PlainText ) ;
}
if ( chunk = = 0 )
m_chunks = chunks ;
else if ( chunk + 1 > = m_chunks )
m_chunks = 0 ;
if ( m_recvIcons > 0 | | m_chunks > 0 )
{ //Some custom emoticons are waiting to be received. so append the message to the queue
//Or the message has not been fully received, so same thing
kdDebug ( 14140 ) < < k_funcinfo < < " Message not fully received => append to queue. Emoticon left: " < < m_recvIcons < < " chunks: " < < chunk + 1 < < " of " < < m_chunks < < endl ;
m_msgQueue . append ( kmsg ) ;
if ( ! m_emoticonTimer ) //to be sure no message will be lost, we will appends message to
{ // the queue in 15 secondes even if we have not received emoticons
m_emoticonTimer = new TQTimer ( this ) ;
TQObject : : connect ( m_emoticonTimer , TQT_SIGNAL ( timeout ( ) ) , this , TQT_SLOT ( cleanQueue ( ) ) ) ;
m_emoticonTimer - > start ( 15000 , true ) ;
}
}
else
emit msgReceived ( parseCustomEmoticons ( kmsg ) ) ;
}
else if ( type = = " text/x-mms-emoticon " | | type = = " text/x-mms-animemoticon " )
{
// TODO remove Displatcher.
TDEConfig * config = TDEGlobal : : config ( ) ;
config - > setGroup ( " MSN " ) ;
if ( config - > readBoolEntry ( " useCustomEmoticons " , true ) )
{
TQRegExp rx ( " ([^ \\ s]*) [ \ \ s ] * ( < msnobj [ ^ > ] * > ) " ) ;
rx . setMinimal ( true ) ;
int pos = rx . search ( msg ) ;
while ( pos ! = - 1 )
{
TQString msnobj = rx . cap ( 2 ) ;
TQString txt = rx . cap ( 1 ) ;
kdDebug ( 14140 ) < < k_funcinfo < < " emoticon: " < < txt < < " msnobj: " < < msnobj < < endl ;
if ( ! m_emoticons . contains ( msnobj ) | | ! m_emoticons [ msnobj ] . second )
{
m_emoticons . insert ( msnobj , tqMakePair ( txt , ( KTempFile * ) 0L ) ) ;
MSNContact * c = static_cast < MSNContact * > ( m_account - > contacts ( ) [ m_msgHandle ] ) ;
if ( ! c )
return ;
// we are receiving emoticons, so delay message display until received signal
m_recvIcons + + ;
PeerDispatcher ( ) - > requestDisplayIcon ( m_msgHandle , msnobj ) ;
}
pos = rx . search ( msg , pos + rx . matchedLength ( ) ) ;
}
}
}
else if ( type = = " application/x-msnmsgrp2p " )
{
PeerDispatcher ( ) - > slotReadMessage ( m_msgHandle , bytes ) ;
}
else if ( type = = " text/x-clientcaps " )
{
rx = TQRegExp ( " Client-Name: ([A-Za-z0-9.$!*/% \\ -]*) " ) ;
rx . search ( msg ) ;
clientStr = unescape ( rx . cap ( 1 ) ) ;
if ( ! clientStr . isNull ( ) & & ! m_msgHandle . isNull ( ) )
{
Kopete : : Contact * c = m_account - > contacts ( ) [ m_msgHandle ] ;
if ( c )
c - > setProperty ( MSNProtocol : : protocol ( ) - > propClient , clientStr ) ;
}
if ( ! m_clientcapsSent )
{
TDEConfig * config = TDEGlobal : : config ( ) ;
config - > setGroup ( " MSN " ) ;
TQString JabberID ;
if ( config - > readBoolEntry ( " SendJabber " , true ) )
JabberID = config - > readEntry ( " JabberAccount " ) ;
if ( ! JabberID . isEmpty ( ) )
JabberID = " JabberID: " + JabberID + " \r \n " ;
if ( config - > readBoolEntry ( " SendClientInfo " , true ) | | ! JabberID . isEmpty ( ) )
{
TQCString message = TQString ( " MIME-Version: 1.0 \r \n "
" Content-Type: text/x-clientcaps \r \n "
" Client-Name: Kopete/ " + escape ( kapp - > aboutData ( ) - > version ( ) ) + " \r \n "
+ JabberID +
" \r \n " ) . utf8 ( ) ;
TQString args = " U " ;
sendCommand ( " MSG " , args , true , message ) ;
}
m_clientcapsSent = true ;
}
}
else if ( type = = " image/gif " | | msg . contains ( " Message-ID: " ) )
{
// Incoming inkformatgif.
TQRegExp regex ( " Message-ID: \\ {([0-9A-F \\ -]*) \ \ } " ) ;
regex . search ( msg ) ;
TQString messageId = regex . cap ( 1 ) ;
regex = TQRegExp ( " Chunks: ( \\ d+) " ) ;
regex . search ( msg ) ;
TQString chunks = regex . cap ( 1 ) ;
regex = TQRegExp ( " Chunk: ( \\ d+) " ) ;
regex . search ( msg ) ;
TQString chunk = regex . cap ( 1 ) ;
if ( ! messageId . isNull ( ) )
{
bool valid = true ;
// Retrieve the nmber of data chunks.
TQ_UINT32 numberOfChunks = chunks . toUInt ( & valid ) ;
if ( valid & & ( numberOfChunks > 1 ) )
{
regex = TQRegExp ( " base64:([0-9a-zA-Z+/=]+) " ) ;
regex . search ( msg ) ;
// Retrieve the first chunk of the ink format gif.
TQString base64 = regex . cap ( 1 ) ;
// More chunks are expected, buffer the chunk received.
InkMessage inkMessage ;
inkMessage . chunks = numberOfChunks ;
inkMessage . data + = base64 ;
m_inkMessageBuffer . insert ( messageId , inkMessage ) ;
}
}
else
{
// There is only one chunk of data.
regex = TQRegExp ( " base64:([0-9a-zA-Z+/=]*) " ) ;
regex . search ( msg ) ;
// Retrieve the base64 encoded ink data.
TQString data = regex . cap ( 1 ) ;
DispatchInkMessage ( data ) ;
}
if ( ! messageId . isNull ( ) )
{
if ( m_inkMessageBuffer . contains ( messageId ) )
{
if ( chunks . isNull ( ) )
{
InkMessage inkMessage = m_inkMessageBuffer [ messageId ] ;
inkMessage . data + = msg . section ( " \r \n \r \n " , - 1 ) ;
if ( inkMessage . chunks = = chunk . toUInt ( ) + 1 )
{
DispatchInkMessage ( inkMessage . data ) ;
// Remove the ink message from the buffer.
m_inkMessageBuffer . remove ( messageId ) ;
}
}
}
}
}
else
{
kdDebug ( 14140 ) < < k_funcinfo < < " Unknown type ' " < < type < < " ' message: \n " < < msg < < endl ;
}
}
void MSNSwitchBoardSocket : : DispatchInkMessage ( const TQString & base64String )
{
TQByteArray image ;
// Convert from base64 encoded string to byte array.
KCodecs : : base64Decode ( base64String . utf8 ( ) , image ) ;
KTempFile * inkImage = new KTempFile ( locateLocal ( " tmp " , " inkformatgif- " ) , " .gif " ) ;
inkImage - > setAutoDelete ( true ) ;
inkImage - > file ( ) - > writeBlock ( image . data ( ) , image . size ( ) ) ;
inkImage - > file ( ) - > close ( ) ;
slotEmoticonReceived ( inkImage , " inkformatgif " ) ;
inkImage = 0l ;
}
void MSNSwitchBoardSocket : : sendTypingMsg ( bool isTyping )
{
if ( ! isTyping )
return ;
if ( onlineStatus ( ) ! = Connected | | m_chatMembers . empty ( ) )
{
//we are not yet in a chat.
//if we send that command now, we may get disconnected.
return ;
}
TQCString message = TQString ( " MIME-Version: 1.0 \r \n "
" Content-Type: text/x-msmsgscontrol \r \n "
" TypingUser: " + m_myHandle + " \r \n "
" \r \n " ) . utf8 ( ) ;
// Length is appended by sendCommand()
TQString args = " U " ;
sendCommand ( " MSG " , args , true , message ) ;
}
// this Invites an Contact
void MSNSwitchBoardSocket : : slotInviteContact ( const TQString & handle )
{
m_msgHandle = handle ;
sendCommand ( " CAL " , handle ) ;
}
//
// Send a custum emoticon
//
int MSNSwitchBoardSocket : : sendCustomEmoticon ( const TQString & name , const TQString & filename )
{
TQString picObj ;
//try to find it in the cache.
const TQMap < TQString , TQString > objectList = PeerDispatcher ( ) - > objectList ;
for ( TQMap < TQString , TQString > : : ConstIterator it = objectList . begin ( ) ; it ! = objectList . end ( ) ; + + it )
{
if ( it . data ( ) = = filename )
{
picObj = it . key ( ) ;
break ;
}
}
if ( picObj . isNull ( ) )
{ //if not found in the cache, generate the picture object
TQFileInfo fi ( filename ) ;
// open the icon file
TQFile pictFile ( fi . filePath ( ) ) ;
if ( pictFile . open ( IO_ReadOnly ) ) {
TQByteArray ar = pictFile . readAll ( ) ;
pictFile . close ( ) ;
TQString sha1d = TQString ( KCodecs : : base64Encode ( SHA1 : : hash ( ar ) ) ) ;
TQString size = TQString : : number ( pictFile . size ( ) ) ;
TQString all = " Creator " + m_account - > accountId ( ) + " Size " + size + " Type2Location " + fi . fileName ( ) + " FriendlyAAA=SHA1D " + sha1d ;
TQString sha1c = TQString ( KCodecs : : base64Encode ( SHA1 : : hashString ( all . utf8 ( ) ) ) ) ;
picObj = " <msnobj Creator= \" " + m_account - > accountId ( ) + " \" Size= \" " + size + " \" Type= \" 2 \" Location= \" " + fi . fileName ( ) + " \" Friendly= \" AAA= \" SHA1D= \" " + sha1d + " \" SHA1C= \" " + sha1c + " \" /> " ;
PeerDispatcher ( ) - > objectList . insert ( picObj , filename ) ;
}
else
return 0 ;
}
TQString msg = " MIME-Version: 1.0 \r \n "
" Content-Type: text/x-mms-emoticon \r \n "
" \r \n " +
name + " \t " + picObj + " \t \r \n " ;
return sendCommand ( " MSG " , " A " , true , msg . utf8 ( ) ) ;
}
// this sends a short message to the server
int MSNSwitchBoardSocket : : sendMsg ( const Kopete : : Message & msg )
{
if ( onlineStatus ( ) ! = Connected | | m_chatMembers . empty ( ) )
{
// m_messagesQueue.append(msg);
return - 1 ;
}
#if 0 //this is to test webcam
if ( msg . plainBody ( ) . contains ( " /webcam " ) )
{
PeerDispatcher ( ) - > startWebcam ( m_myHandle , m_msgHandle ) ;
return - 3 ;
}
# endif
TDEConfig * config = TDEGlobal : : config ( ) ;
config - > setGroup ( " MSN " ) ;
if ( config - > readBoolEntry ( " exportEmoticons " , false ) )
{
TQMap < TQString , TQStringList > emap = Kopete : : Emoticons : : self ( ) - > emoticonAndPicList ( ) ;
// Check the list for any custom emoticons
for ( TQMap < TQString , TQStringList > : : const_iterator itr = emap . begin ( ) ; itr ! = emap . end ( ) ; itr + + )
{
for ( TQStringList : : const_iterator itr2 = itr . data ( ) . constBegin ( ) ; itr2 ! = itr . data ( ) . constEnd ( ) ; + + itr2 )
{
if ( msg . plainBody ( ) . contains ( * itr2 ) )
sendCustomEmoticon ( * itr2 , itr . key ( ) ) ;
}
}
}
if ( msg . format ( ) & Kopete : : Message : : RichText )
{
TQRegExp regex ( " ^ \\ s*<img src= \" ([^> \" ]+) \ " [^>]*> \\ s*$ " ) ;
if ( regex . search ( msg . escapedBody ( ) ) ! = - 1 )
{
// FIXME why are we sending the images.. the contact should request them.
PeerDispatcher ( ) - > sendImage ( regex . cap ( 1 ) , m_msgHandle ) ;
return - 3 ;
}
}
// User-Agent is not a official flag, but PIDGIN has it
TQString UA ;
if ( config - > readBoolEntry ( " SendClientInfo " , true ) )
{
UA = " User-Agent: Kopete/ " + escape ( kapp - > aboutData ( ) - > version ( ) ) + " \r \n " ;
}
TQString head =
" MIME-Version: 1.0 \r \n "
" Content-Type: text/plain; charset=UTF-8 \r \n "
+ UA +
" X-MMS-IM-Format: " ;
if ( msg . font ( ) ! = TQFont ( ) )
{
//It's verry strange that if the font name is bigger than 31 char, the _server_ close the socket and don't deliver the message.
// the real question is why ? my guess is that MS patched the server because a bug in their client, but that's just a guess.
// - Olivier 06-2005
head + = " FN= " + escape ( msg . font ( ) . family ( ) . left ( 31 ) ) ;
head + = " ; EF= " ;
if ( msg . font ( ) . bold ( ) )
head + = " B " ;
if ( msg . font ( ) . italic ( ) )
head + = " I " ;
if ( msg . font ( ) . strikeOut ( ) )
head + = " S " ;
if ( msg . font ( ) . underline ( ) )
head + = " U " ;
head + = " ; " ;
}
else head + = " FN=; EF=; " ;
/*
* I don ' t know what to set by default , so i decided to set nothing . CF Bug 82734
* ( but don ' t forgeto to add an empty FN = and EF = , or webmessenger will break . ( CF Bug 102371 ) )
else head + = " FN=MS%20Serif; EF=; " ;
*/
// Color support
if ( msg . fg ( ) . isValid ( ) )
{
TQString colorCode = TQColor ( msg . fg ( ) . blue ( ) , msg . fg ( ) . green ( ) , msg . fg ( ) . red ( ) ) . name ( ) . remove ( 0 , 1 ) ; //colors aren't sent in RGB but in BGR (O.G.)
head + = " CO= " + colorCode ;
}
else
{
head + = " CO=0 " ;
}
head + = " ; CS=0; PF=0 " ;
if ( msg . plainBody ( ) . isRightToLeft ( ) )
head + = " ; RL=1 " ;
head + = " \r \n " ;
TQString message = msg . plainBody ( ) . replace ( " \n " , " \r \n " ) ;
//-- Check if the message isn't too big, TODO: do that at the libkopete level.
int len_H = head . utf8 ( ) . length ( ) ; // != head.length() because i need the size in butes and
int len_M = message . utf8 ( ) . length ( ) ; // some utf8 char may be longer than one byte
if ( len_H + len_M > = 1660 ) //1664 is the maximum size of messages allowed by the server
{
//We will certenly split the message in several ones.
//It's possible to made the opposite client join them, as explained in this MS Word document
//http://www.bot-depot.com/forums/index.php?act=Attach&type=post&id=35110
head + = " Message-ID: {7B7B34E6-7A8D-44FF-926C-1799156B58 " + TQString : : number ( rand ( ) % 10 ) + TQString : : number ( rand ( ) % 10 ) + " } \r \n " ;
int len_H = head . utf8 ( ) . length ( ) + 14 ; //14 is the size of "Chunks: x"
//this is the size of each part of the message (excluding the header)
int futurmessages_size = 1400 ; //1400 is a common good size
//int futurmessages_size=1664-len_H;
int nb = ( int ) ceil ( ( float ) ( len_M ) / ( float ) ( futurmessages_size ) ) ;
if ( KMessageBox : : warningContinueCancel ( 0L /* FIXME: we should try to find a parent somewere*/ ,
i18n ( " The message you are trying to send is too long; it will be split into %1 messages. " ) . arg ( nb ) ,
i18n ( " Message too big - MSN Plugin " ) , KStdGuiItem : : cont ( ) , " SendLongMessages " )
= = KMessageBox : : Continue )
{
int place = 0 ;
int result ;
int chunk = 0 ;
do
{
TQString m = message . mid ( place , futurmessages_size ) ;
place + = futurmessages_size ;
//make sure the size is not too big because of utf8
int d = m . utf8 ( ) . length ( ) + len_H - 1664 ;
if ( d > 0 )
{ //it contains some utf8 chars, so we strip the string a bit.
m = m . left ( futurmessages_size - d ) ;
place - = d ;
}
//try to snip on space if possible
int len = m . length ( ) ;
d = 0 ;
while ( d < 200 & & ! m [ len - d ] . isSpace ( ) )
d + + ;
if ( d < 200 )
{
m = m . left ( len - d ) ;
place - = d ;
}
TQString chunk_str ;
if ( chunk = = 0 )
chunk_str = " Chunks: " + TQString : : number ( nb ) + " \r \n " ;
else if ( chunk < nb )
chunk_str = " Chunk: " + TQString : : number ( chunk ) + " \r \n " ;
else
{
kdDebug ( 14140 ) < < k_funcinfo < < " The message is slit in more than initially estimated " < < endl ;
}
result = sendCommand ( " MSG " , " A " , true , ( head + chunk_str + " \r \n " + m ) . utf8 ( ) ) ;
chunk + + ;
}
while ( place < len_M ) ;
while ( chunk < nb )
{
kdDebug ( 14140 ) < < k_funcinfo < < " The message is plit in less than initially estimated. Sending empty message to complete " < < endl ;
TQString chunk_str = " Chunk: " + TQString : : number ( chunk ) ;
sendCommand ( " MSG " , " A " , true , ( head + chunk_str + " \r \n " ) . utf8 ( ) ) ;
chunk + + ;
}
return result ;
}
return - 2 ; //the message hasn't been sent.
}
if ( ! m_keepAlive )
{
m_keepAliveNb = 20 ;
m_keepAlive = new TQTimer ( this ) ;
TQObject : : connect ( m_keepAlive , TQT_SIGNAL ( timeout ( ) ) , this , TQT_SLOT ( slotKeepAliveTimer ( ) ) ) ;
m_keepAlive - > start ( 50 * 1000 ) ;
}
return sendCommand ( " MSG " , " A " , true , ( head + " \r \n " + message ) . utf8 ( ) ) ;
}
void MSNSwitchBoardSocket : : slotSocketClosed ( )
{
for ( TQStringList : : Iterator it = m_chatMembers . begin ( ) ; it ! = m_chatMembers . end ( ) ; + + it )
{
emit userLeft ( ( * it ) , i18n ( " connection closed " ) ) ;
}
// we have lost the connection, send a message to chatwindow (this will not displayed)
// emit switchBoardIsActive(false);
emit switchBoardClosed ( ) ;
}
void MSNSwitchBoardSocket : : slotCloseSession ( )
{
sendCommand ( " OUT " , TQString ( ) , false ) ;
disconnect ( ) ;
}
// Check if we are connected. If so, then send the handshake.
void MSNSwitchBoardSocket : : slotOnlineStatusChanged ( MSNSocket : : OnlineStatus status )
{
if ( status = = Connected )
{
TQCString command ;
TQString args ;
if ( ! m_ID ) // we're inviting
{
command = " USR " ;
args = m_myHandle + " " + m_auth ;
}
else // we're invited
{
command = " ANS " ;
args = m_myHandle + " " + m_auth + " " + m_ID ;
}
sendCommand ( command , args ) ;
if ( ! m_keepAlive )
{
m_keepAliveNb = 20 ;
m_keepAlive = new TQTimer ( this ) ;
TQObject : : connect ( m_keepAlive , TQT_SIGNAL ( timeout ( ) ) , this , TQT_SLOT ( slotKeepAliveTimer ( ) ) ) ;
m_keepAlive - > start ( 50 * 1000 ) ;
}
}
}
void MSNSwitchBoardSocket : : userLeftChat ( const TQString & handle , const TQString & reason )
{
emit userLeft ( handle , reason ) ;
if ( m_chatMembers . contains ( handle ) )
m_chatMembers . remove ( handle ) ;
if ( m_chatMembers . isEmpty ( ) )
disconnect ( ) ;
}
void MSNSwitchBoardSocket : : requestDisplayPicture ( )
{
MSNContact * contact = static_cast < MSNContact * > ( m_account - > contacts ( ) [ m_msgHandle ] ) ;
if ( ! contact ) return ;
PeerDispatcher ( ) - > requestDisplayIcon ( m_msgHandle , contact - > object ( ) ) ;
}
void MSNSwitchBoardSocket : : slotEmoticonReceived ( KTempFile * file , const TQString & msnObj )
{
kdDebug ( 14141 ) < < k_funcinfo < < msnObj < < endl ;
if ( m_emoticons . contains ( msnObj ) )
{ //it's an emoticon
m_emoticons [ msnObj ] . second = file ;
if ( m_recvIcons > 0 )
m_recvIcons - - ;
kdDebug ( 14140 ) < < k_funcinfo < < " emoticons received queue is now: " < < m_recvIcons < < endl ;
if ( m_recvIcons < = 0 )
cleanQueue ( ) ;
}
else if ( msnObj = = " inkformatgif " )
{
TQString msg = i18n ( " <img src= \" %1 \" alt= \" Typewrited message \" /> " ) . arg ( file - > name ( ) ) ;
kdDebug ( 14140 ) < < k_funcinfo < < file - > name ( ) < < endl ;
m_typewrited . append ( file ) ;
m_typewrited . setAutoDelete ( true ) ;
TQPtrList < Kopete : : Contact > others ;
others . append ( m_account - > myself ( ) ) ;
TQStringList : : iterator it2 ;
for ( it2 = m_chatMembers . begin ( ) ; it2 ! = m_chatMembers . end ( ) ; + + it2 )
{
if ( * it2 ! = m_msgHandle )
others . append ( m_account - > contacts ( ) [ * it2 ] ) ;
}
if ( ! m_account - > contacts ( ) [ m_msgHandle ] )
{
//this may happens if the contact has been deleted.
kdDebug ( 14140 ) < < k_funcinfo < < " WARNING: contact is null, adding it " < < endl ;
if ( ! m_chatMembers . contains ( m_msgHandle ) )
m_chatMembers . append ( m_msgHandle ) ;
emit userJoined ( m_msgHandle , m_msgHandle , false ) ;
}
Kopete : : Message kmsg ( m_account - > contacts ( ) [ m_msgHandle ] , others ,
msg , Kopete : : Message : : Inbound , Kopete : : Message : : RichText ) ;
emit msgReceived ( kmsg ) ;
}
else //if it is not an emoticon,
{ // it's certenly the displaypicture.
MSNContact * c = static_cast < MSNContact * > ( m_account - > contacts ( ) [ m_msgHandle ] ) ;
if ( c & & c - > object ( ) = = msnObj )
c - > setDisplayPicture ( file ) ;
else
delete file ;
}
}
void MSNSwitchBoardSocket : : slotIncomingFileTransfer ( const TQString & from , const TQString & /*fileName*/ , TQ_INT64 /*fileSize*/ )
{
TQPtrList < Kopete : : Contact > others ;
others . append ( m_account - > myself ( ) ) ;
TQStringList : : iterator it2 ;
for ( it2 = m_chatMembers . begin ( ) ; it2 ! = m_chatMembers . end ( ) ; + + it2 )
{
if ( * it2 ! = m_msgHandle )
others . append ( m_account - > contacts ( ) [ * it2 ] ) ;
}
if ( ! m_account - > contacts ( ) [ m_msgHandle ] )
{
//this may happens if the contact has been deleted.
kdDebug ( 14140 ) < < k_funcinfo < < " WARNING: contact is null, adding it " < < endl ;
if ( ! m_chatMembers . contains ( m_msgHandle ) )
m_chatMembers . append ( m_msgHandle ) ;
emit userJoined ( m_msgHandle , m_msgHandle , false ) ;
}
TQString invite = " Incoming file transfer. " ;
Kopete : : Message msg =
Kopete : : Message ( m_account - > contacts ( ) [ from ] , others , invite , Kopete : : Message : : Internal , Kopete : : Message : : PlainText ) ;
emit msgReceived ( msg ) ;
}
void MSNSwitchBoardSocket : : cleanQueue ( )
{
if ( m_emoticonTimer )
{
m_emoticonTimer - > stop ( ) ;
m_emoticonTimer - > deleteLater ( ) ;
m_emoticonTimer = 0L ;
}
kdDebug ( 14141 ) < < k_funcinfo < < m_msgQueue . count ( ) < < endl ;
TQValueList < const Kopete : : Message > : : Iterator it_msg ;
for ( it_msg = m_msgQueue . begin ( ) ; it_msg ! = m_msgQueue . end ( ) ; + + it_msg )
{
Kopete : : Message kmsg = ( * it_msg ) ;
emit msgReceived ( parseCustomEmoticons ( kmsg ) ) ;
}
m_msgQueue . clear ( ) ;
}
Kopete : : Message & MSNSwitchBoardSocket : : parseCustomEmoticons ( Kopete : : Message & kmsg )
{
TQString message = kmsg . escapedBody ( ) ;
TQMap < TQString , TQPair < TQString , KTempFile * > > : : Iterator it ;
for ( it = m_emoticons . begin ( ) ; it ! = m_emoticons . end ( ) ; + + it )
{
TQString es = TQStyleSheet : : escape ( it . data ( ) . first ) ;
KTempFile * f = it . data ( ) . second ;
if ( message . contains ( es ) & & f )
{
TQString imgPath = f - > name ( ) ;
TQImage iconImage ( imgPath ) ;
/* We don't use a comple algoritm (like the one in the #if) because the msn client shows
* emoticons like that . So , in that case , we show like the MSN client */
#if 0
TQString em = TQRegExp : : escape ( es ) ;
message . replace ( TQRegExp ( TQString : : fromLatin1 ( " (^|[ \\ W \\ s]|%1)(%2)(?! \\ w) " ) . arg ( em ) . arg ( em ) ) ,
TQString : : fromLatin1 ( " \\ 1<img align= \" center \" width= \" " ) +
# endif
//match any occurence which is not in a html tag.
message . replace ( TQRegExp ( TQString : : fromLatin1 ( " %1(?![^><]*>) " ) . arg ( TQRegExp : : escape ( es ) ) ) ,
TQString : : fromLatin1 ( " <img align= \" center \" width= \" " ) +
TQString : : number ( iconImage . width ( ) ) +
TQString : : fromLatin1 ( " \" height= \" " ) +
TQString : : number ( iconImage . height ( ) ) +
TQString : : fromLatin1 ( " \" src= \" " ) + imgPath +
TQString : : fromLatin1 ( " \" title= \" " ) + es +
TQString : : fromLatin1 ( " \" alt= \" " ) + es +
TQString : : fromLatin1 ( " \" /> " ) ) ;
kmsg . setBody ( message , Kopete : : Message : : RichText ) ;
}
}
return kmsg ;
}
int MSNSwitchBoardSocket : : sendNudge ( )
{
TQCString message = TQString ( " MIME-Version: 1.0 \r \n "
" Content-Type: text/x-msnmsgr-datacast \r \n "
" \r \n "
" ID: 1 \r \n "
" \r \n \r \n " ) . utf8 ( ) ;
TQString args = " U " ;
return sendCommand ( " MSG " , args , true , message ) ;
}
// FIXME: This is nasty... replace with a regexp or so.
TQString MSNSwitchBoardSocket : : parseFontAttr ( TQString str , TQString attr )
{
TQString tmp ;
int pos1 = 0 , pos2 = 0 ;
pos1 = str . find ( attr + " = " ) ;
if ( pos1 = = - 1 )
return " " ;
pos2 = str . find ( " ; " , pos1 + 3 ) ;
if ( pos2 = = - 1 )
tmp = str . mid ( pos1 + 3 , str . length ( ) - pos1 - 3 ) ;
else
tmp = str . mid ( pos1 + 3 , pos2 - pos1 - 3 ) ;
return tmp ;
}
Dispatcher * MSNSwitchBoardSocket : : PeerDispatcher ( )
{
if ( ! m_dispatcher )
{
// Create a new msnslp dispatcher to handle
// all peer to peer requests.
TQStringList ip ;
if ( m_account - > notifySocket ( ) )
{
ip < < m_account - > notifySocket ( ) - > localIP ( ) ;
if ( m_account - > notifySocket ( ) - > localIP ( ) ! = m_account - > notifySocket ( ) - > getLocalIP ( ) )
ip < < m_account - > notifySocket ( ) - > getLocalIP ( ) ;
}
m_dispatcher = new Dispatcher ( this , m_account - > accountId ( ) , ip ) ;
// TQObject::connect(this, TQT_SIGNAL(blockRead(const TQByteArray&)), m_dispatcher, TQT_SLOT(slotReadMessage(const TQByteArray&)));
// TQObject::connect(m_dispatcher, TQT_SIGNAL(sendCommand(const TQString&, const TQString&, bool, const TQByteArray&, bool)), this, TQT_SLOT(sendCommand(const TQString&, const TQString&, bool, const TQByteArray&, bool)));
TQObject : : connect ( m_dispatcher , TQT_SIGNAL ( incomingTransfer ( const TQString & , const TQString & , TQ_INT64 ) ) , this , TQT_SLOT ( slotIncomingFileTransfer ( const TQString & , const TQString & , TQ_INT64 ) ) ) ;
TQObject : : connect ( m_dispatcher , TQT_SIGNAL ( displayIconReceived ( KTempFile * , const TQString & ) ) , this , TQT_SLOT ( slotEmoticonReceived ( KTempFile * , const TQString & ) ) ) ;
TQObject : : connect ( this , TQT_SIGNAL ( msgAcknowledgement ( unsigned int , bool ) ) , m_dispatcher , TQT_SLOT ( messageAcknowledged ( unsigned int , bool ) ) ) ;
m_dispatcher - > m_pictureUrl = m_account - > pictureUrl ( ) ;
}
return m_dispatcher ;
}
void MSNSwitchBoardSocket : : slotKeepAliveTimer ( )
{
/*
This is a workaround against the bug 113425
The problem : the P2P : : Webcam class is parent of us , and when we get deleted , it get deleted .
the correct solution would be to change that .
The second problem : after one minute of inactivity , the official client close the chat socket .
the workaround : we simulate the activity by sending small packet each 50 seconds
the nice side effect : the " xxx has closed the chat " is now meaningfull
the bad side effect : some switchboard connection may be maintained for really long time !
*/
if ( onlineStatus ( ) ! = Connected | | m_chatMembers . empty ( ) )
{
//we are not yet in a chat.
//if we send that command now, we may get disconnected.
return ;
}
TQCString message = TQString ( " MIME-Version: 1.0 \r \n "
" Content-Type: text/x-keepalive \r \n "
" \r \n " ) . utf8 ( ) ;
// Length is appended by sendCommand()
TQString args = " U " ;
sendCommand ( " MSG " , args , true , message ) ;
m_keepAliveNb - - ;
if ( m_keepAliveNb < = 0 )
{
m_keepAlive - > deleteLater ( ) ;
m_keepAlive = 0L ;
}
}
# include "msnswitchboardsocket.moc"
// vim: set noet ts=4 sts=4 sw=4: