diff options
Diffstat (limited to 'modules')
42 files changed, 3314 insertions, 2697 deletions
diff --git a/modules/commands/bs_kick.cpp b/modules/commands/bs_kick.cpp index 74ccaf05c..2c41b4b90 100644 --- a/modules/commands/bs_kick.cpp +++ b/modules/commands/bs_kick.cpp @@ -577,26 +577,60 @@ class CommandBSKick : public Command } }; -struct BanData +struct BanData : public ExtensibleItem { - Anope::string mask; - time_t last_use; - int16 ttb[TTB_SIZE]; + struct Data + { + Anope::string mask; + time_t last_use; + int16 ttb[TTB_SIZE]; + + Data() + { + this->Clear(); + } + + void Clear() + { + last_use = 0; + for (int i = 0; i < TTB_SIZE; ++i) + this->ttb[i] = 0; + } + }; + + private: + typedef std::map<Anope::string, Data, std::less<ci::string> > data_type; + data_type data_map; - BanData() + public: + Data &get(const Anope::string &key) { - this->Clear(); + return this->data_map[key]; } - void Clear() + bool empty() const + { + return this->data_map.empty(); + } + + void purge() { - last_use = 0; - for (int i = 0; i < TTB_SIZE; ++i) - this->ttb[i] = 0; + for (data_type::iterator it = data_map.begin(), it_end = data_map.end(); it != it_end;) + { + const Anope::string &user = it->first; + Data &bd = it->second; + ++it; + + if (Anope::CurTime - bd.last_use > Config->BSKeepData) + { + data_map.erase(user); + continue; + } + } } }; -struct UserData +struct UserData : public ExtensibleItem { UserData() { @@ -621,6 +655,11 @@ struct UserData Anope::string lastline; Anope::string lasttarget; int16 times; + + void OnDelete() + { + delete this; + } }; @@ -637,23 +676,11 @@ class BanDataPurger : public CallBack { Channel *c = it->second; - std::map<Anope::string, BanData> bandata; - if (c->GetExtRegular("bs_main_bandata", bandata)) + BanData *bd = c->GetExt<BanData *>("bs_main_bandata"); + if (bd != NULL) { - for (std::map<Anope::string, BanData>::iterator it2 = bandata.begin(), it2_end = bandata.end(); it2 != it2_end;) - { - const Anope::string &user = it2->first; - BanData *bd = &it2->second; - ++it2; - - if (Anope::CurTime - bd->last_use > Config->BSKeepData) - { - bandata.erase(user); - continue; - } - } - - if (bandata.empty()) + bd->purge(); + if (bd->empty()) c->Shrink("bs_main_bandata"); } } @@ -665,30 +692,32 @@ class BSKick : public Module CommandBSKick commandbskick; BanDataPurger purger; - BanData *GetBanData(User *u, Channel *c) + BanData::Data &GetBanData(User *u, Channel *c) { - std::map<Anope::string, BanData> bandatamap; - if (!c->GetExtRegular("bs_main_bandata", bandatamap)); - c->Extend("bs_main_bandata", new ExtensibleItemRegular<std::map<Anope::string, BanData> >(bandatamap)); - c->GetExtRegular("bs_main_bandata", bandatamap); - - BanData *bd = &bandatamap[u->GetMask()]; - if (bd->last_use && Anope::CurTime - bd->last_use > Config->BSKeepData) - bd->Clear(); - bd->last_use = Anope::CurTime; - return bd; + BanData *bd = c->GetExt<BanData *>("bs_main_bandata"); + if (bd == NULL) + { + bd = new BanData(); + c->Extend("bs_main_bandata", bd); + } + + return bd->get(u->GetMask()); } UserData *GetUserData(User *u, Channel *c) { - UserData *ud = NULL; UserContainer *uc = c->FindUser(u); - if (uc != NULL && !uc->GetExtPointer("bs_main_userdata", ud)) + if (uc == NULL) + return NULL; + + UserData *ud = uc->GetExt<UserData *>("bs_main_userdata"); + if (ud == NULL) { ud = new UserData(); - uc->Extend("bs_main_userdata", new ExtensibleItemPointer<UserData>(ud)); + uc->Extend("bs_main_userdata", ud); } - return ud; + + return ud; } void check_ban(ChannelInfo *ci, User *u, int ttbtype) @@ -697,17 +726,17 @@ class BSKick : public Module if (u->server->IsULined()) return; - BanData *bd = this->GetBanData(u, ci->c); + BanData::Data &bd = this->GetBanData(u, ci->c); - ++bd->ttb[ttbtype]; - if (ci->ttb[ttbtype] && bd->ttb[ttbtype] >= ci->ttb[ttbtype]) + ++bd.ttb[ttbtype]; + if (ci->ttb[ttbtype] && bd.ttb[ttbtype] >= ci->ttb[ttbtype]) { - /* Should not use == here because bd->ttb[ttbtype] could possibly be > ci->ttb[ttbtype] + /* Should not use == here because bd.ttb[ttbtype] could possibly be > ci->ttb[ttbtype] * if the TTB was changed after it was not set (0) before and the user had already been * kicked a few times. Bug #1056 - Adam */ Anope::string mask; - bd->ttb[ttbtype] = 0; + bd.ttb[ttbtype] = 0; get_idealban(ci, u, mask); diff --git a/modules/commands/cs_access.cpp b/modules/commands/cs_access.cpp index 2821ba5c3..3e737b709 100644 --- a/modules/commands/cs_access.cpp +++ b/modules/commands/cs_access.cpp @@ -28,7 +28,7 @@ static void reset_levels(ChannelInfo *ci) ci->SetLevel(it->first, it->second); } -class AccessChanAccess : public ChanAccess +class AccessChanAccess : public ChanAccess, public Serializable<AccessChanAccess> { public: int level; @@ -86,6 +86,41 @@ class AccessChanAccess : public ChanAccess return highest; } } + + Anope::string serialize_name() { return "AccessChanAccess"; } + serialized_data serialize() + { + serialized_data data; + + data["provider"] << this->provider->name; + data["ci"] << this->ci->name; + data["mask"] << this->mask; + data["creator"] << this->creator; + data["last_seen"].setType(Serialize::DT_INT) << this->last_seen; + data["created"].setType(Serialize::DT_INT) << this->created; + data["level"].setType(Serialize::DT_INT) << this->level; + + return data; + } + + static void unserialize(SerializableBase::serialized_data &data) + { + service_reference<AccessProvider> aprovider(data["provider"].astr()); + ChannelInfo *ci = cs_findchan(data["ci"].astr()); + if (!aprovider || !ci) + return; + + AccessChanAccess *access = new AccessChanAccess(aprovider); + access->provider = aprovider; + access->ci = ci; + data["mask"] >> access->mask; + data["creator"] >> access->creator; + data["last_seen"] >> access->last_seen; + data["created"] >> access->created; + data["level"] >> access->level; + + ci->AddAccess(access); + } }; class AccessAccessProvider : public AccessProvider @@ -459,10 +494,10 @@ class CommandCSAccess : public Command source.Reply(ACCESS_DENIED); else { - ci->ClearAccess(); - FOREACH_MOD(I_OnAccessClear, OnAccessClear(ci, u)); + ci->ClearAccess(); + source.Reply(_("Channel %s access list has been cleared."), ci->name.c_str()); bool override = !IsFounder(u, ci); @@ -833,6 +868,7 @@ class CSAccess : public Module { this->SetAuthor("Anope"); + Serializable<AccessChanAccess>::Alloc.Register("AccessChanAccess"); Implementation i[] = { I_OnReload, I_OnCreateChan, I_OnGroupCheckPriv }; ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); diff --git a/modules/commands/cs_entrymsg.cpp b/modules/commands/cs_entrymsg.cpp index 05b97e027..6fa8d5dd3 100644 --- a/modules/commands/cs_entrymsg.cpp +++ b/modules/commands/cs_entrymsg.cpp @@ -13,34 +13,70 @@ #include "module.h" -struct EntryMsg +struct EntryMsg : Serializable<EntryMsg> { - static unsigned MaxEntries; + ChannelInfo *ci; + Anope::string creator; + Anope::string message; + time_t when; - EntryMsg(const Anope::string &cname, const Anope::string &cmessage, time_t ct = Anope::CurTime) + EntryMsg(ChannelInfo *c, const Anope::string &cname, const Anope::string &cmessage, time_t ct = Anope::CurTime) { + + this->ci = c; this->creator = cname; this->message = cmessage; this->when = ct; } - Anope::string creator; - Anope::string message; - time_t when; + serialized_data serialize() + { + serialized_data data; + + data["ci"] << this->ci->name; + data["creator"] << this->creator; + data["message"] << this->message; + data["when"].setType(Serialize::DT_INT) << this->when; + + return data; + } + + static void unserialize(serialized_data &data); }; -unsigned EntryMsg::MaxEntries = 0; + +static unsigned MaxEntries = 0; + +struct EntryMessageList : std::vector<EntryMsg>, ExtensibleItem +{ +}; + +void EntryMsg::unserialize(serialized_data &data) +{ + ChannelInfo *ci = cs_findchan(data["ci"].astr()); + if (!ci) + return; + + EntryMessageList *messages = ci->GetExt<EntryMessageList *>("cs_entrymsg"); + if (messages == NULL) + { + messages = new EntryMessageList(); + ci->Extend("cs_entrymsg", messages); + } + + messages->push_back(EntryMsg(ci, data["creator"].astr(), data["message"].astr())); +} class CommandEntryMessage : public Command { private: void DoList(CommandSource &source, ChannelInfo *ci) { - std::vector<EntryMsg> messages; - if (ci->GetExtRegular("cs_entrymsg", messages)) + EntryMessageList *messages = ci->GetExt<EntryMessageList *>("cs_entrymsg"); + if (messages == NULL) { source.Reply(_("Entry message list for \2%s\2:"), ci->name.c_str()); - for (unsigned i = 0; i < messages.size(); ++i) - source.Reply(CHAN_LIST_ENTRY, i + 1, messages[i].message.c_str(), messages[i].creator.c_str(), do_strftime(messages[i].when).c_str()); + for (unsigned i = 0; i < messages->size(); ++i) + source.Reply(CHAN_LIST_ENTRY, i + 1, (*messages)[i].message.c_str(), (*messages)[i].creator.c_str(), do_strftime((*messages)[i].when).c_str()); source.Reply(_("End of entry message list.")); } else @@ -49,35 +85,36 @@ class CommandEntryMessage : public Command void DoAdd(CommandSource &source, ChannelInfo *ci, const Anope::string &message) { - std::vector<EntryMsg> messages; - ci->GetExtRegular("cs_entrymsg", messages); + EntryMessageList *messages = ci->GetExt<EntryMessageList *>("cs_entrymsg"); + if (messages == NULL) + { + messages = new EntryMessageList(); + ci->Extend("cs_entrymsg", messages); + } - if (EntryMsg::MaxEntries && messages.size() >= EntryMsg::MaxEntries) + if (MaxEntries && messages->size() >= MaxEntries) source.Reply(_("The entry message list for \2%s\2 is full."), ci->name.c_str()); else { - messages.push_back(EntryMsg(source.u->nick, message)); - ci->Extend("cs_entrymsg", new ExtensibleItemRegular<std::vector<EntryMsg> >(messages)); + messages->push_back(EntryMsg(ci, source.u->nick, message)); source.Reply(_("Entry message added to \2%s\2"), ci->name.c_str()); } } void DoDel(CommandSource &source, ChannelInfo *ci, const Anope::string &message) { - std::vector<EntryMsg> messages; + EntryMessageList *messages = ci->GetExt<EntryMessageList *>("cs_entrymsg"); if (!message.is_pos_number_only()) source.Reply(("Entry message \002%s\002 not found on channel \002%s\002."), message.c_str(), ci->name.c_str()); - else if (ci->GetExtRegular("cs_entrymsg", messages)) + else if (messages != NULL) { try { unsigned i = convertTo<unsigned>(message); - if (i > 0 && i <= messages.size()) + if (i > 0 && i <= messages->size()) { - messages.erase(messages.begin() + i - 1); - if (!messages.empty()) - ci->Extend("cs_entrymsg", new ExtensibleItemRegular<std::vector<EntryMsg> >(messages)); - else + messages->erase(messages->begin() + i - 1); + if (messages->empty()) ci->Shrink("cs_entrymsg"); source.Reply(_("Entry message \2%i\2 for \2%s\2 deleted."), i, ci->name.c_str()); } @@ -165,58 +202,27 @@ class CSEntryMessage : public Module { this->SetAuthor("Anope"); - Implementation i[] = { I_OnJoinChannel, I_OnReload, I_OnDatabaseReadMetadata, I_OnDatabaseWriteMetadata }; - ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); - - this->OnReload(); + + Serializable<EntryMsg>::Alloc.Register("EntryMsg"); } void OnJoinChannel(User *u, Channel *c) { if (u && c && c->ci && u->server->IsSynced()) { - std::vector<EntryMsg> messages; + EntryMessageList *messages = c->ci->GetExt<EntryMessageList *>("cs_entrymsg"); - if (c->ci->GetExtRegular("cs_entrymsg", messages)) - for (unsigned i = 0; i < messages.size(); ++i) - u->SendMessage(c->ci->WhoSends(), "[%s] %s", c->ci->name.c_str(), messages[i].message.c_str()); + if (messages != NULL) + for (unsigned i = 0; i < messages->size(); ++i) + u->SendMessage(c->ci->WhoSends(), "[%s] %s", c->ci->name.c_str(), (*messages)[i].message.c_str()); } } void OnReload() { ConfigReader config; - EntryMsg::MaxEntries = config.ReadInteger("cs_entrymsg", "maxentries", "5", 0, true); - } - - EventReturn OnDatabaseReadMetadata(ChannelInfo *ci, const Anope::string &key, const std::vector<Anope::string> ¶ms) - { - if (key.find("CS_ENTRYMSG_") == 0 && params.size() > 2) - { - Anope::string creator = params[0]; - time_t t = params[1].is_pos_number_only() ? convertTo<time_t>(params[1]) : Anope::CurTime; - Anope::string message = params[2]; - for (unsigned j = 3; j < params.size(); ++j) - message += " " + params[j]; - - std::vector<EntryMsg> messages; - ci->GetExtRegular("cs_entrymsg", messages); - messages.push_back(EntryMsg(creator, message, t)); - ci->Extend("cs_entrymsg", new ExtensibleItemRegular<std::vector<EntryMsg> >(messages)); - - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnDatabaseWriteMetadata(void (*WriteMetadata)(const Anope::string &, const Anope::string &), ChannelInfo *ci) - { - std::vector<EntryMsg> messages; - if (ci->GetExtRegular("cs_entrymsg", messages)) - for (unsigned i = 0; i < messages.size(); ++i) - WriteMetadata("CS_ENTRYMSG_" + stringify(i), messages[i].creator + " " + stringify(messages[i].when) + " " + messages[i].message); + MaxEntries = config.ReadInteger("cs_entrymsg", "maxentries", "5", 0, true); } }; diff --git a/modules/commands/cs_flags.cpp b/modules/commands/cs_flags.cpp index 5ead6f2d9..1b790b5f1 100644 --- a/modules/commands/cs_flags.cpp +++ b/modules/commands/cs_flags.cpp @@ -15,7 +15,7 @@ static std::map<Anope::string, char> defaultFlags; -class FlagsChanAccess : public ChanAccess +class FlagsChanAccess : public ChanAccess, public Serializable<FlagsChanAccess> { public: std::set<char> flags; @@ -65,6 +65,41 @@ class FlagsChanAccess : public ChanAccess return Anope::string(buffer.begin(), buffer.end()); } + + Anope::string serialize_name() { return "FlagsChanAccess"; } + serialized_data serialize() + { + serialized_data data; + + data["provider"] << this->provider->name; + data["ci"] << this->ci->name; + data["mask"] << this->mask; + data["creator"] << this->creator; + data["last_seen"].setType(Serialize::DT_INT) << this->last_seen; + data["created"].setType(Serialize::DT_INT) << this->created; + data["flags"] << this->Serialize(); + + return data; + } + + static void unserialize(SerializableBase::serialized_data &data) + { + service_reference<AccessProvider> aprovider(data["provider"].astr()); + ChannelInfo *ci = cs_findchan(data["ci"].astr()); + if (!aprovider || !ci) + return; + + FlagsChanAccess *access = new FlagsChanAccess(aprovider); + access->provider = aprovider; + access->ci = ci; + data["mask"] >> access->mask; + data["creator"] >> access->creator; + data["last_seen"] >> access->last_seen; + data["created"] >> access->created; + access->Unserialize(data["flags"].astr()); + + ci->AddAccess(access); + } }; class FlagsAccessProvider : public AccessProvider @@ -385,6 +420,8 @@ class CSFlags : public Module ModuleManager::Attach(i, this, 1); this->OnReload(); + + Serializable<FlagsChanAccess>::Alloc.Register("FlagsChanAccess"); } void OnReload() diff --git a/modules/commands/cs_info.cpp b/modules/commands/cs_info.cpp index 5fc85666a..e7c8e039e 100644 --- a/modules/commands/cs_info.cpp +++ b/modules/commands/cs_info.cpp @@ -100,10 +100,9 @@ class CommandCSInfo : public Command } if (ci->HasFlag(CI_SUSPENDED)) { - Anope::string by, reason; - ci->GetExtRegular("suspend_by", by); - ci->GetExtRegular("suspend_reason", reason); - source.Reply(_(" Suspended: [%s] %s"), by.c_str(), !reason.empty() ? reason.c_str() : NO_REASON); + Anope::string *by = ci->GetExt<Anope::string *>("suspend_by"), *reason = ci->GetExt<Anope::string *>("suspend_reason"); + if (by != NULL) + source.Reply(_(" Suspended: [%s] %s"), by->c_str(), (reason && !reason->empty() ? reason->c_str() : NO_REASON)); } FOREACH_MOD(I_OnChanInfo, OnChanInfo(source, ci, show_all)); diff --git a/modules/commands/cs_seen.cpp b/modules/commands/cs_seen.cpp index d466acca4..f828e181d 100644 --- a/modules/commands/cs_seen.cpp +++ b/modules/commands/cs_seen.cpp @@ -19,26 +19,55 @@ enum TypeInfo NEW, NICK_TO, NICK_FROM, JOIN, PART, QUIT, KICK }; -struct SeenInfo +struct SeenInfo; +typedef Anope::insensitive_map<SeenInfo *> database_map; +database_map database; + +struct SeenInfo : Serializable<SeenInfo> { + Anope::string nick; Anope::string vhost; TypeInfo type; Anope::string nick2; // for nickchanges and kicks Anope::string channel; // for join/part/kick Anope::string message; // for part/kick/quit time_t last; // the time when the user was last seen -}; -class ModuleConfigClass -{ - public: - time_t purgetime; - time_t expiretimeout; + serialized_data serialize() + { + serialized_data data; + + data["nick"] << nick; + data["vhost"] << vhost; + data["type"] << type; + data["nick2"] << nick2; + data["channel"] << channel; + data["message"] << message; + data["last"].setType(Serialize::DT_INT) << last; + + return data; + } + + static void unserialize(serialized_data &data) + { + SeenInfo *s = new SeenInfo(); + + data["nick"] >> s->nick; + data["vhost"] >> s->vhost; + unsigned int n; + data["type"] >> n; + s->type = static_cast<TypeInfo>(n); + data["nick2"] >> s->nick2; + data["channel"] >> s->channel; + data["message"] >> s->message; + data["last"] >> s->last; + + database[s->nick] = s; + } }; -ModuleConfigClass ModuleConfig; -typedef Anope::insensitive_map<SeenInfo *> database_map; -database_map database; +static time_t purgetime; +static time_t expiretimeout; static SeenInfo *FindInfo(const Anope::string &nick) { @@ -262,7 +291,7 @@ class DataBasePurger : public CallBack database_map::iterator cur = it; ++it; - if ((Anope::CurTime - cur->second->last) > ModuleConfig.purgetime) + if ((Anope::CurTime - cur->second->last) > purgetime) { Log(LOG_DEBUG) << cur->first << " was last seen " << do_strftime(cur->second->last) << ", purging entry"; delete cur->second; @@ -289,48 +318,55 @@ class CSSeen : public Module I_OnUserQuit, I_OnJoinChannel, I_OnPartChannel, - I_OnUserKicked, - I_OnDatabaseRead, - I_OnDatabaseWrite }; + I_OnUserKicked }; ModuleManager::Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnReload(); + + Serializable<SeenInfo>::Alloc.Register("SeenInfo"); } + void OnReload() { ConfigReader config; - ModuleConfig.purgetime = dotime(config.ReadValue("cs_seen", "purgetime", "30d", 0)); - ModuleConfig.expiretimeout = dotime(config.ReadValue("cs_seen", "expiretimeout", "1d", 0)); + purgetime = dotime(config.ReadValue("cs_seen", "purgetime", "30d", 0)); + expiretimeout = dotime(config.ReadValue("cs_seen", "expiretimeout", "1d", 0)); - if (purger.GetSecs() != ModuleConfig.expiretimeout) - purger.SetSecs(ModuleConfig.expiretimeout); + if (purger.GetSecs() != expiretimeout) + purger.SetSecs(expiretimeout); } + void OnUserConnect(User *u) { UpdateUser(u, NEW, u->nick, "", "", ""); } + void OnUserNickChange(User *u, const Anope::string &oldnick) { UpdateUser(u, NICK_TO, oldnick, u->nick, "", ""); UpdateUser(u, NICK_FROM, u->nick, oldnick, "", ""); } + void OnUserQuit(User *u, const Anope::string &msg) { UpdateUser(u, QUIT, u->nick, "", "", msg); } + void OnJoinChannel(User *u, Channel *c) { UpdateUser(u, JOIN, u->nick, "", c->name, ""); } + void OnPartChannel(User *u, Channel *c, const Anope::string &channel, const Anope::string &msg) { UpdateUser(u, PART, u->nick, "", channel, msg); } + void OnUserKicked(Channel *c, User *target, const Anope::string &source, const Anope::string &msg) { UpdateUser(target, KICK, target->nick, source, c->name, msg); } + void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message) { SeenInfo *info = FindInfo(nick); @@ -339,6 +375,7 @@ class CSSeen : public Module info = new SeenInfo; database.insert(std::pair<Anope::string, SeenInfo *>(nick, info)); } + info->nick = nick; info->vhost = u->GetVIdent() + "@" + u->GetDisplayedHost(); info->type = Type; info->last = Anope::CurTime; @@ -346,82 +383,6 @@ class CSSeen : public Module info->channel = channel; info->message = message; } - - EventReturn OnDatabaseRead(const std::vector<Anope::string> ¶ms) - { - if (params[0].equals_ci("SEEN") && (params.size() >= 5)) - { - SeenInfo *info = new SeenInfo; - database.insert(std::pair<Anope::string, SeenInfo *>(params[1], info)); - info->vhost = params[2]; - info->last = params[3].is_pos_number_only() ? convertTo<time_t>(params[3]) : 0 ; - if (params[4].equals_ci("NEW")) - { - info->type = NEW; - } - else if (params[4].equals_ci("NICK_TO") && params.size() == 6) - { - info->type = NICK_TO; - info->nick2 = params[5]; - } - else if (params[4].equals_ci("NICK_FROM") && params.size() == 6) - { - info->type = NICK_FROM; - info->nick2 = params[5]; - } - else if (params[4].equals_ci("JOIN") && params.size() == 6) - { - info->type = JOIN; - info->channel = params[5]; - } - else if (params[4].equals_ci("PART") && params.size() == 7) - { - info->type = PART; - info->channel = params[5]; - info->message = params[6]; - } - else if (params[4].equals_ci("QUIT") && params.size() == 6) - { - info->type = QUIT; - info->message = params[5]; - } - else if (params[4].equals_ci("KICK") && params.size() == 8) - { - info->type = KICK; - info->nick2 = params[5]; - info->channel = params[6]; - info->message = params[7]; - } - return EVENT_STOP; - } - return EVENT_CONTINUE; - } - void OnDatabaseWrite(void (*Write)(const Anope::string &)) - { - for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end; ++it) - { - std::stringstream buf; - buf << "SEEN " << it->first.c_str() << " " << it->second->vhost << " " << it->second->last << " "; - switch (it->second->type) - { - case NEW: - buf << "NEW"; break; - case NICK_TO: - buf << "NICK_TO " << it->second->nick2; break; - case NICK_FROM: - buf << "NICK_FROM " << it->second->nick2; break; - case JOIN: - buf << "JOIN " << it->second->channel; break; - case PART: - buf << "PART " << it->second->channel << " :" << it->second->message; break; - case QUIT: - |
