/* InspIRCd Channel Chat Logging module (c) 2013 Brian Bi Designed for use with InspIRCd 2.0 To use this module, add the following lines to modules.conf: Replace "/var/log/irc-chats" with the desired directory in which to place the chat logs. The directory will be populated with subdirectories, one for each channel, whose name is the same as the name of the channel. Each channel's subdirectory will then create a log file for each day. For example: /var \___ log \___ irc-chats \___ #mafia \___ 2012-07-24 \___ 2012-07-25 (etc.) \___ #test \___ #wcipeg Log format looks something like this: [07:58:55 UTC] <+brian> !vlist [07:58:55 UTC] <@kevinbot> Votes for *none by Alice, brian [07:58:55 UTC] <@kevinbot> Votes for Alice by Bob [07:58:55 UTC] <@kevinbot> No votes from Charlie [07:58:59 UTC] <+Charlie> !vote Alice [07:58:59 UTC] <@kevinbot> Charlie: Your vote has been registered. [07:58:59 UTC] <@kevinbot> The vote is tied with the following leaders: *none, Alice [07:59:02 UTC] <+brian> !vlist [07:59:02 UTC] <@kevinbot> Votes for Alice by Charlie, Bob [07:59:02 UTC] <@kevinbot> Votes for *none by Alice, brian [07:59:33 UTC] *** kevinbot has left #mafia (Quit: Caught signal 2) [08:03:55 UTC] *** Alice has left #mafia (Quit: leaving) [08:03:58 UTC] *** Charlie has left #mafia (Quit: leaving) [08:03:59 UTC] *** Bob has left #mafia (Quit: leaving) */ #include #include #include #include #include #include #include "inspircd.h" #include "modules.h" class ModuleChatLog : public Module { private: std::string log_dir; typedef std::map > logfile_map; logfile_map files; void log_chat(std::string chan, std::string text) { char buf[100]; time_t current_time; time(¤t_time); tm* lt = gmtime(¤t_time); strftime(buf, 99, "%Y-%m-%d", lt); std::string d = buf; strftime(buf, 99, "%H:%M:%S", lt); std::string t = buf; logfile_map::iterator I = files.find(chan); if (I == files.end()) { // Insert new log file. // First, we might have to create the dir. std::string dirname = log_dir + chan; mkdir(dirname.c_str(), 0755); // umask might exist, so chmod it anyway chmod(dirname.c_str(), 0755); std::string filename = log_dir + chan + "/" + d; std::ofstream* os = new std::ofstream(filename.c_str(), std::ofstream::app); // Make sure file has read permission. chmod(filename.c_str(), 0644); I = files.insert(make_pair(chan, make_pair(os, d))).first; } else if (I->second.second != d) { // The date rolled over, so make a new file. // Someone might have removed the dir while we weren't looking, // so we have to make sure it exists anyway. std::string dirname = log_dir + chan; mkdir(dirname.c_str(), 0755); // umask might exist, so chmod it anyway chmod(dirname.c_str(), 0755); delete I->second.first; std::string filename = log_dir + chan + "/" + d; I->second.first = new std::ofstream(filename.c_str(), std::ofstream::app); chmod(filename.c_str(), 0644); I->second.second = d; } // Finally, log the message with a timestamp *(I->second.first) << "[" << t << " UTC] " << text << std::endl; } bool parse_action(std::string& action) { if (action.length() == 0) return false; if (action.substr(0, 7) == "\001ACTION" && *action.rbegin() == '\001') { action.erase(0, 7); action.erase(action.length() - 1, 1); return true; } else return false; } public: ModuleChatLog() { } virtual ~ModuleChatLog() { for (logfile_map::iterator I = files.begin(); I != files.end(); I++) { delete I->second.first; // Note: This calls close() on each file } } virtual void init() { Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_OnUserJoin, I_OnUserPart, I_OnUserQuit, I_OnUserPreNick, I_OnUserKick }; ServerInstance->Modules->Attach(eventlist, this, 7); log_dir = ConfigReader().ReadValue("chatlog", "dir", 0); // add trailing slash if (log_dir.length() == 0 || *log_dir.rbegin() != '/') log_dir.push_back('/'); } virtual Version GetVersion() { return Version("Logs channel messages on the server.", 0); } virtual ModResult OnUserPreMessage( User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list ) { if (target_type == TYPE_USER) { // User* u = (User*)dest; // ServerInstance->Logs->Log("CHAT",DEFAULT,"%s: <%s> %s",u->nick.c_str(), user->nick.c_str(), text.c_str()); } else if (target_type == TYPE_CHANNEL) { Channel* c = (Channel*)dest; char type = ' '; for (UserMembCIter It = c->GetUsers()->begin(); It != c->GetUsers()->end(); It++) { if (user->uuid == It->first->uuid) { if (It->second->hasMode('v')) { type = '+'; } if (It->second->hasMode('h')) { type = '%'; } if (It->second->hasMode('o')) { type = '@'; } } } std::string action = text; if (parse_action(action)) { log_chat( c->name, std::string("* " + user->nick + " " + action) ); } else { log_chat( c->name, std::string("<") + type + user->nick + "> " + text ); } } return MOD_RES_PASSTHRU; } virtual ModResult OnUserPreNotice( User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list ) { return OnUserPreMessage(user, dest, target_type, text, status, exempt_list); } virtual void OnUserJoin(Membership* memb, bool sync, bool created, CUList& except_list) { User* user = memb->user; Channel* chan = memb->chan; log_chat( chan->name, std::string("*** ") + user->nick + " has joined " + chan->name ); } virtual void OnUserPart(Membership* memb, std::string &partmessage, CUList& except_list) { User* user = memb->user; Channel* chan = memb->chan; log_chat( chan->name, std::string("*** ") + user->nick + " has left " + chan->name + " (" + partmessage + ")" ); } virtual void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) { for (UserChanList::iterator It = user->chans.begin(); It != user->chans.end(); ++It) { log_chat( (*It)->name, std::string("*** ") + user->nick + " has left " + (*It)->name + " (" + message + ")" ); } } virtual ModResult OnUserPreNick(User* user, const std::string& newnick) { for (UserChanList::iterator It = user->chans.begin(); It != user->chans.end(); ++It) { log_chat( (*It)->name, std::string("*** ") + user->nick + " is now known as " + newnick ); } return MOD_RES_PASSTHRU; } virtual void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& except_list) { User* user = memb->user; Channel* chan = memb->chan; log_chat( chan->name, std::string("*** ") + user->nick + " was kicked from " + chan->name + " by " + source->nick + " (" + reason + ")" ); } }; MODULE_INIT(ModuleChatLog)