123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- /*
- 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 "Client.h"
- #include <QString>
- #include <stdio.h>
- #include <QTcpSocket>
- #include <QStringList>
- #include <QTime>
- #include <QTimer>
- #include <QFile>
- #include "App.h"
- #include "Settings.h"
- static QRegExp rxColoredPattern("&c[0-9a-fA-F]{3}");
- static QRegExp rxBrackets("\\{[^}]*\\}");
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Client::Client(App *app, ActiveClient* ac):
- QWClient(),
- myActiveClient(ac),
- myApp(app),
- myOnServerFlag(false),
- mySpamMutedFlag(false),
- myQWMutedFlag(false),
- myKeepNickTimer(new QTimer()),
- myFloodTimer(new QTimer()),
- myBroadcastFloodTimer(new QTimer()),
- mySPDetectionTimer(new QTimer()),
- mySPSupport(false),
- mySPAutoDetect(true),
- myMaxClients(0),
- myCmdScheduledTimer(new QTimer())
- {
- rxBrackets.setMinimal(true);
- myKeepNickTimer->setSingleShot(true);
- myFloodTimer->setSingleShot(true);
- myBroadcastFloodTimer->setSingleShot(true);
- mySPDetectionTimer->setSingleShot(true);
- myCmdScheduledTimer->setSingleShot(true);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Client::~Client()
- {
- delete myKeepNickTimer;
- delete myFloodTimer;
- delete myBroadcastFloodTimer;
- delete mySPDetectionTimer;
- delete myCmdScheduledTimer;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::connect(const char *host, quint16 port)
- {
- setPing(13);
- QWClient::connect(host, port);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::say(const QString &msg, const QString &nickName)
- {
- QString cmd("say ");
- if(!nickName.isEmpty() && mySPSupport)
- cmd.append("s-p \"" + nickName + "\" ");
- cmd.append(msg);
- sendCmd(cmd.toLatin1().data());
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::setTeam(const QString &msg)
- {
- sendCmd(QString("setinfo \"team\" \"" + msg + "\"").toLatin1().data());
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::disconnect()
- {
- QWClient::disconnect();
- myOnServerFlag = false;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::print(const QString &msg)
- {
- QString str;
- str = "[" + QTime::currentTime().toString(Qt::ISODate) + "] " + QString(host()) + ":" + QString::number(port()) + "> " + msg;
- QByteArray b = str.toLatin1();
- Client::stripColor(b.data());
- str = QString::fromLatin1(b.data());
- myApp->print(str);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::setPlayerList(PlayerList &playerList)
- {
- myPlayerList = playerList;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onDisconnect()
- {
- print("Disconnected..\n");
- myOnServerFlag = false;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- bool Client::isQWMuted() const
- {
- return myQWMutedFlag;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- bool Client::isSpamMuted() const
- {
- return mySpamMutedFlag;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::parsePrintedLine()
- {
- // Deletes invalid map for redownloading next retry
- if(!myOnServerFlag)
- {
- QRegExp rx("^Map model file does not match \\((.+)\\),");
- if(rx.indexIn(myPrintLine) != -1)
- {
- print("Invalid map, deleting this map for redownload on next retry.\n");
- QFile::remove(quakeDir() + "/" + gameDir() + "/" + rx.cap(1));
- return;
- }
- }
- // Detects whether s-p command is supported
- if(mySPDetectionTimer->isActive())
- {
- if(myPrintLine.startsWith(Settings::globalInstance()->botName() + ": s-p"))
- {
- mySPSupport = false;
- mySPDetectionTimer->stop();
- setAutoDetectSP(false); // Don't try detecting again on next map
- }
- else if(myPrintLine.startsWith("usage: s-p id/name txt"))
- {
- mySPSupport = true;
- mySPDetectionTimer->stop();
- setAutoDetectSP(false); // Don't try detecting again on next map
- }
- }
- // Find the player that printed this line of text
- Player player, bestPlayer;
- quint16 lastMatchSize = 0;
- foreach(player, myPlayerList)
- {
- if(player.spectator && myPrintLine.startsWith("[SPEC] " + player.name))
- {
- if(lastMatchSize < (player.name.size() + 7))
- {
- lastMatchSize = (player.name.size() + 7);
- bestPlayer = player;
- }
- continue;
- }
- if(myPrintLine.startsWith(player.name))
- {
- if(lastMatchSize < player.name.size())
- {
- lastMatchSize = player.name.size();
- bestPlayer = player;
- }
- continue;
- }
- }
- if(!lastMatchSize)
- return;
- QString nick(bestPlayer.name);
- if(bestPlayer.spectator)
- nick.prepend("(spec) ");
- QString message(myPrintLine.right(myPrintLine.size() - lastMatchSize));
- QRegExp regex("^:\\s+\\.(spam|qw|help|qw_mute|qw_unmute|spam_mute|spam_unmute|lm|about|qw_local)\\s*(.+)$");
- if(regex.indexIn(message) == -1)
- return;
- /* Flood prot */
- QTime currentTime = QTime::currentTime();
- int floodProtTime = Settings::globalInstance()->floodProtTime();
- if(myFloodTimer->isActive())
- {
- if(!myFloodMsgPrinted)
- {
- say("FloodProt: Not so fast, wait " + QString::number(floodProtTime + currentTime.secsTo(myFloodTimerStart)) + " sec(s).", bestPlayer.name);
- myFloodMsgPrinted = true;
- }
- return;
- }
- myFloodTimerStart = currentTime;
- myFloodTimer->start(floodProtTime*1000);
- myFloodMsgPrinted = false;
- QString command = regex.capturedTexts().at(1);
- QString args = regex.capturedTexts().at(2);
- if(command == "help")
- {
- say("Broadcast a message: .qw <message> or .spam <message>", bestPlayer.name);
- say("Broadcast locally (only to the game servers this bot is connected to): .qw_local <message>", bestPlayer.name);
- say("(Un)Mute: .qw_mute .qw_unmute or .spam_mute .spam_unmute", bestPlayer.name);
- say("Last 5 messages: .lm", bestPlayer.name);
- say("About this bot: .about", bestPlayer.name);
- return;
- }
- if(command == "about")
- {
- say("ServeMe specbots by mihawk & Haudraufwienix, find us on #qwnet @ irc.quakenet.org", bestPlayer.name);
- return;
- }
- if(command == "qw" || command == "spam" || command == "qw_local")
- {
- if(!args.trimmed().size())
- {
- say("The format is ." + command + " <message>.", bestPlayer.name);
- return;
- }
- /* Floodprot for broadcasting commands */
- if(command == "qw" || command == "spam" || command == "qw_local")
- {
- if(myBroadcastFloodTimer->isActive())
- {
- say("FloodProt: Wait " + QString::number(myBroadcastFloodTimer->interval()/1000 + currentTime.secsTo(myBroadcastFloodTimerStart)) + " sec(s) before issuing a new " + command, bestPlayer.name);
- return;
- }
- int time = 0;
- if(command == "qw")
- time = Settings::globalInstance()->qwFloodProtTime();
- else if(command == "spam")
- time = Settings::globalInstance()->spamFloodProtTime();
- else if(command == "qw_local")
- time = 30;
- myBroadcastFloodTimerStart = currentTime;
- myBroadcastFloodTimer->start(time*1000);
- }
- // Prepare all strings to be broadcasted
- QString server(QString(host()) + ":" + QString::number(port()));
- // Tries to find the hostname for this ip in the cache
- QString resolvedServer(myApp->serverHostName(server));
- if(!resolvedServer.isEmpty())
- resolvedServer.append(":" + QString::number(port()));
- else
- resolvedServer = server;
- // Internal message to be broadcast within our network of bots (don't need to have namefun parsed)
- // The internal message uses the resolvedServer, because this message doesn't pass tru central, thus
- // the server ip isn't replaced by a hostname automatically
- QString internalMessage("-" + command + "- " + nick + " - " + resolvedServer + " " + QString::number(playerCount()) + "/" + QString::number(myMaxClients) + " : " + args.trimmed());
- myApp->broadcast(internalMessage, myActiveClient);
-
- // If this is a local message don't broadcast externally
- if (command == "qw_local") {
- say("Broadcasting within qw servers only..." , bestPlayer.name);
- return;
- }
-
- // Message that will be broadcast to other networks (needs namefun parsing)
- // this message passes tru central, thus the server ip will be replaced by a hostname
- // automatically
- nick = parseNameFun(nick);
- QString parsedMsg = parseNameFun(args.trimmed());
- server.append(" " + QString::number(playerCount()) + "/" + QString::number(myMaxClients)); // append player count to the server (central needs another parm for this!)
- if(!Settings::globalInstance()->developerMode())
- myApp->requestBroadcast(command, nick, server, parsedMsg);
- else
- myApp->requestBroadcast("dev", nick, server, parsedMsg);
- say("Broadcasting...", bestPlayer.name);
- return;
- }
- if(command == "qw_mute")
- {
- say("Qw frequency muted.", bestPlayer.name);
- myQWMutedFlag = true;
- return;
- }
- if(command == "qw_unmute")
- {
- say("Qw frequency unmuted.", bestPlayer.name);
- myQWMutedFlag = false;
- return;
- }
- if(command == "spam_mute")
- {
- say("Spam frequency muted.", bestPlayer.name);
- mySpamMutedFlag = true;
- return;
- }
- if(command == "spam_unmute")
- {
- say("Spam frequency unmuted.", bestPlayer.name);
- mySpamMutedFlag = false;
- return;
- }
- if(command == "lm")
- {
- QStringList messages = myApp->lastMessages();
- if(!messages.size())
- {
- say("None", bestPlayer.name);
- return;
- }
- QString msg;
- int i = 0;
- foreach(msg, messages)
- {
- if(++i > 5)
- break;
- say(msg, bestPlayer.name);
- }
- return;
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- int Client::playerCount() const
- {
- Player c;
- int pc = 0;
- foreach(c, myPlayerList)
- {
- if(c.spectator)
- continue;
- pc++;
- }
- return pc;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::setMaxClients(int maxClients)
- {
- myMaxClients = maxClients;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- int Client::maxClients() const
- {
- return myMaxClients;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onPrint(int, const char *msg)
- {
- if(!strlen(msg))
- return;
- QString text(msg);
- if(text.endsWith('\n'))
- {
- myPrintLine.append(text);
- parsePrintedLine();
- 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;
- mySpamMutedFlag = false;
- myQWMutedFlag = false;
- setPing(13);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onChallenge()
- {
- print("challenge\n");
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onConnection()
- {
- print("connection\n");
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onConnected()
- {
- print("connected\n");
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onDownloadStarted(const char *fileName)
- {
- print("Download started " + QString(fileName) + "\n");
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::run()
- {
- // Keep nick... Simply set name again after 30 secs
- if(!myKeepNickTimer->isActive())
- {
- setName(Settings::globalInstance()->botName().toLatin1().data());
- myKeepNickTimer->start(30000);
- }
- // Scheduled commands
- if(!myCmdScheduled.isEmpty() && !myCmdScheduledTimer->isActive())
- {
- sendCmd(myCmdScheduled.toLatin1().data());
- myCmdScheduled.clear();
- }
- QWClient::run();
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onOOBPrint(const char *msg)
- {
- print(QString(msg));
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::onStuffedCmd(const char *cmd)
- {
- QString strCmd(cmd);
- if(strCmd == "skins") //connection sequence complete
- {
- myOnServerFlag = true;
- setPing(Settings::globalInstance()->botPing());
- if(mySPAutoDetect)
- spDetection();
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 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");
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::setAutoDetectSP(bool autoDetect)
- {
- mySPAutoDetect = autoDetect;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::spDetection()
- {
- say("s-p");
- mySPDetectionTimer->start(2000);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- QString Client::parseNameFun(const QString &string)
- {
- QByteArray b(string.toLatin1());
- // Remove normal QW colored text
- QWClient::stripColor(b.data());
- // Remove new QW colored text
- QString text = QString::fromLatin1(b);
- int pos = 0;
- int index_adj;
- while ((pos = rxBrackets.indexIn(text, pos)) != -1) {
- QString match = rxBrackets.cap(0);
- QString newtext = rxBrackets.cap(0).replace(rxColoredPattern, "");
- newtext = newtext.mid(1, newtext.size()-2);
- index_adj = match.length() - newtext.length();
- text.replace(match, newtext);
- pos += rxBrackets.matchedLength() - index_adj;
- }
- return text;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void Client::scheduleCmd(const QString &cmd, int time)
- {
- myCmdScheduled = cmd;
- myCmdScheduledTimer->start(time);
- }
|