1 /*
2 qqnotifysocket.cpp - Notify Socket for the QQ Protocol
3 forked from msnnotifysocket.cpp
4
5 Copyright (c) 2006 by Hui Jin <blueangel.jin@gmail.com>
6 Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org>
7 Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
8 Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org>
9 Copyright (c) 2005 by Michaël Larouche <larouche@kde.org>
10 Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com>
11
12 Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
13
14 Portions taken from
15 KMerlin (c) 2001 by Olaf Lueg <olueg@olsd.de>
16
17 *************************************************************************
18 * *
19 * This program is free software; you can redistribute it and/or modify *
20 * it under the terms of the GNU General Public License as published by *
21 * the Free Software Foundation; either version 2 of the License, or *
22 * (at your option) any later version. *
23 * *
24 *************************************************************************
25 */
26
27 #include "qqnotifysocket.h"
28
29 #include <kdebug.h>
30 #include <QHostAddress>
31
32 #include "kopetestatusmessage.h"
33 #include "libeva.h"
34
35 #include "qqaccount.h"
36
QQNotifySocket(QQAccount * account,const QString & password)37 QQNotifySocket::QQNotifySocket( QQAccount *account, const QString &password )
38 : QQSocket( account )
39 {
40 m_account = account;
41 m_newstatus = Kopete::OnlineStatus::Offline;
42 Eva::ByteArray pwd( password.toAscii().data(), password.size() );
43 m_passwordKey = Eva::Packet::QQHash(pwd);
44 pwd.release(); // the data is handled in QT
45 m_loginMode = Eva::NormalLogin;
46
47 // FIXME: more error-checking.
48 m_qqId = account->accountId().toInt();
49
50 m_heartbeat = new QTimer(this);
51 QObject::connect( m_heartbeat, SIGNAL(timeout()), SLOT(heartbeat()) );
52 }
53
~QQNotifySocket()54 QQNotifySocket::~QQNotifySocket()
55 {
56 kDebug(14140) ;
57 if( m_heartbeat->isActive() )
58 m_heartbeat->stop();
59
60 delete m_heartbeat;
61 }
62
doneConnect()63 void QQNotifySocket::doneConnect()
64 {
65 // setup the status first
66 QQSocket::doneConnect();
67
68 kDebug( 14140 ) << "Negotiating server protocol version";
69 if( m_token.size() )
70 sendPacket( Eva::login( m_qqId, m_id++, m_passwordKey, m_token, m_loginMode ) );
71 else
72 sendPacket( Eva::loginToken(m_qqId, m_id++) );
73 }
74
disconnect()75 void QQNotifySocket::disconnect()
76 {
77 kDebug(14140) << "online status =" <<
78 onlineStatus() << endl;
79 // FIXME: double check the logic, please.
80 if( m_disconnectReason==Kopete::Account::Unknown )
81 m_disconnectReason=Kopete::Account::Manual;
82 // sendGoodbye, shall we setup the status as well ?
83 if( onlineStatus() == Connected )
84 sendGoodbye();
85
86 // the socket is not connected yet, so I should force the signals
87 if ( onlineStatus() == Disconnected || onlineStatus() == Connecting )
88 emit socketClosed();
89 else
90 QQSocket::disconnect();
91 }
92
handleError(uint code,uint id)93 void QQNotifySocket::handleError( uint code, uint id )
94 {
95 kDebug(14140) ;
96
97 // TODO: Add support for all of these!
98 switch( code )
99 {
100 default:
101 QQSocket::handleError( code, id );
102 break;
103 }
104 }
105
106 // Core functions
handleIncomingPacket(const QByteArray & rawData)107 void QQNotifySocket::handleIncomingPacket( const QByteArray& rawData )
108 {
109 kDebug( 14140 ) << rawData;
110 Eva::Packet packet( rawData.data(), rawData.size() );
111 Eva::ByteArray text;
112
113 Eva::ByteArray initKey((char*) Eva::Packet::getInitKey(), 16 );
114 initKey.release();
115
116 kDebug( 14140 ) << "command = " << packet.command();
117 switch( packet.command() )
118 {
119 case Eva::Command::RequestLoginToken :
120 text = Eva::Packet::loginToken( packet.body() );
121 break;
122
123 case Eva::Command::Login :
124 text = Eva::Packet::decrypt( packet.body(), m_passwordKey );
125 if( text.size() == 0 )
126 text = Eva::Packet::decrypt( packet.body(), initKey );
127 break;
128
129 default:
130 text = Eva::Packet::decrypt( packet.body(), m_sessionKey );
131 if ( text.size() == 0 )
132 text = Eva::Packet::decrypt( packet.body(), m_passwordKey );
133 }
134
135 kDebug( 14140 ) << "text = " << QByteArray( text.c_str(), text.size() );
136
137 switch( packet.command() )
138 {
139 // FIXME: use table-driven pattern ?
140 case Eva::Command::Logout :
141 case Eva::Command::Heartbeat:
142 break;
143 case Eva::Command::UpdateInfo :
144 case Eva::Command::Search :
145 case Eva::Command::UserInfo :
146 {
147 // map std::map to QMap
148 std::map<const char*, std::string, Eva::ltstr> dict = Eva::Packet::contactDetail(text);
149 QMap<const char*, QByteArray> qmap;
150
151 QString id = QString( dict["qqId"].c_str() );
152 std::map<const char*, std::string, Eva::ltstr>::const_iterator it = dict.begin();
153
154 for( ; it != dict.end(); it++ )
155 qmap.insert( (*it).first, QByteArray((*it).second.c_str() ) );
156
157 emit contactDetailReceived(id, qmap);
158 }
159
160 break;
161 case Eva::Command::AddBuddy:
162 case Eva::Command::RemoveBuddy:
163 case Eva::Command::AuthInvite :
164 break;
165 case Eva::Command::ChangeStatus :
166 if( Eva::Packet::replyCode(text) == Eva::ChangeStatusOK )
167 {
168 kDebug( 14140 ) << "ChangeStatus ok";
169 emit statusChanged( m_newstatus );
170 }
171 else // TODO: Debug me.
172 disconnect();
173 break;
174
175 case Eva::Command::AckSysMsg :
176 case Eva::Command::SendMsg :
177 break;
178 case Eva::Command::ReceiveMsg :
179 {
180 Eva::MessageEnvelop envelop(text);
181 kDebug(14140) << "Received message from " << envelop.sender << " to " << envelop.receiver << " type=" << envelop.type;
182 kDebug(14140) << "seq = " << envelop.sequence << " from " << envelop.ip << ":" << envelop.port;
183
184 sendPacket( Eva::messageReply(m_qqId, packet.sequence(), m_sessionKey, Eva::Packet::replyKey(text) ));
185 Eva::ByteArray body( text.data() + sizeof(envelop), text.size() - sizeof(envelop) );
186 body.release();
187
188 // TODO: check whether this is a duplicated message
189 switch( envelop.type )
190 {
191 case 0x0010:
192 kDebug(14140) << "command 0x0010: " << QByteArray( body.c_str(), body.size() );
193 break;
194 case Eva::RcvFromBuddy:
195 {
196 Eva::MessageHeader mh(body);
197 kDebug(14140) << "message header:";
198 kDebug(14140) << "ver:" << mh.version << " sender:" << mh.sender
199 << " receiver:" << mh.receiver
200 << " type:" << mh.type << " seq:" << mh.sequence
201 << " timestamp:" << mh.timestamp << " avatar:" << mh.avatar
202 << endl;
203
204 if( mh.receiver != m_qqId )
205 {
206 kDebug(14140) << "receive other(" << mh.receiver <<")'s message";
207 break;
208 }
209
210 // FIXME: replace the magic number!
211 // FIXME: the code stinks!
212 Eva::uchar* p = body.data()+36;
213 bool hasFontStyle = p[3] != 0;
214 Eva::uchar replyType = p[8];
215
216 // clear compiler warnings
217 Q_UNUSED(hasFontStyle);
218 Q_UNUSED(replyType);
219
220 Eva::ByteArray msg(body.size());
221 p += 9;
222
223 while( *p )
224 msg += *p++;
225 msg += char(0x0);
226
227 kDebug(14140) << "message received: " << msg.data();
228 // FIXME: use a function to generate guid!
229 emit messageReceived(mh, msg);
230
231 break;
232 }
233 default:
234 break;
235 }
236 break;
237 }
238
239 case Eva::Command::RemoveMe :
240 break;
241
242 case Eva::Command::RequestKey :
243 {
244 char type = text.data()[0];
245 char reply = text.data()[1];
246
247 if( reply == Eva::RequestKeyOK )
248 {
249 // NOTE: the type of the key supports TransferKey only.
250 if( type == Eva::TransferKey )
251 {
252 m_transferKey = Eva::Packet::transferKey( text );
253 m_transferToken = Eva::Packet::transferToken( text );
254 kDebug( 14140 ) << "transferKey =" << QByteArray( m_transferKey.c_str(), m_transferKey.size());
255 kDebug( 14140 ) << "transferToken =" << QByteArray( m_transferToken.c_str(), m_transferToken.size());
256
257 }
258 }
259 break;
260 }
261
262 case Eva::Command::GetCell :
263 break;
264
265 case Eva::Command::Login :
266 switch( Eva::Packet::replyCode(text) )
267 {
268 case Eva::LoginOK:
269 kDebug( 14140 ) << "Bingo! QQ:#" << m_qqId << " logged in!";
270 // show off some meta data :
271 m_sessionKey = Eva::Packet::sessionKey(text);
272 kDebug( 14140 ) << "sessionKey = " <<
273 QByteArray( m_sessionKey.c_str(), m_sessionKey.size() ) << endl;
274
275 kDebug( 14140 ) << "remote IP: " << QHostAddress( Eva::Packet::remoteIP(text) ).toString();
276 kDebug( 14140 ) << "remote port: " << Eva::Packet::remotePort(text);
277 kDebug( 14140 ) << "local IP: " << QHostAddress( Eva::Packet::localIP(text) ).toString();
278 kDebug( 14140 ) << "local port: " << Eva::Packet::localPort(text);
279 kDebug( 14140 ) << "login time: " << Eva::Packet::loginTime(text);
280 kDebug( 14140 ) << "last login from: " << QHostAddress( Eva::Packet::lastLoginFrom(text) ).toString();
281 kDebug( 14140 ) << "last login time: " << Eva::Packet::lastLoginTime(text);
282
283 // start the heartbeat
284 if( !m_heartbeat->isActive() )
285 {
286 m_heartbeat->setSingleShot(false);
287 m_heartbeat->start(60000);
288 }
289
290 // FIXME: refactor me!
291 emit newContactList();
292 // FIXME: We might login in as invisible as well.
293 m_newstatus = Kopete::OnlineStatus::Online;
294 sendPacket( Eva::statusUpdate( m_qqId, m_id++, m_sessionKey, Eva::Online) );
295 sendPacket( Eva::transferKey( m_qqId, m_id++, m_sessionKey) );
296
297 // get the meta data for myself
298 contactDetail(m_qqId);
299
300 // fetch the online contacts
301 sendListOnlineContacts();
302
303 break;
304
305 case Eva::LoginRedirect :
306 kDebug( 14140 ) << "Redirect to "
307 << QHostAddress(Eva::Packet::redirectedIP(text)).toString()
308 << " : " << Eva::Packet::redirectedPort(text) << endl;
309 disconnect();
310 connect( QHostAddress( Eva::Packet::redirectedIP(text) ).toString(), Eva::Packet::redirectedPort(text) );
311 break;
312
313 case Eva::LoginWrongPassword :
314 kDebug( 14140 ) << "password is wrong. ";
315 break;
316
317 case Eva::LoginMiscError :
318 kDebug( 14140 ) << "unknown error. ";
319 break;
320
321 default:
322 kDebug( 14140 ) << "Bad, we are not supposed to be here !";
323 break;
324 }
325
326 break;
327
328 case Eva::Command::AllContacts:
329 /*
330 {
331 len = 2;
332 while( len < text.size() )
333 emit contactList( Eva::contactInfo( text.data(), len ) );
334 short pos = ntohs( Eva::type_cast<short> (text.data()) );
335
336 if( pos != Eva::ContactListEnd )
337 sendPacket( Eva::allContacts( m_qqId, m_id++, m_sessionKey, pos ) );
338 }
339 */
340 break;
341 case Eva::Command::ContactsOnline :
342
343 break;
344 case Eva::Command::GetCell2 :
345 case Eva::Command::SIP :
346 case Eva::Command::Test :
347 break;
348 case Eva::Command::GroupNames :
349 groupNames( text );
350 break;
351
352 case Eva::Command::UploadGroups :
353 case Eva::Command::Memo :
354 break;
355 case Eva::Command::DownloadGroups :
356 groupInfos( text );
357 break;
358
359 case Eva::Command::GetLevel :
360 break;
361
362 case Eva::Command::RequestLoginToken :
363 m_token = text;
364 kDebug( 14140 ) << "command = " << packet.command() << ": token = " <<
365 QByteArray ( m_token.c_str(), m_token.size() ) << endl;
366 sendPacket( Eva::login( m_qqId, m_id++, m_passwordKey, m_token, m_loginMode ) );
367 break;
368
369 case Eva::Command::ExtraInfo :
370 case Eva::Command::Signature :
371 case Eva::Command::ReceiveSysMsg :
372 break;
373 case Eva::Command::ContactStausChanged :
374 {
375 kDebug( 14140 ) << "contact status signal";
376 Eva::ContactStatus cs(text.data());
377 kDebug( 14140 ) << "contact status detail:";
378 kDebug( 14140 ) << "id = " << cs.qqId << " status = " << cs.status;
379 emit contactStatusChanged( cs );
380 break;
381 }
382 default:
383 break;
384
385 }
386 }
387
contactDetail(Eva::uint qqId)388 void QQNotifySocket::contactDetail(Eva::uint qqId)
389 {
390 sendPacket( Eva::contactDetail( m_qqId, m_id++, m_sessionKey, qqId) );
391 }
392
sendTextMessage(const uint toId,const QByteArray & message)393 void QQNotifySocket::sendTextMessage( const uint toId, const QByteArray& message )
394 {
395 // Translate the message to Eva::ByteArray
396 // TODO: color and font
397 kDebug( 14140 ) << "Send the message: " << message << " from " << m_qqId << " to " << toId;
398 // attach the ByteArray to QString:
399 // FIXME: Add an adapter to ByteArray
400 Eva::ByteArray text( (char*)message.data(), message.size() );
401 text.release();
402
403 Eva::ByteArray packet = Eva::textMessage(m_qqId, m_id++, m_sessionKey, toId, m_transferKey, text );
404 QQSocket::sendPacket( QByteArray( packet.c_str(), packet.size()) );
405 }
406
heartbeat()407 void QQNotifySocket::heartbeat()
408 {
409 sendPacket( Eva::heartbeat( m_qqId, m_id++, m_sessionKey ));
410 }
411
sendListOnlineContacts(uint pos)412 void QQNotifySocket::sendListOnlineContacts(uint pos)
413 {
414 sendPacket( Eva::onlineContacts( m_qqId, m_id++, m_sessionKey, pos) );
415 }
416
groupNames(const Eva::ByteArray & text)417 void QQNotifySocket::groupNames( const Eva::ByteArray& text )
418 {
419 QStringList ql;
420 std::list< std::string > l = Eva::Packet::groupNames( text );
421 for( std::list<std::string>::const_iterator it = l.begin(); it != l.end(); it++ )
422 ql.append( QString( (*it).c_str() ) );
423
424 kDebug(14140) ;
425 emit groupNames( ql );
426 }
427
groupInfos(const Eva::ByteArray & text)428 void QQNotifySocket::groupInfos( const Eva::ByteArray& text )
429 {
430 kDebug(14140) ;
431 std::list< Eva::GroupInfo > gis = Eva::Packet::groupInfos( text );
432 // TODO: send it one by one.
433 for( std::list< Eva::GroupInfo >::const_iterator it = gis.begin();
434 it != gis.end(); it++ )
435 {
436 kDebug(14140) << "buddy: qqId = " << (*it).qqId << " type = " << (*it).type
437 << " groupId = " << (*it).groupId << endl;
438 emit contactInGroup( (*it).qqId, (*it).type, (*it).groupId );
439 }
440
441 int next = Eva::Packet::nextGroupId( text );
442 if( next )
443 sendDownloadGroups( next );
444 }
445
doGetContactStatuses(const Eva::ByteArray & text)446 void QQNotifySocket::doGetContactStatuses( const Eva::ByteArray& text )
447 {
448 kDebug(14140) ;
449 Eva::uchar pos = Eva::ContactListBegin;
450 std::list< Eva::ContactStatus > css = Eva::Packet::onlineContacts( text, pos );
451 for( std::list< Eva::ContactStatus >::const_iterator it = css.begin();
452 it != css.end(); it++ )
453 {
454 kDebug(14140) << "buddy: qqId = " << (*it).qqId << " status = " << (*it).status;
455 emit contactStatusChanged(*it);
456 }
457
458 if( pos != 0xff )
459 sendListOnlineContacts(pos);
460 }
461
462