/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2011 InspIRCd Development Team * See: http://wiki.inspircd.org/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include "sql.h" /* $ModDesc: Provides channel history for a given number of lines, stored in an SQL database */ namespace m_sql_chanhistory { class DiscardQuery : public SQLQuery { public: DiscardQuery(Module* me) : SQLQuery(me) {} void OnResult(SQLResult& res) {} void OnError(SQLerror& e) { ServerInstance->Logs->Log("m_sql_chanhistory", DEFAULT, "SQL update returned error: %s", e.str.c_str()); } }; class ReplayQuery : public SQLQuery { public: std::string cname, uid; ReplayQuery(Module* me, Membership* memb) : SQLQuery(me), cname(memb->chan->name), uid(memb->user->uuid) { } void OnResult(SQLResult& res) { User* user = ServerInstance->FindUUID(uid); Channel* chan = ServerInstance->FindChan(cname); int rows = res.Rows(); if (!user || !chan || !rows) return; user->WriteServ("NOTICE %s :Replaying %d lines of pre-join history", chan->name.c_str(), rows); SQLEntries row; while (res.GetRow(row)) { user->Write(row[0]); } } void OnError(SQLerror& e) { ServerInstance->Logs->Log("m_sql_chanhistory", DEFAULT, "SQL query returned error: %s", e.str.c_str()); } }; class HistoryMode : public ModeHandler { public: LocalIntExt histID; int maxlines; dynamic_reference sqldb; std::string tablename; HistoryMode(Module* Creator) : ModeHandler(Creator, "history", 'H', PARAM_SETONLY, MODETYPE_CHANNEL), histID(EXTENSIBLE_CHANNEL, "history-id", Creator), sqldb("SQL") { fixed_letter = false; } std::pair ParamParse(const std::string& parameter) { std::string::size_type colon = parameter.find(':'); if (colon == std::string::npos) return std::make_pair(0,0); int len = atoi(parameter.substr(0, colon).c_str()); int time = ServerInstance->Duration(parameter.substr(colon+1)); return std::make_pair(len,time); } ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { if (adding) { std::pair rv = ParamParse(parameter); int len = rv.first; int time = rv.second; if (len <= 0 || time < 0) return MODEACTION_DENY; if (len > maxlines && IS_LOCAL(source)) return MODEACTION_DENY; if (parameter == channel->GetModeParameter(this)) return MODEACTION_DENY; channel->SetModeParam(this, parameter); } else { channel->SetModeParam(this, ""); } ParamL n; n.push_back(tablename); n.push_back(channel->name); sqldb->submit(new DiscardQuery(creator), "DELETE FROM ? WHERE chan = '?'", n); histID.set(channel, 0); return MODEACTION_ALLOW; } }; class ModuleChanHistory : public Module { HistoryMode m; public: ModuleChanHistory() : m(this) { } void init() { ServerInstance->Modules->AddService(m); ServerInstance->Modules->AddService(m.histID); Implementation eventlist[] = { I_OnPostJoin, I_OnUserMessage, I_OnChannelDelete }; ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); ParamL n; n.push_back(m.tablename); m.sqldb->submit(new DiscardQuery(this), "DELETE FROM ?", n); } void ReadConfig(ConfigReadStatus& status) { ConfigTag *tag = ServerInstance->Config->GetTag("chanhistory"); m.maxlines = tag->getInt("maxlines", 50); m.tablename = tag->getString("table", "chanhistory"); if (m.tablename.empty()) status.ReportError(tag, "Table name cannot be empty"); std::string dbid = tag->getString("dbid"); if (dbid.empty()) m.sqldb.SetProvider("SQL"); else m.sqldb.SetProvider("SQL/" + dbid); if (!m.sqldb) status.ReportError(tag, "SQL database not found!"); } ~ModuleChanHistory() { ServerInstance->Modes->DelMode(&m); } void OnUserMessage(User* user,void* dest,int target_type, const std::string &text, char status, const CUList&) { if (target_type == TYPE_CHANNEL && status == 0) { Channel* c = (Channel*)dest; if (!c->IsModeSet(&m)) return; char buf[MAXBUF]; snprintf(buf, MAXBUF, ":%s PRIVMSG %s :%s", user->GetFullHost().c_str(), c->name.c_str(), text.c_str()); int seq = m.histID.get(c); m.histID.set(c, seq + 1); ParamL n; n.push_back(m.tablename); n.push_back(c->name); n.push_back(ConvToStr(seq)); n.push_back(ConvToStr(ServerInstance->Time())); n.push_back(buf); m.sqldb->submit(new DiscardQuery(this), "INSERT INTO ? (chan, line, ts, text) VALUES ('?', '?', '?', '?')", n); n.clear(); std::pair param = m.ParamParse(c->GetModeParameter(&m)); n.push_back(m.tablename); n.push_back(c->name); if (param.first < seq) n.push_back(ConvToStr(seq - param.first)); else n.push_back("0"); n.push_back(ConvToStr(ServerInstance->Time() - param.second)); m.sqldb->submit(new DiscardQuery(this), "DELETE FROM ? WHERE chan = '?' AND (line < ? OR ts < ?)", n); } } void OnPostJoin(Membership* memb) { if (!memb->chan->IsModeSet(&m)) return; std::pair param = m.ParamParse(memb->chan->GetModeParameter(&m)); int seq = m.histID.get(memb->chan); ParamL n; n.push_back(m.tablename); n.push_back(memb->chan->name); if (param.first < seq) n.push_back(ConvToStr(seq - param.first)); else n.push_back("0"); n.push_back(ConvToStr(ServerInstance->Time() - param.second)); m.sqldb->submit(new ReplayQuery(this, memb), "SELECT text FROM ? WHERE chan = '?' AND line >= ? AND ts >= ? ORDER BY line", n); } void OnChannelDelete(Channel* chan) { if (chan->IsModeSet(&m)) { ParamL n; n.push_back(m.tablename); n.push_back(chan->name); m.sqldb->submit(new DiscardQuery(this), "DELETE FROM ? WHERE chan = '?'", n); } } Version GetVersion() { return Version("Provides channel history replayed on join", VF_VENDOR); } }; } using m_sql_chanhistory::ModuleChanHistory; MODULE_INIT(ModuleChanHistory)