/** * \fn Flux::string search(Flux::string s, Flux::string command) * \brief generates search links for url's * This is what generates the search links. */ Flux::string search(const Flux::string &text, const Flux::string &command) { Flux::string searchstring = text.url_str(); if(searchstring.empty()) return "Empty searchstring."; else { if(command.equals_ci("google")) return "http://www.google.com/search?q=" + searchstring; else if(command.equals_ci("youtube")) return "http://www.youtube.com/results?search_query=" + searchstring; else if(command.equals_ci("tpb")) return "http://thepiratebay.org/search/" + searchstring; else if(command.equals_ci("define")) return "http://dictionary.reference.com/browse/" + searchstring; else if(command.equals_ci("urban")) return "http://www.urbandictionary.com/define.php?term=" + searchstring; else if(command.equals_ci("movie")) return "www.letmewatchthis.ch/index.php?search_keywords=" + searchstring; else if(command.equals_ci("lmgtfy")) return "http://lmgtfy.com/?q=" + searchstring; else return "http://www.google.com/search?q=" + searchstring; } }
/** * \fn void ModuleHandler::SanitizeRuntime() * \brief Deletes all files in the runtime directory. */ void ModuleHandler::SanitizeRuntime() { Log(LOG_DEBUG) << "Cleaning up runtime directory."; Flux::string dirbuf = binary_dir + "/runtime/"; if(!TextFile::IsDirectory(dirbuf)) { #ifndef _WIN32 if(mkdir(dirbuf.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) throw CoreException(printfify("Error making new runtime directory: %s", strerror(errno))); #else if(!CreateDirectory(dirbuf.c_str(), NULL)) throw CoreException(printfify("Error making runtime new directory: %s", strerror(errno))); #endif } else { Flux::vector files = TextFile::DirectoryListing(dirbuf); for(Flux::vector::iterator it = files.begin(); it != files.end(); ++it) Delete(Flux::string(dirbuf + (*it)).c_str()); } }
cidr::cidr(const Flux::string &ip) { if (ip.find_first_not_of("01234567890:./") != Flux::string::npos) throw SocketException("Invalid IP"); bool ipv6 = ip.search(':'); size_t sl = ip.find_last_of('/'); if (sl == Flux::string::npos) { this->cidr_ip = ip; this->cidr_len = ipv6 ? 128 : 32; this->addr.pton(ipv6 ? AF_INET6 : AF_INET, ip); } else { Flux::string real_ip = ip.substr(0, sl); Flux::string cidr_range = ip.substr(sl + 1); if (!cidr_range.is_pos_number_only()) throw SocketException("Invalid CIDR range"); this->cidr_ip = real_ip; this->cidr_len = value_cast<unsigned int>(cidr_range); this->addr.pton(ipv6 ? AF_INET6 : AF_INET, real_ip); } }
/** The equivalent of inet_pton * @param type AF_INET or AF_INET6 * @param address The address to place in the sockaddr structures * @param pport An option port to include in the sockaddr structures * @throws A socket exception if given invalid IPs */ void sockaddrs::pton(int type, const Flux::string &address, int pport) { switch (type) { case AF_INET: int i = inet_pton(type, address.c_str(), &sa4.sin_addr); if (i == 0) throw SocketException("Invalid host"); else if (i <= -1) throw SocketException(printfify("Invalid host: %s", strerror(errno))); sa4.sin_family = type; sa4.sin_port = htons(pport); return; case AF_INET6: int i = inet_pton(type, address.c_str(), &sa6.sin6_addr); if (i == 0) throw SocketException("Invalid host"); else if (i <= -1) throw SocketException(printfify("Invalid host: %s", strerror(errno))); sa6.sin6_family = type; sa6.sin6_port = htons(pport); return; default: break; } throw CoreException("Invalid socket type"); }
void Run(CommandSource &source, const Flux::vector ¶ms) { Flux::string chan = params[1]; User *u = source.u; if(!u->IsOwner()) { source.Reply(ACCESS_DENIED); Log(u) << "attempted to make bot part " << chan; return; } if(!IsValidChannel(chan)) source.Reply(CHANNEL_X_INVALID, chan.c_str()); else { Channel *c = findchannel(chan); if(c) c->SendPart(); else source.Reply("I am not in channel \2%s\2", chan.c_str()); Log(u) << "made the bot part " << chan; } }
/** * \fn bool ModuleHandler::DeleteModule(Module *m) * \brief Delete the Module from Module lists and unload it from navn completely * \param Module the Module to be removed */ bool ModuleHandler::DeleteModule(Module *m) { SET_SEGV_LOCATION(); if(!m || !m->handle) return false; void *handle = m->handle; Flux::string filepath = m->filepath; dlerror(); void (*df)(Module *) = function_cast<void ( *)(Module *)>(dlsym(m->handle, "ModunInit")); const char *err = dlerror(); if(!df && err && *err) { Log(LOG_WARN) << "No destroy function found for " << m->name << ", chancing delete..."; delete m; /* we just have to chance they haven't overwrote the delete operator then... */ } else df(m); /* Let the Module delete it self, just in case */ if(handle) if(dlclose(handle)) Log() << "[" << m->name << ".so] " << dlerror(); if(!filepath.empty()) Delete(filepath.c_str()); return true; }
void OnChannelOp(User *u, Channel *c, const Flux::string &mode, const Flux::string &nick) { if(!u || !c) return; if(c->name != Config->LogChannel) return; CLog("*** %s sets mode %s %s %s", u->nick.c_str(), c->name.c_str(), mode.c_str(), nick.c_str()); }
// Private Function! used by Flux::string.implode() Flux::string __ConcatenateString(const Flux::vector &str, char delim = ' ') { Flux::string temp; for (unsigned i = 0; i < str.size(); ++i) { if(!temp.empty()) temp += delim; temp += str[i]; } return temp; }
/** * \fn Flux::string NoTermColor(const Flux::string &ret) * \brief Strip all *nix terminal style colors * \param buffer Buffer to strip */ Flux::string NoTermColor(const Flux::string &ret) { Flux::string str; bool in_term_color = false; for(unsigned i = 0; i < ret.length(); ++i) { char c = ret[i]; if(in_term_color) { if(c == 'm') in_term_color = false; continue; } if(c == '\033') { in_term_color = true; continue; } if(!in_term_color) str += c; } return str; }
cidr::cidr(const Flux::string &ip, unsigned char len) { bool ipv6 = ip.search(':'); this->addr.pton(ipv6 ? AF_INET6 : AF_INET, ip); this->cidr_ip = ip; this->cidr_len = len; }
void User::SetNewNick(const Flux::string &newnick) { if(newnick.empty()) throw CoreException("User::SetNewNick() was called with empty arguement"); Log(LOG_TERMINAL) << "Setting new nickname: " << this->nick << " -> " << newnick; this->n->UserNickList.erase(this->nick); this->nick = newnick; this->n->UserNickList[this->nick] = this; }
void OnPart(User *u, Channel *c, const Flux::string &reason) { if(!u || !c) return; if(c->name != Config->LogChannel) return; CLog("*** %s has parted %s (%s)", u->nick.c_str(), c->name.c_str(), reason.c_str()); }
void OnNoticeChannel(User *u, Channel *c, const Flux::vector ¶ms) { if(!u || !c) return; if(c->name != Config->LogChannel) return; Flux::string msg; for(unsigned i = 0; i < params.size(); ++i) msg += params[i] + ' '; msg.trim(); Flux::string nolog = params.size() == 2 ? params[1] : ""; if(nolog != "#nl" && u) CLog("-Notice- %s: %s", u->nick.c_str(), Flux::Sanitize(msg).c_str()); }
int uname(struct utsname *info) { // get the system information. OSVERSIONINFOEX wininfo; SYSTEM_INFO si; Flux::string WindowsVer = GetWindowsVersion(); Flux::string cputype; char hostname[256] = "\0"; ZeroMemory(&wininfo, sizeof(OSVERSIONINFOEX)); ZeroMemory(&si, sizeof(SYSTEM_INFO)); wininfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(!GetVersionEx(reinterpret_cast<OSVERSIONINFO *>(&wininfo))) return -1; GetSystemInfo(&si); // Get the hostname if(gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) return -1; if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) cputype = "64-bit"; else if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) cputype = "32-bit"; else if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) cputype = "Itanium 64-bit"; // Fill the utsname struct with the windows system info strcpy(info->sysname, "Windows"); strcpy(info->nodename, hostname); strcpy(info->release, WindowsVer.c_str()); strcpy(info->version, printfify("%ld.%ld-%ld", wininfo.dwMajorVersion, wininfo.dwMinorVersion, wininfo.dwBuildNumber).c_str()); strcpy(info->machine, cputype.c_str()); // Null-Terminate info->nodename[strlen(info->nodename) - 1] = '\0'; info->sysname[strlen(info->sysname) - 1] = '\0'; info->release[strlen(info->sysname) - 1] = '\0'; info->version[strlen(info->version) - 1] = '\0'; info->machine[strlen(info->machine) - 1] = '\0'; }
User::User(Network *net, const Flux::string &snick, const Flux::string &sident, const Flux::string &shost, const Flux::string &srealname, const Flux::string &sserver) : nick(snick), host(shost), realname(srealname), ident(sident), fullhost(snick+"!"+sident+"@"+shost), server(sserver), n(net) { /* check to see if a empty string was passed into the constructor */ if(snick.empty() || sident.empty() || shost.empty()) throw CoreException("Bad args sent to User constructor"); if(!net) throw CoreException("User created with no network??"); this->n->UserNickList[snick] = this; CUserMap[this] = std::vector<Channel*>(); Log(LOG_RAWIO) << "New user! " << this->nick << '!' << this->ident << '@' << this->host << (this->realname.empty()?"":" :"+this->realname); ++usercnt; if(usercnt > maxusercnt) { maxusercnt = usercnt; Log(LOG_TERMINAL) << "New maximum user count: " << maxusercnt; } }
/** * \fn bool SocketIO::Read(const Flux::string &buf) const * \brief Read from a socket for the IRC processor * \param buffer Raw socket buffer */ bool SocketIO::Read(const Flux::string &buf) const { if(buf.search_ci("ERROR :Closing link:")) { FOREACH_MOD(I_OnSocketError, OnSocketError(buf)); return false; } Log(LOG_RAWIO) << "Received: " << Flux::Sanitize(buf); process(buf); /* Process the buffer for navn */ return true; }
void Brain(User *u, Flux::string q) { if(!(cdt.requested >= 30)) { cdt.requested++; Log(LOG_TERMINAL) << "Requests: " << cdt.requested; Sanitize(q); Flux::string str = "python brain.py "+q; Flux::string information = execute(str.c_str()); information.trim(); if (information.search_ci("For search options, see Help:Searching")) u->SendMessage(RandomOops()); else if (information.search_ci("may refer to:") || information.search_ci("reasons this message may be displayed:") ) u->SendMessage(RandomAmb()); else u->SendMessage(information.strip()); }else u->SendMessage(TooManyRequests()); }
void OnPrivmsgChannel(User *u, Channel *c, const Flux::vector ¶ms) { Flux::string nolog = params.size() == 2 ? params[1] : ""; if(!u || !c) return; if(c->name != Config->LogChannel) return; Flux::string msg = ConcatinateVector(params); if(nolog != "#nl" && u) { if(nolog == "\001ACTION") { msg = msg.erase(0, 8); CLog("*** %s %s", u->nick.c_str(), Flux::Sanitize(msg).c_str()); } else CLog("<%s> %s", u->nick.c_str(), msg.c_str()); } }
void Run(CommandSource &source, const std::vector<Flux::string> ¶ms) { Flux::string cmds; if(!params.empty()) { Command *c = FindCommand(params[1], C_CHANNEL); if(c && !c->OnHelp(source, "")) source.Reply("No help available for \2%s\2", params[1].c_str()); else if(!c) source.Reply("No help available for \2%s\2", params[1].c_str()); Log(source.u) << "used help command on " << params[1]; } else { for(auto val : ChanCommandMap) //As you can see C++11 is MUCH better than C++98 cmds += val.second->name+" "; cmds.trim(); cmds.tolower(); source.u->SendMessage("Local %s Commands:", source.c->name.c_str()); source.u->SendMessage(cmds); Log(source.u) << "used help command"; } }
void Run(CommandSource &source, const Flux::vector ¶ms) { User *u = source.u; Flux::string chan = params[1]; if(!u->IsOwner()) { source.Reply(ACCESS_DENIED); Log(u) << "attempted to make the bot join " << chan; return; } if(!IsValidChannel(chan)) source.Reply(CHANNEL_X_INVALID, chan.c_str()); else { Log(u) << "made the bot join " << chan; Channel *c = findchannel(chan); if(!c) { ircproto->join(chan); Flux::string WelcomeMessage = Config->WelcomeMessage.replace_all_ci("{botnick}", Config->BotNick); WelcomeMessage.trim(); if(!WelcomeMessage.empty()) ircproto->privmsg(chan, WelcomeMessage); } else { Log(u) << "tried to make bot join " << c->name << " but we're already in that channel"; source.Reply("Already in \2%s\2", c->name.c_str()); } } }
void Sanitize(Flux::string &victim) { victim = victim.replace_all_cs("&",""); victim = victim.replace_all_cs("|",""); victim = victim.replace_all_cs(">",""); victim = victim.replace_all_cs("<",""); victim = victim.replace_all_cs("!",""); victim = victim.replace_all_cs("(",""); victim = victim.replace_all_cs(")",""); victim = victim.replace_all_cs("*",""); victim = victim.replace_all_cs("{",""); victim = victim.replace_all_cs("}",""); victim = victim.replace_all_cs("`",""); victim = victim.replace_all_cs("\"",""); victim = victim.replace_all_cs("\'",""); victim = victim.replace_all_cs(".",""); victim = victim.replace_all_cs("?",""); }
Flux::string ForwardResolution(const Flux::string &hostname) { struct addrinfo *result, *res; int err = getaddrinfo(hostname.c_str(), NULL, NULL, &result); if(err != 0) { Log(LOG_TERMINAL) << "Failed to resolve " << hostname << ": " << gai_strerror(err); return ""; } bool gothost = false; Flux::string ret = hostname; for(res = result; res != NULL && !gothost; res = res->ai_next) { struct sockaddr *haddr; haddr = res->ai_addr; char address[INET6_ADDRSTRLEN + 1] = "\0"; switch(haddr->sa_family) { case AF_INET: struct sockaddr_in *v4; v4 = reinterpret_cast<struct sockaddr_in*>(haddr); if (!inet_ntop(AF_INET, &v4->sin_addr, address, sizeof(address))) { Log(LOG_DEBUG) << "DNS: " << strerror(errno); return ""; } break; case AF_INET6: struct sockaddr_in6 *v6; v6 = reinterpret_cast<struct sockaddr_in6*>(haddr); if (!inet_ntop(AF_INET6, &v6->sin6_addr, address, sizeof(address))) { Log(LOG_DEBUG) << "DNS: " << strerror(errno); return ""; } break; } ret = address; gothost = true; } freeaddrinfo(result); return ret; }
void OnPrivmsgChannel(User *u, Channel *c, const Flux::vector ¶ms) { //Flux::vector MessageParams = StringVector(params, ' '); Flux::string msg; for(unsigned i=0; i < params.size(); ++i) msg += params[i]+' '; Flux::string cmd = params.empty()?"":params[0]; if(cmd.equals_ci("!encyclopedia")) { SetQuery(1, params); Brain(u,query); } if(msg.search_ci(Config->BotNick+", what do you know about ")) { SetQuery(6, params); Brain(u, query); } else if(msg.search_ci(Config->BotNick+", what is a ") ^ msg.search_ci(Config->BotNick+", what is the") ^ msg.search_ci(Config->BotNick+", tell me about ") ^ msg.search_ci(Config->BotNick+", who are the ") ^ msg.search_ci(Config->BotNick+", what is an ")) { SetQuery(4, params); Brain(u, query); } else if(msg.search_ci(Config->BotNick+", what is ") ^ msg.search_ci(Config->BotNick+", what are ") ^ msg.search_ci(Config->BotNick+", who is ") ^ msg.search_ci(Config->BotNick+", what's a ") ^ msg.search_ci(Config->BotNick+", what's an ")) { SetQuery(3, params); Brain(u, query); } else if(msg.search_ci(Config->BotNick+", tell me what you know about ")) { SetQuery(7, params); Brain(u, query); } }
bool BufferedSocket::ProcessRead() { char tbuffer[NET_BUFSIZE]; this->RecvLen = 0; int len = this->IO->Recv(this, tbuffer, sizeof(tbuffer) - 1); if (len <= 0) return false; tbuffer[len] = 0; this->RecvLen = len; Flux::string sbuffer = this->extrabuf; sbuffer += tbuffer; this->extrabuf.clear(); size_t lastnewline = sbuffer.rfind('\n'); if (lastnewline == Flux::string::npos) { this->extrabuf = sbuffer; return true; } if (lastnewline < sbuffer.length() - 1) { this->extrabuf = sbuffer.substr(lastnewline); this->extrabuf.trim(); sbuffer = sbuffer.substr(0, lastnewline); } sepstream stream(sbuffer, '\n'); Flux::string tbuf; while (stream.GetToken(tbuf)) { // Log(LOG_TERMINAL) << "BUFFER: " << tbuf; // Stupid HTTP protocol requires that there be a blank line sent to // the read socket, therefore we cannot check if the buffer is empty // and force the user to check for their protocol. :< tbuf.trim(); if (/*!tbuf.empty() &&*/ !Read(tbuf)) return false; } return true; }
/** * \fn void CheckLogDelete(Log *log) * \brief Check to see if logs need to be removed due to old age * \param log A log class variable */ void CheckLogDelete(Log *log) { Flux::string dir = binary_dir + "/logs/"; if(!TextFile::IsDirectory(dir)) { Log(LOG_TERMINAL) << "Directory " << dir << " does not exist, making new directory."; #ifndef _WIN32 if(mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) throw LogException("Failed to create directory " + dir + ": " + Flux::string(strerror(errno))); #else if(!CreateDirectory(dir.c_str(), NULL)) throw LogException("Failed to create directory " + dir + ": " + Flux::string(strerror(errno))); #endif } Flux::vector files = TextFile::DirectoryListing(dir); if(log) files.push_back(log->filename); if(files.empty()) Log(LOG_TERMINAL) << "No Logs!"; for(Flux::vector::iterator it = files.begin(); it != files.end(); ++it) { Flux::string file = dir + (*it); if(TextFile::IsFile(file)) { Flux::string t = file.isolate('-', ' ').strip('-'); int timestamp = static_cast<int>(t); if(timestamp > (time(NULL) - 86400 * Config->LogAge) && timestamp != starttime) { Delete(file.c_str()); Log(LOG_DEBUG) << "Deleted old logfile " << file; } } } }
bool sepstream::GetToken(Flux::string &token) { Flux::string::iterator lsp = last_starting_position; while (n != tokens.end()) { if (*n == sep || n + 1 == tokens.end()) { last_starting_position = n + 1; token = Flux::string(lsp, n + 1 == tokens.end() ? n + 1 : n); while (token.length() && token.rfind(sep) == token.length() - 1) token.erase(token.end() - 1); ++n; return true; } ++n; } token.clear(); return false; }
void OnNumeric(int i, Network *n, const Flux::vector ¶ms) { if((i == 5)) { // Skip the nickname and the "are supported by this server" parts of the message for(unsigned o = 1; o < params.size() - 1; ++o) { Flux::vector sentence = ParamitizeString(params[o], '='); Flux::string word = sentence[0]; Flux::string param = sentence.size() > 1 ? sentence[1] : ""; if(word.equals_ci("NETWORK")) n->isupport.Network = param; if(word.equals_ci("CHANTYPES")) n->isupport.ChanTypes = param; if(word.equals_ci("AWAYLEN")) n->isupport.AwayLen = static_cast<int>(param); if(word.equals_ci("KICKLEN")) n->isupport.KickLen = static_cast<int>(param); if(word.equals_ci("MAXBANS")) n->isupport.MaxBans = static_cast<int>(param); if(word.equals_ci("MAXCHANNELS")) n->isupport.MaxChannels = static_cast<int>(param); if(word.equals_ci("CHANNELLEN")) n->isupport.ChannelLen = static_cast<int>(param); if(word.equals_ci("NICKLEN")) n->isupport.NickLen = static_cast<int>(param); if(word.equals_ci("TOPICLEN")) n->isupport.TopicLen = static_cast<int>(param); n->isupport.other[word] = param; } } if((i == 4)) { /* Numeric 004 * params[0] = Bots nickname * params[1] = servername * params[2] = ircd version * params[3] = user modes * params[4] = channel modes */ n->b->CheckNickname(); n->isupport.ServerHost = params[1]; n->isupport.IRCdVersion = params[2]; n->isupport.UserModes = params[3]; n->isupport.ChanModes = params[4]; } if((i == 376)) new SyncTimer(n); /* Nickname is in use numeric * Numeric 433 * params[0] = our current nickname * params[1] = Attempted nickname * params[2] = message (useless) */ if((i == 433)) { // Make sure we're not incrementing if the server sends a random 443 if(params[1].search(Config->NicknamePrefix)) { Flux::string number = params[1].substr(Config->NicknamePrefix.size()); if(static_cast<int>(number) == n->b->BotNum) n->b->BotNum++; } n->b->CheckNickname(); } }
// Have a little fun with the system, these are useful to see how lagged the system is or if it's // even working properly void OnChannelAction(User *u, Channel *c, const std::vector<Flux::string> ¶ms) { Flux::string msg = CondenseVector(params); Bot *b = u->n->b; // Imported from CIA.vc c: if(msg.search_ci("rubs "+b->nick+"'s tummy")) c->SendMessage("*purr*"); if(msg.search_ci("hugs "+b->nick)) c->SendAction("hugs %s", u->nick.c_str()); if(msg.search_ci("eats "+b->nick)) c->SendAction("tastes crunchy"); if(msg.search_ci("kicks "+b->nick)) c->SendMessage("ow"); // Justasic's additions if(msg.search_ci("smashes "+b->nick) || msg.search_ci("steps on "+b->nick)) c->SendAction("is flattened"); if(msg.search_ci("burns "+b->nick) || msg.search_ci("sets "+b->nick+" on fire")) c->SendAction("runs around"); if(msg.search_ci("punches "+b->nick)) c->SendAction("cries"); if(msg.search_ci("fries "+b->nick)) c->SendAction("sizzles"); if(msg.search_ci("pets "+b->nick)) c->SendMessage("*purr*"); if(msg.search_ci("pokes "+b->nick)) c->SendAction("farts"); if(msg.search_ci("slaps "+b->nick)) c->SendAction("punches %s in the esophagus!", u->nick.c_str()); if(msg.search_ci("adopts "+b->nick)) c->SendAction("runs away in 4 years"); if(msg.search_ci("murders "+b->nick)) c->SendAction("dies"); }
XMLFile::Tag::Tag(const Flux::string n, Flux::string conts) { Name = n; Content = conts; for(unsigned i = 0; i < conts.size(); i++) { char c = conts.at(i); if(c == '<' && conts.at(i + 1) != '/' && conts.at(i + 1) != ' ') { bool SelfContained = false; std::string tag = ""; std::string contents = ""; for(unsigned j = 1; conts.at(i + j) != '>' && conts.at(i + j) != ' '; j++) { tag += conts.at(i + j); } for(unsigned o = 1; conts.at(i + o) != '>'; o++) { if((conts.at(i + o) == '/' || conts.at(i + o) == '?') && conts.at(i + o + 1) == '>') { SelfContained = true; break; } } if(!SelfContained) { for(unsigned k = conts.find('>', i) + 1; k < conts.find("</" + tag, i); k++) { contents += conts.at(k); } } Tag t(tag, contents); for(unsigned l = 1; conts.at(i + l) != '>'; l++) { char cc = conts.at(i + l); if(cc == '=' && conts.at(i + l + 1) == '"') { std::string attribName = ""; std::string attribVal = ""; for(int m = -1; conts.at(i + l + m) != ' '; m--) { attribName.insert(0, 1, conts.at(i + l + m)); } for(unsigned nn = 2; conts.at(i + l + nn) != '"'; nn++) { attribVal += conts.at(i + l + nn); } Tag::Attribute att(attribVal); t.Attributes[attribName] = att; } } Tags[tag] = t; i += contents.size(); } } }
void OnQuit(User *u, const Flux::string &reason) { CLog("*** %s has quit (%s)", u->nick.c_str(), reason.c_str()); }