ソースを参照

Initial commit.

Mihawk 12 年 前
コミット
40604f161b
12 ファイル変更1763 行追加0 行削除
  1. 486 0
      App.cpp
  2. 87 0
      App.h
  3. 246 0
      Client.cpp
  4. 66 0
      Client.h
  5. 265 0
      ServerQuery.cpp
  6. 93 0
      ServerQuery.h
  7. 136 0
      ServerQueryThread.cpp
  8. 55 0
      ServerQueryThread.h
  9. 212 0
      SshClient.cpp
  10. 68 0
      SshClient.h
  11. 25 0
      cimsqwbot.pro
  12. 24 0
      main.cpp

+ 486 - 0
App.cpp

@@ -0,0 +1,486 @@
+#include "App.h"
+#include "Client.h"
+#include <QStringList>
+#include <QTcpSocket>
+#include <QTcpServer>
+#include <QRegExp>
+#include <QHostInfo>
+#include <QTimerEvent>
+#include <QDebug>
+#include <QtSql/QSqlQuery>
+#include <QCryptographicHash>
+#include <QHostAddress>
+#include "SshClient.h"
+#include "ServerQueryThread.h"
+
+App::App(int &argc, char **argv) :
+  QCoreApplication(argc, argv),
+  myServer(new QTcpServer()),
+  mySocketConnectedFlag(false),
+  myQWNETSshClient(new SshClient(this))
+{
+  print("CIMS Bot Service v0.101\n=========================================================\n");
+
+  setApplicationName("CIMSBOT");
+	setOrganizationDomain("qwbr.tk");
+  setOrganizationName("CIMS");
+  setApplicationVersion("0.101");
+
+  myServer->listen(QHostAddress::Any, 45000);
+	connect(myServer, SIGNAL(newConnection()), SLOT(onNewConnection()));
+
+	myClientsFrameTimerID = startTimer(0);
+}
+
+void App::onNewConnection()
+{
+	if(mySocketConnectedFlag)
+	{
+		print("Someone just connected to the bot.\nBye Bye.\n");
+		mySocket->disconnectFromHost();
+		if(mySocket->state() != QTcpSocket::UnconnectedState)
+			mySocket->waitForDisconnected();
+		mySocketConnectedFlag = false;
+	}
+
+	mySocket = myServer->nextPendingConnection();
+  mySocketConnectedFlag = true;
+  connect(mySocket, SIGNAL(readyRead()), SLOT(onDataArrival()));
+  connect(mySocket, SIGNAL(disconnected()), SLOT(onDisconnection()));
+
+  print("Connected to CIMS's bot service.\n");
+
+  /* Set socket on clients for echoing */
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->setSocket(mySocket);
+	}
+}
+
+void App::onDisconnection()
+{
+	/* set all sockets to null so it doenst echo anything on a dangling pointer */
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->setSocket(NULL);
+	}
+	mySocketConnectedFlag = false;
+	mySocket->deleteLater();
+}
+
+void App::loadClients()
+{
+
+}
+
+void App::connectToServer(const QString &args)
+{
+	if(!args.size())
+	{
+		print("No server specified\n");
+		return;
+	}
+	if(args.contains(" "))
+	{
+		print("Invalid server address\n");
+		return;
+	}
+
+	QStringList clientData = args.split(":");
+	if(clientData.size() == 1)
+	{
+		addClient(clientData.at(0), 27500);
+		return;
+	}
+
+	if(clientData.size() == 2)
+	{
+		addClient(clientData.at(0), clientData.at(1).toUShort());
+		return;
+	}
+}
+
+void App::disconnectFromServer(const QString &args)
+{
+	if(!args.size())
+	{
+		print("No server specified\n");
+		return;
+	}
+
+	QStringList clientData = args.split(":");
+	if(clientData.size() == 1)
+	{
+		removeClient(clientData.at(0), 27500);
+		return;
+	}
+
+	if(clientData.size() == 2)
+	{
+		removeClient(clientData.at(0), clientData.at(1).toUShort());
+		return;
+	}
+}
+
+void App::onDataArrival()
+{
+	while(mySocket->canReadLine())
+	{
+		QByteArray		line;
+
+		line = mySocket->readLine();
+
+		QString data(line);
+		QRegExp regex("([0-9a-zA-Z]+)\\s+([a-z]+)\\s+(.*)");
+		if(regex.indexIn(data) == -1)
+		{
+			print("command format: <password> <command> ?<arguments>\nEg.: pss help\nDisconnected\n");
+			mySocket->disconnectFromHost();
+			return;
+		}
+
+		QString pass = regex.capturedTexts().at(1);
+		if(!checkPassword(pass))
+		{
+			print("Wrong password\nDisconnected\n");
+			mySocket->disconnectFromHost();
+			return;
+		}
+
+		QString cmd  = regex.capturedTexts().at(2);
+		QString args = regex.capturedTexts().at(3).trimmed();
+
+		if(cmd == "connect")
+		{
+			connectToServer(args);
+			return;
+		}
+
+		if(cmd == "disconnect")
+		{
+			disconnectFromServer(args);
+			return;
+		}
+
+		if(cmd == "say")
+		{
+			say(args);
+			return;
+		}
+
+		if(cmd == "say_team")
+		{
+			sayTeam(args);
+			return;
+		}
+
+		if(cmd == "servers")
+		{
+			listClients();
+			return;
+		}
+
+		if(cmd == "name")
+		{
+			setNick(args);
+			return;
+		}
+
+		if(cmd == "color")
+		{
+			setColor(args);
+			return;
+		}
+
+		if(cmd == "setping")
+		{
+			setPing(args);
+			return;
+		}
+
+		if(cmd == "team")
+		{
+			setTeam(args);
+			return;
+		}
+
+		if(cmd == "quit")
+		{
+			mySocket->disconnectFromHost();
+			return;
+		}
+
+		if(cmd == "help")
+		{
+			help();
+			return;
+		}
+	}
+}
+
+void App::help()
+{
+	print("connect server:port -> connects the bot on a server\n");
+	print("disconnect server:port -> removes the bot from a server\n");
+	print("say message -> says the message on all servers where the bot is connected\n");
+	print("say_team message -> says the message on all servers where the bot is connected\n");
+	print("servers -> lists all servers the bot is connected\n");
+	print("name nick -> changes the bot name to nick\n");
+	print("color x x -> changes the player color\n");
+	print("setping x -> sets the bot ping to x. ofc you can't lower your actual ping with this.\n");
+	print("team teamname -> sets the bot team\n");
+	print("help -> displays this message\n");
+}
+
+void App::setTeam(const QString &args)
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->setTeam(args);
+	}
+	print("Team changed.\n");
+}
+
+void App::setColor(const QString &args)
+{
+  ActiveClient* ac;
+	quint8 bottom, top;
+	QStringList colors = args.split(" ");
+
+	if(colors.size() < 2)
+		return;
+
+	bottom = colors.at(0).toUShort();
+	top = colors.at(1).toUShort();
+
+  foreach(ac, myClients)
+	{
+    ac->client()->setColor(bottom, top);
+	}
+	print("All clients colors have changed.\n");
+}
+
+void App::setPing(const QString &args)
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->setPing(args.toInt());
+	}
+	print("All clients pings have changed.\n");
+}
+
+void App::setNick(const QString &args)
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->setName(args.toAscii().data());
+	}
+	print("All clients nicks have changed.\n");
+}
+
+bool App::checkPassword(const QString &password)
+{
+	if(QCryptographicHash::hash(password.toAscii(), QCryptographicHash::Md4).toHex().toLower() == "bf4df9f74d05c50ea00492224fb02854")
+		return true;
+	return false;
+}
+
+void App::print(const QString &msg)
+{
+	printf("%s", msg.toAscii().data());
+  if(mySocketConnectedFlag)
+  {
+    mySocket->write(msg.toAscii());
+    mySocket->waitForBytesWritten();
+  }
+}
+
+void App::addClient(const QString &host, quint16 port)
+{
+  ActiveClient* ac;
+	QHostAddress ha = QHostInfo::fromName(host).addresses().at(0);
+
+  foreach(ac, myClients)
+	{
+    if(QString(ac->client()->host()) == ha.toString() && ac->client()->port() == port)
+		{
+			print("That client is already on the list.\n");
+			return;
+		}
+	}
+
+  ac = new ActiveClient(this);
+  if(mySocketConnectedFlag)
+	{
+		if(mySocket->state() == QTcpSocket::ConnectedState)
+		{
+      ac->client()->setSocket(mySocket);
+		}
+	}
+  ac->setAddress(ha, port);
+  ac->client()->setQuakeFolder(mySettings.value("quakeFolder", App::applicationDirPath()).toString().toAscii().data());
+  ac->client()->setName(mySettings.value("botName", "cimsbot").toString().toAscii().data());
+  ac->client()->setSpectator(true);
+  ac->client()->setPing(120);
+  ac->client()->setColor(11, 13);
+  myClients.push_back(ac);
+}
+
+void App::removeClient(const QString &host, quint16 port)
+{
+  ActiveClient* ac;
+	QHostAddress ha = QHostInfo::fromName(host).addresses().at(0);
+
+  foreach(ac, myClients)
+	{
+    if(QString(ac->client()->host()) == ha.toString() && ac->client()->port() == port)
+		{
+      ac->client()->disconnect();
+      delete ac;
+      myClients.removeAll(ac);
+
+
+      print("Client removed.\n");
+			return;
+		}
+	}
+	print("Client not found on the list.\n");
+}
+
+void App::say(const QString &msg)
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->say(msg);
+	}
+	print("Say command sent to all clients\n");
+}
+
+void App::sayTeam(const QString &msg)
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->sayTeam(msg);
+	}
+	print("Say_team command sent to all clients\n");
+}
+
+void App::listClients()
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    QString reply(ac->client()->host());
+    reply.append(":" + QString::number(ac->client()->port()) + "\n");
+		print(reply.toAscii().data());
+	}
+}
+
+void App::timerEvent(QTimerEvent *e)
+{
+	if(e->timerId() == myClientsFrameTimerID)
+	{
+    ActiveClient *ac;
+    foreach(ac, myClients)
+		{
+      ac->run();
+    }
+		return;
+	}
+}
+
+void App::broadcast(const QString &msg)
+{
+	say(msg);
+}
+
+void App::cleanup()
+{
+  ActiveClient* ac;
+  foreach(ac, myClients)
+	{
+    ac->client()->disconnect();
+    delete ac;
+	}
+}
+
+App::~App()
+{
+	cleanup();
+	delete myServer;
+}
+
+//========================================================================
+
+App::ActiveClient::ActiveClient(App *app):
+  myApp(app),
+  myClient(new Client(app)),
+  myQueryThread(new ServerQueryThread())
+{
+}
+
+App::ActiveClient::~ActiveClient()
+{
+  delete myClient;
+
+  if(queryThread()->isRunning())
+    queryThread()->stopQueryThread();
+  while(queryThread()->isRunning());
+
+  delete myQueryThread;
+}
+
+Client* App::ActiveClient::client()
+{
+  return myClient;
+}
+
+ServerQueryThread* App::ActiveClient::queryThread()
+{
+  return myQueryThread;
+}
+
+void App::ActiveClient::setAddress(const QHostAddress &address, quint16 port)
+{
+  queryThread()->setAddress(address, port);
+}
+
+void App::ActiveClient::run()
+{
+  if(client()->state() == Client::DisconnectedState)
+  {
+    if(!queryThread()->isRunning())
+    {
+      queryThread()->startQueryThread();
+      return;
+    }
+
+    int playerCount = queryThread()->playerCount();
+    if(playerCount != 255 && playerCount > 0 && myDisconnectTime.elapsed() > 10000)
+    {
+      myApp->print("Players online on server " + queryThread()->serverAddress().toString() + ":" + QString::number(queryThread()->serverPort()) + ". Joining...\n");
+      client()->connect(queryThread()->serverAddress().toString().toAscii(), queryThread()->serverPort());
+    }
+    return;
+  }
+
+  if(client()->state() == Client::ConnectedState)
+  {
+    int playerCount = queryThread()->playerCount();
+    if(playerCount == 1 && client()->isOnServer())
+    {
+      myApp->print("I was left alone on " + queryThread()->serverAddress().toString() + ":" + QString::number(queryThread()->serverPort()) + ". Leaving...\n");
+      client()->disconnect();
+      myDisconnectTime.restart();
+      return;
+    }
+  }
+  client()->run();
+}

+ 87 - 0
App.h

@@ -0,0 +1,87 @@
+#ifndef APP_H
+#define APP_H
+
+#include <QCoreApplication>
+#include <QList>
+#include <QSettings>
+#include <QHostAddress>
+#include <QTime>
+
+class SshClient;
+class QTcpSocket;
+class QTcpServer;
+class Client;
+class ServerQueryThread;
+
+class App : public QCoreApplication
+{
+  //TODO:
+	friend class Client;
+
+	Q_OBJECT
+public:
+	explicit							App(int &argc, char **argv);
+												~App();
+
+  void									print(const QString& msg);
+protected:
+	void									timerEvent(QTimerEvent *e);
+
+private:
+  class ActiveClient
+  {
+  public:
+    ActiveClient(App* app);
+    ~ActiveClient();
+
+    Client*           client();
+    ServerQueryThread*queryThread();
+    void              run();
+    void              setAddress(const QHostAddress &address, quint16 port);
+
+  private:
+    App*              myApp;
+    Client*           myClient;
+    ServerQueryThread*myQueryThread;
+    ActiveClient(const ActiveClient&) {}
+    QTime             myDisconnectTime;
+  };
+
+  QTcpSocket*           mySocket;
+  QTcpServer*           myServer;
+	bool									mySocketConnectedFlag;
+  SshClient*            myQWNETSshClient;
+
+  QList<ActiveClient*>  myClients;
+	quint32								myCurrentClient;
+	QSettings							mySettings;
+	int										myClientsFrameTimerID; //timer for mainloop
+
+	void									broadcast(const QString& msg);
+
+	void									loadClients();
+	void									cleanup();
+
+	/* TCP Server */
+	bool									checkPassword(const QString& password);
+  void									addClient(const QString& host, quint16 port);
+	void									removeClient(const QString& host, quint16 port);
+	void									connectToServer(const QString& args);
+	void									disconnectFromServer(const QString& args);
+	void									say(const QString& msg);
+	void									sayTeam(const QString& msg);
+	void									setTeam(const QString& team);
+	void									listClients();
+	void									setColor(const QString& args);
+	void									setNick(const QString& args);
+	void									setPing(const QString& args);
+	void									help();
+
+private slots:
+	/* TCP server */
+	void									onDataArrival();
+	void									onNewConnection();
+	void									onDisconnection();
+};
+
+#endif // APP_H

+ 246 - 0
Client.cpp

@@ -0,0 +1,246 @@
+#include "Client.h"
+#include <QString>
+#include <stdio.h>
+#include <QTcpSocket>
+#include <QStringList>
+#include "App.h"
+
+Client::Client(App *app):
+  QWClient(),
+  myApp(app),
+  mySocket(NULL),
+  myConnectionRetries(0),
+  myOnServerFlag(false)
+{
+}
+
+Client::~Client()
+{
+}
+
+void Client::setSocket(QTcpSocket *socket)
+{
+	mySocket = socket;
+}
+
+void Client::say(const QString &msg)
+{
+	QString cmd = "say " + msg;
+	sendCmd(cmd.toAscii().data());
+}
+
+void Client::sayTeam(const QString &msg)
+{
+	QString cmd = "say_team " + msg;
+	sendCmd(cmd.toAscii().data());
+}
+
+void Client::setTeam(const QString &msg)
+{
+	sendCmd(QString("setinfo \"team\" \"" + msg + "\"").toAscii().data());
+}
+
+void Client::disconnect()
+{
+  QWClient::disconnect();
+  myOnServerFlag = false;
+}
+
+void Client::print(const QString &msg)
+{
+	QString str;
+
+	str = QString(host()) + ":" + QString::number(port()) + "> " + msg;
+	QByteArray b = str.toAscii();
+	Client::stripColor(b.data());
+	str = QString::fromAscii(b.data());
+
+	printf("%s", str.toAscii().data());
+	if(mySocket)
+	{
+		mySocket->write(str.toAscii());
+		mySocket->waitForBytesWritten();
+	}
+}
+
+void Client::yellowText(const QString &text)
+{
+	unsigned char *i;
+
+	for(i = (unsigned char*)text.toAscii().data(); *i; i++)
+	{
+		if(*i >= '0' && *i <= '9')
+			*i += (unsigned char) (18 - '0');
+		else if(*i > 32 && *i < 128)
+			*i |= 128;
+		else if(*i == 13)
+			*i = ' ';
+	}
+	return;
+}
+
+void Client::onDisconnect()
+{
+	print("Disconnected..\n");
+  myOnServerFlag = false;
+}
+
+void Client::retryConnection()
+{
+	if(myConnectionRetries == ConnectionRetries)
+	{
+		print("Giving up!\n");
+		disconnect();
+    myOnServerFlag = false;
+		return;
+	}
+
+	print("Reconnecting...\n");
+	reconnect();
+	myConnectionRetries++;
+}
+
+void Client::parsePrintedLine()
+{
+	QRegExp regex("^(.+):\\s+!([A-Za-z]+)\\s*(.*)$");
+
+	if(regex.indexIn(myPrintLine) == -1)
+		return;
+
+	/* Flood prot */
+	int elapsed = myTimer.elapsed();
+	if(elapsed < 20000)
+	{
+		if(!myFloodMsgPrinted)
+		{
+			say("Wait " + QString::number((20000-elapsed) / 1000) + " second(s) before issuing a new command.");
+			myFloodMsgPrinted = true;
+		}
+		return;
+	}
+
+	QString nick = regex.capturedTexts().at(1);
+	QString command = regex.capturedTexts().at(2);
+	QString args = regex.capturedTexts().at(3);
+
+	myTimer.restart();
+	myFloodMsgPrinted = false;
+
+	if(command == "help")
+	{
+		say("type:");
+    say("> !msg <message> to broadcast a message.");
+    say("> !help to show this help message.");
+		return;
+	}
+
+	if(command == "msg")
+	{
+    say("Broadcasting...");
+		QString hostPort("[" + nick + " - " + QString(host()) + ":" + QString::number(port()) + "]");
+		myApp->broadcast(hostPort + args);
+		return;
+	}
+}
+
+void Client::onPrint(int, const char *msg)
+{
+	if(!strlen(msg))
+		return;
+
+	QString text(msg);
+	if(text.endsWith('\n'))
+	{
+		myPrintLine.append(text);
+		parsePrintedLine();
+		print(myPrintLine);
+		myPrintLine.clear();
+	}
+	else
+	{
+		myPrintLine.append(text);
+	}
+}
+
+bool Client::isOnServer() const
+{
+  return myOnServerFlag;
+}
+
+void Client::onError(const char *description)
+{
+	QString desc(description);
+	if(desc == "Client Timed Out.")
+	{
+		print("Error (" + QString(description) + ")\n");
+	}
+	else
+	{
+		print("Error (" + QString(description) + ")\n");
+	}
+  myOnServerFlag = false;
+}
+
+void Client::onLevelChanged(int, const char *levelName, float, float, float, float, float, float, float, float, float, float)
+{
+	print(QString(levelName) + "\n");
+	myDownloadProgressPrintedFlag = false;
+}
+
+void Client::onChallenge()
+{
+	print("challenge\n");
+}
+
+void Client::onConnection()
+{
+	print("connection\n");
+}
+
+void Client::onConnected()
+{
+	print("connected\n");
+	myTimer.start();
+}
+
+void Client::onDownloadStarted(const char *fileName)
+{
+	print("Download started " + QString(fileName) + "\n");
+}
+
+void Client::onOOBPrint(const char *msg)
+{
+	print(QString(msg));
+}
+
+void Client::onStuffedCmd(const char *cmd)
+{
+  printf("[%s]\n", cmd);
+	QString strCmd(cmd);
+	if(strCmd == "skins") //connection sequence complete
+	{
+		myConnectionRetries = 0;
+    myOnServerFlag = true;
+	}
+}
+
+void Client::onDownloadProgress(int percent)
+{
+	if(!(percent % 10))
+	{
+		if(!myDownloadProgressPrintedFlag)
+		{
+			print("Download " + QString::number(percent) + "%\n");
+			myDownloadProgressPrintedFlag = true;
+		}
+	}
+	else
+	{
+		myDownloadProgressPrintedFlag = false;
+	}
+}
+
+void Client::onDownloadFinished()
+{
+	print("Download 100% finished.\n");
+}

+ 66 - 0
Client.h

@@ -0,0 +1,66 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "QWClient.h"
+#include <QString>
+#include <QList>
+#include <QTime>
+
+class App;
+class QTcpSocket;
+
+class Client : public QWClient
+{
+public:
+  Client(App* app);
+  ~Client();
+  void disconnect();
+  void say(const QString& msg);
+	void sayTeam(const QString& msg);
+  void setTeam(const QString& msg);
+	void setSocket(class QTcpSocket* socket);
+  void retryConnection(); //unused
+  bool isOnServer() const;
+//  quint8 playerCount() const;
+
+protected:
+	void onLevelChanged(int playerNum, const char *levelName, float gravity, float stopSpeed, float maxSpeed, float spectatorMaxSpeed, float accelerate, float airAccelerate, float waterAccelerate, float friction, float waterFriction, float entGravity);
+	void onDisconnect();
+	void onPrint(int level, const char *msg);
+	void onChallenge();
+	void onConnection();
+	void onConnected();
+	void onDownloadStarted(const char *fileName);
+	void onDownloadFinished();
+	void onDownloadProgress(int percent);
+	void onStuffedCmd(const char *cmd);
+	void onError(const char *description);
+	void onOOBPrint(const char *msg);
+
+private:
+	App*							myApp;
+  QTcpSocket*       mySocket;
+	QTime							myTimer;
+	bool							myFloodMsgPrinted;
+	int								myConnectionRetries;
+	static const int	ConnectionRetries = 10;
+	QString						myPrintLine;
+	bool							myDownloadProgressPrintedFlag;
+  bool              myOnServerFlag;
+//	struct Player {
+//		int slot;
+//		int userID;
+//		QString team;
+//		QString name;
+//	};
+//  Player*           myPlayers[32];
+//  quint8            myPlayerCount;
+//  void cleanupPlayers();
+
+	void print(const QString& msg);
+	void parsePrintedLine();
+//	static const QString& getInfoKeyValue(const QStringList& infoString, const QString& key);
+	static void yellowText(const QString& text);
+};
+
+#endif // CLIENT_H

+ 265 - 0
ServerQuery.cpp

@@ -0,0 +1,265 @@
+/*
+GNU General Public License version 3 notice
+
+Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
+
+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 3 of the License, or
+(at your option) any later version.
+
+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, see < http://www.gnu.org/licenses/ >.
+*/
+
+#include "ServerQuery.h"
+
+#include <QUdpSocket>
+#include <QHostInfo>
+#include <QStringList>
+#include <QRegExp>
+#include <QTime>
+
+ServerQuery::ServerQuery(QObject *parent) :
+  QObject(parent),
+  mySocket(new QUdpSocket(this)),
+  myPort(27500)
+{
+}
+
+ServerQuery::~ServerQuery()
+{
+  delete mySocket;
+}
+
+bool ServerQuery::query(bool pingServer)
+{
+  myLastErrorCode = NoError;
+
+  if(myAddress.isNull() || !myPort)
+  {
+    myLastErrorCode = HostLookupError;
+		return false;
+  }
+
+	if(mySocket->isOpen())
+		mySocket->close();
+
+  mySocket->connectToHost(myAddress, myPort);
+  if(mySocket->write("\xff\xff\xff\xffstatus 23\n", 14) == -1)
+	{
+    myLastErrorCode = SendError;
+		return false;
+	}
+
+	if(!mySocket->waitForReadyRead(3000))
+	{
+    myLastErrorCode = TimedOutError;
+		return false;
+	}
+
+  if(pingServer)
+    myPing = ping(10); //avg ping
+  else
+    myPing = 0;
+
+  return parseServerInfo();
+}
+
+bool ServerQuery::setAddress(const QHostAddress &address, quint16 port)
+{
+  myAddress = address;
+  myPort = port;
+  return true;
+}
+
+bool ServerQuery::setAddress(const QString &address, quint16 port)
+{
+  QHostInfo hi = QHostInfo::fromName(address);
+  if(hi.error() != QHostInfo::NoError)
+  {
+    myLastErrorCode = HostLookupError;
+    return false;
+  }
+  myAddress = hi.addresses().at(0);
+  myPort = port;
+
+  return true;
+}
+
+bool ServerQuery::parseServerInfo()
+{
+	QByteArray serverData = mySocket->readAll();
+
+  if(serverData.isEmpty())
+	{
+    myLastErrorCode = EmptyResponseError;
+		return false;
+	}
+
+	if(!serverData.startsWith("\xff\xff\xff\xffn"))
+	{
+    myLastErrorCode = InvalidResponseError;
+		return false;
+	}
+
+  /* Parse server rules */
+  myRules.clear();
+  QString     infoString(serverData.data()+5);
+	QStringList splitInfoString = infoString.split("\n", QString::SkipEmptyParts);
+  int         i;
+  QStringList rules = splitInfoString.at(0).split("\\", QString::SkipEmptyParts);
+  if((rules.size() % 2) != 0)
+	{
+    myLastErrorCode = InvalidInfoStringError;
+		return false;
+	}
+
+	ServerRule	rule;
+  for(i = 0; i < rules.size(); i += 2)
+	{
+    rule.rule = rules.at(i);
+    rule.value = rules.at(i+1);
+
+    /* Proxy detection */
+		if(rule.rule == "*QTV")
+		{
+      myIsProxyFlag = true;
+		}
+		else if(rule.rule == "*version")
+		{
+      QString value = rule.value;
+			if(value.startsWith("qwfwd", Qt::CaseInsensitive) || value.startsWith("QTV", Qt::CaseInsensitive) || value.startsWith("2.91"))
+        myIsProxyFlag = true;
+		}
+
+    /* Adjust FPS */
+    if(rule.rule == "maxfps" && myPing > 0)
+      myPing += rule.value.toUInt() * 12 / 77;
+
+    myRules.append(rule);
+	}
+
+  /* Parse player info */
+  myPlayers.clear();
+	Player	player;
+	QRegExp regex("^([-0-9]+)\\s+([-0-9]+)\\s+([-0-9]+)\\s+([-0-9]+)\\s+\"([^\"]*)\"\\s+\"([^\"]*)\"\\s+([0-9]+)\\s+([0-9]+)(?:\\s+\"([^\"]*)\")?$");
+	QStringList	playerInfo;
+	for(i = 1; i < splitInfoString.size(); i++)
+	{
+		if(regex.indexIn(splitInfoString.at(i)) == -1)
+		{
+      myLastErrorCode = InvalidPlayerInfoError;
+			continue;
+		}
+
+		playerInfo = regex.capturedTexts();
+		player.id = playerInfo.at(1).toInt();
+		player.frags = playerInfo.at(2).toInt();
+		player.time = playerInfo.at(3).toInt();
+		player.ping = playerInfo.at(4).toInt();
+    player.name = convertNameFun(playerInfo.at(5));
+		player.skin = playerInfo.at(6);
+		player.topColor = playerInfo.at(7).toInt();
+		player.bottomColor = playerInfo.at(8).toInt();
+    player.team = convertNameFun(playerInfo.at(9));
+
+		if(player.name.startsWith("\\s\\"))
+		{
+			player.spectator = true;
+			player.team = "(spec)";
+			player.name = player.name.replace(QRegExp("^\\\\s\\\\"), "");
+			if(player.ping < 0)
+				player.ping = -player.ping;
+		}
+		else
+		{
+			player.spectator = false;
+		}
+    myPlayers.append(player);
+	}
+  return true;
+}
+
+int ServerQuery::ping(int count, int timeout)
+{
+	int pong = 0;
+	QTime timer;
+
+	timer.start();
+	for(int i = 0; i < count; i++)
+	{
+		mySocket->write("\xff\xff\xff\xffk\n");
+		timer.restart();
+		if(!mySocket->waitForReadyRead(timeout))
+		{
+			pong += 999;
+		}
+		else
+		{
+			if(mySocket->readAll() == "l")
+				pong += timer.elapsed();
+			else
+				pong += 999;
+		}
+	}
+
+	return pong/count;
+}
+
+ServerQuery::Error ServerQuery::lastError() const
+{
+  return myLastErrorCode;
+}
+
+PlayerList ServerQuery::playerList() const
+{
+  return myPlayers;
+}
+
+ServerRules ServerQuery::serverRules() const
+{
+  return myRules;
+}
+
+//========================================================================
+/* Name fun */
+char ServerQuery::ourReadableCharsTable[256] = {	'.', '_' , '_' , '_' , '_' , '.' , '_' , '_' , '_' , '_' , '\n' , '_' , '\n' , '>' , '.' , '.',
+                                       '[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '_', '_'
+                                       };
+bool ServerQuery::ourReadableCharsTableInitialized = false;
+
+void ServerQuery::fillReadableCharsTable()
+{
+	int i;
+
+	for(i = 32; i < 127; i++)
+		ourReadableCharsTable[i] = ourReadableCharsTable[128 + i] = i;
+	ourReadableCharsTable[127] = ourReadableCharsTable[128 + 127] = '_';
+
+	for(i = 0; i < 32; i++)
+		ourReadableCharsTable[128 + i] = ourReadableCharsTable[i];
+	ourReadableCharsTable[128] = '_';
+	ourReadableCharsTable[10 + 128] = '_';
+	ourReadableCharsTable[12 + 128] = '_';
+
+	ourReadableCharsTableInitialized = true;
+}
+
+QString ServerQuery::convertNameFun(const QString &name)
+{
+	if(!ourReadableCharsTableInitialized)
+		fillReadableCharsTable();
+
+	QString stripped;
+
+	for(int i = 0; i < name.length(); i++)
+		stripped.append(QChar(ourReadableCharsTable[(unsigned char)name.at(i).toAscii()] & 127));
+
+	return stripped;
+}

+ 93 - 0
ServerQuery.h

@@ -0,0 +1,93 @@
+/*
+GNU General Public License version 3 notice
+
+Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
+
+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 3 of the License, or
+(at your option) any later version.
+
+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, see < http://www.gnu.org/licenses/ >.
+*/
+
+/**
+	Requests a server information and returns everything parsed in a ServerInfo struct
+*/
+#ifndef SERVERQUERY_H
+#define SERVERQUERY_H
+
+#include <QObject>
+#include <QHostAddress>
+
+class QUdpSocket;
+
+struct Player
+{
+  int     id;
+  QString team;
+  QString name;
+  QString skin;
+  int     frags;
+  int     time;
+  int     ping;
+  int     topColor;
+  int     bottomColor;
+  bool    spectator;
+};
+typedef QList<Player> PlayerList;
+
+struct ServerRule
+{
+  QString   rule;
+  QString   value;
+};
+typedef QList<ServerRule> ServerRules;
+
+class ServerQuery : public QObject
+{
+	Q_OBJECT
+public:
+
+	enum Error { NoError, TimedOutError, EmptyResponseError, InvalidResponseError, InvalidInfoStringError, InvalidPlayerInfoError, SendError, HostLookupError, UnknownError };
+
+	explicit ServerQuery(QObject *parent = 0);
+  ~ServerQuery();
+
+  bool          setAddress(const QString &address, quint16 port = 27500);
+  bool          setAddress(const QHostAddress &address, quint16 port = 27500);
+  bool          query(bool pingServer = false);
+  Error         lastError() const;
+  PlayerList    playerList() const;
+  ServerRules   serverRules() const;
+
+private:
+  QUdpSocket*   mySocket;
+  QHostAddress	myAddress;
+	quint16				myPort;
+  Error					myLastErrorCode;
+  PlayerList    myPlayers;
+  ServerRules   myRules;
+  quint16       myPing;
+  bool          myIsProxyFlag;
+
+  bool          parseServerInfo();
+  int           ping(int count = 3, int timeout = 1000);
+
+
+  //========================================================================
+  /* Name fun conversion */
+  static char   ourReadableCharsTable[256];
+  static bool   ourReadableCharsTableInitialized;
+  static void   fillReadableCharsTable();
+public:
+  static QString convertNameFun(const QString &name);
+};
+
+#endif // SERVERQUERY_H

+ 136 - 0
ServerQueryThread.cpp

@@ -0,0 +1,136 @@
+/*
+GNU General Public License version 3 notice
+
+Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
+
+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 3 of the License, or
+(at your option) any later version.
+
+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, see < http://www.gnu.org/licenses/ >.
+*/
+
+#include "ServerQueryThread.h"
+#include <QReadWriteLock>
+#include <QThreadPool>
+#include <QThread>
+
+class Sleeper: public QThread
+{
+public:
+  static void msleep(unsigned long msec)
+  {
+    QThread::msleep(msec);
+  }
+};
+
+ServerQueryThread::ServerQueryThread():
+  QRunnable(),
+  myStopMutex(new QReadWriteLock()),
+  myServerInfoMutex(new QReadWriteLock()),
+  myStopThreadFlag(true),
+  myThreadRunningFlag(false)
+{
+  setAutoDelete(false);
+}
+
+ServerQueryThread::~ServerQueryThread()
+{
+  delete myStopMutex;
+  delete myServerInfoMutex;
+}
+
+void ServerQueryThread::setAddress(const QHostAddress &address, quint16 port)
+{
+  myServerAddress = address;
+  myServerPort = port;
+  return;
+}
+
+bool ServerQueryThread::startQueryThread()
+{
+  if(!isRunning())
+  {
+    QThreadPool::globalInstance()->start(this);
+    myStopThreadFlag = false;
+    myThreadRunningFlag = true;
+    return true;
+  }
+  return false;
+}
+
+void ServerQueryThread::stopQueryThread()
+{
+  myStopMutex->lockForWrite();
+  myStopThreadFlag = true;
+  myStopMutex->unlock();
+}
+
+quint8 ServerQueryThread::playerCount() const
+{
+  myServerInfoMutex->lockForRead();
+  quint8 c = myServerPlayerCount;
+  myServerInfoMutex->unlock();
+  return c;
+}
+
+bool ServerQueryThread::isRunning() const
+{
+  myStopMutex->lockForRead();
+  bool r = myThreadRunningFlag;
+  myStopMutex->unlock();
+  return r;
+}
+
+const QHostAddress& ServerQueryThread::serverAddress() const
+{
+  return myServerAddress;
+}
+
+quint16 ServerQueryThread::serverPort() const
+{
+  return myServerPort;
+}
+
+void ServerQueryThread::run()
+{
+  ServerQuery* query = new ServerQuery();
+  query->setAddress(myServerAddress, myServerPort);
+
+  forever
+  {
+    /* Handle thread stop request */
+    myStopMutex->lockForRead();
+    if(myStopThreadFlag)
+    {
+      myThreadRunningFlag = false;
+      myStopMutex->unlock();
+      delete query;
+      return;
+    }
+    myStopMutex->unlock();
+
+    if(query->query())
+    {
+      myServerInfoMutex->lockForWrite();
+      myServerPlayerCount = query->playerList().size();
+      myServerInfoMutex->unlock();
+    }
+    else
+    {
+      /* On fail set to 0xff to indicate bad read, so that the client don't act on this */
+      myServerInfoMutex->lockForWrite();
+      myServerPlayerCount = 0xff;
+      myServerInfoMutex->unlock();
+    }
+    Sleeper::msleep(1000);
+  }
+}
+

+ 55 - 0
ServerQueryThread.h

@@ -0,0 +1,55 @@
+/*
+GNU General Public License version 3 notice
+
+Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
+
+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 3 of the License, or
+(at your option) any later version.
+
+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, see < http://www.gnu.org/licenses/ >.
+*/
+
+#ifndef SERVERQUERYTHREAD_H
+#define SERVERQUERYTHREAD_H
+
+#include <QRunnable>
+#include "ServerQuery.h"
+
+class QReadWriteLock;
+
+class ServerQueryThread : public QRunnable
+{
+public:
+  ServerQueryThread();
+  virtual ~ServerQueryThread();
+
+  void setAddress(const QHostAddress& serverAddress, quint16 serverPort);
+  bool startQueryThread();
+  void stopQueryThread();
+  quint8 playerCount() const;
+  bool isRunning() const;
+  const QHostAddress& serverAddress() const;
+  quint16 serverPort() const;
+
+protected:
+  void run();
+
+private:
+  QReadWriteLock* myStopMutex; /* For reading thread state and for stopping the thread */
+  QReadWriteLock* myServerInfoMutex; /* For retrieving server information regarding the player count */
+  bool myStopThreadFlag;
+  bool myThreadRunningFlag;
+  QHostAddress myServerAddress;
+  quint16 myServerPort;
+  quint8 myServerPlayerCount;
+};
+
+#endif // SERVERQUERYTHREAD_H

+ 212 - 0
SshClient.cpp

@@ -0,0 +1,212 @@
+/*
+GNU General Public License version 3 notice
+
+Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
+
+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 3 of the License, or
+(at your option) any later version.
+
+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, see < http://www.gnu.org/licenses/ >.
+*/
+
+#include "SshClient.h"
+#include <QProcess>
+#include <QRegExp>
+#include <QDateTime>
+#include <QTimerEvent>
+#include <QStringList>
+#include <QDebug>
+
+SshClient::SshClient(QObject *parent) :
+  QObject(parent),
+  myProcess(new QProcess(this)),
+  myCommandRegex(new QRegExp("^([0-9]{4}-[0-9]{2}-[0-9]{2}\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\s\\+[0-9]{4}):\\s([A-Za-z]+):?\\s?(.*)$")),
+  myConnectedFlag(false),
+  myConnectionTimerID(0),
+  myPingTimerID(0),
+  myPongTimerID(0),
+  myClients(new QStringList())
+{
+  connect(myProcess, SIGNAL(readyRead()), SLOT(read()));
+  connect(myProcess, SIGNAL(finished(int)), SLOT(exited(int)));
+}
+
+SshClient::~SshClient()
+{
+  delete myCommandRegex;
+  delete myClients;
+}
+
+bool SshClient::isConnected() const
+{
+  return myConnectedFlag;
+}
+
+void SshClient::read()
+{
+  QString data(myProcess->readAll().trimmed());
+  if(data.isEmpty())
+    return;
+
+  QStringList lines = data.split("\n", QString::SkipEmptyParts);
+  QString line;
+  foreach(line, lines)
+  {
+    line = line.trimmed();
+    if(myCommandRegex->indexIn(line) != -1)
+    {
+      QDateTime time = QDateTime::fromString(myCommandRegex->capturedTexts().at(1).section(' ', 0, 1), "yyyy-MM-dd HH:mm:ss");
+      parse(time,
+            myCommandRegex->capturedTexts().at(2),
+            myCommandRegex->capturedTexts().at(3)
+            );
+    }
+  }
+}
+
+void SshClient::exited(int exitCode)
+{
+  qDebug() << "finished" << exitCode;
+}
+
+void SshClient::ping()
+{
+  write("PING\n");
+  myPingTimerID = startTimer(30000);
+}
+
+void SshClient::pong()
+{
+  killTimer(myPingTimerID);
+  myPongTimerID = startTimer(30000);
+}
+
+void SshClient::write(const QString &data)
+{
+  myProcess->write(data.toAscii());
+  myProcess->waitForBytesWritten();
+}
+
+void SshClient::parse(const QDateTime &time, const QString &command, const QString &commandData)
+{
+  QString cmdData = commandData.trimmed();
+  if(cmdData.startsWith(": "))
+    cmdData.remove(0, 2);
+
+  /* JOINED - Another user has just joined central */
+  if(command == "JOINED")
+  {
+    QRegExp a("^User '([a-zA-Z]+)'.+$");
+    if(a.indexIn(commandData) == -1)
+    {
+      if(!myClients->contains(a.capturedTexts().at(1)))
+        myClients->push_back(a.capturedTexts().at(1));
+    }
+    return;
+  }
+
+  /* PARTED - Another user has left central */
+  if(command == "PARTED")
+  {
+    QRegExp a("^User '([a-zA-Z]+)'.+$");
+    if(a.indexIn(commandData) == -1)
+      myClients->removeAll(a.capturedTexts().at(1));
+    return;
+  }
+
+  /* HELLO - Server acknowledge */
+  if(command == "HELLO")
+  {
+    ping();
+    myConnectedFlag = true;
+    killTimer(myConnectionTimerID);
+    emit connected();
+    return;
+  }
+
+  /* PING PONG - The connection is still up */
+  if(command == "PONG")
+  {
+    pong();
+    return;
+  }
+
+  /* SYS - Central generic message */
+  if(command == "SYS")
+  {
+    qDebug() << "Central Message" << commandData;
+    return;
+  }
+
+  /* BC - Broadcast order from central */
+  if(command == "BC")
+  {
+    QRegExp a("^([A-Za-z0-9]+) (.+),(.+),(.+),'(.+)','(.+)'$");
+    if(a.indexIn(commandData) == -1)
+      return;
+
+
+    qDebug() << "Broadcast Request" << commandData;
+//    BC 0x0hash hash QDEV,-dev-,qw://123.123,'testuser','test, with '',``twists''$ hehe'
+    return;
+  }
+
+  qDebug() << time << command << cmdData;
+}
+
+bool SshClient::connectToHost(const QString &user, const QString &host)
+{
+  myProcess->start("ssh", QStringList() << user + "@" + host);
+  bool r = myProcess->waitForStarted();
+  if(!r)
+    return false;
+  else
+  {
+    myConnectionTimerID = startTimer(30000);
+    return true;
+  }
+}
+
+void SshClient::disconnectFromHost()
+{
+  killTimer(myConnectionTimerID);
+  killTimer(myPingTimerID);
+  killTimer(myPongTimerID);
+
+  myProcess->terminate();
+  myProcess->waitForFinished();
+
+  myConnectedFlag = false;
+}
+
+void SshClient::timerEvent(QTimerEvent *e)
+{
+  if(e->timerId() == myConnectionTimerID)
+  {
+    disconnectFromHost();
+    emit error(ConnectionTimedOutError);
+    return;
+  }
+
+  if(e->timerId() == myPingTimerID)
+  {
+    disconnectFromHost();
+    emit error(ConnectionTimedOutError);
+    return;
+  }
+
+  if(e->timerId() == myPongTimerID)
+  {
+    killTimer(myPongTimerID);
+    ping();
+    return;
+  }
+}

+ 68 - 0
SshClient.h

@@ -0,0 +1,68 @@
+/*
+GNU General Public License version 3 notice
+
+Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
+
+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 3 of the License, or
+(at your option) any later version.
+
+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, see < http://www.gnu.org/licenses/ >.
+*/
+
+/* SSH client for connecting to QWNET central */
+#ifndef SSHCLIENT_H
+#define SSHCLIENT_H
+
+#include <QObject>
+
+class QProcess;
+class QDateTime;
+class QStringList;
+
+class SshClient : public QObject
+{
+  Q_OBJECT
+public:
+  enum Error { NoError, ConnectionTimedOutError };
+
+  explicit SshClient(QObject *parent = 0);
+  ~SshClient();
+  bool connectToHost(const QString& user, const QString &host);
+  bool isConnected() const;
+  void disconnectFromHost();
+
+signals:
+  void error(Error errorCode);
+  void connected();
+
+protected:
+  void timerEvent(QTimerEvent *e);
+
+private slots:
+  void read();
+  void exited(int exitCode);
+
+private:
+  QProcess* myProcess;
+  QRegExp*  myCommandRegex;
+  bool      myConnectedFlag;
+  int       myConnectionTimerID;
+  int       myPingTimerID;
+  int       myPongTimerID;
+  QStringList* myClients;
+
+  void parse(const QDateTime& time, const QString& command, const QString& commandData);
+  void write(const QString& data);
+  void ping();
+  void pong();
+};
+
+#endif // SSHCLIENT_H

+ 25 - 0
cimsqwbot.pro

@@ -0,0 +1,25 @@
+TEMPLATE = app
+CONFIG += console
+QT     += network sql xml
+QT     -= gui
+
+SOURCES += main.cpp \
+    Client.cpp \
+    App.cpp \
+    ServerQuery.cpp \
+    SshClient.cpp \
+    ServerQueryThread.cpp
+
+HEADERS += \
+    Client.h \
+    App.h \
+    ServerQuery.h \
+    SshClient.h \
+    ServerQueryThread.h
+
+win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../qwclient/release/ -lqwclient
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../qwclient/debug/ -lqwclient
+else:unix:!macx:!symbian: LIBS += -L$$PWD/../qwclient/ -lqwclient
+
+INCLUDEPATH += $$PWD/../qwclient
+DEPENDPATH += $$PWD/../qwclient

+ 24 - 0
main.cpp

@@ -0,0 +1,24 @@
+#include "Client.h"
+#include "App.h"
+#include <QThread>
+
+class Sleeper: public QThread
+{
+public:
+	static void msleep(unsigned long msecs)
+	{
+		QThread::msleep(msecs);
+	}
+};
+
+int main(int argc, char **argv)
+{
+	App a(argc, argv);
+
+	for(;;)
+	{
+		App::processEvents();
+		Sleeper::msleep(1);
+	}
+	return 0;
+}