/* ircusercontact.cpp - IRC User Contact Copyright (c) 2002 by Nick Betcher Kopete (c) 2002 by the Kopete developers ************************************************************************* * * * 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 "ircusercontact.h" #include "ircservercontact.h" #include "ircchannelcontact.h" #include "irccontactmanager.h" #include "ircaccount.h" #include "ircprotocol.h" #include "kcodecaction.h" #include "kopetemetacontact.h" #include "kopeteview.h" #include #include #include #include #include IRCUserContact::IRCUserContact(IRCContactManager *contactManager, const TQString &nickname, Kopete::MetaContact *m ) : IRCContact(contactManager, nickname, m ), actionCtcpMenu(0L) { setFileCapable(true); mOnlineTimer = new TQTimer( this ); TQObject::connect(mOnlineTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT( slotUserOffline() ) ); TQObject::connect(kircEngine(), TQT_SIGNAL(incomingChannelModeChange(const TQString&, const TQString&, const TQString&)), this, TQT_SLOT(slotIncomingModeChange(const TQString&,const TQString&, const TQString&))); mInfo.isOperator = false; mInfo.isIdentified = false; mInfo.idle = 0; mInfo.hops = 0; mInfo.away = false; mInfo.online = metaContact()->isTemporary(); updateStatus(); } void IRCUserContact::updateStatus() { //kdDebug(14120) << k_funcinfo << endl; Kopete::OnlineStatus newStatus; switch (kircEngine()->status()) { case KIRC::Engine::Idle: newStatus = m_protocol->m_UserStatusOffline; break; case KIRC::Engine::Connecting: case KIRC::Engine::Authentifying: if (this == ircAccount()->mySelf()) newStatus = m_protocol->m_UserStatusConnecting; else newStatus = m_protocol->m_UserStatusOffline; break; case KIRC::Engine::Connected: case KIRC::Engine::Closing: if (mInfo.away) newStatus = m_protocol->m_UserStatusAway; else if (mInfo.online) newStatus = m_protocol->m_UserStatusOnline; break; default: newStatus = m_protocol->m_StatusUnknown; } // Try hard not to emit several onlineStatusChanged() signals. bool onlineStatusChanged = false; /* The away status is global, so if the user goes away, we must set * the new status on all channels. */ // This may not be created yet ( for myself() on startup ) if( ircAccount()->contactManager() ) { TQValueList channels = ircAccount()->contactManager()->findChannelsByMember(this); for( TQValueList::iterator it = channels.begin(); it != channels.end(); ++it ) { IRCChannelContact *channel = *it; Kopete::OnlineStatus currentStatus = channel->manager()->contactOnlineStatus(this); //kdDebug(14120) << k_funcinfo << "iterating channel " << channel->nickName() << " internal status: " << currentStatus.internalStatus() << endl; if( currentStatus.internalStatus() >= IRCProtocol::Online ) { onlineStatusChanged = true; if( !(currentStatus.internalStatus() & IRCProtocol::Away) && newStatus == m_protocol->m_UserStatusAway ) { setOnlineStatus( newStatus ); //kdDebug(14120) << k_funcinfo << "was NOT away, but is now, channel " << channel->nickName() << endl; adjustInternalOnlineStatusBits(channel, IRCProtocol::Away, AddBits); } else if( (currentStatus.internalStatus() & IRCProtocol::Away) && newStatus == m_protocol->m_UserStatusOnline ) { setOnlineStatus( newStatus ); //kdDebug(14120) << k_funcinfo << "was away, but not anymore, channel " << channel->nickName() << endl; adjustInternalOnlineStatusBits(channel, IRCProtocol::Away, RemoveBits); } else if( newStatus.internalStatus() < IRCProtocol::Away ) { //kdDebug(14120) << k_funcinfo << "offline or connecting?" << endl; channel->manager()->setContactOnlineStatus( this, newStatus ); } } } } if (!onlineStatusChanged) { //kdDebug(14120) << k_funcinfo << "setting status at last" << endl; setOnlineStatus( newStatus ); } } void IRCUserContact::sendFile(const KURL &sourceURL, const TQString&, unsigned int) { TQString filePath; //If the file location is null, then get it from a file open dialog if( !sourceURL.isValid() ) filePath = KFileDialog::getOpenFileName(TQString(), "*", 0l , i18n("Kopete File Transfer")); else filePath = sourceURL.path(-1); kdDebug(14120) << k_funcinfo << "File chosen to send:" << filePath << endl; if (!filePath.isEmpty()) kircEngine()->CtcpRequest_dcc( m_nickName, filePath, 0, KIRC::Transfer::FileOutgoing); } void IRCUserContact::slotUserOffline() { mInfo.online = false; mInfo.away = false; updateStatus(); if( !metaContact()->isTemporary() ) kircEngine()->writeMessage( TQString::fromLatin1("WHOWAS %1").tqarg(m_nickName) ); removeProperty( m_protocol->propUserInfo ); removeProperty( m_protocol->propServer ); removeProperty( m_protocol->propChannels ); } void IRCUserContact::setAway(bool isAway) { //kdDebug(14120) << k_funcinfo << isAway << endl; mInfo.away = isAway; updateStatus(); } void IRCUserContact::incomingUserIsAway(const TQString &reason) { if( manager( Kopete::Contact::CannotCreate ) ) { Kopete::Message msg( (Kopete::Contact*)ircAccount()->myServer(), mMyself, i18n("%1 is away (%2)").tqarg( m_nickName ).tqarg( reason ), Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW ); manager(Kopete::Contact::CanCreate)->appendMessage(msg); } } void IRCUserContact::userOnline() { mInfo.online = true; updateStatus(); if (this != ircAccount()->mySelf() && !metaContact()->isTemporary() && ircAccount()->isConnected()) { mOnlineTimer->start( 45000, true ); ircAccount()->setCurrentCommandSource(0); kircEngine()->whois(m_nickName); } removeProperty( m_protocol->propLastSeen ); } void IRCUserContact::slotUserInfo() { if (isChatting()) { ircAccount()->setCurrentCommandSource(manager()); kircEngine()->whois(m_nickName); } } const TQString IRCUserContact::caption() const { return i18n("%1 @ %2").tqarg(m_nickName).tqarg(kircEngine()->currentHost()); } void IRCUserContact::slotOp() { contactMode( TQString::fromLatin1("+o") ); } void IRCUserContact::slotDeop() { contactMode( TQString::fromLatin1("-o") ); } void IRCUserContact::slotVoice() { contactMode( TQString::fromLatin1("+v") ); } void IRCUserContact::slotDevoice() { contactMode( TQString::fromLatin1("-v") ); } void IRCUserContact::slotBanHost() { // MODE #foofoofoo +b *!*@host.domain.net if (mInfo.hostName.isEmpty()) { if (kircEngine()->isConnected()) { kircEngine()->whois(m_nickName); TQTimer::singleShot( 750, this, TQT_SLOT( slotBanHostOnce() ) ); } } else { slotBanHostOnce(); } } void IRCUserContact::slotBanHostOnce() { if (mInfo.hostName.isEmpty()) return; Kopete::ContactPtrList members = mActiveManager->members(); TQString channelName = static_cast(members.first())->nickName(); kircEngine()->mode(channelName, TQString::fromLatin1("+b *!*@%1").tqarg(mInfo.hostName)); } void IRCUserContact::slotBanUserHost() { // MODE #foofoofoo +b *!*user@host.domain.net if (mInfo.hostName.isEmpty()) { if (kircEngine()->isConnected()) { kircEngine()->whois(m_nickName); TQTimer::singleShot( 750, this, TQT_SLOT( slotBanUserHostOnce() ) ); } } else { slotBanUserHostOnce(); } } void IRCUserContact::slotBanUserHostOnce() { if (mInfo.hostName.isEmpty()) return; Kopete::ContactPtrList members = mActiveManager->members(); TQString channelName = static_cast(members.first())->nickName(); kircEngine()->mode(channelName, TQString::fromLatin1("+b *!*%1@%2").tqarg(mInfo.userName, mInfo.hostName)); } void IRCUserContact::slotBanDomain() { // MODE #foofoofoo +b *!*@*.domain.net if (mInfo.hostName.isEmpty()) { if (kircEngine()->isConnected()) { kircEngine()->whois(m_nickName); TQTimer::singleShot( 750, this, TQT_SLOT( slotBanDomainOnce() ) ); } } else { slotBanDomainOnce(); } } void IRCUserContact::slotBanDomainOnce() { if (mInfo.hostName.isEmpty()) return; Kopete::ContactPtrList members = mActiveManager->members(); TQString channelName = static_cast(members.first())->nickName(); TQString domain = mInfo.hostName.section('.', 1); kircEngine()->mode(channelName, TQString::fromLatin1("+b *!*@*.%1").tqarg(domain)); } void IRCUserContact::slotBanUserDomain() { // MODE #foofoofoo +b *!*user@*.domain.net if (mInfo.hostName.isEmpty()) { if (kircEngine()->isConnected()) { kircEngine()->whois(m_nickName); TQTimer::singleShot( 750, this, TQT_SLOT( slotBanUserDomainOnce() ) ); } } else { slotBanUserDomainOnce(); } } void IRCUserContact::slotBanUserDomainOnce() { if (mInfo.hostName.isEmpty()) return; Kopete::ContactPtrList members = mActiveManager->members(); TQString channelName = static_cast(members.first())->nickName(); TQString domain = mInfo.hostName.section('.', 1); kircEngine()->mode(channelName, TQString::fromLatin1("+b *!*%1@*.%2").tqarg(mInfo.userName, domain)); } void IRCUserContact::slotKick() { Kopete::ContactPtrList members = mActiveManager->members(); TQString channelName = static_cast(members.first())->nickName(); kircEngine()->kick(m_nickName, channelName, TQString()); } void IRCUserContact::contactMode(const TQString &mode) { Kopete::ContactPtrList members = mActiveManager->members(); TQString channelName = static_cast(members.first())->nickName(); kircEngine()->mode(channelName, TQString::fromLatin1("%1 %2").tqarg(mode).tqarg(m_nickName)); } void IRCUserContact::slotCtcpPing() { kircEngine()->CtcpRequest_ping(m_nickName); } void IRCUserContact::slotCtcpVersion() { kircEngine()->CtcpRequest_version(m_nickName); } void IRCUserContact::newWhoIsUser(const TQString &username, const TQString &hostname, const TQString &realname) { mInfo.channels.clear(); mInfo.userName = username; mInfo.hostName = hostname; mInfo.realName = realname; if( onlineStatus().status() == Kopete::OnlineStatus::Offline ) { setProperty( m_protocol->propUserInfo, TQString::fromLatin1("%1@%2") .tqarg(mInfo.userName).tqarg(mInfo.hostName) ); setProperty( m_protocol->propServer, mInfo.serverName ); setProperty( m_protocol->propFullName, mInfo.realName ); } } void IRCUserContact::newWhoIsServer(const TQString &servername, const TQString &serverinfo) { mInfo.serverName = servername; if( metaContact()->isTemporary() || onlineStatus().status() == Kopete::OnlineStatus::Online || onlineStatus().status() == Kopete::OnlineStatus::Away ) mInfo.serverInfo = serverinfo; else { //kdDebug(14120)<< "Setting last online: " << serverinfo << endl; // Try to convert first, since server can return depending if // user is online or not: // // 312 mynick othernick localhost.localdomain :FooNet Server // 312 mynick othernick localhost.localdomain :Thu Jun 16 21:00:36 2005 TQDateTime lastSeen = TQDateTime::fromString( serverinfo ); if( lastSeen.isValid() ) setProperty( m_protocol->propLastSeen, lastSeen ); } } void IRCUserContact::newWhoIsIdle(unsigned long idle) { mInfo.idle = idle; } void IRCUserContact::newWhoIsOperator() { mInfo.isOperator = true; } void IRCUserContact::newWhoIsIdentified() { mInfo.isIdentified = true; setProperty( m_protocol->propIsIdentified, i18n("True") ); } void IRCUserContact::newWhoIsChannels(const TQString &channel) { mInfo.channels.append( channel ); } void IRCUserContact::whoIsComplete() { Kopete::ChatSession *commandSource = ircAccount()->currentCommandSource(); updateInfo(); if( isChatting() && commandSource && commandSource == manager(Kopete::Contact::CannotCreate) ) { //User info TQString msg = i18n("%1 is (%2@%3): %4
") .tqarg(m_nickName) .tqarg(mInfo.userName) .tqarg(mInfo.hostName) .tqarg(mInfo.realName); if( mInfo.isIdentified ) msg += i18n("%1 is authenticated with NICKSERV
").tqarg(m_nickName); if( mInfo.isOperator ) msg += i18n("%1 is an IRC operator
").tqarg(m_nickName); //Channels msg += i18n("on channels %1
").tqarg(mInfo.channels.join(" ; ")); //Server msg += i18n("on IRC via server %1 ( %2 )
").tqarg(mInfo.serverName).tqarg(mInfo.serverInfo); //Idle TQString idleTime = formattedIdleTime(); msg += i18n("idle: %2
").tqarg( idleTime.isEmpty() ? TQString::number(0) : idleTime ); //End ircAccount()->appendMessage(msg, IRCAccount::InfoReply ); ircAccount()->setCurrentCommandSource(0); } } void IRCUserContact::whoWasComplete() { if( isChatting() && ircAccount()->currentCommandSource() == manager() ) { //User info TQString msg = i18n("%1 was (%2@%3): %4\n") .tqarg(m_nickName) .tqarg(mInfo.userName) .tqarg(mInfo.hostName) .tqarg(mInfo.realName); msg += i18n("Last Online: %1\n").tqarg( KGlobal::locale()->formatDateTime( property( m_protocol->propLastSeen ).value().toDateTime() ) ); ircAccount()->appendMessage(msg, IRCAccount::InfoReply ); ircAccount()->setCurrentCommandSource(0); } } TQString IRCUserContact::formattedName() const { return mInfo.realName; } void IRCUserContact::updateInfo() { setProperty( m_protocol->propUserInfo, TQString::fromLatin1("%1@%2") .tqarg(mInfo.userName).tqarg(mInfo.hostName) ); setProperty( m_protocol->propServer, mInfo.serverName ); setProperty( m_protocol->propChannels, mInfo.channels.join(" ") ); setProperty( m_protocol->propHops, TQString::number(mInfo.hops) ); setProperty( m_protocol->propFullName, mInfo.realName ); setIdleTime( mInfo.idle ); mInfo.lastUpdate = TQTime::currentTime(); } void IRCUserContact::newWhoReply( const TQString &channel, const TQString &user, const TQString &host, const TQString &server, bool away, const TQString &flags, uint hops, const TQString &realName ) { if( !mInfo.channels.contains( channel ) ) mInfo.channels.append( channel ); mInfo.userName = user; mInfo.hostName = host; mInfo.serverName = server; mInfo.flags = flags; mInfo.hops = hops; mInfo.realName = realName; setAway(away); updateInfo(); if( isChatting() && ircAccount()->currentCommandSource() == manager() ) { ircAccount()->setCurrentCommandSource(0); } } TQPtrList *IRCUserContact::customContextMenuActions( Kopete::ChatSession *manager ) { if( manager ) { TQPtrList *mCustomActions = new TQPtrList (); mActiveManager = manager; Kopete::ContactPtrList members = mActiveManager->members(); IRCChannelContact *isChannel = dynamic_cast( members.first() ); if( !actionCtcpMenu ) { actionCtcpMenu = new KActionMenu(i18n("C&TCP"), 0, this ); actionCtcpMenu->insert( new KAction(i18n("&Version"), 0, this, TQT_SLOT(slotCtcpVersion()), actionCtcpMenu) ); actionCtcpMenu->insert( new KAction(i18n("&Ping"), 0, this, TQT_SLOT(slotCtcpPing()), actionCtcpMenu) ); actionModeMenu = new KActionMenu(i18n("&Modes"), 0, this, "actionModeMenu"); actionModeMenu->insert( new KAction(i18n("&Op"), 0, this, TQT_SLOT(slotOp()), actionModeMenu, "actionOp") ); actionModeMenu->insert( new KAction(i18n("&Deop"), 0, this, TQT_SLOT(slotDeop()), actionModeMenu, "actionDeop") ); actionModeMenu->insert( new KAction(i18n("&Voice"), 0, this, TQT_SLOT(slotVoice()), actionModeMenu, "actionVoice") ); actionModeMenu->insert( new KAction(i18n("Devoice"), 0, this, TQT_SLOT(slotDevoice()), actionModeMenu, "actionDevoice") ); actionModeMenu->setEnabled( false ); actionKick = new KAction(i18n("&Kick"), 0, this, TQT_SLOT(slotKick()), this); actionKick->setEnabled( false ); actionBanMenu = new KActionMenu(i18n("&Ban"), 0, this, "actionBanMenu"); actionBanMenu->insert( new KAction(i18n("Host (*!*@host.domain.net)"), 0, this, TQT_SLOT(slotBanHost()), actionBanMenu ) ); actionBanMenu->insert( new KAction(i18n("Domain (*!*@*.domain.net)"), 0, this, TQT_SLOT(slotBanDomain()), actionBanMenu ) ); actionBanMenu->insert( new KAction(i18n("User@Host (*!*user@host.domain.net)"), 0, this, TQT_SLOT(slotBanUserHost()), actionBanMenu ) ); actionBanMenu->insert( new KAction(i18n("User@Domain (*!*user@*.domain.net)"), 0, this, TQT_SLOT(slotBanUserDomain()), actionBanMenu ) ); actionBanMenu->setEnabled( false ); codecAction = new KCodecAction( i18n("&Encoding"), 0, this, "selectcharset" ); connect( codecAction, TQT_SIGNAL( activated( const TQTextCodec * ) ), this, TQT_SLOT( setCodec( const TQTextCodec *) ) ); codecAction->setCodec( codec() ); } mCustomActions->append( actionCtcpMenu ); mCustomActions->append( actionModeMenu ); mCustomActions->append( actionKick ); mCustomActions->append( actionBanMenu ); mCustomActions->append( codecAction ); if( isChannel ) { bool isOperator = ( manager->contactOnlineStatus( account()->myself() ).internalStatus() & IRCProtocol::Operator ); actionModeMenu->setEnabled(isOperator); actionBanMenu->setEnabled(isOperator); actionKick->setEnabled(isOperator); } return mCustomActions; } mActiveManager = 0L; return 0L; } void IRCUserContact::slotIncomingModeChange( const TQString &channel, const TQString &, const TQString &mode_ ) { kdDebug(14120) << k_funcinfo << "channel: " << channel << " mode: " << mode_ << endl; IRCChannelContact *chan = ircAccount()->contactManager()->findChannel( channel ); if( !chan->locateUser( m_nickName ) ) return; // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +o kakkonen // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +o-o foobar001 kakkonen // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +oo kakkonen foobar001 // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +o-ov foobar001 kakkonen foobar001 // // irssi manual example: /MODE #channel +nto-o+v nick1 nick2 nick3 TQStringList users = TQStringList::split(' ', mode_); users.pop_front(); const TQString mode = mode_.section(' ', 0, 0); bitAdjustment adjMode = RemoveBits; TQStringList::iterator user = users.begin(); //kdDebug(14120) << "me: " << m_nickName << " users: " << users << " mode: " << mode << endl; for( uint i=0; i < mode.length(); i++ ) { switch( mode[i] ) { case '+': adjMode = AddBits; break; case '-': adjMode = RemoveBits; break; default: //kdDebug(14120) << "got " << mode[i] << ", user: " << *user << endl; if (mode[i] == 'o') { if (user == users.end()) return; if ((*user).lower() == m_nickName.lower()) adjustInternalOnlineStatusBits(chan, IRCProtocol::Operator, adjMode); ++user; } else if (mode[i] == 'v') { if (user == users.end()) return; if ((*user).lower() == m_nickName.lower()) adjustInternalOnlineStatusBits(chan, IRCProtocol::Voiced, adjMode); ++user; } break; } } } /* Remove or add the given bits for the given channel from the current internal online status. * * You could fiddle with bits like IRCProtocol::Operator, IRCProtocol::Voiced, etc. */ void IRCUserContact::adjustInternalOnlineStatusBits(IRCChannelContact *channel, unsigned statusAdjustment, bitAdjustment adj) { Kopete::OnlineStatus currentStatus = channel->manager()->contactOnlineStatus(this); Kopete::OnlineStatus newStatus; if (adj == RemoveBits) { // If the bit is not set in the current internal status, stop here. if ((currentStatus.internalStatus() & ~statusAdjustment) == currentStatus.internalStatus()) return; newStatus = m_protocol->statusLookup( (IRCProtocol::IRCStatus)(currentStatus.internalStatus() & ~statusAdjustment) ); } else if (adj == AddBits) { // If the bit is already set in the current internal status, stop here. if ((currentStatus.internalStatus() | statusAdjustment) == currentStatus.internalStatus()) return; newStatus = m_protocol->statusLookup( (IRCProtocol::IRCStatus)(currentStatus.internalStatus() | statusAdjustment) ); } channel->manager()->setContactOnlineStatus(this, newStatus); } void IRCUserContact::privateMessage(IRCContact *from, IRCContact *to, const TQString &message) { if (to == this) { if(to==account()->myself()) { Kopete::Message msg(from, from->manager(Kopete::Contact::CanCreate)->members(), message, Kopete::Message::Inbound, Kopete::Message::RichText, CHAT_VIEW); from->appendMessage(msg); } else { kdDebug(14120) << "IRC Server error: Received a private message for " << to->nickName() << ":" << message << endl; // emit/call something on main ircservercontact } } } void IRCUserContact::newAction(const TQString &to, const TQString &action) { IRCAccount *account = ircAccount(); IRCContact *t = account->contactManager()->findUser(to); Kopete::Message::MessageDirection dir = (this == account->mySelf()) ? Kopete::Message::Outbound : Kopete::Message::Inbound; Kopete::Message msg(this, t, action, dir, Kopete::Message::RichText, CHAT_VIEW, Kopete::Message::TypeAction); //Either this is from me to a guy, or from a guy to me. Either way its a PM if (dir == Kopete::Message::Outbound) t->appendMessage(msg); else appendMessage(msg); } #include "ircusercontact.moc"