1
0
mirror of https://github.com/anope/anope.git synced 2026-07-08 15:03:12 +02:00

Move modules/stats under extras because it depends on m_mysql, update its config a bit to look similar to all of the other config files

This commit is contained in:
Adam
2014-01-03 19:54:14 -05:00
parent 2781b6946d
commit e1ce6174ce
9 changed files with 56 additions and 57 deletions
+179
View File
@@ -0,0 +1,179 @@
/* Chanstats core functions
*
* (C) 2003-2014 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*
* Based on the original code of Epona by Lara.
* Based on the original code of Services by Andy Church.
*/
#include "module.h"
#include "modules/sql.h"
class MySQLInterface : public SQL::Interface
{
public:
MySQLInterface(Module *o) : SQL::Interface(o) { }
void OnResult(const SQL::Result &r) anope_override
{
}
void OnError(const SQL::Result &r) anope_override
{
if (!r.GetQuery().query.empty())
Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError();
else
Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError();
}
};
class CommandCSStats : public Command
{
public:
CommandCSStats(Module *creator) : Command (creator, "chanserv/stats", 0, 2)
{
this->SetDesc(_("Displays your Channel Stats"));
this->SetSyntax(_("[\037channel\037] [\037nick\037]"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params);
};
class CommandCSGStats : public Command
{
public:
CommandCSGStats(Module *creator) : Command (creator, "chanserv/gstats", 0, 2)
{
this->SetDesc(_("Displays your Global Stats"));
this->SetSyntax(_("\037nick\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params);
};
class CSStats;
static CSStats *me;
class CSStats : public Module
{
CommandCSStats commandcsstats;
CommandCSGStats commandcsgstats;
ServiceReference<SQL::Provider> sql;
MySQLInterface sqlinterface;
Anope::string prefix;
public:
CSStats(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandcsstats(this), commandcsgstats(this), sql("", ""), sqlinterface(this)
{
me = this;
}
void OnReload(Configuration::Conf *conf) anope_override
{
prefix = conf->GetModule("m_chanstats")->Get<const Anope::string>("prefix");
if (prefix.empty())
prefix = "anope_";
this->sql = ServiceReference<SQL::Provider>("SQL::Provider", conf->GetModule("m_chanstats")->Get<const Anope::string>("engine"));
}
SQL::Result RunQuery(const SQL::Query &query)
{
if (!this->sql)
throw SQL::Exception("Unable to locate SQL reference, is m_mysql loaded and configured correctly?");
SQL::Result res = this->sql->RunQuery(query);
if (!res.GetError().empty())
throw SQL::Exception(res.GetError());
return res;
}
void DoStats(CommandSource &source, const bool is_global, const std::vector<Anope::string> &params)
{
Anope::string display, channel;
/*
* possible parameters are:
* stats [channel] [nick]
* stats [channel]
* stats [nick]
* stats
*/
switch (params.size())
{
case 2:
channel = params[0];
display = params[1];
break;
case 1:
if (params[0][0] == '#')
channel = params[0];
else
{
if (NickAlias *na = NickAlias::Find(params[0]))
display = na->nc->display;
else
{
source.Reply(_("%s not found."), params[0].c_str());
return;
}
}
break;
}
if (display.empty())
display = source.nc->display;
try
{
SQL::Query query;
query = "SELECT letters, words, line, smileys_happy+smileys_sad+smileys_other as smileys,"
"actions FROM `" + prefix + "chanstats` "
"WHERE `nick` = @nick@ AND `chan` = @channel@ AND `type` = 'total';";
if (is_global || channel.empty())
query.SetValue("channel", "");
else
query.SetValue("channel", channel);
query.SetValue("nick", display);
SQL::Result res = this->RunQuery(query);
if (res.Rows() > 0)
{
if (is_global)
source.Reply(_("Network stats for %s:"), display.c_str());
else
source.Reply(_("Channel stats for %s on %s:"), display.c_str(), channel.c_str());
source.Reply(_("letters: %s, words: %s, lines: %s, smileys %s, actions: %s"),
res.Get(0, "letters").c_str(), res.Get(0, "words").c_str(),
res.Get(0, "line").c_str(), res.Get(0, "smileys").c_str(),
res.Get(0, "actions").c_str());
}
else
source.Reply(_("No stats for %s."), display.c_str());
}
catch (const SQL::Exception &ex)
{
Log(LOG_DEBUG) << ex.GetReason();
}
}
};
void CommandCSStats::Execute(CommandSource &source, const std::vector<Anope::string> &params)
{
me->DoStats(source, false, params);
}
void CommandCSGStats::Execute(CommandSource &source, const std::vector<Anope::string> &params)
{
me->DoStats(source, true, params);
}
MODULE_INIT(CSStats)
+191
View File
@@ -0,0 +1,191 @@
/* Chanstats core functions
*
* (C) 2003-2014 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*
* Based on the original code of Epona by Lara.
* Based on the original code of Services by Andy Church.
*/
#include "module.h"
#include "modules/sql.h"
class MySQLInterface : public SQL::Interface
{
public:
MySQLInterface(Module *o) : SQL::Interface(o) { }
void OnResult(const SQL::Result &r) anope_override
{
}
void OnError(const SQL::Result &r) anope_override
{
if (!r.GetQuery().query.empty())
Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError();
else
Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError();
}
};
class CommandCSTop : public Command
{
public:
CommandCSTop(Module *creator) : Command (creator, "chanserv/top", 0, 2)
{
this->SetDesc(_("Displays the top 3 users of a channel"));
this->SetSyntax(_("\037channel\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params);
};
class CommandCSTop10 : public Command
{
public:
CommandCSTop10(Module *creator) : Command (creator, "chanserv/top10", 0, 2)
{
this->SetDesc(_("Displays the top 10 users of a channel"));
this->SetSyntax(_("\037channel\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params);
};
class CommandCSGTop : public Command
{
public:
CommandCSGTop(Module *creator) : Command (creator, "chanserv/gtop", 0, 1)
{
this->SetDesc(_("Displays the top 3 users of the network"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params);
};
class CommandCSGTop10 : public Command
{
public:
CommandCSGTop10(Module *creator) : Command (creator, "chanserv/gtop10", 0, 1)
{
this->SetDesc(_("Displays the top 10 users of the network"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params);
};
class CSTop;
static CSTop *me;
class CSTop : public Module
{
CommandCSTop commandcstop;
CommandCSGTop commandcsgtop;
CommandCSTop10 commandcstop10;
CommandCSGTop10 commandcsgtop10;
ServiceReference<SQL::Provider> sql;
MySQLInterface sqlinterface;
Anope::string prefix;
public:
CSTop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandcstop(this), commandcsgtop(this), commandcstop10(this), commandcsgtop10(this), sql("", ""),
sqlinterface(this)
{
me = this;
}
void OnReload(Configuration::Conf *conf) anope_override
{
prefix = conf->GetModule("m_chanstats")->Get<const Anope::string>("prefix");
if (prefix.empty())
prefix = "anope_";
this->sql = ServiceReference<SQL::Provider>("SQL::Provider", conf->GetModule("m_chanstats")->Get<const Anope::string>("engine"));
}
SQL::Result RunQuery(const SQL::Query &query)
{
if (!this->sql)
throw SQL::Exception("Unable to locate SQL reference, is m_mysql loaded and configured correctly?");
SQL::Result res = sql->RunQuery(query);
if (!res.GetError().empty())
throw SQL::Exception(res.GetError());
return res;
}
void DoTop(CommandSource &source, const std::vector<Anope::string> &params, bool is_global, int limit = 1)
{
Anope::string channel;
if (!params.empty())
channel = params[0];
else if (source.c && source.c->ci)
channel = source.c->ci->name;
if (!is_global && channel.empty())
is_global = true;
try
{
SQL::Query query;
query = "SELECT nick, letters, words, line, actions,"
"smileys_happy+smileys_sad+smileys_other as smileys "
"FROM `" + prefix + "chanstats` "
"WHERE `nick` != '' AND `chan` = @channel@ AND `type` = 'total' "
"ORDER BY `letters` DESC LIMIT @limit@;";
query.SetValue("limit", limit, false);
if (is_global)
query.SetValue("channel", "");
else
query.SetValue("channel", channel.c_str());
SQL::Result res = this->RunQuery(query);
if (res.Rows() > 0)
{
source.Reply(_("Top %i of %s"), limit, (is_global ? "Network" : channel.c_str()));
for (int i = 0; i < res.Rows(); ++i)
{
source.Reply(_("%2lu \002%-16s\002 letters: %s, words: %s, lines: %s, smileys %s, actions: %s"),
i+1, res.Get(i, "nick").c_str(), res.Get(i, "letters").c_str(),
res.Get(i, "words").c_str(), res.Get(i, "line").c_str(),
res.Get(0, "smileys").c_str(), res.Get(0, "actions").c_str());
}
}
else
source.Reply(_("No stats for %s."), is_global ? "Network" : channel.c_str());
}
catch (const SQL::Exception &ex)
{
Log(LOG_DEBUG) << ex.GetReason();
}
}
};
void CommandCSTop::Execute(CommandSource &source, const std::vector<Anope::string> &params)
{
me->DoTop(source, params, false, 3);
}
void CommandCSTop10::Execute(CommandSource &source, const std::vector<Anope::string> &params)
{
me->DoTop(source, params, false, 10);
}
void CommandCSGTop::Execute(CommandSource &source, const std::vector<Anope::string> &params)
{
me->DoTop(source, params, true, 3);
}
void CommandCSGTop10::Execute(CommandSource &source, const std::vector<Anope::string> &params)
{
me->DoTop(source, params, true, 10);
}
MODULE_INIT(CSTop)
@@ -0,0 +1,2 @@
build_subdir(${CMAKE_CURRENT_SOURCE_DIR})
+285
View File
@@ -0,0 +1,285 @@
#include "irc2sql.h"
void IRC2SQL::OnShutdown() anope_override
{
// TODO: test if we really have to use blocking query here
// (sometimes m_mysql get unloaded before the other thread executed all queries)
SQL::Result r = this->sql->RunQuery(SQL::Query("CALL " + prefix + "OnShutdown()"));
quitting = true;
}
void IRC2SQL::OnReload(Configuration::Conf *conf) anope_override
{
Configuration::Block *block = Config->GetModule(this);
prefix = block->Get<const Anope::string>("prefix", "anope_");
UseGeoIP = block->Get<bool>("GeoIPLookup", "no");
GeoIPDB = block->Get<const Anope::string>("GeoIPDatabase", "country");
ctcpuser = block->Get<bool>("ctcpuser", "no");
ctcpeob = block->Get<bool>("ctcpeob", "yes");
Anope::string engine = block->Get<const Anope::string>("engine");
this->sql = ServiceReference<SQL::Provider>("SQL::Provider", engine);
if (sql)
this->CheckTables();
else
Log() << "IRC2SQL: no database connection to " << engine;
const Anope::string &snick = block->Get<const Anope::string>("client");
if (snick.empty())
throw ConfigException(Module::name + ": <client> must be defined");
StatServ = BotInfo::Find(snick, true);
if (!StatServ)
throw ConfigException(Module::name + ": no bot named " + snick);
if (firstrun)
{
firstrun = false;
for (Anope::map<Server *>::const_iterator it = Servers::ByName.begin(); it != Servers::ByName.end(); ++it)
{
this->OnNewServer(it->second);
}
for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it)
{
this->OnChannelCreate(it->second);
}
for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
{
User *u = it->second;
bool exempt = false;
this->OnUserConnect(u, exempt);
for (User::ChanUserList::const_iterator cit = u->chans.begin(), cit_end = u->chans.end(); cit != cit_end; ++cit)
{
this->OnJoinChannel(u, cit->second->chan);
}
}
}
}
void IRC2SQL::OnNewServer(Server *server) anope_override
{
query = "INSERT DELAYED INTO `" + prefix + "server` (name, hops, comment, link_time, online, ulined) "
"VALUES (@name@, @hops@, @comment@, now(), 'Y', @ulined@) "
"ON DUPLICATE KEY UPDATE name=VALUES(name), hops=VALUES(hops), comment=VALUES(comment), "
"link_time=VALUES(link_time), online=VALUES(online), ulined=(ulined)";
query.SetValue("name", server->GetName());
query.SetValue("hops", server->GetHops());
query.SetValue("comment", server->GetDescription());
query.SetValue("ulined", server->IsULined() ? "Y" : "N");
this->RunQuery(query);
}
void IRC2SQL::OnServerQuit(Server *server) anope_override
{
if (quitting)
return;
query = "CALL " + prefix + "ServerQuit(@name@)";
query.SetValue("name", server->GetName());
this->RunQuery(query);
}
void IRC2SQL::OnUserConnect(User *u, bool &exempt) anope_override
{
if (!introduced_myself)
{
this->OnNewServer(Me);
introduced_myself = true;
}
query = "CALL " + prefix + "UserConnect(@nick@,@host@,@vhost@,@chost@,@realname@,@ip@,@ident@,@vident@,"
"@account@,@secure@,@fingerprint@,@signon@,@server@,@uuid@,@modes@,@oper@)";
query.SetValue("nick", u->nick);
query.SetValue("host", u->host);
query.SetValue("vhost", u->vhost);
query.SetValue("chost", u->chost);
query.SetValue("realname", u->realname);
query.SetValue("ip", u->ip);
query.SetValue("ident", u->GetIdent());
query.SetValue("vident", u->GetVIdent());
query.SetValue("secure", u->HasMode("SSL") || u->HasExt("ssl") ? "Y" : "N");
query.SetValue("account", u->Account() ? u->Account()->display : "");
query.SetValue("fingerprint", u->fingerprint);
query.SetValue("signon", u->signon);
query.SetValue("server", u->server->GetName());
query.SetValue("uuid", u->GetUID());
query.SetValue("modes", u->GetModes());
query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N");
this->RunQuery(query);
if (ctcpuser && (Me->IsSynced() || ctcpeob) && u->server != Me)
IRCD->SendPrivmsg(StatServ, u->GetUID(), "\1VERSION\1");
}
void IRC2SQL::OnUserQuit(User *u, const Anope::string &msg) anope_override
{
if (quitting || u->server->IsQuitting())
return;
query = "CALL " + prefix + "UserQuit(@nick@)";
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnUserNickChange(User *u, const Anope::string &oldnick) anope_override
{
query = "UPDATE `" + prefix + "user` SET nick=@newnick@ WHERE nick=@oldnick@";
query.SetValue("newnick", u->nick);
query.SetValue("oldnick", oldnick);
this->RunQuery(query);
}
void IRC2SQL::OnFingerprint(User *u) anope_override
{
query = "UPDATE `" + prefix + "user` SET secure=@secure@, fingerprint=@fingerprint@ WHERE nick=@nick@";
query.SetValue("secure", u->HasMode("SSL") || u->HasExt("ssl") ? "Y" : "N");
query.SetValue("fingerprint", u->fingerprint);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnUserModeSet(User *u, const Anope::string &mname) anope_override
{
query = "UPDATE `" + prefix + "user` SET modes=@modes@, oper=@oper@ WHERE nick=@nick@";
query.SetValue("nick", u->nick);
query.SetValue("modes", u->GetModes());
query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N");
this->RunQuery(query);
}
void IRC2SQL::OnUserModeUnset(User *u, const Anope::string &mname) anope_override
{
this->OnUserModeSet(u, mname);
}
void IRC2SQL::OnUserLogin(User *u)
{
query = "UPDATE `" + prefix + "user` SET account=@account@ WHERE nick=@nick@";
query.SetValue("nick", u->nick);
query.SetValue("account", u->Account() ? u->Account()->display : "");
this->RunQuery(query);
}
void IRC2SQL::OnNickLogout(User *u) anope_override
{
this->OnUserLogin(u);
}
void IRC2SQL::OnSetDisplayedHost(User *u) anope_override
{
query = "UPDATE `" + prefix + "user` "
"SET vhost=@vhost@ "
"WHERE nick=@nick@";
query.SetValue("vhost", u->GetDisplayedHost());
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnChannelCreate(Channel *c) anope_override
{
query = "INSERT INTO `" + prefix + "chan` (channel, topic, topicauthor, topictime, modes) "
"VALUES (@channel@,@topic@,@topicauthor@,@topictime@,@modes@) "
"ON DUPLICATE KEY UPDATE channel=VALUES(channel), topic=VALUES(topic),"
"topicauthor=VALUES(topicauthor), topictime=VALUES(topictime), modes=VALUES(modes)";
query.SetValue("channel", c->name);
query.SetValue("topic", c->topic);
query.SetValue("topicauthor", c->topic_setter);
query.SetValue("topictime", c->topic_ts);
query.SetValue("modes", c->GetModes(true,true));
this->RunQuery(query);
}
void IRC2SQL::OnChannelDelete(Channel *c) anope_override
{
query = "DELETE FROM `" + prefix + "chan` WHERE channel=@channel@";
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnJoinChannel(User *u, Channel *c) anope_override
{
Anope::string modes;
ChanUserContainer *cu = u->FindChannel(c);
if (cu)
modes = cu->status.Modes();
query = "CALL " + prefix + "JoinUser(@nick@,@channel@,@modes@)";
query.SetValue("nick", u->nick);
query.SetValue("channel", c->name);
query.SetValue("modes", modes);
this->RunQuery(query);
}
EventReturn IRC2SQL::OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) anope_override
{
query = "UPDATE `" + prefix + "chan` SET modes=@modes@ WHERE channel=@channel@";
query.SetValue("channel", c->name);
query.SetValue("modes", c->GetModes(true,true));
this->RunQuery(query);
return EVENT_CONTINUE;
}
EventReturn IRC2SQL::OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) anope_override
{
this->OnChannelModeSet(c, setter, mode, param);
return EVENT_CONTINUE;
}
void IRC2SQL::OnLeaveChannel(User *u, Channel *c) anope_override
{
if (quitting)
return;
/*
* user is quitting, we already received a OnUserQuit()
* at this point the user is already removed from SQL and all channels
*/
if (u->Quitting())
return;
query = "CALL " + prefix + "PartUser(@nick@,@channel@)";
query.SetValue("nick", u->nick);
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnTopicUpdated(Channel *c, const Anope::string &user, const Anope::string &topic) anope_override
{
query = "UPDATE `" + prefix + "chan` "
"SET topic=@topic@, topicauthor=@author@, topictime=FROM_UNIXTIME(@time@) "
"WHERE channel=@channel@";
query.SetValue("topic", c->topic);
query.SetValue("author", c->topic_setter);
query.SetValue("time", c->topic_ts);
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnBotNotice(User *u, BotInfo *bi, Anope::string &message) anope_override
{
Anope::string versionstr;
if (bi != StatServ)
return;
if (message[0] == '\1' && message[message.length() - 1] == '\1')
{
if (message.substr(0, 9).equals_ci("\1VERSION "))
{
if (u->HasExt("CTCPVERSION"))
return;
u->Extend<bool>("CTCPVERSION");
versionstr = Anope::NormalizeBuffer(message.substr(9, message.length() - 10));
if (versionstr.empty())
return;
query = "UPDATE `" + prefix + "user` "
"SET version=@version@ "
"WHERE nick=@nick@";
query.SetValue("version", versionstr);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
}
}
MODULE_INIT(IRC2SQL)
+77
View File
@@ -0,0 +1,77 @@
#include "module.h"
#include "modules/sql.h"
class MySQLInterface : public SQL::Interface
{
public:
MySQLInterface(Module *o) : SQL::Interface(o) { }
void OnResult(const SQL::Result &r) anope_override
{
}
void OnError(const SQL::Result &r) anope_override
{
if (!r.GetQuery().query.empty())
Log(LOG_DEBUG) << "m_irc2sql: Error executing query " << r.finished_query << ": " << r.GetError();
else
Log(LOG_DEBUG) << "m_irc2sql: Error executing query: " << r.GetError();
}
};
class IRC2SQL : public Module
{
ServiceReference<SQL::Provider> sql;
MySQLInterface sqlinterface;
SQL::Query query;
std::vector<Anope::string> TableList, ProcedureList, EventList;
Anope::string prefix, GeoIPDB;
bool quitting, introduced_myself, UseGeoIP, ctcpuser, ctcpeob, firstrun;
BotInfo *StatServ;
PrimitiveExtensibleItem<bool> versionreply;
void RunQuery(const SQL::Query &q);
void GetTables();
bool HasTable(const Anope::string &table);
bool HasProcedure(const Anope::string &table);
bool HasEvent(const Anope::string &table);
void CheckTables();
public:
IRC2SQL(const Anope::string &modname, const Anope::string &creator) :
Module(modname, creator, EXTRA | VENDOR), sql("", ""), sqlinterface(this), versionreply(this, "CTCPVERSION")
{
firstrun = true;
quitting = false;
introduced_myself = false;
}
void OnShutdown() anope_override;
void OnReload(Configuration::Conf *config) anope_override;
void OnNewServer(Server *server) anope_override;
void OnServerQuit(Server *server) anope_override;
void OnUserConnect(User *u, bool &exempt) anope_override;
void OnUserQuit(User *u, const Anope::string &msg) anope_override;
void OnUserNickChange(User *u, const Anope::string &oldnick) anope_override;
void OnFingerprint(User *u) anope_override;
void OnUserModeSet(User *u, const Anope::string &mname) anope_override;
void OnUserModeUnset(User *u, const Anope::string &mname) anope_override;
void OnUserLogin(User *u) anope_override;
void OnNickLogout(User *u) anope_override;
void OnSetDisplayedHost(User *u) anope_override;
void OnChannelCreate(Channel *c) anope_override;
void OnChannelDelete(Channel *c) anope_override;
void OnLeaveChannel(User *u, Channel *c) anope_override;
void OnJoinChannel(User *u, Channel *c) anope_override;
EventReturn OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) anope_override;
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) anope_override;
void OnTopicUpdated(Channel *c, const Anope::string &user, const Anope::string &topic) anope_override;
void OnBotNotice(User *u, BotInfo *bi, Anope::string &message) anope_override;
};
+382
View File
@@ -0,0 +1,382 @@
#include "irc2sql.h"
void IRC2SQL::CheckTables()
{
Anope::string geoquery("");
if (firstrun)
{
/*
* reset some tables to make sure they are really empty
*/
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "user");
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "chan");
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "ison");
this->sql->RunQuery("UPDATE `" + prefix + "server` SET currentusers=0, online='N'");
}
this->GetTables();
if (UseGeoIP && GeoIPDB.equals_ci("country") && !this->HasTable(prefix + "geoip_country"))
{
query = "CREATE TABLE `" + prefix + "geoip_country` ("
"`start` INT UNSIGNED NOT NULL,"
"`end` INT UNSIGNED NOT NULL,"
"`countrycode` varchar(2),"
"`countryname` varchar(50),"
"PRIMARY KEY `end` (`end`),"
"KEY `start` (`start`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (UseGeoIP && GeoIPDB.equals_ci("city") && !this->HasTable(prefix + "geoip_city_blocks"))
{
query = "CREATE TABLE `" + prefix + "geoip_city_blocks` ("
"`start` INT UNSIGNED NOT NULL,"
"`end` INT UNSIGNED NOT NULL,"
"`locId` INT UNSIGNED NOT NULL,"
"PRIMARY KEY `end` (`end`),"
"KEY `start` (`start`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (UseGeoIP && GeoIPDB.equals_ci("city") && !this->HasTable(prefix + "geoip_city_location"))
{
query = "CREATE TABLE `" + prefix + "geoip_city_location` ("
"`locId` INT UNSIGNED NOT NULL,"
"`country` CHAR(2) NOT NULL,"
"`region` CHAR(2) NOT NULL,"
"`city` VARCHAR(50),"
"`latitude` FLOAT,"
"`longitude` FLOAT,"
"`areaCode` INT,"
"PRIMARY KEY (`locId`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (UseGeoIP && GeoIPDB.equals_ci("city") && !this->HasTable(prefix + "geoip_city_region"))
{ query = "CREATE TABLE `" + prefix + "geoip_city_region` ("
"`country` CHAR(2) NOT NULL,"
"`region` CHAR(2) NOT NULL,"
"`regionname` VARCHAR(100) NOT NULL,"
"PRIMARY KEY (`country`,`region`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "server"))
{
query = "CREATE TABLE `" + prefix + "server` ("
"`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,"
"`name` varchar(64) NOT NULL,"
"`hops` tinyint(3) NOT NULL,"
"`comment` varchar(255) NOT NULL,"
"`link_time` datetime DEFAULT NULL,"
"`split_time` datetime DEFAULT NULL,"
"`version` varchar(127) NOT NULL,"
"`currentusers` int(15) NOT NULL,"
"`online` enum('Y','N') NOT NULL DEFAULT 'Y',"
"`ulined` enum('Y','N') NOT NULL DEFAULT 'N',"
"PRIMARY KEY (`id`),"
"UNIQUE KEY `name` (`name`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "chan"))
{
query = "CREATE TABLE `" + prefix + "chan` ("
"`chanid` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,"
"`channel` varchar(255) NOT NULL,"
"`currentusers` int(15) NOT NULL DEFAULT 0,"
"`topic` varchar(255) DEFAULT NULL,"
"`topicauthor` varchar(255) DEFAULT NULL,"
"`topictime` datetime DEFAULT NULL,"
"`modes` varchar(512) DEFAULT NULL,"
"PRIMARY KEY (`chanid`),"
"UNIQUE KEY `channel`(`channel`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "user"))
{
query = "CREATE TABLE `" + prefix + "user` ("
"`nickid` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,"
"`nick` varchar(255) NOT NULL DEFAULT '',"
"`host` varchar(255) NOT NULL DEFAULT '',"
"`vhost` varchar(255) NOT NULL DEFAULT '',"
"`chost` varchar(255) NOT NULL DEFAULT '',"
"`realname` varchar(255) NOT NULL DEFAULT '',"
"`ip` varchar(255) NOT NULL DEFAULT '',"
"`ident` varchar(32) NOT NULL DEFAULT '',"
"`vident` varchar(32) NOT NULL DEFAULT '',"
"`modes` varchar(255) NOT NULL DEFAULT '',"
"`account` varchar(255) NOT NULL DEFAULT '',"
"`secure` enum('Y','N') NOT NULL DEFAULT 'N',"
"`fingerprint` varchar(128) NOT NULL DEFAULT '',"
"`signon` datetime DEFAULT NULL,"
"`server` varchar(255) NOT NULL DEFAULT '',"
"`servid` int(11) UNSIGNED NOT NULL DEFAULT '0',"
"`uuid` varchar(32) NOT NULL DEFAULT '',"
"`oper` enum('Y','N') NOT NULL DEFAULT 'N',"
"`away` enum('Y','N') NOT NULL DEFAULT 'N',"
"`awaymsg` varchar(255) NOT NULL DEFAULT '',"
"`version` varchar(255) NOT NULL DEFAULT '',"
"`geocode` varchar(16) NOT NULL DEFAULT '',"
"`geocountry` varchar(64) NOT NULL DEFAULT '',"
"`georegion` varchar(100) NOT NULL DEFAULT '',"
"`geocity` varchar(128) NOT NULL DEFAULT '',"
"`locId` INT UNSIGNED,"
"PRIMARY KEY (`nickid`),"
"UNIQUE KEY `nick` (`nick`),"
"KEY `servid` (`servid`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "ison"))
{
query = "CREATE TABLE `" + prefix + "ison` ("
"`nickid` int(11) unsigned NOT NULL default '0',"
"`chanid` int(11) unsigned NOT NULL default '0',"
"`modes` varchar(255) NOT NULL default '',"
"PRIMARY KEY (`nickid`,`chanid`),"
"KEY `modes` (`modes`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "maxusers"))
{
query = "CREATE TABLE `" + prefix + "maxusers` ("
"`name` VARCHAR(255) NOT NULL,"
"`maxusers` INT(15) NOT NULL,"
"`maxtime` DATETIME NOT NULL,"
"`lastused` DATETIME NOT NULL,"
"UNIQUE KEY `name` (`name`)"
") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
if (this->HasProcedure(prefix + "UserConnect"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserConnect"));
if (UseGeoIP)
{
if (GeoIPDB.equals_ci("country"))
geoquery = "UPDATE `" + prefix + "user` AS u "
"JOIN ( SELECT `countrycode`, `countryname` "
"FROM `" + prefix + "geoip_country` "
"WHERE INET_ATON(ip_) <= `end` "
"AND `start` <= INET_ATON(ip_) "
"ORDER BY `end` ASC LIMIT 1 ) as c "
"SET u.geocode = c.countrycode, u.geocountry = c.countryname "
"WHERE u.nick = nick_; ";
else if (GeoIPDB.equals_ci("city"))
geoquery = "UPDATE `" + prefix + "user` as u "
"JOIN ( SELECT * FROM `" + prefix + "geoip_city_location` "
"WHERE `locID` = ( SELECT `locID` "
"FROM `" + prefix + "geoip_city_blocks` "
"WHERE INET_ATON(ip_) <= `end` "
"AND `start` <= INET_ATON(ip_) "
"ORDER BY `end` ASC LIMIT 1 ) "
") as l "
"SET u.geocode = l.country, "
"u.geocity = l.city, "
"u.locID = l.locID, "
"u.georegion = ( SELECT `regionname` "
"FROM `" + prefix + "geoip_city_region` "
"WHERE `country` = l.country "
"AND `region` = l.region )"
"WHERE u.nick = nick_;";
}
query = "CREATE PROCEDURE `" + prefix + "UserConnect`"
"(nick_ varchar(255), host_ varchar(255), vhost_ varchar(255), "
"chost_ varchar(255), realname_ varchar(255), ip_ varchar(255), "
"ident_ varchar(255), vident_ varchar(255), account_ varchar(255), "
"secure_ enum('Y','N'), fingerprint_ varchar(255), signon_ int(15), "
"server_ varchar(255), uuid_ varchar(32), modes_ varchar(255), "
"oper_ enum('Y','N')) "
"BEGIN "
"DECLARE cur int(15);"
"DECLARE max int(15);"
"INSERT INTO `" + prefix + "user` "
"(nick, host, vhost, chost, realname, ip, ident, vident, account, "
"secure, fingerprint, signon, server, uuid, modes, oper) "
"VALUES (nick_, host_, vhost_, chost_, realname_, ip_, ident_, vident_, "
"account_, secure_, fingerprint_, FROM_UNIXTIME(signon_), server_, "
"uuid_, modes_, oper_) "
"ON DUPLICATE KEY UPDATE host=VALUES(host), vhost=VALUES(vhost), "
"chost=VALUES(chost), realname=VALUES(realname), ip=VALUES(ip), "
"ident=VALUES(ident), vident=VALUES(vident), account=VALUES(account), "
"secure=VALUES(secure), fingerprint=VALUES(fingerprint), signon=VALUES(signon), "
"server=VALUES(server), uuid=VALUES(uuid), modes=VALUES(modes), "
"oper=VALUES(oper);"
"UPDATE `" + prefix + "user` as `u`, `" + prefix + "server` as `s`"
"SET u.servid = s.id, "
"s.currentusers = s.currentusers + 1 "
"WHERE s.name = server_ AND u.nick = nick_;"
"SELECT `currentusers` INTO cur FROM `" + prefix + "server` WHERE name=server_;"
"SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=server_;"
"IF found_rows() AND cur <= max THEN "
"UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=server_;"
"ELSE "
"INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) "
"VALUES ( server_, cur, now(), now() ) "
"ON DUPLICATE KEY UPDATE "
"name=VALUES(name), maxusers=VALUES(maxusers),"
"maxtime=VALUES(maxtime), lastused=VALUES(lastused);"
"END IF;"
+ geoquery +
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "ServerQuit"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ServerQuit"));
query = "CREATE PROCEDURE " + prefix + "ServerQuit(sname_ varchar(255)) "
"BEGIN "
/* 1.
* loop through all channels and decrease the user count
* by the number of users that are on this channel AND
* on the splitting server
*
* we dont have to care about channels that get empty, there will be
* an extra OnChannelDelete event triggered from anope.
*/
"DECLARE no_more_rows BOOLEAN DEFAULT FALSE;"
"DECLARE channel_ varchar(255);"
"DECLARE ucount_ int;"
"DECLARE channel_cursor CURSOR FOR "
"SELECT c.channel "
"FROM `" + prefix + "chan` as c, `" + prefix + "ison` as i, "
"`" + prefix + "user` as u, `" + prefix + "server` as s "
"WHERE c.chanid = i.chanid "
"AND i.nickid = u.nickid "
"AND u.servid = s.id "
"AND s.name = sname_;"
"DECLARE CONTINUE HANDLER FOR NOT FOUND "
"SET no_more_rows = TRUE;"
"OPEN channel_cursor;"
"the_loop: LOOP "
"FETCH channel_cursor INTO channel_;"
"IF no_more_rows THEN "
"CLOSE channel_cursor;"
"LEAVE the_loop;"
"END IF;"
"SELECT COUNT(*) INTO ucount_ "
"FROM `" + prefix + "ison` AS i, `" + prefix + "chan` AS c,"
"`" + prefix + "user` AS u, `" + prefix + "server` AS s "
"WHERE i.nickid = u.nickid "
"AND u.servid = s.id "
"AND i.chanid = c.chanid "
"AND c.channel = channel_ "
"AND s.name = sname_; "
"UPDATE `" + prefix + "chan` "
"SET currentusers = currentusers - ucount_ "
"WHERE channel = channel_;"
"END LOOP;"
/* 2.
* remove all users on the splitting server from the ison table
*/
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "server` AS s "
"INNER JOIN `" + prefix + "user` AS u "
"WHERE i.nickid = u.nickid "
"AND u.servid = s.id "
"AND s.name = sname_;"
/* 3.
* remove all users on the splitting server from the user table
*/
"DELETE u FROM `" + prefix + "user` AS u "
"INNER JOIN `" + prefix + "server` AS s "
"WHERE s.id = u.servid "
"AND s.name = sname_;"
/* 4.
* on the splitting server, set usercount = 0, split_time = now(), online = 'N'
*/
"UPDATE `" + prefix + "server` SET currentusers = 0, split_time = now(), online = 'N' "
"WHERE name = sname_;"
"END;"; // end of the procedure
this->RunQuery(query);
if (this->HasProcedure(prefix + "UserQuit"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserQuit"));
query = "CREATE PROCEDURE `" + prefix + "UserQuit`"
"(nick_ varchar(255)) "
"BEGIN "
/* decrease usercount on the server where the user was on */
"UPDATE `" + prefix + "user` AS `u`, `" + prefix + "server` AS `s` "
"SET s.currentusers = s.currentusers - 1 "
"WHERE u.nick=nick_ AND u.servid = s.id; "
/* decrease the usercount on all channels where the user was on */
"UPDATE `" + prefix + "user` AS u, `" + prefix + "ison` AS i, "
"`" + prefix + "chan` AS c "
"SET c.currentusers = c.currentusers - 1 "
"WHERE u.nick=nick_ AND u.nickid = i.nickid "
"AND i.chanid = c.chanid; "
/* remove from all channels where the user was on */
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "user` as u "
"WHERE u.nick = nick_ "
"AND i.nickid = u.nickid;"
/* remove the user from the user table */
"DELETE FROM `" + prefix + "user` WHERE nick = nick_; "
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "ShutDown"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ShutDown"));
query = "CREATE PROCEDURE `" + prefix + "ShutDown`()"
"BEGIN "
"UPDATE `" + prefix + "server` "
"SET currentusers=0, online='N', split_time=now();"
"TRUNCATE TABLE `" + prefix + "user`;"
"TRUNCATE TABLE `" + prefix + "chan`;"
"TRUNCATE TABLE `" + prefix + "ison`;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "JoinUser"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "JoinUser"));
query = "CREATE PROCEDURE `"+ prefix + "JoinUser`"
"(nick_ varchar(255), channel_ varchar(255), modes_ varchar(255)) "
"BEGIN "
"DECLARE cur int(15);"
"DECLARE max int(15);"
"INSERT INTO `" + prefix + "ison` (nickid, chanid, modes) "
"SELECT u.nickid, c.chanid, modes_ "
"FROM " + prefix + "user AS u, " + prefix + "chan AS c "
"WHERE u.nick=nick_ AND c.channel=channel_;"
"UPDATE `" + prefix + "chan` SET currentusers=currentusers+1 "
"WHERE channel=channel_;"
"SELECT `currentusers` INTO cur FROM `" + prefix + "chan` WHERE channel=channel_;"
"SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=channel_;"
"IF found_rows() AND cur <= max THEN "
"UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=channel_;"
"ELSE "
"INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) "
"VALUES ( channel_, cur, now(), now() ) "
"ON DUPLICATE KEY UPDATE "
"name=VALUES(name), maxusers=VALUES(maxusers),"
"maxtime=VALUES(maxtime), lastused=VALUES(lastused);"
"END IF;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "PartUser"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "PartUser"));
query = "CREATE PROCEDURE `" + prefix + "PartUser`"
"(nick_ varchar(255), channel_ varchar(255)) "
"BEGIN "
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "user` AS u "
"INNER JOIN `" + prefix + "chan` AS c "
"WHERE i.nickid = u.nickid "
"AND u.nick = nick_ "
"AND i.chanid = c.chanid "
"AND c.channel = channel_;"
"UPDATE `" + prefix + "chan` SET currentusers=currentusers-1 "
"WHERE channel=channel_;"
"END";
this->RunQuery(query);
}
+61
View File
@@ -0,0 +1,61 @@
#include "irc2sql.h"
void IRC2SQL::RunQuery(const SQL::Query &q)
{
if (sql)
sql->Run(&sqlinterface, q);
}
void IRC2SQL::GetTables()
{
TableList.clear();
ProcedureList.clear();
EventList.clear();
if (!sql)
return;
SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix));
for (int i = 0; i < r.Rows(); ++i)
{
const std::map<Anope::string, Anope::string> &map = r.Row(i);
for (std::map<Anope::string, Anope::string>::const_iterator it = map.begin(); it != map.end(); ++it)
TableList.push_back(it->second);
}
query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();";
r = this->sql->RunQuery(query);
for (int i = 0; i < r.Rows(); ++i)
{
ProcedureList.push_back(r.Get(i, "Name"));
}
query = "SHOW EVENTS WHERE `Db` = Database();";
r = this->sql->RunQuery(query);
for (int i = 0; i < r.Rows(); ++i)
{
EventList.push_back(r.Get(i, "Name"));
}
}
bool IRC2SQL::HasTable(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = TableList.begin(); it != TableList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool IRC2SQL::HasProcedure(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool IRC2SQL::HasEvent(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = EventList.begin(); it != EventList.end(); ++it)
if (*it == table)
return true;
return false;
}
+630
View File
@@ -0,0 +1,630 @@
#include "module.h"
#include "modules/sql.h"
class CommandCSSetChanstats : public Command
{
public:
CommandCSSetChanstats(Module *creator) : Command(creator, "chanserv/set/chanstats", 2, 2)
{
this->SetDesc(_("Turn chanstats statistics on or off"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
{
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (!ci)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
ci->Extend<bool>("CS_STATS");
source.Reply(_("Chanstats statistics are now enabled for this channel."));
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable chanstats";
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable chanstats";
ci->Shrink<bool>("CS_STATS");
source.Reply(_("Chanstats statistics are now disabled for this channel."));
}
else
this->OnSyntaxError(source, "");
}
bool OnHelp(CommandSource &source, const Anope::string &) anope_override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply("Turn Chanstats channel statistics ON or OFF.");
return true;
}
};
class CommandNSSetChanstats : public Command
{
public:
CommandNSSetChanstats(Module *creator, const Anope::string &sname = "nickserv/set/chanstats", size_t min = 1 ) : Command(creator, sname, min, min + 1)
{
this->SetDesc(_("Turn chanstats statistics on or off"));
this->SetSyntax("{ON | OFF}");
}
void Run(CommandSource &source, const Anope::string &user, const Anope::string &param, bool saset = false)
{
NickAlias *na = NickAlias::Find(user);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, user.c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetNickOption, MOD_RESULT, (source, this, na->nc, param));
if (MOD_RESULT == EVENT_STOP)
return;
if (param.equals_ci("ON"))
{
Log(na->nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable chanstats for " << na->nc->display;
na->nc->Extend<bool>("NS_STATS");
if (saset)
source.Reply(_("Chanstats statistics are now enabled for %s"), na->nc->display.c_str());
else
source.Reply(_("Chanstats statistics are now enabled for your nick."));
}
else if (param.equals_ci("OFF"))
{
Log(na->nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable chanstats for " << na->nc->display;
na->nc->Shrink<bool>("NS_STATS");
if (saset)
source.Reply(_("Chanstats statistics are now disabled for %s"), na->nc->display.c_str());
else
source.Reply(_("Chanstats statistics are now disabled for your nick."));
}
else
this->OnSyntaxError(source, "CHANSTATS");
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
{
this->Run(source, source.nc->display, params[0]);
}
bool OnHelp(CommandSource &source, const Anope::string &) anope_override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_("Turns Chanstats statistics ON or OFF."));
return true;
}
};
class CommandNSSASetChanstats : public CommandNSSetChanstats
{
public:
CommandNSSASetChanstats(Module *creator) : CommandNSSetChanstats(creator, "nickserv/saset/chanstats", 2)
{
this->ClearSyntax();
this->SetSyntax(_("\037nickname\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
{
this->Run(source, params[0], params[1], true);
}
bool OnHelp(CommandSource &source, const Anope::string &) anope_override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_("Turns chanstats channel statistics ON or OFF for this user."));
return true;
}
};
class MySQLInterface : public SQL::Interface
{
public:
MySQLInterface(Module *o) : SQL::Interface(o) { }
void OnResult(const SQL::Result &r) anope_override
{
}
void OnError(const SQL::Result &r) anope_override
{
if (!r.GetQuery().query.empty())
Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError();
else
Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError();
}
};
class MChanstats : public Module
{
SerializableExtensibleItem<bool> cs_stats, ns_stats;
CommandCSSetChanstats commandcssetchanstats;
CommandNSSetChanstats commandnssetchanstats;
CommandNSSASetChanstats commandnssasetchanstats;
ServiceReference<SQL::Provider> sql;
MySQLInterface sqlinterface;
SQL::Query query;
Anope::string SmileysHappy, SmileysSad, SmileysOther, prefix;
std::vector<Anope::string> TableList, ProcedureList, EventList;
void RunQuery(const SQL::Query &q)
{
if (sql)
sql->Run(&sqlinterface, q);
}
size_t CountWords(const Anope::string &msg)
{
size_t words = 0;
for (size_t pos = 0; pos != Anope::string::npos; pos = msg.find(" ", pos+1))
words++;
return words;
}
size_t CountSmileys(const Anope::string &msg, const Anope::string &smileylist)
{
size_t smileys = 0;
spacesepstream sep(smileylist);
Anope::string buf;
while (sep.GetToken(buf) && !buf.empty())
{
for (size_t pos = msg.find(buf, 0); pos != Anope::string::npos; pos = msg.find(buf, pos+1))
smileys++;
}
return smileys;
}
const Anope::string GetDisplay(User *u)
{
if (u && u->Account() && ns_stats.HasExt(u->Account()))
return u->Account()->display;
else
return "";
}
void GetTables()
{
TableList.clear();
ProcedureList.clear();
EventList.clear();
if (!sql)
return;
SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix));
for (int i = 0; i < r.Rows(); ++i)
{
const std::map<Anope::string, Anope::string> &map = r.Row(i);
for (std::map<Anope::string, Anope::string>::const_iterator it = map.begin(); it != map.end(); ++it)
TableList.push_back(it->second);
}
query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();";
r = this->sql->RunQuery(query);
for (int i = 0; i < r.Rows(); ++i)
{
ProcedureList.push_back(r.Get(i, "Name"));
}
query = "SHOW EVENTS WHERE `Db` = Database();";
r = this->sql->RunQuery(query);
for (int i = 0; i < r.Rows(); ++i)
{
EventList.push_back(r.Get(i, "Name"));
}
}
bool HasTable(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = TableList.begin(); it != TableList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool HasProcedure(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool HasEvent(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = EventList.begin(); it != EventList.end(); ++it)
if (*it == table)
return true;
return false;
}
void CheckTables()
{
this->GetTables();
if (!this->HasTable(prefix +"chanstats"))
{
query = "CREATE TABLE `" + prefix + "chanstats` ("
"`id` int(11) NOT NULL AUTO_INCREMENT,"
"`chan` varchar(64) NOT NULL DEFAULT '',"
"`nick` varchar(64) NOT NULL DEFAULT '',"
"`type` ENUM('total', 'monthly', 'weekly', 'daily') NOT NULL,"
"`letters` int(10) unsigned NOT NULL DEFAULT '0',"
"`words` int(10) unsigned NOT NULL DEFAULT '0',"
"`line` int(10) unsigned NOT NULL DEFAULT '0',"
"`actions` int(10) unsigned NOT NULL DEFAULT '0',"
"`smileys_happy` int(10) unsigned NOT NULL DEFAULT '0',"
"`smileys_sad` int(10) unsigned NOT NULL DEFAULT '0',"
"`smileys_other` int(10) unsigned NOT NULL DEFAULT '0',"
"`kicks` int(10) unsigned NOT NULL DEFAULT '0',"
"`kicked` int(10) unsigned NOT NULL DEFAULT '0',"
"`modes` int(10) unsigned NOT NULL DEFAULT '0',"
"`topics` int(10) unsigned NOT NULL DEFAULT '0',"
"`time0` int(10) unsigned NOT NULL default '0',"
"`time1` int(10) unsigned NOT NULL default '0',"
"`time2` int(10) unsigned NOT NULL default '0',"
"`time3` int(10) unsigned NOT NULL default '0',"
"`time4` int(10) unsigned NOT NULL default '0',"
"`time5` int(10) unsigned NOT NULL default '0',"
"`time6` int(10) unsigned NOT NULL default '0',"
"`time7` int(10) unsigned NOT NULL default '0',"
"`time8` int(10) unsigned NOT NULL default '0',"
"`time9` int(10) unsigned NOT NULL default '0',"
"`time10` int(10) unsigned NOT NULL default '0',"
"`time11` int(10) unsigned NOT NULL default '0',"
"`time12` int(10) unsigned NOT NULL default '0',"
"`time13` int(10) unsigned NOT NULL default '0',"
"`time14` int(10) unsigned NOT NULL default '0',"
"`time15` int(10) unsigned NOT NULL default '0',"
"`time16` int(10) unsigned NOT NULL default '0',"
"`time17` int(10) unsigned NOT NULL default '0',"
"`time18` int(10) unsigned NOT NULL default '0',"
"`time19` int(10) unsigned NOT NULL default '0',"
"`time20` int(10) unsigned NOT NULL default '0',"
"`time21` int(10) unsigned NOT NULL default '0',"
"`time22` int(10) unsigned NOT NULL default '0',"
"`time23` int(10) unsigned NOT NULL default '0',"
"PRIMARY KEY (`id`),"
"UNIQUE KEY `chan` (`chan`,`nick`,`type`),"
"KEY `nick` (`nick`),"
"KEY `chan_` (`chan`),"
"KEY `type` (`type`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
this->RunQuery(query);
}
/* There is no CREATE OR REPLACE PROCEDURE in MySQL */
if (this->HasProcedure(prefix + "chanstats_proc_update"))
{
query = "DROP PROCEDURE " + prefix + "chanstats_proc_update";
this->RunQuery(query);
}
query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_update`"
"(chan_ VARCHAR(255), nick_ VARCHAR(255), line_ INT(10), letters_ INT(10),"
"words_ INT(10), actions_ INT(10), sm_h_ INT(10), sm_s_ INT(10), sm_o_ INT(10),"
"kicks_ INT(10), kicked_ INT(10), modes_ INT(10), topics_ INT(10))"
"BEGIN "
"DECLARE time_ VARCHAR(20);"
"SET time_ = CONCAT('time', hour(now()));"
"INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES "
"('', chan_, 'total'), ('', chan_, 'monthly'),"
"('', chan_, 'weekly'), ('', chan_, 'daily');"
"IF nick_ != '' THEN "
"INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES "
"(nick_, chan_, 'total'), (nick_, chan_, 'monthly'),"
"(nick_, chan_, 'weekly'),(nick_, chan_, 'daily'),"
"(nick_, '', 'total'), (nick_, '', 'monthly'),"
"(nick_, '', 'weekly'), (nick_, '', 'daily');"
"END IF;"
"SET @update_query = CONCAT('UPDATE `" + prefix + "chanstats` SET line=line+', line_, ',"
"letters=letters+', letters_, ' , words=words+', words_, ', actions=actions+', actions_, ', "
"smileys_happy=smileys_happy+', sm_h_, ', smileys_sad=smileys_sad+', sm_s_, ', "
"smileys_other=smileys_other+', sm_o_, ', kicks=kicks+', kicks_, ', kicked=kicked+', kicked_, ', "
"modes=modes+', modes_, ', topics=topics+', topics_, ', ', time_ , '=', time_, '+', line_ ,' "
"WHERE (nick='''' OR nick=''', nick_, ''') AND (chan='''' OR chan=''', chan_, ''')');"
"PREPARE update_query FROM @update_query;"
"EXECUTE update_query;"
"DEALLOCATE PREPARE update_query;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "chanstats_proc_chgdisplay"))
{
query = "DROP PROCEDURE " + prefix + "chanstats_proc_chgdisplay;";
this->RunQuery(query);
}
query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_chgdisplay`"
"(old_nick varchar(255), new_nick varchar(255))"
"BEGIN "
"DECLARE res_count int(10) unsigned;"
"SELECT COUNT(nick) INTO res_count FROM `" + prefix + "chanstats` WHERE nick = new_nick;"
"IF res_count = 0 THEN "
"UPDATE `" + prefix + "chanstats` SET `nick` = new_nick WHERE `nick` = old_nick;"
"ELSE "
"my_cursor: BEGIN "
"DECLARE no_more_rows BOOLEAN DEFAULT FALSE;"
"DECLARE chan_ VARCHAR(255);"
"DECLARE type_ ENUM('total', 'monthly', 'weekly', 'daily');"
"DECLARE letters_, words_, line_, actions_, smileys_happy_,"
"smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_,"
"time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, time9_,"
"time10_, time11_, time12_, time13_, time14_, time15_, time16_, time17_, time18_,"
"time19_, time20_, time21_, time22_, time23_ INT(10) unsigned;"
"DECLARE stats_cursor CURSOR FOR "
"SELECT chan, type, letters, words, line, actions, smileys_happy,"
"smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1,"
"time2, time3, time4, time5, time6, time7, time8, time9, time10, time11,"
"time12, time13, time14, time15, time16, time17, time18, time19, time20,"
"time21, time22, time23 "
"FROM `" + prefix + "chanstats` "
"WHERE `nick` = old_nick;"
"DECLARE CONTINUE HANDLER FOR NOT FOUND "
"SET no_more_rows = TRUE;"
"OPEN stats_cursor;"
"the_loop: LOOP "
"FETCH stats_cursor "
"INTO chan_, type_, letters_, words_, line_, actions_, smileys_happy_,"
"smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_,"
"time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_,"
"time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_,"
"time17_, time18_, time19_, time20_, time21_, time22_, time23_;"
"IF no_more_rows THEN "
"CLOSE stats_cursor;"
"LEAVE the_loop;"
"END IF;"
"INSERT INTO `" + prefix + "chanstats` "
"(chan, nick, type, letters, words, line, actions, smileys_happy, "
"smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1, "
"time2, time3, time4, time5, time6, time7, time8, time9, time10, time11,"
"time12, time13, time14, time15, time16, time17, time18, time19, time20,"
"time21, time22, time23)"
"VALUES (chan_, new_nick, type_, letters_, words_, line_, actions_, smileys_happy_,"
"smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_,"
"time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, "
"time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_, "
"time17_, time18_, time19_, time20_, time21_, time22_, time23_)"
"ON DUPLICATE KEY UPDATE letters=letters+VALUES(letters), words=words+VALUES(words),"
"line=line+VALUES(line), actions=actions+VALUES(actions),"
"smileys_happy=smileys_happy+VALUES(smileys_happy),"
"smileys_sad=smileys_sad+VALUES(smileys_sad),"
"smileys_other=smileys_other+VALUES(smileys_other),"
"kicks=kicks+VALUES(kicks), kicked=kicked+VALUES(kicked),"
"modes=modes+VALUES(modes), topics=topics+VALUES(topics),"
"time1=time1+VALUES(time1), time2=time2+VALUES(time2), time3=time3+VALUES(time3),"
"time4=time4+VALUES(time4), time5=time5+VALUES(time5), time6=time6+VALUES(time6),"
"time7=time7+VALUES(time7), time8=time8+VALUES(time8), time9=time9+VALUES(time9),"
"time10=time10+VALUES(time10), time11=time11+VALUES(time11), time12=time12+VALUES(time12),"
"time13=time13+VALUES(time13), time14=time14+VALUES(time14), time15=time15+VALUES(time15),"
"time16=time16+VALUES(time16), time17=time17+VALUES(time17), time18=time18+VALUES(time18),"
"time19=time19+VALUES(time19), time20=time20+VALUES(time20), time21=time21+VALUES(time21),"
"time22=time22+VALUES(time22), time23=time23+VALUES(time23);"
"END LOOP;"
"DELETE FROM `" + prefix + "chanstats` WHERE `nick` = old_nick;"
"END my_cursor;"
"END IF;"
"END;";
this->RunQuery(query);
/* dont prepend any database prefix to events so we can always delete/change old events */
if (this->HasEvent("chanstats_event_cleanup_daily"))
{
query = "DROP EVENT chanstats_event_cleanup_daily";
this->RunQuery(query);
}
query = "CREATE EVENT `chanstats_event_cleanup_daily` "
"ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE "
"DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0,"
"smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0,"
"time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0,"
"time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0,"
"time20=0, time21=0, time22=0, time23=0 "
"WHERE type='daily';";
this->RunQuery(query);
if (this->HasEvent("chanstats_event_cleanup_weekly"))
{
query = "DROP EVENT `chanstats_event_cleanup_weekly`";
this->RunQuery(query);
}
query = "CREATE EVENT `chanstats_event_cleanup_weekly` "
"ON SCHEDULE EVERY 1 WEEK STARTS ADDDATE(CURDATE(), INTERVAL 1-DAYOFWEEK(CURDATE()) DAY) "
"DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0,"
"smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0,"
"time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0,"
"time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0,"
"time20=0, time21=0, time22=0, time23=0 "
"WHERE type='weekly';";
this->RunQuery(query);
if (this->HasEvent("chanstats_event_cleanup_monthly"))
{
query = "DROP EVENT `chanstats_event_cleanup_monthly`;";
this->RunQuery(query);
}
query = "CREATE EVENT `chanstats_event_cleanup_monthly` "
"ON SCHEDULE EVERY 1 MONTH STARTS LAST_DAY(CURRENT_TIMESTAMP) + INTERVAL 1 DAY "
"DO BEGIN "
"UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0,"
"smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0,"
"time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0,"
"time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0, "
"time20=0, time21=0, time22=0, time23=0 "
"WHERE type='monthly';"
"OPTIMIZE TABLE `" + prefix + "chanstats`;"
"END;";
this->RunQuery(query);
}
public:
MChanstats(const Anope::string &modname, const Anope::string &creator) :
Module(modname, creator, EXTRA | VENDOR),
cs_stats(this, "CS_STATS"), ns_stats(this, "NS_STATS"),
commandcssetchanstats(this), commandnssetchanstats(this), commandnssasetchanstats(this),
sqlinterface(this)
{
}
void OnReload(Configuration::Conf *conf) anope_override
{
Configuration::Block *block = conf->GetModule(this);
prefix = block->Get<const Anope::string>("prefix", "anope_");
SmileysHappy = block->Get<const Anope::string>("SmileysHappy");
SmileysSad = block->Get<const Anope::string>("SmileysSad");
SmileysOther = block->Get<const Anope::string>("SmileysOther");
Anope::string engine = block->Get<const Anope::string>("engine");
this->sql = ServiceReference<SQL::Provider>("SQL::Provider", engine);
if (sql)
this->CheckTables();
else
Log(this) << "no database connection to " << engine;
}
void OnChanInfo(CommandSource &source, ChannelInfo *ci, InfoFormatter &info, bool show_all) anope_override
{
if (!show_all)
return;
if (cs_stats.HasExt(ci))
info.AddOption(_("Chanstats"));
}
void OnNickInfo(CommandSource &source, NickAlias *na, InfoFormatter &info, bool show_hidden) anope_override
{
if (!show_hidden)
return;
if (ns_stats.HasExt(na->nc))
info.AddOption(_("Chanstats"));
}
void OnTopicUpdated(Channel *c, const Anope::string &user, const Anope::string &topic) anope_override
{
User *u = User::Find(user);
if (!u || !u->Account() || !c->ci || !cs_stats.HasExt(c->ci))
return;
query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);";
query.SetValue("channel", c->name);
query.SetValue("nick", GetDisplay(u));
this->RunQuery(query);
}
EventReturn OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) anope_override
{
this->OnModeChange(c, setter.GetUser());
return EVENT_CONTINUE;
}
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *, const Anope::string &param) anope_override
{
this->OnModeChange(c, setter.GetUser());
return EVENT_CONTINUE;
}
private:
void OnModeChange(Channel *c, User *u)
{
if (!u || !u->Account() || !c->ci || !cs_stats.HasExt(c->ci))
return;
query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);";
query.SetValue("channel", c->name);
query.SetValue("nick", GetDisplay(u));
this->RunQuery(query);
}
public:
void OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &kickmsg) anope_override
{
if (!cu->chan->ci || !cs_stats.HasExt(cu->chan->ci))
return;
query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);";
query.SetValue("channel", cu->chan->name);
query.SetValue("nick", GetDisplay(cu->user));
this->RunQuery(query);
query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);";
query.SetValue("channel", cu->chan->name);
query.SetValue("nick", GetDisplay(source.GetUser()));
this->RunQuery(query);
}
void OnPrivmsg(User *u, Channel *c, Anope::string &msg) anope_override
{
if (!c->ci || !cs_stats.HasExt(c->ci))
return;
size_t letters = msg.length();
size_t words = this->CountWords(msg);
size_t action = 0;
if (msg.find("\01ACTION")!=Anope::string::npos)
{
action = 1;
letters = letters - 7;
words--;
}
// count smileys
size_t smileys_happy = CountSmileys(msg, SmileysHappy);
size_t smileys_sad = CountSmileys(msg, SmileysSad);
size_t smileys_other = CountSmileys(msg, SmileysOther);
// do not count smileys as words
words = words - smileys_happy - smileys_sad - smileys_other;
query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 1, @letters@, @words@, @action@, "
"@smileys_happy@, @smileys_sad@, @smileys_other@, '0', '0', '0', '0');";
query.SetValue("channel", c->name);
query.SetValue("nick", GetDisplay(u));
query.SetValue("letters", letters);
query.SetValue("words", words);
query.SetValue("action", action);
query.SetValue("smileys_happy", smileys_happy);
query.SetValue("smileys_sad", smileys_sad);
query.SetValue("smileys_other", smileys_other);
this->RunQuery(query);
}
void OnDelCore(NickCore *nc) anope_override
{
query = "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = @nick@;";
query.SetValue("nick", nc->display);
this->RunQuery(query);
}
void OnChangeCoreDisplay(NickCore *nc, const Anope::string &newdisplay) anope_override
{
query = "CALL " + prefix + "chanstats_proc_chgdisplay(@old_display@, @new_display@);";
query.SetValue("old_display", nc->display);
query.SetValue("new_display", newdisplay);
this->RunQuery(query);
}
void OnDelChan(ChannelInfo *ci) anope_override
{
query = "DELETE FROM `" + prefix + "chanstats` WHERE `chan` = @channel@;";
query.SetValue("channel", ci->name);
this->RunQuery(query);
}
};
MODULE_INIT(MChanstats)