Move alias handling into the client
[quassel.git] / src / client / clientuserinputhandler.cpp
index 296fc1c..bb6e3ce 100644 (file)
 #include "ircuser.h"
 #include "network.h"
 
-ClientUserInputHandler::ClientUserInputHandler(QObject *parent) : QObject(parent) {
+ClientUserInputHandler::ClientUserInputHandler(QObject *parent)
+: QObject(parent),
+  _initialized(false)
+{
   TabCompletionSettings s;
   s.notify("CompletionSuffix", this, SLOT(completionSuffixChanged(QVariant)));
   completionSuffixChanged(s.completionSuffix());
+
+  // we need this signal for future connects to reset the data;
+  connect(Client::instance(), SIGNAL(connected()), SLOT(clientConnected()));
+  connect(Client::instance(), SIGNAL(disconnected()), SLOT(clientDisconnected()));
+  if(Client::isConnected())
+    clientConnected();
+}
+
+void ClientUserInputHandler::clientConnected() {
+  _aliasManager = AliasManager();
+  Client::signalProxy()->synchronize(&_aliasManager);
+  connect(&_aliasManager, SIGNAL(initDone()), SLOT(initDone()));
+}
+
+void ClientUserInputHandler::clientDisconnected() {
+  // clear alias manager
+  _aliasManager = AliasManager();
+  _initialized = false;
+}
+
+void ClientUserInputHandler::initDone() {
+  _initialized = true;
+  for(int i = 0; i < _inputBuffer.count(); i++)
+    handleUserInput(_inputBuffer.at(i).first, _inputBuffer.at(i).second);
+  _inputBuffer.clear();
 }
 
 void ClientUserInputHandler::completionSuffixChanged(const QVariant &v) {
@@ -40,17 +68,100 @@ void ClientUserInputHandler::completionSuffixChanged(const QVariant &v) {
 }
 
 // this would be the place for a client-side hook
-void ClientUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg) {
-  // check if we addressed a user and update its timestamp in that case
-  if(bufferInfo.type() == BufferInfo::ChannelBuffer) {
-    if(!msg.startsWith('/')) {
-      if(_nickRx.indexIn(msg) == 0) {
-        const Network *net = Client::network(bufferInfo.networkId());
-        IrcUser *user = net ? net->ircUser(_nickRx.cap(1)) : 0;
-        if(user)
-          user->setLastSpokenTo(bufferInfo.bufferId(), QDateTime::currentDateTime().toUTC());
+void ClientUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg_) {
+  QString msg = msg_;
+
+  if(!_initialized) { // aliases not yet synced
+    _inputBuffer.append(qMakePair(bufferInfo, msg));
+    return;
+  }
+
+  // leading slashes indicate there's a command to call unless there is another one in the first section (like a path /proc/cpuinfo)
+  int secondSlashPos = msg.indexOf('/', 1);
+  int firstSpacePos = msg.indexOf(' ');
+  if(!msg.startsWith('/') || (secondSlashPos != -1 && (secondSlashPos < firstSpacePos || firstSpacePos == -1))) {
+    if(msg.startsWith("//"))
+      msg.remove(0, 1); // //asdf is transformed to /asdf
+
+    // check if we addressed a user and update its timestamp in that case
+    if(bufferInfo.type() == BufferInfo::ChannelBuffer) {
+      if(!msg.startsWith('/')) {
+        if(_nickRx.indexIn(msg) == 0) {
+          const Network *net = Client::network(bufferInfo.networkId());
+          IrcUser *user = net ? net->ircUser(_nickRx.cap(1)) : 0;
+          if(user)
+            user->setLastSpokenTo(bufferInfo.bufferId(), QDateTime::currentDateTime().toUTC());
+        }
+      }
+    }
+    msg.prepend("/SAY ");  // make sure we only send proper commands to the core
+
+  } else {
+    // check for aliases
+    QString cmd = msg.section(' ', 0, 0).remove(0, 1).toUpper();
+    for(int i = 0; i < _aliasManager.count(); i++) {
+      if(_aliasManager[i].name.toLower() == cmd.toLower()) {
+        expand(_aliasManager[i].expansion, bufferInfo, msg.section(' ', 1));
+        return;
       }
     }
   }
+
+  // all clear, send off to core.
   emit sendInput(bufferInfo, msg);
 }
+
+void ClientUserInputHandler::expand(const QString &alias, const BufferInfo &bufferInfo, const QString &msg) {
+  const Network *network = Client::network(bufferInfo.networkId());
+  if(!network) {
+    // FIXME send error as soon as we have a method for that!
+    return;
+  }
+
+  QRegExp paramRangeR("\\$(\\d+)\\.\\.(\\d*)");
+  QStringList commands = alias.split(QRegExp("; ?"));
+  QStringList params = msg.split(' ');
+  QStringList expandedCommands;
+  for(int i = 0; i < commands.count(); i++) {
+    QString command = commands[i];
+
+    // replace ranges like $1..3
+    if(!params.isEmpty()) {
+      int pos;
+      while((pos = paramRangeR.indexIn(command)) != -1) {
+        int start = paramRangeR.cap(1).toInt();
+        bool ok;
+        int end = paramRangeR.cap(2).toInt(&ok);
+        if(!ok) {
+          end = params.count();
+        }
+        if(end < start)
+          command = command.replace(pos, paramRangeR.matchedLength(), QString());
+        else {
+          command = command.replace(pos, paramRangeR.matchedLength(), QStringList(params.mid(start - 1, end - start + 1)).join(" "));
+        }
+      }
+    }
+
+    for(int j = params.count(); j > 0; j--) {
+      IrcUser *ircUser = network->ircUser(params[j - 1]);
+      command = command.replace(QString("$%1:hostname").arg(j), ircUser ? ircUser->host() : QString("*"));
+      command = command.replace(QString("$%1").arg(j), params[j - 1]);
+    }
+    command = command.replace("$0", msg);
+    command = command.replace("$channelname", bufferInfo.bufferName());
+    command = command.replace("$currentnick", network->myNick());
+    expandedCommands << command;
+  }
+
+  while(!expandedCommands.isEmpty()) {
+    QString command;
+    if(expandedCommands[0].trimmed().toLower().startsWith("/wait")) {
+      command = expandedCommands.join("; ");
+      expandedCommands.clear();
+    } else {
+      command = expandedCommands.takeFirst();
+    }
+    handleUserInput(bufferInfo, command);
+  }
+}