| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 | /*GNU General Public License version 3 noticeCopyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.This program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong 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()),  myQWBroadcastFloodTimer(new QTimer()),  mySpamBroadcastFloodTimer(new QTimer()),  mySPDetectionTimer(new QTimer()),  mySPSupport(false),  mySPAutoDetect(true),  myMaxClients(0),  myCmdScheduledTimer(new QTimer()){  rxBrackets.setMinimal(true);  myKeepNickTimer->setSingleShot(true);  myFloodTimer->setSingleShot(true);  myQWBroadcastFloodTimer->setSingleShot(true);  mySpamBroadcastFloodTimer->setSingleShot(true);  mySPDetectionTimer->setSingleShot(true);  myCmdScheduledTimer->setSingleShot(true);}// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Client::~Client(){  delete myKeepNickTimer;  delete myFloodTimer;  delete myQWBroadcastFloodTimer;  delete mySpamBroadcastFloodTimer;  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)\\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> and .spam <message>", bestPlayer.name);    say("(Un)Mute: .qw_mute .qw_unmute or .spam_mute .spam_unmute", bestPlayer.name);    say("Last 5 messages: .lm", bestPlayer.name);    return;  }  if(command == "qw" || command == "spam")  {    if(!args.trimmed().size())    {      say("The format is ." + command + " <message>.", bestPlayer.name);      return;    }    /* Floodprot for broadcasting commands */    if(command == "qw")    {      int qwFloodProtTime = Settings::globalInstance()->qwFloodProtTime();      if(myQWBroadcastFloodTimer->isActive())      {        say("FloodProt: Wait " + QString::number(qwFloodProtTime + currentTime.secsTo(myQWBroadcastFloodTimerStart)) + " secs before new .qw", bestPlayer.name);        return;      }      myQWBroadcastFloodTimerStart = currentTime;      myQWBroadcastFloodTimer->start(qwFloodProtTime*1000);    }    else if(command == "spam")    {      say("Only .qw is allowed within QW servers. Please use .qw to broadcast your message.");      return;//      int spamFloodProtTime = Settings::globalInstance()->spamFloodProtTime();//      if(mySpamBroadcastFloodTimer->isActive())//      {//        say("FloodProt: Wait " + QString::number(spamFloodProtTime + currentTime.secsTo(mySpamBroadcastFloodTimerStart)) + " secs before new .spam", bestPlayer.name);//        return;//      }//      mySpamBroadcastFloodTimerStart = currentTime;//      mySpamBroadcastFloodTimer->start(spamFloodProtTime*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);    // 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(){  //  if(!myJoinMessageTimer->isActive() && !myJoinMessagePrinted)  //  {  //    say("Hi, I am QWNET's bot, type .help to see my commands.");  //    myJoinMessagePrinted = true;  //  }  // 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(){//  scheduleCmd("say s-p", 2000);  say("s-p");  mySPDetectionTimer->start(2000);}// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static QRegExp coloredTextStrings("(\\{&c[0-9a-fA-F]{3}).*?(\\{&c[0-9a-fA-F]{3})|(\\})");QString Client::parseNameFun(const QString &string){  QByteArray b(string.toLatin1());  QWClient::stripColor(b.data());  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);}
 |