/* kopetecommandhandler.cpp - Command Handler Copyright (c) 2003 by Jason Keirstead Kopete (c) 2002-2003 by the Kopete developers ************************************************************************* * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * ************************************************************************* */ #include #include #include #include #include #include #include #include #include #include "kopetechatsessionmanager.h" #include "kopeteprotocol.h" #include "kopetepluginmanager.h" #include "kopeteview.h" #include "kopeteaccountmanager.h" #include "kopeteaccount.h" #include "kopetecommandhandler.h" #include "kopetecontact.h" #include "kopetecommand.h" using Kopete::CommandList; typedef TQMap PluginCommandMap; typedef TQMap CommandMap; typedef TQPair ManagerPair; class KopeteCommandGUIClient : public TQObject, public KXMLGUIClient { public: KopeteCommandGUIClient( Kopete::ChatSession *manager ) : TQObject(manager), KXMLGUIClient(manager) { setXMLFile( TQString::fromLatin1("kopetecommandui.rc") ); TQDomDocument doc = domDocument(); TQDomNode menu = doc.documentElement().firstChild().firstChild().firstChild(); CommandList mCommands = Kopete::CommandHandler::commandHandler()->commands( manager->protocol() ); for( TQDictIterator it( mCommands ); it.current(); ++it ) { TDEAction *a = static_cast( it.current() ); actionCollection()->insert( a ); TQDomElement newNode = doc.createElement( TQString::fromLatin1("Action") ); newNode.setAttribute( TQString::fromLatin1("name"), TQString::fromLatin1( a->name() ) ); bool added = false; for( TQDomElement n = menu.firstChild().toElement(); !n.isNull(); n = n.nextSibling().toElement() ) { if( TQString::fromLatin1(a->name()) < n.attribute(TQString::fromLatin1("name"))) { menu.insertBefore( newNode, n ); added = true; break; } } if( !added ) { menu.appendChild( newNode ); } } setDOMDocument( doc ); } }; struct CommandHandlerPrivate { PluginCommandMap pluginCommands; Kopete::CommandHandler *s_handler; TQMap processMap; bool inCommand; TQPtrList m_commands; }; CommandHandlerPrivate *Kopete::CommandHandler::p = 0L; Kopete::CommandHandler::CommandHandler() : TQObject( tqApp ) { p->s_handler = this; p->inCommand = false; CommandList mCommands(31, false); mCommands.setAutoDelete( true ); p->pluginCommands.insert( this, mCommands ); registerCommand( this, TQString::fromLatin1("help"), TQT_SLOT( slotHelpCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /help [] - Used to list available commands, or show help for a specified command." ), 0, 1 ); registerCommand( this, TQString::fromLatin1("close"), TQT_SLOT( slotCloseCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /close - Closes the current view." ) ); // FIXME: What's the difference with /close? The help doesn't explain it - Martijn registerCommand( this, TQString::fromLatin1("part"), TQT_SLOT( slotPartCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /part - Closes the current view." ) ); registerCommand( this, TQString::fromLatin1("clear"), TQT_SLOT( slotClearCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /clear - Clears the active view's chat buffer." ) ); //registerCommand( this, TQString::fromLatin1("me"), TQT_SLOT( slotMeCommand( const TQString &, Kopete::ChatSession * ) ), // i18n( "USAGE: /me - Formats message as in ' went to the store'." ) ); registerCommand( this, TQString::fromLatin1("away"), TQT_SLOT( slotAwayCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /away [] - Marks you as away/back for the current account only." ) ); registerCommand( this, TQString::fromLatin1("awayall"), TQT_SLOT( slotAwayAllCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /awayall [] - Marks you as away/back for all accounts." ) ); registerCommand( this, TQString::fromLatin1("say"), TQT_SLOT( slotSayCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /say - Say text in this chat. This is the same as just typing a message, but is very " "useful for scripts." ), 1 ); registerCommand( this, TQString::fromLatin1("exec"), TQT_SLOT( slotExecCommand( const TQString &, Kopete::ChatSession * ) ), i18n( "USAGE: /exec [-o] - Executes the specified command and displays the output in the chat buffer. " "If -o is specified, the output is sent to all members of the chat."), 1 ); connect( Kopete::PluginManager::self(), TQT_SIGNAL( pluginLoaded( Kopete::Plugin*) ), this, TQT_SLOT(slotPluginLoaded(Kopete::Plugin*) ) ); connect( Kopete::ChatSessionManager::self(), TQT_SIGNAL( viewCreated( KopeteView * ) ), this, TQT_SLOT( slotViewCreated( KopeteView* ) ) ); } Kopete::CommandHandler::~CommandHandler() { delete p; } Kopete::CommandHandler *Kopete::CommandHandler::commandHandler() { if( !p ) { p = new CommandHandlerPrivate; p->s_handler = new Kopete::CommandHandler(); } return p->s_handler; } void Kopete::CommandHandler::registerCommand( TQObject *parent, const TQString &command, const char* handlerSlot, const TQString &help, uint minArgs, int maxArgs, const TDEShortcut &cut, const TQString &pix ) { TQString lowerCommand = command.lower(); Kopete::Command *mCommand = new Kopete::Command( parent, lowerCommand, handlerSlot, help, Normal, TQString(), minArgs, maxArgs, cut, pix); p->pluginCommands[ parent ].insert( lowerCommand, mCommand ); } void Kopete::CommandHandler::unregisterCommand( TQObject *parent, const TQString &command ) { if( p->pluginCommands[ parent ].find(command) ) p->pluginCommands[ parent ].remove( command ); } void Kopete::CommandHandler::registerAlias( TQObject *parent, const TQString &alias, const TQString &formatString, const TQString &help, CommandType type, uint minArgs, int maxArgs, const TDEShortcut &cut, const TQString &pix ) { TQString lowerAlias = alias.lower(); Kopete::Command *mCommand = new Kopete::Command( parent, lowerAlias, 0L, help, type, formatString, minArgs, maxArgs, cut, pix ); p->pluginCommands[ parent ].insert( lowerAlias, mCommand ); } void Kopete::CommandHandler::unregisterAlias( TQObject *parent, const TQString &alias ) { if( p->pluginCommands[ parent ].find(alias) ) p->pluginCommands[ parent ].remove( alias ); } bool Kopete::CommandHandler::processMessage( const TQString &msg, Kopete::ChatSession *manager ) { if( p->inCommand ) return false; TQRegExp splitRx( TQString::fromLatin1("^/([\\S]+)(.*)") ); TQString command; TQString args; if(splitRx.search(msg) != -1) { command = splitRx.cap(1); args = splitRx.cap(2).mid(1); } else return false; CommandList mCommands = commands( manager->protocol() ); Kopete::Command *c = mCommands[ command ]; if(c) { kdDebug(14010) << k_funcinfo << "Handled Command" << endl; if( c->type() != SystemAlias && c->type() != UserAlias ) p->inCommand = true; c->processCommand( args, manager ); p->inCommand = false; return true; } return false; } bool Kopete::CommandHandler::processMessage( Kopete::Message &msg, Kopete::ChatSession *manager ) { TQString messageBody = msg.plainBody(); return processMessage( messageBody, manager ); } void Kopete::CommandHandler::slotHelpCommand( const TQString &args, Kopete::ChatSession *manager ) { TQString output; if( args.isEmpty() ) { int commandCount = 0; output = i18n( "Available Commands:\n" ); CommandList mCommands = commands( manager->myself()->protocol() ); TQDictIterator it( mCommands ); for( ; it.current(); ++it ) { output.append( it.current()->command().upper() + '\t' ); if( commandCount++ == 5 ) { commandCount = 0; output.append( '\n' ); } } output.append( i18n( "\nType /help for more information." ) ); } else { TQString command = parseArguments( args ).front().lower(); Kopete::Command *c = commands( manager->myself()->protocol() )[ command ]; if( c && !c->help().isNull() ) output = c->help(); else output = i18n("There is no help available for '%1'.").arg( command ); } Kopete::Message msg(manager->myself(), manager->members(), output, Kopete::Message::Internal, Kopete::Message::PlainText); manager->appendMessage(msg); } void Kopete::CommandHandler::slotSayCommand( const TQString &args, Kopete::ChatSession *manager ) { //Just say whatever is passed Kopete::Message msg(manager->myself(), manager->members(), args, Kopete::Message::Outbound, Kopete::Message::PlainText); manager->sendMessage(msg); } void Kopete::CommandHandler::slotExecCommand( const TQString &args, Kopete::ChatSession *manager ) { if( !args.isEmpty() ) { TDEProcess *proc = 0L; if ( kapp->authorize( TQString::fromLatin1( "shell_access" ) ) ) proc = new TDEProcess(manager); if( proc ) { *proc << TQString::fromLatin1("sh") << TQString::fromLatin1("-c"); TQStringList argsList = parseArguments( args ); if( argsList.front() == TQString::fromLatin1("-o") ) { p->processMap.insert( proc, ManagerPair(manager, Kopete::Message::Outbound) ); *proc << args.section(TQRegExp(TQString::fromLatin1("\\s+")), 1); } else { p->processMap.insert( proc, ManagerPair(manager, Kopete::Message::Internal) ); *proc << args; } connect(proc, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), this, TQT_SLOT(slotExecReturnedData(TDEProcess *, char *, int))); connect(proc, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)), this, TQT_SLOT(slotExecReturnedData(TDEProcess *, char *, int))); proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput ); } else { Kopete::Message msg(manager->myself(), manager->members(), i18n( "ERROR: Shell access has been restricted on your system. The /exec command will not function." ), Kopete::Message::Internal, Kopete::Message::PlainText ); manager->sendMessage( msg ); } } } void Kopete::CommandHandler::slotClearCommand( const TQString &, Kopete::ChatSession *manager ) { if( manager->view() ) manager->view()->clear(); } void Kopete::CommandHandler::slotPartCommand( const TQString &, Kopete::ChatSession *manager ) { if( manager->view() ) manager->view()->closeView(); } void Kopete::CommandHandler::slotAwayCommand( const TQString &args, Kopete::ChatSession *manager ) { bool goAway = !manager->account()->isAway(); if( args.isEmpty() ) manager->account()->setAway( goAway ); else manager->account()->setAway( goAway, args ); } void Kopete::CommandHandler::slotAwayAllCommand( const TQString &args, Kopete::ChatSession *manager ) { if( manager->account()->isAway() ) Kopete::AccountManager::self()->setAvailableAll(); else { if( args.isEmpty() ) Kopete::AccountManager::self()->setAwayAll(); else Kopete::AccountManager::self()->setAwayAll( args ); } } void Kopete::CommandHandler::slotCloseCommand( const TQString &, Kopete::ChatSession *manager ) { if( manager->view() ) manager->view()->closeView(); } void Kopete::CommandHandler::slotExecReturnedData(TDEProcess *proc, char *buff, int bufflen ) { kdDebug(14010) << k_funcinfo << endl; TQString buffer = TQString::fromLocal8Bit( buff, bufflen ); ManagerPair mgrPair = p->processMap[ proc ]; Kopete::Message msg( mgrPair.first->myself(), mgrPair.first->members(), buffer, mgrPair.second, Kopete::Message::PlainText ); if( mgrPair.second == Kopete::Message::Outbound ) mgrPair.first->sendMessage( msg ); else mgrPair.first->appendMessage( msg ); } void Kopete::CommandHandler::slotExecFinished(TDEProcess *proc) { delete proc; p->processMap.remove( proc ); } TQStringList Kopete::CommandHandler::parseArguments( const TQString &args ) { TQStringList arguments; TQRegExp quotedArgs( TQString::fromLatin1("\"(.*)\"") ); quotedArgs.setMinimal( true ); if ( quotedArgs.search( args ) != -1 ) { for( int i = 0; i< quotedArgs.numCaptures(); i++ ) arguments.append( quotedArgs.cap(i) ); } TQStringList otherArgs = TQStringList::split( TQRegExp(TQString::fromLatin1("\\s+")), args.section( quotedArgs, 0 ) ); for( TQStringList::Iterator it = otherArgs.begin(); it != otherArgs.end(); ++it ) arguments.append( *it ); return arguments; } bool Kopete::CommandHandler::commandHandled( const TQString &command ) { for( PluginCommandMap::Iterator it = p->pluginCommands.begin(); it != p->pluginCommands.end(); ++it ) { if( it.data()[ command ] ) return true; } return false; } bool Kopete::CommandHandler::commandHandledByProtocol( const TQString &command, Kopete::Protocol *protocol ) { // Make sure the protocol is not NULL if(!protocol) return false; // Fetch the commands for the protocol CommandList commandList = commands( protocol ); TQDictIterator it ( commandList ); // Loop through commands and check if they match the supplied command for( ; it.current(); ++it ) { if( it.current()->command().lower() == command ) return true; } // No commands found return false; } CommandList Kopete::CommandHandler::commands( Kopete::Protocol *protocol ) { CommandList commandList(63, false); //Add plugin user aliases first addCommands( p->pluginCommands[protocol], commandList, UserAlias ); //Add plugin system aliases next addCommands( p->pluginCommands[protocol], commandList, SystemAlias ); //Add the commands for this protocol next addCommands( p->pluginCommands[protocol], commandList ); //Add plugin commands for( PluginCommandMap::Iterator it = p->pluginCommands.begin(); it != p->pluginCommands.end(); ++it ) { if( !it.key()->inherits("Kopete::Protocol") && it.key()->inherits("Kopete::Plugin") ) addCommands( it.data(), commandList ); } //Add global user aliases first addCommands( p->pluginCommands[this], commandList, UserAlias ); //Add global system aliases next addCommands( p->pluginCommands[this], commandList, SystemAlias ); //Add the internal commands *last* addCommands( p->pluginCommands[this], commandList ); return commandList; } void Kopete::CommandHandler::addCommands( CommandList &from, CommandList &to, CommandType type ) { TQDictIterator itDict( from ); for( ; itDict.current(); ++itDict ) { if( !to[ itDict.currentKey() ] && ( type == Undefined || itDict.current()->type() == type ) ) to.insert( itDict.currentKey(), itDict.current() ); } } void Kopete::CommandHandler::slotViewCreated( KopeteView *view ) { new KopeteCommandGUIClient( view->msgManager() ); } void Kopete::CommandHandler::slotPluginLoaded( Kopete::Plugin *plugin ) { connect( plugin, TQT_SIGNAL( destroyed( TQObject * ) ), this, TQT_SLOT( slotPluginDestroyed( TQObject * ) ) ); if( !p->pluginCommands.contains( plugin ) ) { //Create a TQDict optomized for a larger # of commands, and case insensitive CommandList mCommands(31, false); mCommands.setAutoDelete( true ); p->pluginCommands.insert( plugin, mCommands ); } } void Kopete::CommandHandler::slotPluginDestroyed( TQObject *plugin ) { p->pluginCommands.remove( static_cast(plugin) ); } #include "kopetecommandhandler.moc" // vim: set noet ts=4 sts=4 sw=4: