1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
/*
* Copyright (C) 2004-2013 ZNC, see the NOTICE file for details.
* Copyright (C) 2013 Rasmus Eskola <fruitiex@gmail.com>
* based on sample.cpp sample module code
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <znc/FileUtils.h>
#include <znc/Client.h>
#include <znc/Chan.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/Modules.h>
#include <dirent.h>
#include <vector>
#include <algorithm>
class CBacklogMod : public CModule {
public:
MODCONSTRUCTOR(CBacklogMod) {}
virtual bool OnLoad(const CString& sArgs, CString& sMessage);
virtual ~CBacklogMod();
virtual void OnModCommand(const CString& sCommand);
};
bool CBacklogMod::OnLoad(const CString& sArgs, CString& sMessage) {
CString LogPath = sArgs;
if(LogPath.empty()) {
LogPath = GetNV("LogPath");
if(LogPath.empty()) {
// TODO: guess logpath?
PutModule("LogPath is empty, set it with the LogPath command (help for more info)");
}
} else {
SetNV("LogPath", LogPath);
PutModule("LogPath set to: " + LogPath);
}
return true;
}
CBacklogMod::~CBacklogMod() {
}
void CBacklogMod::OnModCommand(const CString& sCommand) {
CString Arg = sCommand.Token(1);
if (sCommand.Token(0).Equals("help")) {
// TODO: proper help text, look how AddHelpCommand() does it in other ZNC code
PutModule("Usage:");
PutModule("<window-name> [num-lines] (e.g. #foo 42)");
PutModule("");
PutModule("Commands:");
PutModule("Help (print this text)");
PutModule("LogPath <path> (use keywords $USER, $NETWORK, $WINDOW, $user, $network and an asterisk * for date)");
PutModule("PrintStatusMsgs true/false (print join/part/rename messages)");
return;
} else if (sCommand.Token(0).Equals("logpath")) {
if(Arg.empty()) {
PutModule("Usage: LogPath <path> (use keywords $USER, $NETWORK, $WINDOW, $user, $network and an asterisk * for date:)");
PutModule("Current LogPath is set to: " + GetNV("LogPath"));
return;
}
CString LogPath = sCommand.Token(1, true);
SetNV("LogPath", LogPath);
PutModule("LogPath set to: " + LogPath);
return;
} else if (sCommand.Token(0).Equals("PrintStatusMsgs")) {
if(Arg.empty() || (!Arg.Equals("true", true) && !Arg.Equals("false", true))) {
PutModule("Usage: PrintStatusMsgs true/false");
return;
}
SetNV("PrintStatusMsgs", Arg);
PutModule("PrintStatusMsgs set to: " + Arg);
return;
}
// TODO: handle these differently depending on how the module was loaded
CString User = (m_pUser ? m_pUser->GetUserName() : "UNKNOWN");
CString Network = (m_pNetwork ? m_pNetwork->GetName() : "znc");
CString Channel = sCommand.Token(0);
int printedLines = 0;
int reqLines = sCommand.Token(1).ToInt();
if(reqLines <= 0) {
reqLines = 150;
}
reqLines = std::max(std::min(reqLines, 1000), 1);
CString Path = GetNV("LogPath").substr(); // make copy
Path.Replace("$NETWORK", Network);
Path.Replace("$network", Network.AsLower());
Path.Replace("$WINDOW", CString(Channel.Replace_n("/", "-").Replace_n("\\", "-")).AsLower());
Path.Replace("$USER", User);
Path.Replace("$user", User.AsLower());
CString DirPath = Path.substr(0, Path.find_last_of("/"));
CString FilePath;
std::vector<CString> FileList;
std::vector<CString> LinesToPrint;
// gather list of all log files for requested channel/window
DIR *dir;
struct dirent *ent;
if ((dir = opendir (DirPath.c_str())) != NULL) {
while ((ent = readdir (dir)) != NULL) {
FilePath = DirPath + "/" + ent->d_name;
//PutModule("DEBUG: " + FilePath + " " + Path);
if(FilePath.AsLower().StrCmp(Path.AsLower(), Path.find_last_of("*")) == 0) {
FileList.push_back(FilePath);
}
}
closedir (dir);
} else {
PutModule("Could not list directory " + DirPath + ": " + strerror(errno));
return;
}
std::sort(FileList.begin(), FileList.end());
// loop through list of log files one by one starting from most recent...
for (std::vector<CString>::reverse_iterator it = FileList.rbegin(); it != FileList.rend(); ++it) {
CFile LogFile(*it);
CString Line;
std::vector<CString> Lines;
if (LogFile.GetShortName() == "." || LogFile.GetShortName() == "..") {
continue;
}
if (LogFile.Open()) {
Lines.push_back("[00:00:00] <***> File: " + LogFile.GetShortName()); //Parse name to get date prettier?
printedLines--;
while (LogFile.ReadLine(Line)) {
try {
// is line a part/join/rename etc message (nick ***), do we want to print it?
// find nick by finding first whitespace, then moving one char right
if(Line.at(Line.find_first_of(' ') + 1) == '*' && !GetNV("PrintStatusMsgs").ToBool()) {
continue;
}
Lines.push_back(Line);
} catch (int e) {
// malformed log line, ignore
PutModule("Malformed log line found in " + *it + ": " + Line);
}
}
} else {
PutModule("Could not open log file [" + sCommand + "]: " + strerror(errno));
continue;
}
LogFile.Close();
// loop through Lines in reverse order, push to LinesToPrint
for (std::vector<CString>::reverse_iterator itl = Lines.rbegin(); itl != Lines.rend(); ++itl) {
LinesToPrint.push_back(*itl);
printedLines++;
if(printedLines >= reqLines) {
break;
}
}
if(printedLines >= reqLines) {
break;
}
}
if(printedLines <= 0) {
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + Channel + " :No log files found for window " + Channel + " in " + DirPath + "/", GetClient());
return;
} else {
//RFC prefixes: # ! & +
if (Channel.StartsWith("#") || Channel.StartsWith("&") ||
Channel.StartsWith("!") || Channel.StartsWith("+")) { //Normal channel
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + Channel + " :Backlog playback...", GetClient());
} else {
m_pNetwork->PutUser(":" + Channel + "!znc@znc.in PRIVMSG " + Channel + " :<***> Backlog playback...", GetClient());
}
}
// now actually print
for (std::vector<CString>::reverse_iterator it = LinesToPrint.rbegin(); it != LinesToPrint.rend(); ++it) {
CString Line = *it;
size_t FirstWS = Line.find_first_of(' '); // position of first whitespace char in line
size_t NickLen = 3;
CString Nick = "***";
CString MessageType = "PRIVMSG ";
try {
// a log line looks like: [HH:MM:SS] <Nick> Message
// < and > are illegal characters in nicknames, so we can
// search for these
if(Line.at(FirstWS + 1) == '<') { // normal message
// nicklen includes surrounding < >
NickLen = Line.find_first_of('>') - Line.find_first_of('<') + 1;
// but we don't want them in Nick so subtract by two
Nick = Line.substr(FirstWS + 2, NickLen - 2);
}
else if(Line.at(FirstWS + 1) == '-') { // IRC notice
// nicklen includes surrounding - -
NickLen = Line.find_first_of('-', FirstWS + 2) - Line.find_first_of('-') + 1;
// but we don't want them in Nick so subtract by two
Nick = Line.substr(FirstWS + 2, NickLen - 2);
MessageType = "NOTICE ";
}
//RFC prefixes: # ! & +
if (Channel.StartsWith("#") || Channel.StartsWith("&") ||
Channel.StartsWith("!") || Channel.StartsWith("+")) { //Normal channel
m_pNetwork->PutUser(":" + Nick + "!znc@znc.in " + MessageType + Channel + " :" + Line.substr(0, FirstWS) + Line.substr(FirstWS + NickLen + 1, Line.npos), GetClient());
} else { //Privmsg
m_pNetwork->PutUser(":" + Channel + "!znc@znc.in " + MessageType + Channel + " :" + Line.substr(0, FirstWS) + " <" + Nick + ">" + Line.substr(FirstWS + NickLen + 1, Line.npos), GetClient());
}
} catch (int e) {
PutModule("Malformed log line! " + Line);
}
}
if (Channel.StartsWith("#") || Channel.StartsWith("&") ||
Channel.StartsWith("!") || Channel.StartsWith("+")) { //Normal channel
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + Channel + " :" + "Playback Complete.", GetClient());
} else {
m_pNetwork->PutUser(":" + Channel + "!znc@znc.in PRIVMSG " + Channel + " :<***> " + "Playback Complete.", GetClient());
}
}
template<> void TModInfo<CBacklogMod>(CModInfo& Info) {
Info.AddType(CModInfo::NetworkModule);
Info.AddType(CModInfo::GlobalModule);
Info.SetWikiPage("backlog");
Info.SetArgsHelpText("Takes as optional argument (and if given, sets) the log path, use keywords: $USER, $NETWORK and $WINDOW");
Info.SetHasArgs(true);
}
NETWORKMODULEDEFS(CBacklogMod, "Module for getting the last X lines of a channels log.")
|