You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/irc/ircusercontact.cpp

735 lines
21 KiB

/*
ircusercontact.cpp - IRC User Contact
Copyright (c) 2002 by Nick Betcher <nbetcher@kde.org>
Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org>
*************************************************************************
* *
* 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 <kaction.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <klocale.h>
#include <tqtimer.h>
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();
updatetqStatus();
}
void IRCUserContact::updatetqStatus()
{
//kdDebug(14120) << k_funcinfo << endl;
Kopete::OnlineStatus newtqStatus;
switch (kircEngine()->status())
{
case KIRC::Engine::Idle:
newtqStatus = m_protocol->m_UserStatusOffline;
break;
case KIRC::Engine::Connecting:
case KIRC::Engine::Authentifying:
if (this == ircAccount()->mySelf())
newtqStatus = m_protocol->m_UserStatusConnecting;
else
newtqStatus = m_protocol->m_UserStatusOffline;
break;
case KIRC::Engine::Connected:
case KIRC::Engine::Closing:
if (mInfo.away)
newtqStatus = m_protocol->m_UserStatusAway;
else if (mInfo.online)
newtqStatus = m_protocol->m_UserStatusOnline;
break;
default:
newtqStatus = 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<IRCChannelContact*> channels = ircAccount()->contactManager()->findChannelsByMember(this);
for( TQValueList<IRCChannelContact*>::iterator it = channels.begin(); it != channels.end(); ++it )
{
IRCChannelContact *channel = *it;
Kopete::OnlineStatus currenttqStatus = channel->manager()->contactOnlineStatus(this);
//kdDebug(14120) << k_funcinfo << "iterating channel " << channel->nickName() << " internal status: " << currenttqStatus.internalStatus() << endl;
if( currenttqStatus.internalStatus() >= IRCProtocol::Online )
{
onlineStatusChanged = true;
if( !(currenttqStatus.internalStatus() & IRCProtocol::Away) && newtqStatus == m_protocol->m_UserStatusAway )
{
setOnlineStatus( newtqStatus );
//kdDebug(14120) << k_funcinfo << "was NOT away, but is now, channel " << channel->nickName() << endl;
adjustInternalOnlineStatusBits(channel, IRCProtocol::Away, AddBits);
}
else if( (currenttqStatus.internalStatus() & IRCProtocol::Away) && newtqStatus == m_protocol->m_UserStatusOnline )
{
setOnlineStatus( newtqStatus );
//kdDebug(14120) << k_funcinfo << "was away, but not anymore, channel " << channel->nickName() << endl;
adjustInternalOnlineStatusBits(channel, IRCProtocol::Away, RemoveBits);
}
else if( newtqStatus.internalStatus() < IRCProtocol::Away )
{
//kdDebug(14120) << k_funcinfo << "offline or connecting?" << endl;
channel->manager()->setContactOnlineStatus( this, newtqStatus );
}
}
}
}
if (!onlineStatusChanged) {
//kdDebug(14120) << k_funcinfo << "setting status at last" << endl;
setOnlineStatus( newtqStatus );
}
}
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;
updatetqStatus();
if( !metaContact()->isTemporary() )
kircEngine()->writeMessage( TQString::tqfromLatin1("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;
updatetqStatus();
}
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;
updatetqStatus();
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::tqfromLatin1("+o") );
}
void IRCUserContact::slotDeop()
{
contactMode( TQString::tqfromLatin1("-o") );
}
void IRCUserContact::slotVoice()
{
contactMode( TQString::tqfromLatin1("+v") );
}
void IRCUserContact::slotDevoice()
{
contactMode( TQString::tqfromLatin1("-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<IRCContact*>(members.first())->nickName();
kircEngine()->mode(channelName, TQString::tqfromLatin1("+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<IRCContact*>(members.first())->nickName();
kircEngine()->mode(channelName, TQString::tqfromLatin1("+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<IRCContact*>(members.first())->nickName();
TQString domain = mInfo.hostName.section('.', 1);
kircEngine()->mode(channelName, TQString::tqfromLatin1("+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<IRCContact*>(members.first())->nickName();
TQString domain = mInfo.hostName.section('.', 1);
kircEngine()->mode(channelName, TQString::tqfromLatin1("+b *!*%1@*.%2").tqarg(mInfo.userName, domain));
}
void IRCUserContact::slotKick()
{
Kopete::ContactPtrList members = mActiveManager->members();
TQString channelName = static_cast<IRCContact*>(members.first())->nickName();
kircEngine()->kick(m_nickName, channelName, TQString());
}
void IRCUserContact::contactMode(const TQString &mode)
{
Kopete::ContactPtrList members = mActiveManager->members();
TQString channelName = static_cast<IRCContact*>(members.first())->nickName();
kircEngine()->mode(channelName, TQString::tqfromLatin1("%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( onlinetqStatus().status() == Kopete::OnlineStatus::Offline )
{
setProperty( m_protocol->propUserInfo, TQString::tqfromLatin1("%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() || onlinetqStatus().status() == Kopete::OnlineStatus::Online
|| onlinetqStatus().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<br/>")
.tqarg(m_nickName)
.tqarg(mInfo.userName)
.tqarg(mInfo.hostName)
.tqarg(mInfo.realName);
if( mInfo.isIdentified )
msg += i18n("%1 is authenticated with NICKSERV<br/>").tqarg(m_nickName);
if( mInfo.isOperator )
msg += i18n("%1 is an IRC operator<br/>").tqarg(m_nickName);
//Channels
msg += i18n("on channels %1<br/>").tqarg(mInfo.channels.join(" ; "));
//Server
msg += i18n("on IRC via server %1 ( %2 )<br/>").tqarg(mInfo.serverName).tqarg(mInfo.serverInfo);
//Idle
TQString idleTime = formattedIdleTime();
msg += i18n("idle: %2<br/>").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::tqfromLatin1("%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.tqcontains( 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<KAction> *IRCUserContact::customContextMenuActions( Kopete::ChatSession *manager )
{
if( manager )
{
TQPtrList<KAction> *mCustomActions = new TQPtrList<KAction> ();
mActiveManager = manager;
Kopete::ContactPtrList members = mActiveManager->members();
IRCChannelContact *isChannel = dynamic_cast<IRCChannelContact*>( 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 currenttqStatus = channel->manager()->contactOnlineStatus(this);
Kopete::OnlineStatus newtqStatus;
if (adj == RemoveBits) {
// If the bit is not set in the current internal status, stop here.
if ((currenttqStatus.internalStatus() & ~statusAdjustment) == currenttqStatus.internalStatus())
return;
newtqStatus = m_protocol->statusLookup(
(IRCProtocol::IRCtqStatus)(currenttqStatus.internalStatus() & ~statusAdjustment)
);
} else if (adj == AddBits) {
// If the bit is already set in the current internal status, stop here.
if ((currenttqStatus.internalStatus() | statusAdjustment) == currenttqStatus.internalStatus())
return;
newtqStatus = m_protocol->statusLookup(
(IRCProtocol::IRCtqStatus)(currenttqStatus.internalStatus() | statusAdjustment)
);
}
channel->manager()->setContactOnlineStatus(this, newtqStatus);
}
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"