#include <libircpp/libircpp.h> #include <time.h> #include <iostream> #include <fstream> using namespace std; using namespace ircpp; #define NICK "logbot" #define SERVER "my.irc.server.net" #define CHANNEL "#mychannel" #define SHUTDOWNPASSWORD "libircpp" void connect_handler(); void log_handler(const message& msg); void join_handler(const message& msg); void invite_handler(const message& msg); void kick_handler(const message& msg); void password_handler(const message& msg); void start_logging(); void stop_logging(); const string& timestamp(); ofstream logfile; session ircsession; int main(int argc, char **argv) { ircsession.set_nick(NICK); ircsession.set_username(NICK); ircsession.set_realname("channel logger bot"); ircsession.sig_connected.connect(connect_handler); ircsession.sig_join_msg.connect(join_handler); ircsession.sig_invite_msg.connect(invite_handler); ircsession.sig_privmsg_msg.connect(password_handler); for(;;) { ircsession.connect(SERVER); ircsession.run(); if(logfile.is_open()) { logfile << timestamp() << " *** Disconnected" << endl; stop_logging(); } sleep(10); } } void connect_handler() { ircsession.cmd_join(CHANNEL); } void join_handler(const message& msg) { if(msg.from() == ircsession.me()) { if(msg.to().name() == CHANNEL) { start_logging(); const channel& c = msg.to().to_channel(); c.sig_privmsg_msg.connect(log_handler); c.sig_notice_msg.connect(log_handler); c.sig_join_msg.connect(log_handler); c.sig_part_msg.connect(log_handler); c.sig_quit_msg.connect(log_handler); c.sig_mode_msg.connect(log_handler); c.sig_ctcp_msg.connect(log_handler); c.sig_kick_msg.connect(1, log_handler); c.sig_kick_msg.connect(2, kick_handler); } } } void invite_handler(const message& msg) { if(msg.to() == ircsession.me()) { if(msg.target().name() == CHANNEL) { ircsession.cmd_join(CHANNEL); } } } void start_logging(void) { string filename = CHANNEL ".log"; logfile.open(filename.c_str(), ios::app); logfile << "Starting logging at " << timestamp() << endl; } void log_handler(const message& msg) { const channel& c = msg.to().to_channel(); switch(msg.cmd()) { case PRIVMSG: if(msg.is_ctcp() && msg.param(0) == "ACTION") { logfile << timestamp() << " * " << msg.from().nick() << " " << msg.params(1) << endl; } else { logfile << timestamp() << " <" << msg.from().nick() << "> " << msg.params() << endl; } break; case NOTICE: logfile << timestamp() << " -" << msg.from().nick() << "- " << msg.params() << endl; break; case JOIN: logfile << timestamp() << " *** " << msg.from().nick() << " has joined " << msg.to().name() << endl; break; case PART: logfile << timestamp() << " *** " << msg.from().nick() << " has left " << msg.to().name() << endl; break; case KICK: logfile << timestamp() << " *** " << msg.from().nick() << " kicked " << msg.target().nick() << " from " << msg.to().name() << " (" << msg.params() << ")" << endl; break; case QUIT: logfile << timestamp() << " *** " << msg.from().nick() << " has quit (" << msg.params() << ")" << endl; break; case MODE: logfile << timestamp() << " *** " << msg.from().nick() << " sets mode " << msg.to().name() << " " << msg.params() << endl; break; default: break; } } void kick_handler(const message& msg) { if(msg.target() == ircsession.me()) { stop_logging(); } } void password_handler(const message& msg) { if(msg.to() == ircsession.me()) { if(msg.param(0) == SHUTDOWNPASSWORD) { stop_logging(); ircsession.cmd_quit("Shutting down"); exit(0); } } } const string& timestamp() { static string timestamp; time_t t = time(NULL); timestamp.clear(); timestamp = ctime(&t); timestamp.erase(timestamp.end() - 1); return timestamp; } void stop_logging(void) { if(logfile.is_open()) { logfile << "Stopping logging at " << timestamp() << endl; logfile.close(); } }
#include <libircpp/libircpp.h> #include <time.h> #include <iostream> #include <fstream> using namespace std; using namespace ircpp;The standard libircpp header file plus others required for file I/O and timestamping. The "using" declarations are included for convenience, but if you prefer not to pollute your namespaces, you can prefix everything ircpp-related with ircpp:: in your code.
#define NICK "logbot" #define SERVER "my.irc.server.net" #define CHANNEL "#mychannel" #define SHUTDOWNPASSWORD "libircpp"Here we define the bot's nick, the server it should connect to, the channel it should log, and a password to make it shut down. You should change these to your own values. If you wanted to improve the bot, you could read these from the command line or a config file, but they are hardcoded here to make the bot simpler.
void connect_handler(); void log_handler(const message& msg); void join_handler(const message& msg); void invite_handler(const message& msg); void kick_handler(const message& msg); void password_handler(const message& msg); void start_logging(); void stop_logging(); const string& timestamp(); ofstream logfile; session ircsession;Declarations of all the local functions (these will be explained below as they are encountered), and global variables. We declare an ofstream object for writing to the log file, and a session object that we will use to connect to the IRC server.
int main(int argc, char **argv) { ircsession.set_nick(NICK); ircsession.set_username(NICK); ircsession.set_realname("channel logger bot"); ircsession.sig_connected.connect(connect_handler); ircsession.sig_join_msg.connect(join_handler); ircsession.sig_invite_msg.connect(invite_handler); ircsession.sig_privmsg_msg.connect(password_handler);The start of the main function. First we set the session's nick, username, and realname, the we connect some of the session object's signals to handlers. Note that we only connect signals here where we want to receive all messages, rather than ones for the specific channel we are logging. Those are connected when we join the channel.
for(;;) { ircsession.connect(SERVER); ircsession.run(); if(logfile.is_open()) { logfile << timestamp() << " *** Disconnected" << endl; stop_logging(); } sleep(10); } }The main loop, which loops forever. We connect the session to the server, then call the session's run() function, which will only return when we are disconnected. If we are disconnected, we log this event and stop logging, then wait 10 seconds before we go back round the loop and reconnect.
void connect_handler() { ircsession.cmd_join(CHANNEL); }This is a handler that we connected in main(). It is called when we successfully connect to the irc server (or reconnect after disconnection). We use it to send the JOIN command for the channel we want to log.
void join_handler(const message& msg) { if(msg.from() == ircsession.me()) { if(msg.to().name() == CHANNEL) { start_logging(); const channel& c = msg.to().to_channel(); c.sig_privmsg_msg.connect(log_handler); c.sig_notice_msg.connect(log_handler); c.sig_join_msg.connect(log_handler); c.sig_part_msg.connect(log_handler); c.sig_quit_msg.connect(log_handler); c.sig_mode_msg.connect(log_handler); c.sig_ctcp_msg.connect(log_handler); c.sig_kick_msg.connect(1, log_handler); c.sig_kick_msg.connect(2, kick_handler); } } }This handler is called on all JOIN messages (because it's connected to the session's sig_join_msg, not a channel's sig_join_msg). We actually only want to know when we join the channel we want to log, not all join messages ever, but we can't connect to the channel object's sig_join_msg because the channel object doesn't exist yet. So first, we check that the message is from us (i.e. we are joining), then we check that the channel is the one we want to log.
If these two conditions are met, then we start logging by calling the start_logging() function (see below). Then, we retrieve the channel object by converting the message's recipient (to()) to a channel (we know it's a channel because join messages can only be sent to a channel). The channel object has signals for many types of message, and we connect the ones we want to log to our log_handler() function. Additionally, we connect the channel's sig_kick_msg signal to kick_handler() so we can tell if we are kicked from the channel.
Note that we connect log_handler() using a lower number than kick_handler(), so that when we are kicked, the kick message is logged first before kick_handler() runs and terminates logging.
There is no need to disconnect these signals, as they are automatically disconnected when the channel object is destroyed, which will happen if we leave the channel for any reason, or if we're disconnected. If the lists had been set in manual mode, we would have had to destroy the channel object manually.
void invite_handler(const message& msg) { if(msg.to() == ircsession.me()) { if(msg.target().name() == CHANNEL) { ircsession.cmd_join(CHANNEL); } } }This handler is called on any INVITE messages. In a similar way to join_handler(), we're only interested in invite messages that invite us (not anyone else) to the channel we want to log. So we check if the message is to us, and if the target is the channel we want to log. If these conditions are met, we send a join message to join the channel.
void start_logging(void) { string filename = CHANNEL ".log"; logfile.open(filename.c_str(), ios::app); logfile << "Starting logging at " << timestamp() << endl; }This function, called from join_handler(), starts logging to a file. It constructs the filename (the name of our channel with .log on the end), and opens it for appending. It then writes a "starting logging" message to the logfile.
void log_handler(const message& msg) { const channel& c = msg.to().to_channel(); switch(msg.cmd()) { case PRIVMSG: if(msg.is_ctcp() && msg.param(0) == "ACTION") { logfile << timestamp() << " * " << msg.from().nick() << " " << msg.params(1) << endl; } else { logfile << timestamp() << " <" << msg.from().nick() << "> " << msg.params() << endl; } break; case NOTICE: logfile << timestamp() << " -" << msg.from().nick() << "- " << msg.params() << endl; break; case JOIN: logfile << timestamp() << " *** " << msg.from().nick() << " has joined " << msg.to().name() << endl; break; case PART: logfile << timestamp() << " *** " << msg.from().nick() << " has left " << msg.to().name() << endl; break; case KICK: logfile << timestamp() << " *** " << msg.from().nick() << " kicked " << msg.target().nick() << " from " << msg.to().name() << " (" << msg.params() << ")" << endl; break; case QUIT: logfile << timestamp() << " *** " << msg.from().nick() << " has quit (" << msg.params() << ")" << endl; break; case MODE: logfile << timestamp() << " *** " << msg.from().nick() << " sets mode " << msg.to().name() << " " << msg.params() << endl; break; default: break; } }log_handler() is the function that logs messages to the logfile. It is attached to different signals, one per type of message to be logged, and so it will be called when any one of them is recieved. It first converts the message recipient into a channel object (we know the messages will all be sent to a channel, because the signals we connected to come from the channel object). Then we switch() on the message's command, and write an appropriate line to the logfile, using the names of the message sender, recipient, and target as required, as well as the parameters.
The PRIVMSG case is slightly different because it handles both ordinary messages and CTCP ACTIONS (/me), both of which are sent as PRIVMSGs. The message's is_ctcp() function is used to differentiate between CTCP and ordinary PRIVMSGs.
void kick_handler(const message& msg) { if(msg.target() == ircsession.me()) { stop_logging(); } }This handler, attached to the channel's sig_kick_msg, is to detect if we are kicked from the channel. Because it is called on all kick messages (not just us being kicked), we check that the target of the kick is us. If it is, we stop logging. Note that log_handler() is also connected to sig_kick_msg. Because log_handler() was connected with a lower number than kick_handler(), it will be called first, so the log file will record the kick, and then logging will be stopped.
void password_handler(const message& msg) { if(msg.to() == ircsession.me()) { if(msg.param(0) == SHUTDOWNPASSWORD) { stop_logging(); ircsession.cmd_quit("Shutting down"); exit(0); } } }password_handler() terminates the bot when we /msg it with the correct shutdown password. Because it is connected to the session's sig_privmsg_msg, it is called on all PRIVMSGs, including channel ones. Therefore, we first check to see that the message is to us (rather than a channel), then whether the first word of the message is our shutdown password. If it is, we stop logging, quit irc, and exit.
const string& timestamp() { static string timestamp; time_t t = time(NULL); timestamp.clear(); timestamp = ctime(&t); timestamp.erase(timestamp.end() - 1); return timestamp; }timestamp() returns a formatted representation of the current time, using ctime(). The trailing newline is removed so that we can prepend the resulting string directly to the log message.
void stop_logging(void) { if(logfile.is_open()) { logfile << "Stopping logging at " << timestamp() << endl; logfile.close(); } }stop_logging() writes a message to the logfile then closes it.