summaryrefslogtreecommitdiff
path: root/backlog.cpp
blob: 4552b469df03280790a08dd536dccceb2bc5d5ac (plain)
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
/*
 * 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);

private:
	bool inChan(const CString& Chan);
};

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 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 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("$WINDOW", Channel);
	Path.Replace("$USER", User);

	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.StrCmp(Path, 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.Open()) {
			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;
		}
	}

	bool isInChan = CBacklogMod::inChan(Channel);

	if(printedLines == 0) {
		PutModule("No log files found for window " + Channel + " in " + DirPath + "/");
		return;
	} else if (isInChan) {
		m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + Channel + " :Backlog playback...", GetClient());
	} else {
		PutModule("*** Backlog playback...");
	}

	// now actually print
	for (std::vector<CString>::reverse_iterator it = LinesToPrint.rbegin(); it != LinesToPrint.rend(); ++it) {
		 //if(isInChan || true) {
			CString Line = *it;
			size_t FirstWS = Line.find_first_of(' '); // position of first whitespace char in line
			size_t NickLen = 3;
			CString Nick = "***";

			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);
				}

				m_pNetwork->PutUser(":" + Nick + "!znc@znc.in PRIVMSG " + Channel + " :" + Line.substr(0, FirstWS) + Line.substr(FirstWS + NickLen + 1, Line.npos), GetClient());
				PutModule(":" + Nick + "!znc@znc.in PRIVMSG " + Channel + " :" + Line.substr(0, FirstWS) + Line.substr(FirstWS + NickLen + 1, Line.npos));
			} catch (int e) {
				PutModule("Malformed log line! " + Line);
			}
		 //} else {
			//PutModule(*it);
		 //}
	}

	if (isInChan) {
		m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + Channel + " :" + "Playback complete.", GetClient());
	} else {
		PutModule("*** Playback complete.");
	}
}

bool CBacklogMod::inChan(const CString& Chan) {
	const std::vector <CChan*>& vChans (m_pNetwork->GetChans());

	for (std::vector<CChan*>::const_iterator it = vChans.begin(); it != vChans.end(); ++it) {
		CChan *curChan = *it;
		if(Chan.StrCmp(curChan->GetName()) == 0) {
			return true;
		}
	}

	return false;
}



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.")