2 ircaccount.cpp - IRC Account
4 Copyright (c) 2002 by Nick Betcher <nbetcher@kde.org>
5 Copyright (c) 2003-2004 by Jason Keirstead <jason@keirstead.org>
6 Copyright (c) 2003-2007 by Michel Hermier <michel.hermier@gmail.com>
8 Kopete (c) 2002-2007 by the Kopete developers <kopete-devel@kde.org>
10 *************************************************************************
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
17 *************************************************************************
20 #include "ircaccount.h"
22 #include "irccontact.h"
23 #include "ircprotocol.h"
25 #include "kirccontext.h"
26 #include "kircclientsocket.h"
27 #include "kircstdmessages.h"
28 #include "kircconst.h"
29 #include "kircevent.h"
31 #include "kopeteaccountmanager.h"
32 #include "kopetechatsessionmanager.h"
33 #include "kopetecommandhandler.h"
34 #include "kopetecontactlist.h"
35 #include "kopetemetacontact.h"
36 #include "kopeteuiglobal.h"
37 #include "kopeteview.h"
38 #include "kopetepassword.h"
40 #include <kactionmenu.h>
42 #include <kcompletionbox.h>
45 #include <kinputdialog.h>
46 //#include <klineedit.h>
47 //#include <klineeditdlg.h>
50 #include <kmessagebox.h>
54 #include <qtextcodec.h>
58 using namespace Kopete
;
60 class IRCAccount::Private
69 , joinChannelAction(0)
70 , searchChannelAction(0)
73 Kopete::ChatSession
*manager
;
76 KIrc::Context
*clientContext
;
77 KIrc::ClientSocket
*client
;
81 QList
<IRCContact
*> contacts
;
85 Kopete::OnlineStatus expectedOnlineStatus
;
86 QString expectedReason
;
88 QMap
<QString
, QString
> customCtcp
;
89 Kopete::ChatSession
*commandSource
;
91 KAction
*joinChannelAction
;
92 KAction
*searchChannelAction
;
97 IRCAccount::IRCAccount(const QString
&accountId
, const QString
&autoChan
, const QString
& netName
, const QString
&nickName
)
98 : PasswordedAccount(IRCProtocol::self(), accountId
, true),
101 d
->clientContext
= new KIrc::Context(this);
102 d
->client
= new KIrc::ClientSocket(d
->clientContext
);
103 d
->autoConnect
= autoChan
;
106 QObject::connect(d
->client
, SIGNAL(connectionStateChanged(KIrc::Socket::ConnectionState
)),
107 this, SLOT(clientConnectionStateChanged(KIrc::Socket::ConnectionState
)));
109 QObject::connect(d
->clientContext
,SIGNAL( ircEvent( QEvent
* ) ),
110 this, SLOT( receivedEvent( QEvent
* ) ) );
114 d
->server
= new IRCContact(this, d
->client
->server());
115 d
->self
= new IRCContact(this, d
->client
->owner());
116 d
->contacts
.append( d
->server
);
117 d
->contacts
.append( d
->self
);
121 kDebug()<<"accId="<<accountId
<<" autoChan="<<autoChan
<<" netName="<<netName
<<" nickname="<<nickName
;
123 QString networkName
=netName
;
124 if(networkName
.isEmpty())
125 networkName
=this->networkName();
127 if ( networkName
.isEmpty() && QRegExp( "[^#+&\\s]+@[\\w-\\.]+:\\d+" ).exactMatch( accountId
) )
129 kDebug(14120) << "Creating account from " << accountId
;
131 // mNickName = accountId.section('@',0,0);
132 QString serverInfo
= accountId
.section('@',1);
133 QString hostName
= serverInfo
.section(':',0,0);
135 IRC::NetworkList networks
= IRC::Networks::self()->networks();
136 foreach(const IRC::Network
&net
, networks
)
138 foreach(const IRC::Host
& host
, net
.hosts
)
140 if( host
.host
== hostName
)
142 setNetworkByName(net
.name
);
147 if( !networkName
.isEmpty() )
151 if( networkName
.isEmpty() )
153 // Could not find this host. Add it to the networks structure
155 d
->network
.name
= i18n("Temporary Network - %1", hostName
);
156 d
->network
.description
= i18n("Network imported from previous version of Kopete, or an IRC URI");
159 host
.host
= hostName
;
160 host
.port
= serverInfo
.section(':',1).toInt();
161 if (!password().cachedValue().isEmpty())
162 host
.password
= password().cachedValue();
165 d
->network
.hosts
.append( host
);
166 // d->protocol->addNetwork( d->network );
168 setNetworkByName(networkName
);
169 // config->writeEntry(Config::NICKNAME, mNickName);
172 else if( !networkName
.isEmpty() )
174 setNetworkByName(networkName
);
178 kError() << "No network name defined, and could not import network information from ID" << endl
;
182 // setAccountLabel( QString::fromLatin1("%1@%2").arg(mNickName,networkName) );
186 #warning spurus slot calls for now
188 d
->joinChannelAction
= new KAction(i18n("Join Channel..."), this);
189 QObject::connect(d
->joinChannelAction
, SIGNAL(triggered()), this, SLOT(slotJoinChannel()));
190 d
->searchChannelAction
= new KAction(i18n("Search Channels..."), this);
191 QObject::connect(d
->searchChannelAction
, SIGNAL(triggered()), this, SLOT(slotSearchChannels()));
194 IRCAccount::~IRCAccount()
197 // KIrc::StdMessage::quit(d->client, i18n("Plugin Unloaded"));
203 void IRCAccount::clientSetup()
205 //d->client->setDefaultCodec(codec());
207 // Build the URL instead
209 url.setUser(userName());
210 // url.setPass(password());
213 d->client->setNickName(nickName());
214 url.addQuery(URL_REALNAME, realName());
215 d->client->setVersionString(IRC::Version);
217 QMap<QString, QString> replies = customCtcpReplies();
218 for (QMap<QString, QString>::ConstIterator it = replies.begin(); it != replies.end(); ++it)
219 d->client->addCustomCtcp(it.key(), it.data());
222 d
->network
= IRC::Networks::self()->network(networkName());
224 // if prefer SSL is set, sort by SSL first
225 if (configGroup()->readEntry("PreferSSL",false))
227 QList
<IRC::Host
> sslFirst
;
228 QList
<IRC::Host
> noSSL
;
230 foreach(const IRC::Host
&host
,d
->network
.hosts
)
232 if ( host
.ssl
== true )
234 sslFirst
.append( host
);
237 noSSL
.append( host
);
240 //Now append the non ssl servers
241 foreach(const IRC::Host
&host
,noSSL
)
243 sslFirst
.append(host
);
246 d
->network
.hosts
= sslFirst
;
251 void IRCAccount::clientConnect()
255 if (d
->network
.name
.isEmpty())
257 KMessageBox::queuedMessageBox(
258 UI::Global::mainWidget(), KMessageBox::Error
,
259 i18n("<qt>The network associated with this account has no valid hosts. "
260 "Please ensure that the account has a valid network.</qt>"),
261 i18n("Network is Empty"), 0 );
263 else if (d
->network
.hosts
.isEmpty())
265 KMessageBox::queuedMessageBox(
266 UI::Global::mainWidget(), KMessageBox::Error
,
267 i18n("<qt>The network associated with this account, <b>%1</b>, has no valid hosts. "
268 "Please ensure that the account has a valid network.</qt>", d
->network
.name
),
269 i18n("Network is Empty"), 0 );
271 else if( d
->currentHost
== d
->network
.hosts
.count() )
273 KMessageBox::queuedMessageBox(
274 UI::Global::mainWidget(), KMessageBox::Error
,
275 i18n("<qt>Kopete could not connect to any of the servers in the network "
276 "associated with this account (<b>%1</b>). Please try again later.</qt>",
278 i18n("Network is Unavailable"), 0 );
284 const IRC::Host
& host
= d
->network
.hosts
[ d
->currentHost
++ ];
285 //appendInternalMessage( i18n("Connecting to %1...", host.host ) );
290 //appendInternalMessage( i18n("Using SSL") );
291 urlString
= "ircs://";
293 urlString
= "irc://";
296 urlString
+= nickName() + '@' + host
.host
+ ':' + QString::number(host
.port
);
299 //TODO use the constants in kircconst.h
300 url
.addQueryItem("realname",realName());
301 url
.addQueryItem("nickname",nickName());
302 //d->client->connectToServer( host->host, host->port, mNickName, host->ssl );
303 d
->client
->connectToServer(url
);
307 int IRCAccount::codecMib() const
310 return configGroup()->readEntry(Config::CODECMIB
, 0);
313 void IRCAccount::setCodecFromMib(int mib
)
316 configGroup()->writeEntry(Config::CODECMIB
, mib
);
317 d
->clientContext
->setDefaultCodec(QTextCodec::codecForMib(mib
));
320 QTextCodec
*IRCAccount::codec() const
323 return QTextCodec::codecForMib(codecMib());
326 void IRCAccount::setCodec( QTextCodec
*codec
)
330 setCodecFromMib(codec
->mibEnum());
332 setCodecFromMib(-1); // MIBenum are >= 0 so we inforce an error value
335 const QString
IRCAccount::networkName() const
337 return configGroup()->readEntry(Config::NETWORKNAME
, QString());
340 void IRCAccount::setNetworkByName(const QString
&networkName
)
342 configGroup()->writeEntry(Config::NETWORKNAME
, networkName
);
343 // setAccountLabel(network.name);
346 IRCNetwork network() const
351 const QString
IRCAccount::userName() const
353 return configGroup()->readEntry(Config::USERNAME
, QString());
356 void IRCAccount::setUserName(const QString
&userName
)
358 configGroup()->writeEntry(Config::USERNAME
, userName
);
361 const QString
IRCAccount::realName() const
363 return configGroup()->readEntry(Config::REALNAME
, QString());
366 void IRCAccount::setRealName( const QString
&userName
)
368 configGroup()->writeEntry(Config::REALNAME
, userName
);
371 const QString
IRCAccount::nickName() const
373 return configGroup()->readEntry(Config::NICKNAME
, QString());
376 void IRCAccount::setNickName(const QString
&nickName
)
378 configGroup()->writeEntry(Config::NICKNAME
, nickName
);
379 // d->self->setNickName(nickName);
382 const QString
IRCAccount::partMessage() const
384 return configGroup()->readEntry(QLatin1String("defaultPart"), QString());
387 void IRCAccount::setPartMessage( const QString
&partMessage
)
389 configGroup()->writeEntry(QLatin1String("defaultPart"), partMessage
);
392 const QString
IRCAccount::quitMessage() const
394 return configGroup()->readEntry(QLatin1String("defaultQuit"), QString());
397 void IRCAccount::setQuitMessage(const QString
&quitMessage
)
399 configGroup()->writeEntry( QLatin1String("defaultQuit"), quitMessage
);
402 bool IRCAccount::autoShowServerWindow() const
404 return configGroup()->readEntry(QString::fromLatin1("AutoShowServerWindow"), false);
407 void IRCAccount::setAutoShowServerWindow(bool autoShow
)
409 configGroup()->writeEntry(QString::fromLatin1("AutoShowServerWindow"), autoShow
);
412 KIrc::ClientSocket
*IRCAccount::client() const
418 void IRCAccount::setCustomCtcpReplies(const QMap
<QString
, QString
> &replies
)
422 for( QMap< QString, QString >::ConstIterator it = replies.begin(); it != replies.end(); ++it )
424 d->client->addCustomCtcp( it.key(), it.data() );
425 val.append( QString::fromLatin1("%1=%2").arg( it.key() ).arg( it.data() ) );
428 configGroup()->writeEntry( "CustomCtcp", val );
432 const QMap
<QString
, QString
> IRCAccount::customCtcpReplies() const
435 QMap
< QString
, QString
> replies
;
437 QStringList replyList;
439 replyList = configGroup()->readListEntry( "CustomCtcp" );
441 for( QStringList::Iterator it = replyList.begin(); it != replyList.end(); ++it )
442 replies[ (*it).section('=', 0, 0 ) ] = (*it).section('=', 1 );
447 void IRCAccount::setConnectCommands( const QStringList
&commands
) const
449 configGroup()->writeEntry("ConnectCommands", commands
);
452 const QStringList
IRCAccount::connectCommands() const
454 return configGroup()->readEntry("ConnectCommands", QStringList());
457 void IRCAccount::fillActionMenu( KActionMenu
*actionMenu
)
460 QString menuTitle
= QString::fromLatin1( " %1 <%2> " ).arg( accountId() ).arg( myself()->onlineStatus().description() );
462 Account::fillActionMenu( actionMenu
);
464 d
->joinChannelAction
->setEnabled( isConnected() );
465 d
->searchChannelAction
->setEnabled( isConnected() );
467 actionMenu
->addSeparator();
468 actionMenu
->addAction(d
->joinChannelAction
);
469 actionMenu
->addAction(d
->searchChannelAction
);
471 actionMenu->insert( new KAction ( i18n("Show Server Window"), QString(), 0, this, SLOT(slotShowServerWindow()), actionMenu ) );
473 // if (d->client->isConnected() && d->client->useSSL())
475 actionMenu->insert( new KAction ( i18n("Show Security Information"), "", 0, d->client,
476 SLOT(showInfoDialog()), actionMenu ) );
481 void IRCAccount::connectWithPassword(const QString
&password
)
483 // d->client->setPassword(password);
485 kDebug(14120) << "Connecting with password.";
489 void IRCAccount::clientConnectionStateChanged(KIrc::Socket::ConnectionState newstate
)
493 mySelf()->updateStatus();
497 case KIrc::Socket::HostLookup
:
498 case KIrc::Socket::HostFound
:
499 case KIrc::Socket::Connecting
:
500 // d->expectedOnlineStatus check and use it
501 mySelf()->setOnlineStatus(Kopete::OnlineStatus::Connecting
);
503 if (autoShowServerWindow())
504 myServer()->startChat();
507 case KIrc::Socket::Authentified
:
508 mySelf()->setOnlineStatus(Kopete::OnlineStatus::Online
);
510 //Reset the host so re-connection will start over at first server
512 // d->contactManager->addToNotifyList( d->client->nickName() );
514 // HACK! See bug #85200 for details. Some servers still cannot accept commands
515 // after the 001 is sent, you need to wait until all the init junk is done.
516 // Unfortunately, there is no way for us to know when it is done (it could be
517 // spewing out any number of replies), so just try delaying it
518 QTimer::singleShot( 250, this, SLOT( slotPerformOnConnectCommands() ) );
521 case KIrc::Socket::Closing:
522 // mySelf()->setOnlineStatus( protocol->m_UserStatusOffline );
523 // d->contactManager->removeFromNotifyList( d->client->nickName() );
525 // if (d->contactManager && !autoConnect.isNull())
526 // AccountManager::self()->removeAccount( this );
528 // case KIrc::Socket::Timeout:
534 kDebug(14120) << "Doing nothing on state" << newstate
;
538 // Put that in error handling
539 void IRCAccount::slotFailedServerPassword()
542 password().setWrong();
546 void IRCAccount::slotPerformOnConnectCommands()
549 ChatSession
*manager
= myServer()->manager(Contact::CanCreate
);
553 // if (!d->autoConnect.isEmpty())
554 // CommandHandler::commandHandler()->processMessage( QString::fromLatin1("/join %1").arg(d->autoConnect), manager);
556 QStringList
commands(connectCommands());
557 // for (QStringList::Iterator it=commands.begin(); it != commands.end(); ++it)
558 // CommandHandler::commandHandler()->processMessage(*it, manager);
561 void IRCAccount::quit( const QString
&quitMessage
)
563 kDebug(14120) << "Quitting IRC: " << quitMessage
;
565 // KIrc::StdCommands::quit(d->client, quitMessage.isEmpty() ? defaultQuitMessage() : quitMessage);
568 void IRCAccount::setAway(bool isAway
, const QString
&awayMessage
)
570 kDebug(14120) << isAway
<< " " << awayMessage
;
571 // KIrc::StdCommands::away(d->client, awayMessage);
574 void IRCAccount::slotShowServerWindow()
576 d
->server
->startChat();
579 void IRCAccount::slotJoinChannel()
584 QStringList chans
= configGroup()->readEntry( "Recent Channel list", QStringList() );
585 //kdDebug(14120) << "Recent channel list from config: " << chans << endl;
586 QString channelName
=KInputDialog::getText( i18n( "Join Channel" ),
587 i18n("Please enter the name of the channel you want to join:"),
589 Kopete::UI::Global::mainWidget(),
590 0, QString(), 0, chans
593 if ( !channelName
.isNull() )
595 kDebug( 14120 )<<"joining channel"<<channelName
;
596 chans
.prepend( channelName
);
597 configGroup()->writeEntry( "Recent Channel list", chans
);
599 KIrc::EntityPtr channel
=d
->client
->joinChannel( channelName
.toUtf8() );
600 getContact( channel
)->startChat();
604 void IRCAccount::setOnlineStatus(const OnlineStatus
& status
, const StatusMessage
&messageStatus
, const OnlineStatusOptions
& options
)
607 d
->expectedOnlineStatus
= status
;
608 //d->expectedReason = reason;
610 OnlineStatus::StatusType current
= myself()->onlineStatus().status();
611 OnlineStatus::StatusType expected
= d
->expectedOnlineStatus
.status();
613 if ( expected
!= OnlineStatus::Offline
&& (current
== OnlineStatus::Offline
|| current
== OnlineStatus::Unknown
) )
615 kDebug(14120) << "Connecting.";
621 if ( expected
== OnlineStatus::Offline
&& current
!= OnlineStatus::Offline
)
623 kDebug(14120) << "Disconnecting.";
628 void IRCAccount::setStatusMessage(const StatusMessage
&messageStatus
)
633 bool IRCAccount::createContact(const QString
&contactId
, MetaContact
*metac
)
636 /* if (contactId == mNickName)
638 KMessageBox::error( UI::Global::mainWidget(),
639 i18n("\"You are not allowed to add yourself to your contact list."), i18n("IRC Plugin")
644 IRCContact *contact = getContact(contactId, metac);
646 if (contact->metaContact() != metac )
647 {//This should NEVER happen
648 MetaContact *old = contact->metaContact();
649 contact->setMetaContact(metac);
650 ContactPtrList children = old->contacts();
651 if (children.isEmpty())
652 ContactList::self()->removeMetaContact( old );
654 else if (contact->metaContact()->isTemporary())
655 metac->setTemporary(false);
660 void IRCAccount::setCurrentCommandSource( ChatSession
*session
)
663 d
->commandSource
= session
;
666 ChatSession
*IRCAccount::currentCommandSource()
669 return d
->commandSource
;
672 IRCContact
*IRCAccount::myServer() const
677 IRCContact
*IRCAccount::mySelf() const
682 IRCContact
*IRCAccount::getContact(const QByteArray
&name
, MetaContact
*metac
)
684 kDebug(14120) << name
;
685 // return getContact(d->client->entityManager()->entityByName(name), metac);
689 IRCContact
*IRCAccount::getContact(const KIrc::EntityPtr
&entity
, MetaContact
*metac
)
691 IRCContact
*contact
= 0;
693 kDebug( 14120)<<"finding contact for name "<<entity
->name();
695 //TODO: use hash or something to speed up searching?
696 foreach( IRCContact
*tmp
, d
->contacts
)
698 if ( tmp
->entity()==entity
)
708 #warning Make a temporary meta contact if metac is null
710 contact
= new IRCContact(this, entity
, metac
);
711 d
->contacts
.append(contact
);
714 QObject::connect(contact
, SIGNAL(destroyed(IRCContact
*)), SLOT(destroyed(IRCContact
*)));
718 QList
<Kopete::Contact
*> IRCAccount::getContacts( const KIrc::EntityList
&entities
)
720 QList
<Kopete::Contact
*> contacts
;
721 foreach( const KIrc::EntityPtr
&e
, entities
)
722 contacts
<<getContact( e
);
727 void IRCAccount::destroyed(IRCContact
*contact
)
730 d
->contacts
.removeAll(contact
);
733 void IRCAccount::receivedEvent(QEvent
*event
)
735 kDebug(14120)<<"received event";
736 if ( event
->type()==KIrc::TextEvent::Type
)
738 KIrc::TextEvent
* txtEvent
=static_cast< KIrc::TextEvent
* >( event
);
739 kDebug(14120)<<"type: " << txtEvent
->eventId();
740 kDebug(14120)<<"from: " << txtEvent
->from()->name();
741 kDebug(14120)<<"message:" << txtEvent
->text();
743 IRCContact
*from
= getContact( txtEvent
->from() );
744 QList
<Kopete::Contact
*> to
= getContacts( txtEvent
->to() );
745 Kopete::Message::MessageType msgType
= Kopete::Message::TypeNormal
;
746 Kopete::Message::MessageImportance msgImportance
= Kopete::Message::Low
;
748 if ( txtEvent
->eventId()=="PRIVMSG" )
750 // if ( !to->isChannel() )
751 // importance = Kopete::Message::Normal;
753 else if ( txtEvent
->eventId() == "DCC_ACTION" )
755 msgType
= Kopete::Message::TypeAction
;
758 else if ( txtEvent
->eventId().startWith("ERR_") )
760 msgImportance
= Kopete::Message::Highlight
;
763 appendMessage( from
, to
, txtEvent
->text(), msgType
);
766 QList<Kopete::Contact*> to = getContacts(txtEvent->to());
767 //QList<IRCContact*> cc = getContacts(txtEvent->cc());
769 Kopete::Message::MessageType msgType;
770 if ( txtEvent->eventId()=="ServerMessage" )
772 msgType = Kopete::Message::TypeAction;
777 case KIrc::????: // Action
778 msgType = Kopete::Message::TypeAction;
781 msgType = Kopete::Message::????;
784 // make a notification if needed, istead of posting the message to the toContact.
785 // toContact may be the wrong contact where to post in case of private user chat
789 postContact->appendMessage(msg);
793 void IRCAccount::appendMessage(IRCContact
* from
, QList
<Contact
*> to
,const QString
& text
, Kopete::Message::MessageType type
)
795 Kopete::Message::MessageDirection msgDirection
=
796 from
== mySelf() ? Kopete::Message::Outbound
: Kopete::Message::Inbound
;
798 Kopete::Message
msg(from
, to
);
799 msg
.setDirection( msgDirection
);
800 msg
.setPlainBody( text
);
803 foreach( Kopete::Contact
* c
, to
)
805 if ( c
==myself() ) //If we are the target of the message, append it to the chatsession of the origin
806 from
->appendMessage( msg
);
808 dynamic_cast<IRCContact
*> ( c
)->appendMessage( msg
);
813 void IRCContact::slotUserDisconnected(const QString &user, const QString &reason)
817 QString nickname = user.section('!', 0, 0);
818 Contact *c = locateUser( nickname );
821 d->chatSession->removeContact(c, i18n("Quit: \"%1\" ",reason), Message::RichText);
822 // c->setOnlineStatus(IRCProtocol::self()->m_UserStatusOffline);
827 void IRCContact::slotNewNickChange(const QString &oldnickname, const QString &newnickname)
829 IRCAccount *account = ircAccount();
831 IRCContact *user = static_cast<IRCContact*>( locateUser(oldnickname) );
834 user->setNickName( newnickname );
836 //If the user is in our contact list, then change the notify list nickname
837 // if (!user->metaContact()->isTemporary())
839 // account->contactManager()->removeFromNotifyList( oldnickname );
840 // account->contactManager()->addToNotifyList( newnickname );
845 void IRCAccount::successfullyChangedNick(const QString &oldnick, const QString &newnick)
847 // kDebug(14120) << "Changing nick to " << newnick;
848 // mNickName = newnick;
849 // mySelf()->setNickName( mNickName );
850 // d->contactManager->removeFromNotifyList( oldnick );
851 // d->contactManager->addToNotifyList( newnick );
854 #include "ircaccount.moc"