// IRCClient.cpp // This file is (C) 2004 Royce Mitchell III // and released under the BSD & LGPL licenses #ifdef _MSC_VER #pragma warning ( disable : 4786 ) #endif//_MSC_VER #include #include #include "IRCClient.h" #include "md5.h" #include "cram_md5.h" #include "trim.h" #include "chomp.h" #include "SplitJoin.h" #include "base64.h" using std::string; using std::stringstream; using std::vector; bool IRCClient::_debug = true; IRCClient::IRCClient() : _timeout(10*60*1000), _inRun(false) { } bool IRCClient::Connect ( const string& server, short port ) { string buf; Close(); Attach ( suTcpSocket() ); if ( !suConnect ( *this, server.c_str(), port ) ) return false; return true; } bool IRCClient::User ( const string& user, const string& mode, const string& network, const string& realname ) { string buf; buf = "USER " + user + " \"" + mode + "\" \"" + network + "\" :" + realname + "\n"; return Send ( buf ); } bool IRCClient::Nick ( const string& nick ) { _nick = nick; return Send ( "NICK " + _nick + "\n" ); } bool IRCClient::Mode ( const string& mode ) { return Send ( "MODE " + _nick + " " + mode + "\n" ); } bool IRCClient::Names ( const string& channel ) { return Send ( "NAMES " + channel + "\n" ); } bool IRCClient::Mode ( const string& channel, const string& mode, const string& target ) { return Send ( "MODE " + channel + " " + mode + " " + target + "\n" ); } bool IRCClient::Join ( const string& channel ) { return Send ( "JOIN " + channel + "\n" ); } bool IRCClient::PrivMsg ( const string& to, const string& text ) { return Send ( "PRIVMSG " + to + " :" + text + "\n" ); } bool IRCClient::Part ( const string& channel, const string& text ) { return Send ( "PART " + channel + " :" + text + "\n" ); } bool IRCClient::Quit ( const string& text ) { return Send( "QUIT :" + text + "\n"); } bool IRCClient::_Recv ( string& buf ) { bool b = (recvUntil ( buf, '\n', _timeout ) > 0); if ( b && _debug ) { printf ( ">> %s", buf.c_str() ); if ( buf[buf.length()-1] != '\n' ) printf ( "\n" ); } return b; } bool IRCClient::Send ( const string& buf ) { if ( _debug ) { printf ( "<< %s", buf.c_str() ); if ( buf[buf.length()-1] != '\n' ) printf ( "\n" ); } return ( buf.length() == (size_t)send ( *this, buf.c_str(), buf.length(), 0 ) ); } bool IRCClient::OnPing( const string& text ) { return Send( "PONG " + text + "\n" ); } int THREADAPI IRCClient::Callback ( IRCClient* irc ) { return irc->Run ( false ); } int IRCClient::Run ( bool launch_thread ) { if ( (SOCKET)*this == INVALID_SOCKET ) return 0; if ( _inRun ) return 1; if ( launch_thread ) { ThreadPool::Instance().Launch ( (ThreadPoolFunc*)IRCClient::Callback, this ); return 1; } _inRun = true; if ( _debug ) printf ( "IRCClient::Run() - waiting for responses\n" ); string buf; while ( _Recv(buf) ) { if ( !strnicmp ( buf.c_str(), "NOTICE ", 7 ) ) { //printf ( "recv'd NOTICE msg...\n" ); // TODO... //OnAuth ( } else if ( !strnicmp ( buf.c_str(), "PING ", 5 ) ) { const char* p = &buf[5]; // point to first char after "PING " while ( *p == ':' ) // then read past the colons p++; const char* p2 = strpbrk ( p, "\r\n" ); // find the end of line string text ( p, p2-p ); // and set the text OnPing( text ); } else if ( buf[0] == ':' ) { const char* p = &buf[1]; // skip first colon... const char* p2 = strpbrk ( p, " !" ); if ( !p2 ) { printf ( "!!!:OnRecv failure 0: ", buf.c_str() ); continue; } string src ( p, p2-p ); if ( !src.length() ) { printf ( "!!!:OnRecv failure 0.5: %s", buf.c_str() ); continue; } p = p2 + 1; if ( *p2 == '!' ) { p2 = strchr ( p, ' ' ); if ( !p2 ) { printf ( "!!!:OnRecv failure 1: %s", buf.c_str() ); continue; } //string srchost ( p, p2-p ); p = p2 + 1; } p2 = strchr ( p, ' ' ); if ( !p2 ) { printf ( "!!!:OnRecv failure 2: %s", buf.c_str() ); continue; } string cmd ( p, p2-p ); p = p2 + 1; p2 = strpbrk ( p, " :" ); if ( !p2 ) { printf ( "!!!:OnRecv failure 3: %s", buf.c_str() ); continue; } string tgt ( p, p2-p ); p = p2 + 1; p += strspn ( p, " " ); if ( *p == '=' ) { p++; p += strspn ( p, " " ); } if ( *p == ':' ) p++; p2 = strpbrk ( p, "\r\n" ); if ( !p2 ) { printf ( "!!!:OnRecv failure 4: %s", buf.c_str() ); continue; } string text ( p, p2-p ); strlwr ( &cmd[0] ); if ( cmd == "privmsg" ) { if ( !tgt.length() ) { printf ( "!!!:OnRecv failure 5 (PRIVMSG w/o target): %s", buf.c_str() ); continue; } if ( tgt[0] == '#' ) OnChannelMsg ( tgt, src, text ); else OnPrivMsg ( src, text ); } else if ( cmd == "mode" ) { // two diff. kinds of mode notifications... //printf ( "[MODE] src='%s' cmd='%s' tgt='%s' text='%s'", src.c_str(), cmd.c_str(), tgt.c_str(), text.c_str() ); //OnMode ( // self mode change: // [MODE] src=Relic3_14 cmd=mode tgt=Relic3_14 text=+i // channel mode change: // [MODE] src=Royce3 cmd=mode tgt=#Royce3 text=+o Relic3_14 if ( tgt[0] == '#' ) { p = text.c_str(); p2 = strchr ( p, ' ' ); if ( !p2 ) OnChannelMode ( tgt, text ); else { string user ( p, p2-p ); p = p2 + 1; p += strspn ( p, " " ); OnUserModeInChannel ( src, tgt, user, p ); } } else OnMode ( tgt, text ); } else if ( cmd == "join" ) { OnJoin ( src, text ); } else if ( isdigit(cmd[0]) ) { int i = atoi(cmd.c_str()); switch ( i ) { case 1: // "Welcome!" - i.e. it's okay to issue commands now... OnConnected(); break; case 353: // user list for channel.... { p = text.c_str(); p2 = strpbrk ( p, " :" ); if ( !p2 ) continue; string channel ( p, p2-p ); p = strchr ( p2, ':' ); if ( !p ) continue; p++; vector users; while ( *p ) { p2 = strchr ( p, ' ' ); if ( !p2 ) p2 = p + strlen(p); users.push_back ( string ( p, p2-p ) ); p = p2+1; p += strspn ( p, " " ); } OnChannelUsers ( channel, users ); } break; case 366: // END of user list for channel { p = text.c_str(); p2 = strpbrk ( p, " :" ); if ( !p2 ) continue; string channel ( p, p2-p ); OnEndChannelUsers ( channel ); } break; default: if ( _debug ) printf ( "unknown command %i: %s", i, buf.c_str() ); break; } } else { if ( _debug ) printf ( "unrecognized ':' response: %s", buf.c_str() ); } } else { if ( _debug ) printf ( "unrecognized irc msg: %s", buf.c_str() ); } //OnRecv ( buf ); } if ( _debug ) printf ( "IRCClient::Run() - exiting\n" ); _inRun = false; return 0; }