Introduce /exec support for running simple client-side scripts
authorManuel Nickschas <sputnick@quassel-irc.org>
Thu, 12 Mar 2009 23:56:08 +0000 (00:56 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Fri, 13 Mar 2009 19:03:06 +0000 (20:03 +0100)
/exec myscript [param1 param2 ...] runs an executable named "myscript" in one of the acceptable
script dirs ($configdir/scripts, $datadir/scripts) and pipes its stdout into IRC. This is not much,
but enough to run stuff like infobash/inxi or the infamous now-playing stuff :P

It's recommended to use aliases for invoking scripts, e.g. /audio could expand to /exec nowplaying,
where nowplaying is your fancy script that queries your Amarok for the current song.

Now keep 'em scripts coming, good ones will be included in our repo :)

src/client/CMakeLists.txt
src/client/clientuserinputhandler.cpp
src/client/execwrapper.cpp [new file with mode: 0644]
src/client/execwrapper.h [new file with mode: 0644]

index 4780de0..5277844 100644 (file)
@@ -22,6 +22,7 @@ set(SOURCES
     clientsettings.cpp
     clientsyncer.cpp
     clientuserinputhandler.cpp
     clientsettings.cpp
     clientsyncer.cpp
     clientuserinputhandler.cpp
+    execwrapper.cpp
     irclistmodel.cpp
     messagefilter.cpp
     messagemodel.cpp
     irclistmodel.cpp
     messagefilter.cpp
     messagemodel.cpp
@@ -43,6 +44,7 @@ set(MOC_HDRS
     clientirclisthelper.h
     clientuserinputhandler.h
     clientsyncer.h
     clientirclisthelper.h
     clientuserinputhandler.h
     clientsyncer.h
+    execwrapper.h
     irclistmodel.h
     messagefilter.h
     messagemodel.h
     irclistmodel.h
     messagefilter.h
     messagemodel.h
index 049b9d3..a02bc18 100644 (file)
@@ -24,6 +24,7 @@
 #include "clientaliasmanager.h"
 #include "clientuserinputhandler.h"
 #include "clientsettings.h"
 #include "clientaliasmanager.h"
 #include "clientuserinputhandler.h"
 #include "clientsettings.h"
+#include "execwrapper.h"
 #include "ircuser.h"
 #include "network.h"
 
 #include "ircuser.h"
 #include "network.h"
 
@@ -54,8 +55,27 @@ void ClientUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const
     }
   }
 
     }
   }
 
-  AliasManager::CommandList clist = _aliasManager.processInput(bufferInfo, msg);
+  AliasManager::CommandList clist = Client::aliasManager()->processInput(bufferInfo, msg);
+
+  for(int i = 0; i < clist.count(); i++) {
+    QString cmd = clist.at(i).second.section(' ', 0, 0).remove(0, 1).toUpper();
+    if(cmd == "EXEC")
+      handleExec(clist.at(i).first, clist.at(i).second.section(' ', 1));
+    else
+      emit sendInput(clist.at(i).first, clist.at(i).second);
+  }
+}
+
+void ClientUserInputHandler::handleExec(const BufferInfo &bufferInfo, const QString &execString) {
+  QString script;
+  QStringList params;
+  if(execString.contains(' ')) {
+    script = execString.section(' ', 0, 0);
+    params = execString.section(' ', 1).trimmed().split(' '); // FIXME handle args properly, including quoted strings etc
+  } else
+    script = execString;
+
+  ExecWrapper *exec = new ExecWrapper(this);
+  exec->start(bufferInfo, script, params);
 
 
-  for(int i = 0; i < clist.count(); i++)
-    emit sendInput(clist.at(i).first, clist.at(i).second);
 }
 }
diff --git a/src/client/execwrapper.cpp b/src/client/execwrapper.cpp
new file mode 100644 (file)
index 0000000..8ef8194
--- /dev/null
@@ -0,0 +1,93 @@
+/***************************************************************************
+*   Copyright (C) 2005-09 by the Quassel Project                          *
+*   devel@quassel-irc.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) version 3.                                           *
+*                                                                         *
+*   This program is distributed in the hope that it will be useful,       *
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+*   GNU General Public License for more details.                          *
+*                                                                         *
+*   You should have received a copy of the GNU General Public License     *
+*   along with this program; if not, write to the                         *
+*   Free Software Foundation, Inc.,                                       *
+*   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+***************************************************************************/
+
+#include <QFile>
+
+#include "execwrapper.h"
+
+#include "client.h"
+#include "messagemodel.h"
+#include "quassel.h"
+
+ExecWrapper::ExecWrapper(QObject* parent) : QObject(parent) {
+  connect(&_process, SIGNAL(readyReadStandardOutput()), SLOT(processReadStdout()));
+  connect(&_process, SIGNAL(readyReadStandardError()), SLOT(processReadStderr()));
+  connect(&_process, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
+  connect(&_process, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
+
+  connect(this, SIGNAL(stdout(QString)), SLOT(postStdout(QString)));
+  connect(this, SIGNAL(stderr(QString)), SLOT(postStderr(QString)));
+}
+
+void ExecWrapper::start(const BufferInfo &info, const QString &scriptName, const QStringList& params) {
+  _bufferInfo = info;
+  _scriptName = scriptName;
+  foreach(QString scriptDir, Quassel::scriptDirPaths()) {
+    QString fileName = scriptDir + '/' + scriptName;
+    if(!QFile::exists(fileName))
+      continue;
+    _process.start(fileName, params);
+    return;
+  }
+  emit stderr(tr("Could not find script \"%1\"").arg(scriptName));
+  deleteLater();
+}
+
+void ExecWrapper::postStdout(const QString &msg) {
+  if(_bufferInfo.isValid())
+    Client::userInput(_bufferInfo, msg);
+}
+
+void ExecWrapper::postStderr(const QString &msg) {
+  if(_bufferInfo.isValid())
+    Client::messageModel()->insertErrorMessage(_bufferInfo, msg);
+}
+
+void ExecWrapper::processFinished(int exitCode, QProcess::ExitStatus status) {
+  if(status == QProcess::CrashExit) {
+    emit stderr(tr("Script \"%1\" crashed with exit code %2.").arg(_scriptName).arg(exitCode));
+  }
+
+  // TODO empty buffers
+
+  deleteLater();
+}
+
+void ExecWrapper::processError(QProcess::ProcessError error) {
+  emit stderr(tr("Script \"%1\" caused error %2.").arg(_scriptName).arg(error));
+}
+
+void ExecWrapper::processReadStdout() {
+  _stdoutBuffer.append(_process.readAllStandardOutput());
+  int idx;
+  while((idx = _stdoutBuffer.indexOf('\n')) >= 0) {
+    emit stdout(_stdoutBuffer.left(idx));
+    _stdoutBuffer = _stdoutBuffer.mid(idx + 1);
+  }
+}
+
+void ExecWrapper::processReadStderr() {
+  _stderrBuffer.append(_process.readAllStandardError());
+  int idx;
+  while((idx = _stderrBuffer.indexOf('\n')) >= 0) {
+    emit stdout(_stderrBuffer.left(idx));
+    _stderrBuffer = _stderrBuffer.mid(idx + 1);
+  }
+}
diff --git a/src/client/execwrapper.h b/src/client/execwrapper.h
new file mode 100644 (file)
index 0000000..b0c52be
--- /dev/null
@@ -0,0 +1,58 @@
+/***************************************************************************
+*   Copyright (C) 2005-09 by the Quassel Project                          *
+*   devel@quassel-irc.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) version 3.                                           *
+*                                                                         *
+*   This program is distributed in the hope that it will be useful,       *
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+*   GNU General Public License for more details.                          *
+*                                                                         *
+*   You should have received a copy of the GNU General Public License     *
+*   along with this program; if not, write to the                         *
+*   Free Software Foundation, Inc.,                                       *
+*   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+***************************************************************************/
+
+#ifndef EXECWRAPPER_H_
+#define EXECWRAPPER_H_
+
+#include <QProcess>
+
+#include "bufferinfo.h"
+
+class ExecWrapper : public QObject {
+  Q_OBJECT
+
+public:
+  ExecWrapper(QObject *parent = 0);
+
+public slots:
+  void start(const BufferInfo &info, const QString &scriptName, const QStringList &params = QStringList());
+
+signals:
+  void stderr(const QString &errorMsg);
+  void stdout(const QString &out);
+
+private slots:
+  void processReadStdout();
+  void processReadStderr();
+  void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
+  void processError(QProcess::ProcessError);
+
+  void postStdout(const QString &);
+  void postStderr(const QString &);
+
+private:
+  QProcess _process;
+  BufferInfo _bufferInfo;
+  QString _scriptName;
+  QString _stdoutBuffer;
+  QString _stderrBuffer;
+};
+
+#endif