commit 902834079b8860f42c50d965c0aa03e129aa85a8 Author: GRMrGecko Date: Sat Feb 11 13:35:43 2012 -0600 LogSQL Start diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1fe8d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.so \ No newline at end of file diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..e233fd7 --- /dev/null +++ b/License.txt @@ -0,0 +1,7 @@ +ISC License (ISCL) + +Copyright (c) 2012 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/buildLogMySQL b/buildLogMySQL new file mode 100755 index 0000000..44ce1a6 --- /dev/null +++ b/buildLogMySQL @@ -0,0 +1,26 @@ +#!/bin/bash + +ZNCBUILD=`which znc-buildmod` +if [ "$ZNCBUILD" = "" ]; then + echo "Where is znc-buildmod:" + read ZNCBUILD + if [ "$ZNCBUILD" = "" ]; then + exit + fi +fi + +echo "Where is MySQL path [/usr/local/mysql]:" +read MYSQL +if [ "$MYSQL" = "" ]; then + MYSQL="/usr/local/mysql" +fi + +echo "Where is ZNC configuration path [$HOME/.znc]:" +read ZNCCONFIG +if [ "$ZNCCONFIG" = "" ]; then + ZNCCONFIG="$HOME/.znc" +fi + +LDFLAGS="-L $MYSQL/lib -lmysqlclient -I $MYSQL/include" $ZNCBUILD logmysql.cpp +install_name_tool -change libmysqlclient.18.dylib "$MYSQL/libmysqlclient.dylib" logmysql.so +cp logmysql.so "$ZNCCONFIG/modules/logmysql.so" \ No newline at end of file diff --git a/buildLogSQLite b/buildLogSQLite new file mode 100755 index 0000000..82b4180 --- /dev/null +++ b/buildLogSQLite @@ -0,0 +1,25 @@ +#!/bin/bash + +ZNCBUILD=`which znc-buildmod` +if [ "$ZNCBUILD" = "" ]; then + echo "Where is znc-buildmod:" + read ZNCBUILD + if [ "$ZNCBUILD" = "" ]; then + exit + fi +fi + +echo "Where is SQLite path [/usr]:" +read SQLITE +if [ "$SQLITE" = "" ]; then + SQLITE="/usr" +fi + +echo "Where is ZNC configuration path [$HOME/.znc]:" +read ZNCCONFIG +if [ "$ZNCCONFIG" = "" ]; then + ZNCCONFIG="$HOME/.znc" +fi + +LDFLAGS="-L $SQLITE/lib -lsqlite3 -I $SQLITE/include" $ZNCBUILD logsqlite.cpp +cp logsqlite.so "$ZNCCONFIG/modules/logsqlite.so" \ No newline at end of file diff --git a/logmysql.cpp b/logmysql.cpp new file mode 100755 index 0000000..1a02c30 --- /dev/null +++ b/logmysql.cpp @@ -0,0 +1,937 @@ +// +// logmysql.cpp +// LogSQL +// +// Created by Mr. Gecko on 2/10/12. +// Copyright (c) 2012 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ +// +// Permission to use, copy, modify, and/or distribute this software for any purpose +// with or without fee is hereby granted, provided that the above copyright notice +// and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +// OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +// DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include +#include +#include +#include +#include + +#import + +class CLogMySQL : public CModule { +public: + MODCONSTRUCTOR(CLogMySQL) {} + + virtual bool OnLoad(const CString& sArgs, CString& sMessage) { + connected = true; + databaseConnected = false; + + MySQLConnect(); + return true; + } + + virtual ~CLogMySQL() { + if (databaseConnected) { + databaseConnected = false; + mysql_close(database); + database = NULL; + } + } + + void MySQLConnect() { + CString host = GetNV("host"); + CString port = GetNV("port"); + if (port.empty()) { + port = "3306"; + SetNV("port",port); + } + CString username = GetNV("username"); + CString password = GetNV("password"); + CString databaseName = GetNV("databaseName"); + if (!host.empty() && !username.empty() && !databaseName.empty()) { + if (databaseConnected) { + databaseConnected = false; + mysql_close(database); + database = NULL; + } + + database = mysql_init(NULL); + if (database!=NULL) { + void *theRet = mysql_real_connect(database, host.c_str(), username.c_str(), password.c_str(), databaseName.c_str(), (unsigned int)strtoul(port.c_str(), NULL, 10), MYSQL_UNIX_ADDR, CLIENT_COMPRESS); + databaseConnected = (theRet==database); + if (databaseConnected) { + cout << "LogMySQL: Database connected.\n"; + + MYSQL_RES *settings = mysql_list_tables(database, "settings"); + MYSQL_RES *messages = mysql_list_tables(database, "messages"); + if (mysql_num_rows(settings)==0 || mysql_num_rows(messages)==0) { + cout << "Creating tables\n"; + MYSQL_STMT *statement = mysql_stmt_init(database); + int status = mysql_stmt_prepare(statement, "DROP TABLE IF EXISTS `settings`", 31); + if (status==0) { + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + } + statement = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement, "CREATE TABLE `settings` (`name` text,`value` text)", 50); + if (status==0) { + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + } + statement = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement, "DROP TABLE IF EXISTS `messages`", 31); + if (status==0) { + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + } + statement = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement, "CREATE TABLE `messages` (`rowid` int UNSIGNED AUTO_INCREMENT,`target` text,`nick` text,`type` text,`message` longblob,`time` decimal(20,5) UNSIGNED,PRIMARY KEY (`rowid`))", 170); + if (status==0) { + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + } + SetSetting("replayAll","0"); + SetSetting("logLimit","1"); + SetSetting("logLevel","1"); + } + if (settings!=NULL) + mysql_free_result(settings); + if (messages!=NULL) + mysql_free_result(messages); + + replayAll = atoi(GetSetting("replayAll").c_str()); + logLimit = strtoul(GetSetting("logLimit").c_str(), NULL, 10); + logLevel = atoi(GetSetting("logLevel").c_str()); + } else { + cout << "LogMySQL: Database unable to connect.\n"; + } + } + } + } + + CString GetUNIXTime() { + struct timeval time; + gettimeofday(&time, NULL); + double microtime = (double)(time.tv_sec + (time.tv_usec/1000000.00)); + char timeStr[25]; + snprintf(timeStr, sizeof(timeStr), "%f", microtime); + return timeStr; + } + + void AddMessage(const CString& target, const CString& nick, const CString& type, const CString& message) { + if (!databaseConnected) + return; + MYSQL_STMT *statement = mysql_stmt_init(database); + int status = mysql_stmt_prepare(statement, "INSERT INTO `messages` (`target`, `nick`, `type`, `message`, `time`) VALUES (?,?,?,?,?)", 87); + if (status!=0) + return; + + MYSQL_BIND bind[5]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = (void*)target.c_str(); + bind[0].buffer_length = target.length(); + bind[0].is_null = false; + + bind[1].buffer_type = MYSQL_TYPE_STRING; + bind[1].buffer = (void*)nick.c_str(); + bind[1].buffer_length = nick.length(); + bind[1].is_null = false; + + bind[2].buffer_type = MYSQL_TYPE_STRING; + bind[2].buffer = (void*)type.c_str(); + bind[2].buffer_length = type.length(); + bind[2].is_null = false; + + bind[3].buffer_type = MYSQL_TYPE_STRING; + bind[3].buffer = (void*)message.c_str(); + bind[3].buffer_length = message.length(); + bind[3].is_null = false; + + CString time = GetUNIXTime(); + bind[4].buffer_type = MYSQL_TYPE_STRING; + bind[4].buffer = (void*)time.c_str(); + bind[4].buffer_length = time.length(); + bind[4].is_null = false; + + status = mysql_stmt_bind_param(statement, bind); + if (status!=0) { + mysql_stmt_close(statement); + return; + } + + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + + if (logLimit>=2) { + statement = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement, "SELECT COUNT(*) FROM `messages`", 31); + if (status!=0) + return; + + status = mysql_stmt_execute(statement); + if (status!=0) { + mysql_stmt_close(statement); + return; + } + + MYSQL_RES *result = mysql_stmt_result_metadata(statement); + unsigned int dataCount = mysql_num_fields(result); + if (dataCount!=1) { + mysql_free_result(result); + mysql_stmt_close(statement); + cout << "LogMySQL: We are only suppose to receive 1 field. We received the count of " << dataCount << " fields.\n"; + return; + } + MYSQL_FIELD *fields = mysql_fetch_fields(result); + + unsigned long length; + char countData[fields[0].length]; + MYSQL_BIND results[1]; + memset(results, 0, sizeof(results)); + results[0].buffer_type = MYSQL_TYPE_STRING; + results[0].buffer = (void *)countData; + results[0].buffer_length = fields[0].length; + results[0].length = &length; + + status = mysql_stmt_bind_result(statement, results); + if (status!=0) { + mysql_free_result(result); + mysql_stmt_close(statement); + return; + } + + status = mysql_stmt_fetch(statement); + if (status!=0) { + mysql_free_result(result); + mysql_stmt_close(statement); + return; + } + + CString countString = CString(countData, length); + + unsigned long count = strtoul(countString.c_str(), NULL, 10); + if (count<=logLimit) + count = 0; + else + count = count-logLimit; + mysql_free_result(result); + mysql_stmt_close(statement); + + if (count!=0) { + statement = mysql_stmt_init(database); + char *queryStr = (char *)malloc(73); + snprintf(queryStr, 73, "SELECT `rowid` FROM `messages` ORDER BY `time` LIMIT %lu", count); + status = mysql_stmt_prepare(statement, queryStr, strlen(queryStr)); + free(queryStr); + if (status!=0) + return; + + status = mysql_stmt_execute(statement); + if (status!=0) { + mysql_stmt_close(statement); + return; + } + + result = mysql_stmt_result_metadata(statement); + dataCount = mysql_num_fields(result); + if (dataCount!=1) { + mysql_free_result(result); + mysql_stmt_close(statement); + cout << "LogMySQL: We are only suppose to receive 1 field. We received the count of " << dataCount << " fields.\n"; + return; + } + fields = mysql_fetch_fields(result); + + char rowidData[fields[0].length]; + memset(results, 0, sizeof(results)); + results[0].buffer_type = MYSQL_TYPE_STRING; + results[0].buffer = (void *)rowidData; + results[0].buffer_length = fields[0].length; + results[0].length = &length; + + status = mysql_stmt_bind_result(statement, results); + if (status!=0) { + mysql_free_result(result); + mysql_stmt_close(statement); + return; + } + + while (true) { + status = mysql_stmt_fetch(statement); + if (status!=0) + break; + + CString rowid = CString(rowidData, length); + + MYSQL_STMT *statement2 = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement2, "DELETE FROM `messages` WHERE `rowid`=?", 38); + if (status!=0) + continue; + + MYSQL_BIND bind2[1]; + memset(bind, 0, sizeof(bind)); + + bind2[0].buffer_type = MYSQL_TYPE_STRING; + bind2[0].buffer = (void*)rowid.c_str(); + bind2[0].buffer_length = rowid.length(); + bind2[0].is_null = false; + + status = mysql_stmt_bind_param(statement2, bind2); + if (status!=0) { + mysql_stmt_close(statement2); + continue; + } + mysql_stmt_execute(statement2); + mysql_stmt_close(statement2); + } + mysql_free_result(result); + mysql_stmt_close(statement); + } + } + } + + void SetSetting(const CString& name, const CString& value) { + if (!databaseConnected) + return; + bool exists = false; + MYSQL_STMT *statement = mysql_stmt_init(database); + int status = mysql_stmt_prepare(statement, "SELECT `value` FROM `settings` WHERE `name`=?", 45); + if (status==0) { + MYSQL_BIND bind[1]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = (void*)name.c_str(); + bind[0].buffer_length = name.length(); + bind[0].is_null = false; + + status = mysql_stmt_bind_param(statement, bind); + if (status==0) { + mysql_stmt_execute(statement); + status = mysql_stmt_fetch(statement); + if (status==0) { + exists = true; + } + } + mysql_stmt_close(statement); + } + + if (exists) { + statement = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement, "UPDATE `settings` SET `value`=? WHERE `name`=?", 46); + if (status!=0) + return; + + MYSQL_BIND bind[2]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = (void*)value.c_str(); + bind[0].buffer_length = value.length(); + bind[0].is_null = false; + + bind[1].buffer_type = MYSQL_TYPE_STRING; + bind[1].buffer = (void*)name.c_str(); + bind[1].buffer_length = name.length(); + bind[1].is_null = false; + + status = mysql_stmt_bind_param(statement, bind); + if (status!=0) { + mysql_stmt_close(statement); + return; + } + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + } else { + statement = mysql_stmt_init(database); + status = mysql_stmt_prepare(statement, "INSERT INTO `settings` (`name`,`value`) VALUES (?,?)", 52); + if (status!=0) + return; + + MYSQL_BIND bind[2]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = (void*)name.c_str(); + bind[0].buffer_length = name.length(); + bind[0].is_null = false; + + bind[1].buffer_type = MYSQL_TYPE_STRING; + bind[1].buffer = (void*)value.c_str(); + bind[1].buffer_length = value.length(); + bind[1].is_null = false; + + status = mysql_stmt_bind_param(statement, bind); + if (status!=0) { + mysql_stmt_close(statement); + return; + } + mysql_stmt_execute(statement); + mysql_stmt_close(statement); + } + } + CString GetSetting(const CString& name) { + CString stringValue; + if (!databaseConnected) + return stringValue; + MYSQL_STMT *statement = mysql_stmt_init(database); + int status = mysql_stmt_prepare(statement, "SELECT `value` FROM `settings` WHERE `name`=?", 45); + if (status!=0) + return stringValue; + MYSQL_BIND bind[1]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = (void*)name.c_str(); + bind[0].buffer_length = name.length(); + bind[0].is_null = false; + + status = mysql_stmt_bind_param(statement, bind); + if (status!=0) { + mysql_stmt_close(statement); + return stringValue; + } + status = mysql_stmt_execute(statement); + if (status!=0) { + mysql_stmt_close(statement); + return stringValue; + } + + MYSQL_RES *result = mysql_stmt_result_metadata(statement); + unsigned int dataCount = mysql_num_fields(result); + if (dataCount!=1) { + mysql_free_result(result); + mysql_stmt_close(statement); + cout << "LogMySQL: Settings are only suppose to return 1 as the field count. We received the count of " << dataCount << " fields.\n"; + return stringValue; + } + MYSQL_FIELD *fields = mysql_fetch_fields(result); + + unsigned long length; + char valueData[fields[0].length]; + MYSQL_BIND results[1]; + memset(results, 0, sizeof(results)); + results[0].buffer_type = MYSQL_TYPE_STRING; + results[0].buffer = (void *)valueData; + results[0].buffer_length = fields[0].length; + results[0].length = &length; + + status = mysql_stmt_bind_result(statement, results); + if (status!=0) { + mysql_free_result(result); + mysql_stmt_close(statement); + return stringValue; + } + + while (true) { + status = mysql_stmt_fetch(statement); + if (status!=0) + break; + + stringValue = CString(valueData, length); + break; + } + mysql_free_result(result); + mysql_stmt_close(statement); + return stringValue; + } + //Server stuff + + virtual void OnIRCDisconnected() { + if (connected) { + connected = false; + if (logLevel>=3) { + AddMessage("","","DISCONNECT",""); + } + } + } + + virtual void OnIRCConnected() { + if (!connected) { + connected = true; + if (logLevel>=3) { + AddMessage("","","CONNECT",""); + } + } + } + + //User stuff + virtual EModRet OnUserAction(CString& sTarget, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"ACTION",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserMsg(CString& sTarget, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"PRIVMSG",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserNotice(CString& sTarget, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"NOTICE",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserJoin(CString& sChannel, CString& sKey) { + if (logLevel>=4) { + AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"JOIN",""); + } + return CONTINUE; + } + + virtual EModRet OnUserPart(CString& sChannel, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"PART",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserTopic(CString& sChannel, CString& sTopic) { + if (logLevel>=4) { + AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"TOPIC",sTopic); + } + return CONTINUE; + } + + + //Other stuff + virtual void OnRawMode(const CNick& OpNick, CChan& Channel, const CString& sModes, const CString& sArgs) { + if (logLevel>=3) { + AddMessage(Channel.GetName(),OpNick.GetNickMask(),"MODE",sModes+" "+sArgs); + } + } + + virtual void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),OpNick.GetNickMask(),"KICK",sKickedNick+" "+sMessage); + } + } + + virtual void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) { + if (logLevel>=2) { + vector::const_iterator it; + for (it=vChans.begin(); it!=vChans.end(); it++) { + CChan& channel = **it; + AddMessage(channel.GetName(),Nick.GetNickMask(),"QUIT",sMessage); + } + } + } + + virtual void OnJoin(const CNick& Nick, CChan& Channel) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"JOIN",""); + } + } + + virtual void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"JOIN",sMessage); + } + } + + virtual void OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) { + if (logLevel>=2) { + vector::const_iterator it; + for (it=vChans.begin(); it!=vChans.end(); it++) { + CChan& channel = **it; + AddMessage(channel.GetName(),OldNick.GetNickMask(),"NICK",sNewNick); + } + } + } + + virtual EModRet OnPrivAction(CNick& Nick, CString& sMessage) { + if (logLevel>=0) { + AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"ACTION",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) { + if (logLevel==0) { + if (strcasestr(sMessage.c_str(),m_pNetwork->GetCurNick().c_str())) + AddMessage(Channel.GetName(),Nick.GetNickMask(),"ACTION",sMessage); + } else if (logLevel>=1) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"ACTION",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnPrivMsg(CNick& Nick, CString& sMessage) { + if (logLevel>=0) { + AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"PRIVMSG",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { + if (logLevel==0) { + if (strcasestr(sMessage.c_str(),m_pNetwork->GetCurNick().c_str())) + AddMessage(Channel.GetName(),Nick.GetNickMask(),"PRIVMSG",sMessage); + } else if (logLevel>=1) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"PRIVMSG",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnPrivNotice(CNick& Nick, CString& sMessage) { + if (logLevel>=2) { + AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"NOTICE",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"NOTICE",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"TOPIC",sTopic); + } + return CONTINUE; + } + + virtual EModRet OnRaw(CString& sLine) { + if (logLevel>=3) { + CString sCmd = sLine.Token(1); + if ((sCmd.length() == 3) && (isdigit(sCmd[0])) && (isdigit(sCmd[1])) && (isdigit(sCmd[2]))) { + unsigned int uRaw = sCmd.ToUInt(); + if (uRaw!=10 && uRaw!=305 && uRaw!=306 && uRaw!=324 && uRaw!=329 && uRaw<331 && uRaw>333 && uRaw!=352 && uRaw!=353 && uRaw!=366 && uRaw!=432 && uRaw!=433 && uRaw!=437 && uRaw!=451 && uRaw!=670) { + AddMessage("",sLine.Token(0),sCmd,sLine.Token(2, true)); + } + } + } + return CONTINUE; + } + + //Client Connection + virtual void OnClientLogin() { + SetSetting("clientConnected",GetUNIXTime()); + Replay(); + } + + virtual void OnClientDisconnect() { + if (!m_pNetwork->IsUserAttached()) { + SetSetting("clientDisconnected",GetUNIXTime()); + } + } + + virtual void OnModCommand(const CString& sCmdLine) { + CString sCommand = sCmdLine.Token(0); + CString sArgs = sCmdLine.Token(1, true); + + if (sCommand.Equals("HELP")) { + CString host = GetNV("host"); + CString port = GetNV("port"); + if (port.empty()) { + port = "3306"; + SetNV("port",port); + } + CString username = GetNV("username"); + CString password = GetNV("password"); + CString databaseName = GetNV("databaseName"); + + PutModule("host - MySQL host."); + PutModule("port - MySQL port (defualt is 3306)."); + PutModule("username - MySQL username."); + PutModule("password - MySQL password."); + PutModule("databaseName - MySQL database name."); + PutModule("-----"); + PutModule("connect - Reconnect to the MySQL database."); + PutModule("-----"); + PutModule("replay - Play back the messages received."); + PutModule("replayAll - Set LogMySQL to replay all messages stored (default is off)."); + PutModule("logLimit - Set LogMySQL to limit the amount of items to store into the log (0 means to keep everything, 1 means clear after replay, anything else would limit to the count, default is 1)."); + PutModule("logLevel - Set LogMySQL log level (0 messages mentioning you and/or to you only. 1 include all messages sent in an channel. 2 include all actions, join/part, and notices sent in channel and to you. 3 include all server wide messages. 4 include messages sent by you. Default is 1)."); + PutModule("-----"); + PutModule("Layout is as below."); + PutModule("Command [arguments]"); + PutModule("-----"); + PutModule("If you do not include an argument on items that supports arguments, LogMySQL will return the current setting for the item."); + } else if (sCommand.Equals("HOST")) { + if (sArgs.empty()) { + CString host = GetNV("host"); + PutModule("Host is set to: "+host); + } else { + SetNV("host",sArgs); + PutModule("Host is now set to: "+sArgs); + } + } else if (sCommand.Equals("PORT")) { + if (sArgs.empty()) { + CString port = GetNV("port"); + PutModule("Port is set to: "+port); + } else { + CString result; + unsigned int port = strtoul(sArgs.c_str(), NULL, 10); + char portStr[20]; + snprintf(portStr, sizeof(portStr), "%u", port); + result = portStr; + SetNV("port",result); + PutModule("Port is now set to: "+result); + } + } else if (sCommand.Equals("USERNAME")) { + if (sArgs.empty()) { + CString username = GetNV("username"); + PutModule("Username is set to: "+username); + } else { + SetNV("username",sArgs); + PutModule("Username is now set to: "+sArgs); + } + } else if (sCommand.Equals("PASSWORD")) { + if (sArgs.empty()) { + CString password = GetNV("password"); + if (password.empty()) + PutModule("Password is blank."); + else + PutModule("Password is set."); + } else { + SetNV("password",sArgs); + PutModule("Password is now set."); + } + } else if (sCommand.Equals("DATABASENAME")) { + if (sArgs.empty()) { + CString databaseName = GetNV("databaseName"); + PutModule("Database Name is set to: "+databaseName); + } else { + SetNV("databaseName",sArgs); + PutModule("Database Name is now set to: "+sArgs); + } + } else if (sCommand.Equals("CONNECT")) { + MySQLConnect(); + if (databaseConnected) + PutModule("Database is now connected"); + else + PutModule("Unable to connect to database. Check configuration."); + } else if (sCommand.Equals("REPLAY")) { + Replay(); + PutModule("Replayed"); + } else if (sCommand.Equals("REPLAYALL")) { + if (sArgs.empty()) { + CString status = (replayAll ? "On" : "Off"); + PutModule("ReplayAll is set to: "+status); + } else { + if (sArgs.Equals("ON") || sArgs.Equals("1") || sArgs.Equals("true")) { + replayAll = true; + SetSetting("replayAll", "1"); + } else { + replayAll = false; + SetSetting("replayAll", "0"); + } + CString status = (replayAll ? "On" : "Off"); + PutModule("ReplayAll is now set to: "+status); + } + } else if (sCommand.Equals("LOGLIMIT")) { + if (sArgs.empty()) { + CString result; + char limitStr[20]; + snprintf(limitStr, sizeof(limitStr), "%lu", logLimit); + result = limitStr; + PutModule("LogLimit is set to: "+result); + } else { + logLimit = strtoul(sArgs.c_str(), NULL, 10); + CString result; + char limitStr[20]; + snprintf(limitStr, sizeof(limitStr), "%lu", logLimit); + result = limitStr; + SetSetting("logLimit", result); + PutModule("LogLimit is now set to: "+result); + } + } else if (sCommand.Equals("LOGLEVEL")) { + if (sArgs.empty()) { + CString result; + char levelStr[20]; + snprintf(levelStr, sizeof(levelStr), "%d", logLevel); + result = levelStr; + PutModule("LogLevel is set to: "+result); + } else { + logLevel = atoi(sArgs.c_str()); + CString result; + char levelStr[20]; + snprintf(levelStr, sizeof(levelStr), "%d", logLevel); + result = levelStr; + SetSetting("logLevel", result); + PutModule("LogLevel is now set to: "+result); + } + } else + PutModule("Unknown command ["+sCommand+"] for help, type help."); + } + + void Replay() { + if (!databaseConnected) + return; + PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Buffer Playback..."); + + CString lastOnline = GetSetting("clientDisconnected"); + + MYSQL_STMT *statement = mysql_stmt_init(database); + int status = 0; + if (!replayAll && !lastOnline.empty()) { + status = mysql_stmt_prepare(statement, "SELECT * FROM `messages` WHERE `time`>? ORDER BY `time`", 55); + if (status!=0) { + PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + return; + } + + MYSQL_BIND bind[1]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = (void*)lastOnline.c_str(); + bind[0].buffer_length = lastOnline.length(); + bind[0].is_null = false; + + status = mysql_stmt_bind_param(statement, bind); + if (status!=0) { + PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + mysql_stmt_close(statement); + return; + } + } else { + status = mysql_stmt_prepare(statement, "SELECT * FROM `messages` ORDER BY `time`", 40); + if (status!=0) { + PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + return; + } + } + + status = mysql_stmt_execute(statement); + if (status!=0) { + PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + mysql_stmt_close(statement); + return; + } + + MYSQL_RES *result = mysql_stmt_result_metadata(statement); + + unsigned int columnCount = mysql_num_fields(result); + MYSQL_FIELD *fields = mysql_fetch_fields(result); + MYSQL_BIND results[columnCount]; + memset(results, 0, sizeof(results)); + map columnData; + map columnLength; + map columnNull; + map columns; + for (unsigned int i=0; iGetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + mysql_free_result(result); + mysql_stmt_close(statement); + return; + } + + while (true) { + status = mysql_stmt_fetch(statement); + if (status!=0) + break; + + map data; + for (unsigned int i=0; iGetIRCNick().GetNickMask()+" :["+timeStr+"] Server Disconnected"); + } else if (data["type"].Equals("CONNECT")) { + PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :["+timeStr+"] Server Connected"); + } else if (data["type"].Equals("JOIN")) { + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] joined"); + } else if (data["type"].Equals("PART")) { + if (data["message"].Equals("")) + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] parted"); + else + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] parted: "+data["message"]); + } else if (data["type"].Equals("TOPIC")) { + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] changed topic: "+data["message"]); + } else if (data["type"].Equals("QUIT")) { + if (data["message"].Equals("")) + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] quit"); + else + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] quit: "+data["message"]); + } else if (data["type"].Equals("MODE")) { + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] changed mode: "+data["message"]); + } else if (data["type"].Equals("ACTION")) { + PutUser(":"+data["nick"]+" PRIVMSG "+data["target"]+" :\001ACTION ["+timeStr+"] "+data["message"]+"\001"); + } else { + PutUser(":"+data["nick"]+" "+data["type"]+" "+data["target"]+" :["+timeStr+"] "+data["message"]); + } + } + + for (unsigned int i=0; iGetIRCNick().GetNickMask()+" :Playback Complete."); + } + +private: + MYSQL *database; + bool databaseConnected; + bool connected; + bool replayAll; + unsigned long logLimit; + int logLevel; +}; + +template<> void TModInfo(CModInfo& Info) { + Info.SetWikiPage("logmysql"); +} + +NETWORKMODULEDEFS(CLogMySQL, "Add logging to MySQL") \ No newline at end of file diff --git a/logsqlite.cpp b/logsqlite.cpp new file mode 100755 index 0000000..5952382 --- /dev/null +++ b/logsqlite.cpp @@ -0,0 +1,644 @@ +// +// logsqlite.cpp +// LogSQL +// +// Created by Mr. Gecko on 2/3/12. +// Copyright (c) 2012 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ +// +// Permission to use, copy, modify, and/or distribute this software for any purpose +// with or without fee is hereby granted, provided that the above copyright notice +// and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +// OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +// DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include +#include +#include +#include +#include + +#include + +class CLogSQLite : public CModule { +public: + MODCONSTRUCTOR(CLogSQLite) {} + + virtual bool OnLoad(const CString& sArgs, CString& sMessage) { + connected = true; + + CString savePath = GetSavePath(); + savePath += "/log.sqlite"; + + bool found = false; + FILE *fp = fopen(savePath.c_str(), "rb"); + if (fp!=NULL) { + found = true; + fclose(fp); + } + + sqlite3_open(savePath.c_str(), &database); + + if (!found) { + sqlite3_stmt *result; + int status = sqlite3_prepare(database, "CREATE TABLE `settings` (`name` text, `value` text)", -1, &result, NULL); + if (status==SQLITE_OK) { + sqlite3_step(result); + sqlite3_finalize(result); + } + status = sqlite3_prepare(database, "CREATE TABLE `messages` (`target` text, `nick` text, `type` text, `message` text, `time` real(20,5))", -1, &result, NULL); + if (status==SQLITE_OK) { + sqlite3_step(result); + sqlite3_finalize(result); + } + SetSetting("replayAll","0"); + SetSetting("logLimit","1"); + SetSetting("logLevel","1"); + } + replayAll = atoi(GetSetting("replayAll").c_str()); + logLimit = strtoul(GetSetting("logLimit").c_str(), NULL, 10); + logLevel = atoi(GetSetting("logLevel").c_str()); + + return true; + } + + virtual ~CLogSQLite() { + int result = sqlite3_close(database); + if (result==SQLITE_BUSY) { + sqlite3_stmt *stmt; + while ((stmt = sqlite3_next_stmt(database, NULL))!=NULL) { + sqlite3_finalize(stmt); + } + result = sqlite3_close(database); + if (result!=SQLITE_OK) + printf("Unable to close the SQLite Database\n"); + } + } + + CString GetUNIXTime() { + struct timeval time; + gettimeofday(&time, NULL); + double microtime = (double)(time.tv_sec + (time.tv_usec/1000000.00)); + char timeStr[25]; + snprintf(timeStr, sizeof(timeStr), "%f", microtime); + return timeStr; + } + + void AddMessage(const CString& target, const CString& nick, const CString& type, const CString& message) { + sqlite3_stmt *result; + int status = sqlite3_prepare(database, "INSERT INTO `messages` (`target`, `nick`, `type`, `message`, `time`) VALUES (?,?,?,?,?)", -1, &result, NULL); + if (status!=SQLITE_OK) + return; + status = sqlite3_bind_text(result, 1, target.c_str(), target.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + status = sqlite3_bind_text(result, 2, nick.c_str(), nick.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + status = sqlite3_bind_text(result, 3, type.c_str(), type.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + status = sqlite3_bind_text(result, 4, message.c_str(), message.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + CString time = GetUNIXTime(); + status = sqlite3_bind_text(result, 5, time.c_str(), time.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + sqlite3_step(result); + sqlite3_finalize(result); + + if (logLimit>=2) { + status = sqlite3_prepare(database, "SELECT COUNT(*) FROM `messages`", -1, &result, NULL); + if (status!=SQLITE_OK) + return; + status = SQLITE_BUSY; + while (status==SQLITE_BUSY) { + status = sqlite3_step(result); + } + if (status!=SQLITE_ROW) + return; + + int dataCount = sqlite3_data_count(result); + if (dataCount!=1) { + sqlite3_finalize(result); + cout << "LogSQLite: We are only suppose to receive 1 field. We received the count of " << dataCount << " fields.\n"; + return; + } + unsigned long count = strtoul((const char *)sqlite3_column_text(result, 0), NULL, 10); + if (count<=logLimit) + count = 0; + else + count = count-logLimit; + sqlite3_finalize(result); + + if (count!=0) { + char *queryStr = (char *)malloc(73); + snprintf(queryStr, 73, "SELECT `rowid` FROM `messages` ORDER BY `time` LIMIT %lu", count); + status = sqlite3_prepare(database, queryStr, -1, &result, NULL); + free(queryStr); + if (status!=SQLITE_OK) + return; + + while (true) { + status = SQLITE_BUSY; + while (status==SQLITE_BUSY) { + status = sqlite3_step(result); + } + if (status!=SQLITE_ROW) + break; + + dataCount = sqlite3_data_count(result); + if (dataCount!=1) { + sqlite3_finalize(result); + cout << "LogSQLite: We are only suppose to receive 1 field. We received the count of " << dataCount << " fields.\n"; + break; + } + + sqlite3_stmt *result2; + const char *rowid = (const char *)sqlite3_column_text(result, 0); + + status = sqlite3_prepare(database, "DELETE FROM `messages` WHERE `rowid`=?", -1, &result2, NULL); + if (status!=SQLITE_OK) + continue; + status = sqlite3_bind_text(result2, 1, rowid, strlen(rowid), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result2); + continue; + } + sqlite3_step(result2); + sqlite3_finalize(result2); + } + sqlite3_finalize(result); + } + } + } + + void SetSetting(const CString& name, const CString& value) { + bool exists = false; + sqlite3_stmt *result; + int status = sqlite3_prepare(database, "SELECT `value` FROM `settings` WHERE `name`=?", -1, &result, NULL); + if (status==SQLITE_OK) { + status = sqlite3_bind_text(result, 1, name.c_str(), name.length(), SQLITE_STATIC); + if (status==SQLITE_OK) { + status = SQLITE_BUSY; + while (status==SQLITE_BUSY) { + status = sqlite3_step(result); + } + if (status==SQLITE_ROW) { + exists = true; + } + } + sqlite3_finalize(result); + } + + if (exists) { + status = sqlite3_prepare(database, "UPDATE `settings` SET `value`=? WHERE `name`=?", -1, &result, NULL); + if (status!=SQLITE_OK) { + return; + } + status = sqlite3_bind_text(result, 1, value.c_str(), value.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + status = sqlite3_bind_text(result, 2, name.c_str(), name.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + + sqlite3_finalize(result); + return; + } + sqlite3_step(result); + sqlite3_finalize(result); + } else { + status = sqlite3_prepare(database, "INSERT INTO `settings` (`name`,`value`) VALUES (?,?)", -1, &result, NULL); + if (status!=SQLITE_OK) { + return; + } + status = sqlite3_bind_text(result, 1, name.c_str(), name.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + status = sqlite3_bind_text(result, 2, value.c_str(), value.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return; + } + sqlite3_step(result); + sqlite3_finalize(result); + } + } + CString GetSetting(const CString& name) { + CString stringValue; + sqlite3_stmt *result; + int status = sqlite3_prepare(database, "SELECT `value` FROM `settings` WHERE `name`=?", -1, &result, NULL); + if (status!=SQLITE_OK) + return stringValue; + status = sqlite3_bind_text(result, 1, name.c_str(), name.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + sqlite3_finalize(result); + return stringValue; + } + while (true) { + status = SQLITE_BUSY; + while (status==SQLITE_BUSY) { + status = sqlite3_step(result); + } + if (status!=SQLITE_ROW) + break; + + int dataCount = sqlite3_data_count(result); + if (dataCount!=1) { + sqlite3_finalize(result); + cout << "LogSQLite: Settings are only suppose to return 1 as the field count. We received the count of " << dataCount << " fields.\n"; + return stringValue; + } + stringValue = CString((const char *)sqlite3_column_text(result, 0)); + break; + } + sqlite3_finalize(result); + return stringValue; + } + //Server stuff + + virtual void OnIRCDisconnected() { + if (connected) { + connected = false; + if (logLevel>=3) { + AddMessage("","","DISCONNECT",""); + } + } + } + + virtual void OnIRCConnected() { + if (!connected) { + connected = true; + if (logLevel>=3) { + AddMessage("","","CONNECT",""); + } + } + } + + //User stuff + virtual EModRet OnUserAction(CString& sTarget, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"ACTION",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserMsg(CString& sTarget, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"PRIVMSG",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserNotice(CString& sTarget, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"NOTICE",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserJoin(CString& sChannel, CString& sKey) { + if (logLevel>=4) { + AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"JOIN",""); + } + return CONTINUE; + } + + virtual EModRet OnUserPart(CString& sChannel, CString& sMessage) { + if (logLevel>=4) { + AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"PART",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnUserTopic(CString& sChannel, CString& sTopic) { + if (logLevel>=4) { + AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"TOPIC",sTopic); + } + return CONTINUE; + } + + + //Other stuff + virtual void OnRawMode(const CNick& OpNick, CChan& Channel, const CString& sModes, const CString& sArgs) { + if (logLevel>=3) { + AddMessage(Channel.GetName(),OpNick.GetNickMask(),"MODE",sModes+" "+sArgs); + } + } + + virtual void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),OpNick.GetNickMask(),"KICK",sKickedNick+" "+sMessage); + } + } + + virtual void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) { + if (logLevel>=2) { + vector::const_iterator it; + for (it=vChans.begin(); it!=vChans.end(); it++) { + CChan& channel = **it; + AddMessage(channel.GetName(),Nick.GetNickMask(),"QUIT",sMessage); + } + } + } + + virtual void OnJoin(const CNick& Nick, CChan& Channel) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"JOIN",""); + } + } + + virtual void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"JOIN",sMessage); + } + } + + virtual void OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) { + if (logLevel>=2) { + vector::const_iterator it; + for (it=vChans.begin(); it!=vChans.end(); it++) { + CChan& channel = **it; + AddMessage(channel.GetName(),OldNick.GetNickMask(),"NICK",sNewNick); + } + } + } + + virtual EModRet OnPrivAction(CNick& Nick, CString& sMessage) { + if (logLevel>=0) { + AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"ACTION",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) { + if (logLevel==0) { + if (strcasestr(sMessage.c_str(),m_pNetwork->GetCurNick().c_str())) + AddMessage(Channel.GetName(),Nick.GetNickMask(),"ACTION",sMessage); + } else if (logLevel>=1) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"ACTION",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnPrivMsg(CNick& Nick, CString& sMessage) { + if (logLevel>=0) { + AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"PRIVMSG",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { + if (logLevel==0) { + if (strcasestr(sMessage.c_str(),m_pNetwork->GetCurNick().c_str())) + AddMessage(Channel.GetName(),Nick.GetNickMask(),"PRIVMSG",sMessage); + } else if (logLevel>=1) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"PRIVMSG",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnPrivNotice(CNick& Nick, CString& sMessage) { + if (logLevel>=2) { + AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"NOTICE",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"NOTICE",sMessage); + } + return CONTINUE; + } + + virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) { + if (logLevel>=2) { + AddMessage(Channel.GetName(),Nick.GetNickMask(),"TOPIC",sTopic); + } + return CONTINUE; + } + + virtual EModRet OnRaw(CString& sLine) { + if (logLevel>=3) { + CString sCmd = sLine.Token(1); + if ((sCmd.length() == 3) && (isdigit(sCmd[0])) && (isdigit(sCmd[1])) && (isdigit(sCmd[2]))) { + unsigned int uRaw = sCmd.ToUInt(); + if (uRaw!=10 && uRaw!=305 && uRaw!=306 && uRaw!=324 && uRaw!=329 && uRaw<331 && uRaw>333 && uRaw!=352 && uRaw!=353 && uRaw!=366 && uRaw!=432 && uRaw!=433 && uRaw!=437 && uRaw!=451 && uRaw!=670) { + AddMessage("",sLine.Token(0),sCmd,sLine.Token(2, true)); + } + } + } + return CONTINUE; + } + + //Client Connection + virtual void OnClientLogin() { + SetSetting("clientConnected",GetUNIXTime()); + Replay(); + } + + virtual void OnClientDisconnect() { + if (!m_pNetwork->IsUserAttached()) { + SetSetting("clientDisconnected",GetUNIXTime()); + } + } + + virtual void OnModCommand(const CString& sCmdLine) { + CString sCommand = sCmdLine.Token(0); + CString sArgs = sCmdLine.Token(1, true); + + if (sCommand.Equals("HELP")) { + PutModule("replay - Play back the messages received."); + PutModule("replayAll - Set LogSQLite to replay all messages stored (default is off)."); + PutModule("logLimit - Set LogSQLite to limit the amount of items to store into the log (0 means to keep everything, 1 means clear after replay, anything else would limit to the count, default is 1)."); + PutModule("logLevel - Set LogSQLite log level (0 messages mentioning you and/or to you only. 1 include all messages sent in an channel. 2 include all actions, join/part, and notices sent in channel and to you. 3 include all server wide messages. 4 include messages sent by you. Default is 1)."); + PutModule("-----"); + PutModule("Layout is as below."); + PutModule("Command [arguments]"); + PutModule("-----"); + PutModule("If you do not include an argument on items that supports arguments, LogSQLite will return the current setting for the item."); + } else if (sCommand.Equals("REPLAY")) { + Replay(); + PutModule("Replayed"); + } else if (sCommand.Equals("REPLAYALL")) { + if (sArgs.empty()) { + CString status = (replayAll ? "On" : "Off"); + PutModule("ReplayAll is set to: "+status); + } else { + if (sArgs.Equals("ON") || sArgs.Equals("1") || sArgs.Equals("true")) { + replayAll = true; + SetSetting("replayAll", "1"); + } else { + replayAll = false; + SetSetting("replayAll", "0"); + } + CString status = (replayAll ? "On" : "Off"); + PutModule("ReplayAll is now set to: "+status); + } + } else if (sCommand.Equals("LOGLIMIT")) { + if (sArgs.empty()) { + CString result; + char limitStr[20]; + snprintf(limitStr, sizeof(limitStr), "%lu", logLimit); + result = limitStr; + PutModule("LogLimit is set to: "+result); + } else { + logLimit = strtoul(sArgs.c_str(), NULL, 10); + CString result; + char limitStr[20]; + snprintf(limitStr, sizeof(limitStr), "%lu", logLimit); + result = limitStr; + SetSetting("logLimit", result); + PutModule("LogLimit is now set to: "+result); + } + } else if (sCommand.Equals("LOGLEVEL")) { + if (sArgs.empty()) { + CString result; + char levelStr[20]; + snprintf(levelStr, sizeof(levelStr), "%d", logLevel); + result = levelStr; + PutModule("LogLevel is set to: "+result); + } else { + logLevel = atoi(sArgs.c_str()); + CString result; + char levelStr[20]; + snprintf(levelStr, sizeof(levelStr), "%d", logLevel); + result = levelStr; + SetSetting("logLevel", result); + PutModule("LogLevel is now set to: "+result); + } + } else + PutModule("Unknown command ["+sCommand+"] for help, type help."); + } + + void Replay() { + PutUser(":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Buffer Playback..."); + + CString lastOnline = GetSetting("clientDisconnected"); + + sqlite3_stmt *result; + int status = SQLITE_OK; + if (!replayAll && !lastOnline.empty()) { + status = sqlite3_prepare(database, "SELECT * FROM `messages` WHERE `time`>? ORDER BY `time`", -1, &result, NULL); + if (status!=SQLITE_OK) { + PutUser(":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + return; + } + status = sqlite3_bind_text(result, 1, lastOnline.c_str(), lastOnline.length(), SQLITE_STATIC); + if (status!=SQLITE_OK) { + PutUser(":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + sqlite3_finalize(result); + return; + } + } else { + status = sqlite3_prepare(database, "SELECT * FROM `messages` ORDER BY `time`", -1, &result, NULL); + if (status!=SQLITE_OK) { + PutUser(":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem."); + return; + } + } + + int columnCount = sqlite3_column_count(result); + map columns; + for (int i=0; i data; + int dataCount = sqlite3_data_count(result); + for (int i=0; iGetIRCNick().GetNickMask()+" :["+timeStr+"] Server Disconnected"); + } else if (data["type"].Equals("CONNECT")) { + PutUser(":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :["+timeStr+"] Server Connected"); + } else if (data["type"].Equals("JOIN")) { + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] joined"); + } else if (data["type"].Equals("PART")) { + if (data["message"].Equals("")) + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] parted"); + else + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] parted: "+data["message"]); + } else if (data["type"].Equals("TOPIC")) { + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] changed topic: "+data["message"]); + } else if (data["type"].Equals("QUIT")) { + if (data["message"].Equals("")) + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] quit"); + else + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] quit: "+data["message"]); + } else if (data["type"].Equals("MODE")) { + PutUser(":"+data["nick"]+" NOTICE "+data["target"]+" :["+timeStr+"] changed mode: "+data["message"]); + } else if (data["type"].Equals("ACTION")) { + PutUser(":"+data["nick"]+" PRIVMSG "+data["target"]+" :\001ACTION ["+timeStr+"] "+data["message"]+"\001"); + } else { + PutUser(":"+data["nick"]+" "+data["type"]+" "+data["target"]+" :["+timeStr+"] "+data["message"]); + } + } + + sqlite3_finalize(result); + + if (logLimit==1) { + status = sqlite3_prepare(database, "DELETE FROM `messages`", -1, &result, NULL); + if (status==SQLITE_OK) { + sqlite3_step(result); + sqlite3_finalize(result); + } + } + + PutUser(":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback Complete."); + } + +private: + sqlite3 *database; + bool connected; + bool replayAll; + unsigned long logLimit; + int logLevel; +}; + +template<> void TModInfo(CModInfo& Info) { + Info.SetWikiPage("logsqlite"); +} + +NETWORKMODULEDEFS(CLogSQLite, "Add logging to SQLite") \ No newline at end of file