/* GNU General Public License version 3 notice Copyright (C) 2012 Mihawk . 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 "App.h" #include "Client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "SshClient.h" #include "ActiveClient.h" #include "Settings.h" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /** Used just to expose the msleep function from QThread @author Mihawk */ class Sleeper: public QThread { public: static void msleep(unsigned long msecs) { QThread::msleep(msecs); } }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - App::App(int &argc, char **argv) : QCoreApplication(argc, argv), myServer(new QTcpServer()), mySocketConnectedFlag(false), mySshClient(new SshClient(this)) { if(!parseCommandLine()) { QTimer::singleShot(0, this, SLOT(quit())); return; } print("CIMS Bot Service v0.12\n========================================================\n"); setApplicationName("CIMSBOT"); setOrganizationDomain("http://redmine.b4r.org/projects/cimsqwbot/"); setOrganizationName("CIMS"); setApplicationVersion("0.12"); myClientsFrameTimerID = startTimer(0); loadServerList(); print("Connecting to central...\n"); connect(mySshClient, SIGNAL(connected()), SLOT(onCentralConnection())); mySshClient->connectToHost(Settings::globalInstance()->sshUserName(), Settings::globalInstance()->sshHostName()); Settings::globalInstance()->save(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - App::~App() { Settings::globalInstance()->save(); cleanup(); delete myServer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool App::parseCommandLine() { if(argc() < 2) return true; QStringList args = App::arguments(); QString arg; QStringList::const_iterator itr; for(itr = args.constBegin()+1; itr != args.constEnd(); ++itr) { arg = *itr; if(arg == "--help" || arg == "-h") { printf("Usage: %s [--config/-c config_file] [-h]\n", args.at(0).section("/", -1).toAscii().data()); return false; } else if(arg == "--config" || arg == "-c") { itr++; if(itr == args.constEnd()) { printf("--config: Expected config file path.\n"); return false; } arg = *itr; printf("Using config file [%s]...\n", arg.toAscii().data()); Settings::globalInstance()->changeConfigFile(*itr); return true; } } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::onDisconnection() { mySocketConnectedFlag = false; mySocket->deleteLater(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::loadServerList() { Settings::ServerList list = Settings::globalInstance()->serverList(); Settings::Server sv; foreach(sv, list) { ActiveClient* ac = new ActiveClient(this, sv.supportsSendPrivate); ac->setAddress(QHostAddress(sv.address), sv.port); ac->client()->setQuakeFolder(Settings::globalInstance()->quakeFolder().toAscii().data()); ac->client()->setName(Settings::globalInstance()->botName().toAscii().data()); ac->client()->setSpectator(Settings::globalInstance()->botSpectator()); ac->client()->setPing(Settings::globalInstance()->botPing()); ac->client()->setColor(Settings::globalInstance()->botTopColor(), Settings::globalInstance()->botBottomColor()); myClients.push_back(ac); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::saveServerList() { Settings::ServerList list; ActiveClient* ac; foreach(ac, myClients) { Settings::Server sv; sv.address = ac->serverAddressString().split(":").at(0); sv.port = ac->serverAddressString().split(":").at(1).toUShort(); sv.supportsSendPrivate = ac->client()->supportsSendPrivate(); list.push_back(sv); } Settings::globalInstance()->setServerList(list); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::parseAddClientCommand(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::parseRemoveClientCommand(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: ?\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 == "add") { parseAddClientCommand(args); return; } if(cmd == "remove") { parseRemoveClientCommand(args); return; } if(cmd == "say") { broadcast(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; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const QStringList& App::lastMessages() const { return myLastMessages; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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("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 false; //telnet adm disabled!! 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, this); ac->setAddress(ha, port); ac->client()->setQuakeFolder(Settings::globalInstance()->quakeFolder().toAscii().data()); ac->client()->setName(Settings::globalInstance()->botName().toAscii().data()); ac->client()->setSpectator(Settings::globalInstance()->botSpectator()); ac->client()->setPing(Settings::globalInstance()->botPing()); ac->client()->setColor(Settings::globalInstance()->botTopColor(), Settings::globalInstance()->botBottomColor()); myClients.push_back(ac); saveServerList(); print("Client added to watch list.\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::removeClient(const QString &host, quint16 port) { ActiveClient* ac; QHostAddress ha = QHostInfo::fromName(host).addresses().at(0); foreach(ac, myClients) { if(ac->serverAddressString() == QString(ha.toString() + ':' + QString::number(port))) { ac->client()->disconnect(); delete ac; myClients.removeAll(ac); saveServerList(); print("Client removed from watch list.\n"); return; } } print("Client not found on the list.\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::listClients() { ActiveClient* ac; foreach(ac, myClients) { print(QString(ac->serverAddressString() + '\n')); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::activeClientsReplyCounters(int *serverCount, int *playerCount, ActiveClient *ignoreClient) { ActiveClient* ac; foreach(ac, myClients) { if(ac == ignoreClient) continue; if(ac->client()->state() == Client::ConnectedState) { int pc = ac->playerCount(); if(pc == 255 || pc == 0) pc = 0; else pc--; *playerCount += pc; (*serverCount)++; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::timerEvent(QTimerEvent *e) { if(e->timerId() == myClientsFrameTimerID) { ActiveClient *ac; foreach(ac, myClients) { ac->run(); } // HostNames scheduled daily refresh int currentHour = QTime::currentTime().hour(); int refreshHour = Settings::globalInstance()->refreshHostNamesHour(); if(currentHour == refreshHour && !myHostNamesRequested) { print("Refreshing hostname cache...\n"); requestCachedHostNames(); myHostNamesRequested = true; } else if(currentHour != refreshHour && myHostNamesRequested) { myHostNamesRequested = false; } } Sleeper::msleep(1); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::requestBroadcast(const QString &type, const QString &user, const QString &server, const QString &message) { if(!Settings::globalInstance()->developerMode()) mySshClient->write("REQ_BC QWalt,-" + type + "-,qw://" + server + ",'" + user + "','" + message + "'\n"); else mySshClient->write("REQ_BC QDEV,-dev-,qw://" + server + ",'" + user + "','" + message + "'\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::addMessageToHistory(const QString &msg) { myLastMessages.push_back(msg); if(myLastMessages.size() > 5) myLastMessages.removeAt(0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::broadcast(const QString &msg, ActiveClient* ignoredClient) { ActiveClient* ac; QString frequency = msg.section(' ', 0, 0); addMessageToHistory(msg); foreach(ac, myClients) { if(ac == ignoredClient) continue; if((frequency == "-qw-" && ac->client()->isQWMuted()) || (frequency == "-spam-" && ac->client()->isSpamMuted())) continue; if(ac->client()->state() == Client::ConnectedState) ac->client()->say(msg); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::broadcast(const QString &msg, int *serverCount, int *userCount) { ActiveClient* ac; *serverCount = 0; *userCount = 0; QString frequency = msg.section(' ', 0, 0); addMessageToHistory(msg); foreach(ac, myClients) { if((frequency == "-qw-" && ac->client()->isQWMuted()) || (frequency == "-spam-" && ac->client()->isSpamMuted())) continue; if(ac->client()->state() == Client::ConnectedState) { ac->client()->say(msg); *userCount += ac->playerCount() - 1; (*serverCount)++; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::cleanup() { ActiveClient* ac; foreach(ac, myClients) { ac->client()->disconnect(); delete ac; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::setReplyHashAndWaitForReply(const QString &serverAddress, const QString &hash) { ActiveClient* ac; foreach(ac, myClients) { if(serverAddress == ac->serverAddressString()) { ac->setReplyHashAndWaitForReply(hash); return; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::incrementReplyCounters(const QString &hash, int userCount, int channelCount, int playerCount, int serverCount) { ActiveClient* ac; foreach(ac, myClients) { if(ac->replyHash() == hash) { ac->incrementReplyCounters(userCount, channelCount, playerCount, serverCount); return; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::requestCachedHostNames() { ActiveClient* ac; foreach(ac, myClients) { print("Requested HostName for server " + ac->serverAddressString() + "\n"); mySshClient->write("REQ_DNS " + ac->serverAddressString() + "\n"); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::setServerHostName(const QString &serverAddress, const QString &hostName) { ActiveClient* ac; foreach(ac, myClients) { if(ac->serverAddressString() == serverAddress) { print("HostName for " + serverAddress + " set to " + hostName + "\n"); ac->setHostName(hostName); return; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - QString App::serverHostName(const QString &serverAddress) const { ActiveClient* ac; foreach(ac, myClients) { if(ac->serverAddressString() == serverAddress) { return ac->hostName(); } } return QString(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void App::onCentralConnection() { print("Connected!\nRefreshing cached hostnames...\n"); requestCachedHostNames(); }