-
RicBot
Hello everybody :)
A long time ago I started a project called RicBot http://cboard.cprogramming.com/showthread.php?t=75223
Which I just left and didn't bother with anymore, I decided I would go back to it again though. So today I added a little piece of code which will know it has timedout if it didn't send or receive any data within 150 seconds.
There is something I want to do to it, but I'm not sure how, so I'm looking for suggestions. As it is, it receives the data and then loops through the main body of code. This is not a very good method. The data should be parsed into strings. So that each string is a single line of data. Like a little parsing function which seperates strings if they have '\r', '\n' or a combination of both.
So that once it has parsed the string, into several strings it loops through the main body, does what it needs with the string, then loops again with the next string. Also if the data doesn't end with a '\r', '\n' or combination of both, it will save that piece of data until the next time it receives data through the socket, and will add the new data received from the socket to the end of the current string. Then parse that string.
It doesn't have to split it into seperate strings I guess, there is no point in having an array of them I guess. Probably just the main string which receives the data, then that goes into another string to store it. It parses it for lines, puts the first line into the main string which will go through the code, and then stores the rest until it comes back again, then it puts the next string into the main string and loops through the code etc.
I am not sure how to do this, here is the code I got so far.
Code:
/* This is RicBot, written by John */
#pragma warning(disable: 4786)
#include <winsock2.h>
#pragma comment(lib,"WS2_32.lib")
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
/////////////////////////////
//This isn't multithreaded yet, so the code below isn't
//needed but I have added it from an older program I made
//to save me time ;)
//#include <process.h>
//Using MSVC++
//For Win32 Release
//Project -> Settings -> C/C++ -> Code Generation
//Use-Runtime Library -> Multithreaded
//#pragma comment(lib, "libcmt.lib")
//For Win32 Debug
//Project -> Settings -> C/C++ -> Code Generation
//Use-Runtime Library -> Debug Multithreaded
//#pragma comment(lib, "libcmtd.lib")
///////////////////////////////
SOCKET IRC_socket; //Global
using namespace std;
//#define RicBot
#define AUTHENTICATION
//Function Prototypes
int CreateSock();
int ConnectIRCSock(int Port, string IP);
int SendIRC_func(string SendData_string);
int RecvIRC_func(string &RecvData_string);
int OnConnect(string BotNick, string IP);
int ReConnect(int Port, string IP, string BotNick);
int CheckForTimeout(int Port, string IP, string BotNick);
int CheckForPing(string RecvData_string);
int Get_Nick_Ident_Host(string RecvData_string);
int JoinChannel(string Channel);
int MessageChannel(string Message, string Channel);
string RandomNick(string BotNick);
struct IdentNickHostInfo {
string Nick;
string Ident;
string Host;
} AuthInfo;
int PingTime = 0, TimeElapsed = 0;
#ifdef RicBot
#pragma comment(linker,"/subsystem:windows")
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow){
#endif
#ifndef RicBot
#pragma comment(linker,"/subsystem:console")
int main(int argc, char* argv[]){
#endif
string SendData_string, RecvData_string;
int Port = 6667;
string IP = "";
string BotNick = "";
string Channel = "";
//Nick/Ident/Host for Auth
string AuthNick = "";
string AuthIdent = "";
string AuthHost = "";
string CMDprefix = "!";
//our commands
string VersionCMD = "version";
string Version = "RicBot";
string ReconnectCMD = "reconnect";
string KillCMD = "stop";
string RawCMD = "raw";
CreateSock();
ConnectIRCSock(Port, IP);
OnConnect(BotNick, IP);
PingTime = GetTickCount()/1000;
while(1){
CheckForTimeout(Port, IP, BotNick);
if(RecvIRC_func(RecvData_string) == -1){
Sleep(10000);
ReConnect(Port, IP, BotNick);
}
#ifndef RicBot
cout<<RecvData_string;
#endif
CheckForPing(RecvData_string);
if(RecvData_string[0] == ':'){
//--------------------------
string strData = "";
vector<string> SplitData;
int i=0;
while(RecvData_string[i] != '\r'){
if(RecvData_string[i] == ' '){
if(RecvData_string[i+1] != '\r'){
SplitData.push_back(strData);
strData = "";
i++;
}
}
strData = strData + RecvData_string[i];
i++;
}
SplitData.push_back(strData);
//--------------------------
//here our server commands go.
if(SplitData.size() >= 2){
//We can join a channel now.
if(SplitData[1] == "001"){
JoinChannel(Channel);
}
//nick is in use, so change it.
if(SplitData[1] == "433"){
//(BotNick=RandomNick(BotNick));
OnConnect(BotNick=RandomNick(BotNick), IP);
}
}
if(SplitData.size() >= 4){
if(SplitData[1] == "KICK" && SplitData[3] == BotNick){
Sleep(3000);
JoinChannel(Channel);
}
}
//gets the nick/ident/host of user sending message.
if(Get_Nick_Ident_Host(RecvData_string) == 0){
//here are our user commands
if(SplitData.size() >= 4){
//this line will remove the :
SplitData[3] = SplitData[3].substr(1);
//will check if our first character is our prefix
if(SplitData[3][0] == CMDprefix[0]){
//this will remove our prefix.
SplitData[3] = SplitData[3].substr(1);
//--------------------------
//commands without Auth go here
// if they have no arguments
if(SplitData.size() >= 4){
if(SplitData[3] == VersionCMD){
MessageChannel(Version, Channel);
}
}
//Here is our authentication check.
#ifdef AUTHENTICATION
if(AuthInfo.Nick == AuthNick){
if(AuthInfo.Ident == AuthIdent){
if(AuthInfo.Host == AuthHost){
#endif
//commands with Auth go here
//-----------------------
//if command has 0 arguments
if(SplitData.size() == 4){
if(SplitData[3] == ReconnectCMD){
string QUIT = "QUIT Reconnecting...\r";
SendIRC_func(QUIT);
ReConnect(Port, IP, BotNick);
}
if(SplitData[3] == KillCMD){
exit(0);
}
}
//--------------------------
//if command has 1 argument or more
if(SplitData.size() >= 5){
if(SplitData[3] == RawCMD){
int sizerawmessage = (int)SplitData.size() - 4;
ostringstream rawmessage;
for(int i = 0; i < sizerawmessage; i++){
rawmessage<<SplitData[i+4]<<" ";
}
rawmessage<<"\r";
SendIRC_func(rawmessage.str());
}
}
}
}
}
}
#ifdef AUTHENTICATION
}
}
}
#endif
RecvData_string = '\0';
}
return 0;
}
////////////////////////////////////////
int CreateSock(){
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
return -1;
return 0;
}
////////////////////////////////////////
int ConnectIRCSock(int Port, string host){
IRC_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (IRC_socket == INVALID_SOCKET){
WSACleanup();
return -2;
}
sockaddr_in remotehost;
remotehost.sin_family = AF_INET;
remotehost.sin_addr.s_addr = inet_addr(host.c_str());
remotehost.sin_port = htons(Port);
if (connect(IRC_socket, (SOCKADDR*) &remotehost, sizeof(remotehost) ) == SOCKET_ERROR){
closesocket(IRC_socket);
WSACleanup();
return -1;
}
return 0;
}
////////////////////////////////////////
int SendIRC_func(string SendData_string){
if (send(IRC_socket, SendData_string.c_str(), (int)SendData_string.size(), 0 ) <= 0){
WSACleanup();
return -1;
}
PingTime = GetTickCount()/1000;
return 0;
}
////////////////////////////////////////
int RecvIRC_func(string &RecvData_string){
char TempBuffer[1025]; //extra char for null terminating character
int BytesRecv;
if ((BytesRecv = recv(IRC_socket, TempBuffer, 1024, 0 )) <= 0){
WSACleanup();
return -1;
}
TempBuffer[BytesRecv] = '\0';
RecvData_string = TempBuffer;
PingTime = GetTickCount()/1000;
return 0;
}
////////////////////////////////////////
int OnConnect(string BotNick, string IP){
ostringstream tempstr;
tempstr <<"Nick "<<BotNick<<'\n';
SendIRC_func(tempstr.str());
tempstr.str("");
tempstr<<"USER "<<BotNick<<" \"\" \""<< IP <<
"\" :"<<BotNick<<'\n';
SendIRC_func(tempstr.str());
return 0;
}
////////////////////////////////////////
int ReConnect(int Port, string IP, string BotNick){
closesocket(IRC_socket);
CreateSock();
ConnectIRCSock(Port, IP);
OnConnect(BotNick, IP);
return 0;
}
////////////////////////////////////////
int CheckForTimeout(int Port, string IP, string BotNick){
if(TimeElapsed < PingTime+150){
TimeElapsed = GetTickCount()/1000;
return -1;
}
Sleep(10000);
ReConnect(Port, IP, BotNick);
PingTime = GetTickCount()/1000;
return 0;
}
////////////////////////////////////////
int CheckForPing(string RecvData_string){
string ServerPing;
ostringstream SendPong;
int i = 0;
//////////////////////////////////////////////////////
/*if(RecvData_string[i] == 'P' && RecvData_string[i+1] == 'I' &&
RecvData_string[i+2] == 'N' && RecvData_string[i+3] == 'G' ){
i += 5;
while(RecvData_string[i] != '\r'){
ServerPing = ServerPing + RecvData_string[i];
i++;
}
ServerPing = ServerPing + '\0';
SendPong << "PONG " << ServerPing << '\n';
SendIRC_func(SendPong.str());
return -1;
}*/
if(RecvData_string[i] == 'P' && RecvData_string[i+1] == 'I' &&
RecvData_string[i+2] == 'N' && RecvData_string[i+3] == 'G' ){
RecvData_string[1] = 'O';
SendIRC_func(RecvData_string);
}
//////////////////////////////////////////////////////
while(i < (int)RecvData_string.size()){
if(RecvData_string[i] == '\r' && RecvData_string[i+1] == '\n' &&
RecvData_string[i+2] == 'P' && RecvData_string[i+3] == 'I' &&
RecvData_string[i+4] == 'N' && RecvData_string[i+5] == 'G'){
i += 7;
while(RecvData_string[i] != '\r'){
ServerPing = ServerPing + RecvData_string[i];
i++;
}
ServerPing = ServerPing + '\0';
SendPong << "PONG " << ServerPing << '\n';
SendIRC_func(SendPong.str());
return -2;
}
i++;
}
//////////////////////////////////////////////////////
return 0;
}
////////////////////////////////////////
int Get_Nick_Ident_Host(string RecvData_string){
int i = 1;
AuthInfo.Nick = "";
AuthInfo.Ident = "";
AuthInfo.Host = "";
////////////////////////////////////////////
while(RecvData_string[i] != '!'){
AuthInfo.Nick = AuthInfo.Nick + RecvData_string[i];
i++;
if(i == RecvData_string.size()){
return -1;
}
}
////////////////////////////////////////////
i++;
while(RecvData_string[i] != '@'){
AuthInfo.Ident = AuthInfo.Ident + RecvData_string[i];
i++;
if(i == RecvData_string.size()){
return -2;
}
}
////////////////////////////////////////////
i++;
while(RecvData_string[i] != ' '){
AuthInfo.Host = AuthInfo.Host + RecvData_string[i];
i++;
if(i == RecvData_string.size()){
return -3;
}
}
//cout<<"Nick is "<<AuthInfo.Nick<<endl;
//cout<<"Ident is "<<AuthInfo.Ident<<endl;
//cout<<"Host is "<<AuthInfo.Host<<endl;
return 0;
}
////////////////////////////////////////
int JoinChannel(string Channel){
ostringstream joinchan;
joinchan << "JOIN " << Channel << "\r";
SendIRC_func(joinchan.str());
return 0;
}
////////////////////////////////////////
int MessageChannel(string Message, string Channel){
ostringstream ChannelMessage;
ChannelMessage << "PRIVMSG "<<Channel<< " :" <<Message<<"\r";
SendIRC_func(ChannelMessage.str());
return 0;
}
////////////////////////////////////////
string RandomNick(string BotNick){
srand((unsigned)GetTickCount());
int randomNum = rand()%10000;
ostringstream Nick;
Nick << BotNick << randomNum;
return Nick.str();
}
(I didn't upload the code in a .cpp file because it doesn't keep the line breaks the way they are.)
-
I just realised that ping timeout checking code wont work. Because it will just keep waiting for data at recv(), so it would need an extra thread I guess which checks the time and uses a global variable for the time that has gone past. So that when SendIRC_func() RecvIRC_func() complete successfully they reset the time in the global variable and it starts again, I think that would work alright. Not very difficult, but the more important thing is the parselines functions
-
Heres how I laid things out for my bot. This just handles "\r\n" commands.
Code:
void IrcBot::Process()
{
std::string easyHandle;
int recvAmt;
char recvData[1024] = { '\0' };
while(1)
{
recvAmt = recv(serverSocket, recvData, 1023, 0);
if(recvAmt > 0 && recvAmt < 1023)
{
recvData[recvAmt] = '\0';
}
easyHandle = recvData;
HandleRecv(easyHandle);
//Clear out our stuff.
std::memset(recvData,'\0',1024);
//Play nice with cpu
Sleep(1);
}
}
void IrcBot::HandleRecv(const std::string &toRecv)
{
size_t len = toRecv.length();
size_t index = 0;
for(size_t i = 0; index < len && i != std::string::npos; index = i + 2)
{
i = toRecv.find("\r\n", index);
//Make sure we are all good
if(i != std::string::npos)
{
//Send it to get assigned and be parsed
Send_Cmd(toRecv, index, i - index);
}
}
}
void IrcBot::Send_Cmd(const std::string &cmd, size_t index, size_t len)
{
std::string temp;
temp.assign(cmd, index, len);
//Parse that sucka
toLower(temp);
Parse_Cmd(temp);
}
-
I just saw 10 closing braces in a row and gave up bothering to read any more.
Step 1 - learn how to structure code.
-
Thank you for your code example.
I am curious, I don't see where you save any data which does not end with \r\n and append the new data you receive onto the end of it. How come you didn't choose to do this in your code?
-
Well as far as I know IRC commands are all ended with "\r\n" and this is an IRC bot so I basically chop up all the commands and then send them to my Parse_Cmd which does it's thing with the IRC message. Basically grabs the nick, channel, IRC Command(PING, NICK, etc), and the msg sent aka Text user typed.
-
But the server can send data in chunks, for example messages from several users at a time may be sent from the server in one go. You will receive these messages all together as you know which is why they need parsing. But there may be too much data for it to send in one go, so it might send most of it in one go, and there may be a little bit left which it needs to send next time.
Which means you might get 2 full messages and the beginning of the 3rd message but no end of it. The end of the message which has the \r\n will come next time. So after the bot has parsed the first 2 lines, the bot should check to see if the 3rd line ends with \r\n and if it doesn't, assume it is a partial string. Save the data from the partial string and when the data from the next recv() comes, append it to the end and start parsing it again.
You may only miss the occasional message or command, but there will be times (regardless of how unoften) you may miss a full PING line, and then the bot will time out.
-
I see what you are saying. Fairly easy to implement I just honestly never thought about that before.
-
Have you looked into select() ? select() basically takes a list of sockets (here your list would only have 1 socket in it) and waits until they are either ready for reading, writing, or have an error. It can return immediately or sleep until something happens.
And instead of character1 == 'P' && character2 == 'I' && 'N', etc etc. why not use the == operator for strings? Take a substring to get IRC the command. Then:
Code:
if(command == "PING") { blah }
(Really... CBoard forces me to put the above in [code] tags?)