1 //=============================================================================
2 //
3 //   File : KviNotifyList.cpp
4 //   Creation date : Fri Oct 27 2000 23:41:01 CEST by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "kvi_debug.h"
26 #include "KviNotifyList.h"
27 #include "KviConsoleWindow.h"
28 #include "KviIrcSocket.h"
29 #include "KviRegisteredUserDataBase.h"
30 #include "KviUserListView.h"
31 #include "KviChannelWindow.h"
32 #include "KviOptions.h"
33 #include "KviWindow.h"
34 #include "KviLocale.h"
35 #include "kvi_out.h"
36 #include "KviIrcServerParser.h"
37 #include "KviIrcMask.h"
38 #include "KviIrcNumericCodes.h"
39 #include "KviIrcConnection.h"
40 #include "KviApplication.h"
41 #include "KviQString.h"
42 #include "KviLagMeter.h"
43 #include "KviKvsEventTriggers.h"
44 #include "KviIrcMessage.h"
45 
46 #include <QByteArray>
47 #include <QStringList>
48 
49 #include <algorithm>
50 #include <memory>
51 #include <set>
52 #include <vector>
53 
54 // FIXME: #warning "Finish this doc!"
55 
56 /*
57 	@doc: notify_list
58 	@title:
59 		Notify lists
60 	@short:
61 		Tracking users on IRC
62 	@keyterms:
63 		notify property, watch property, notify lists
64 	@body:
65 		The notify list is a means of keeping track of users on IRC.[br]
66 		Once connected to an IRC server, you can tell KVIrc to check
67 		periodically if your friends are online.[br]
68 		This is basically achieved by setting a property in the [doc:registered_users]registered users database[/doc]
69 		entry.[br]
70 		The property is called [i]notify[/i], and you have to set it to the nickname
71 		that you want to look for.[br]
72 		So for example, assume to register a friend of yours like Szymon:
73 		[example]
74 			[cmd:reguser.add]reguser.add[/cmd] Szymon
75 			[cmd:reguser.addmask]reguser.addmask[/cmd] Szymon Pragma!*@*.it
76 		[/example]
77 		And then want it in the notify list; nothing easier, just set
78 		hist [i]notify[/i] property to the nickname that you want him to be [i]looked for[/i]:
79 		[example]
80 			[cmd:reguser.setproperty]reguser.setproperty[/cmd] Szymon notify Pragma
81 		[/example]
82 		In this way, once in a while, KVIrc will send to the server an ISON message
83 		with the nickname Pragma. If Szymon is online, you will be notified with a message:[br]
84 		[i]Pragma [someuser@somehost.it] is on IRC[/i].[br]
85 		If Szymon uses often [i][Pragma][/i] as his secondary nickname, you can do the following:
86 		[example]
87 			[cmd:reguser.addmask]reguser.addmask[/cmd] Szymon [Pragma]*@*.it
88 			[cmd:reguser.setproperty]reguser.setproperty[/cmd] Szymon notify [i]Pragma [Pragma][/i]
89 		[/example]
90 		KVIrc will then look for both nicknames getting online.[br]
91 		KVIrc supports three notify lists management methods:[br]
92 		The [i]stupid ISON method[/i], the [i]intelligent ISON method[/i] and the [i]WATCH method[/i].[br]
93 		The [i]stupid ISON method[/i] will assume that Szymon is online if any user with nickname
94 		Pragma (or [Pragma] in the second example) gets online; this means that also Pragma!someuser@somehost.com will be
95 		assumed to be [i]Szymon[/i] and will be shown in the notify list.[br]
96 		This might be a false assumption (since somehost.com does not even match *.it),
97 		but it is the best result that the [i]stupid ISON method[/i] can achieve.[br]
98 		The [i]intelligent ISON method[/i] will also check the Pragma's username and hostname
99 		and match it in the registered masks; so in the example above, you will be notified if
100 		any user that matches Pragma!*@*.it gets online; (but you will [b]not[/b] be notified if
101 		(for example) Pragma!someuser@somehost.com gets online).[br]
102 		So what's the point in including a stupid method? :) Well... the intelligent
103 		method [i]eats[/i] some of your IRC bandwidth; it has to send USERHOST messages
104 		for every group of 5 users in the notify list. If you have a lot of users
105 		in the notify list, it might become slow and eventually cause a
106 		client to server flood.[br]
107 		So finally, the intelligent method is the default. If you have [i]flood[/i] problems,
108 		or if you think that the notify list is quite slow, try the [i]stupid[/i] method:
109 		it is not that bad after all.[br]
110 		The third notify list management method is the [i]WATCH method[/i].[br]
111 		It uses a totally different (and better) approach to the notify lists management,
112 		and can be used only on the networks that support the WATCH notify method (DALnet, WebNet, etc.).[br]
113 		KVIrc will attempt to guess if the server you're currently using supports the WATCH command
114 		and eventually use this last method.[br]
115 		The WATCH method uses the [i]notify[/i] property to get the nicknames that have to be
116 		sent to the server in the /WATCH commands.
117 */
118 
119 // Basic NotifyListManager: this does completely nothing
120 
KviNotifyListManager(KviIrcConnection * pConnection)121 KviNotifyListManager::KviNotifyListManager(KviIrcConnection * pConnection)
122     : QObject()
123 {
124 	setObjectName("notify_list_manager");
125 	m_pConnection = pConnection;
126 	m_pConsole = pConnection->console();
127 }
128 
129 KviNotifyListManager::~KviNotifyListManager()
130     = default;
131 
start()132 void KviNotifyListManager::start()
133 {
134 }
135 
stop()136 void KviNotifyListManager::stop()
137 {
138 }
139 
handleUserhost(KviIrcMessage *)140 bool KviNotifyListManager::handleUserhost(KviIrcMessage *)
141 {
142 	return false;
143 }
144 
handleIsOn(KviIrcMessage *)145 bool KviNotifyListManager::handleIsOn(KviIrcMessage *)
146 {
147 	return false;
148 }
149 
handleWatchReply(KviIrcMessage *)150 bool KviNotifyListManager::handleWatchReply(KviIrcMessage *)
151 {
152 	return false;
153 }
154 
notifyOnLine(const QString & szNick,const QString & szUser,const QString & szHost,const QString & szReason,bool bJoin)155 void KviNotifyListManager::notifyOnLine(const QString & szNick, const QString & szUser, const QString & szHost, const QString & szReason, bool bJoin)
156 {
157 	if(bJoin)
158 		m_pConsole->notifyListView()->join(szNick, szUser, szHost);
159 
160 	KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolNotifyListChangesToActiveWindow) ? m_pConsole->activeWindow() : m_pConsole;
161 	if(KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnNotifyOnLine, pOut, szNick))
162 		return;
163 
164 	QString szWho;
165 	QString szMsg;
166 
167 	if(!(szUser.isEmpty() || szHost.isEmpty()))
168 		szWho = QString("\r!n\r%1\r [%2@\r!h\r%3\r]").arg(szNick, szUser, szHost);
169 	else
170 		szWho = QString("\r!n\r%1\r").arg(szNick);
171 
172 	KviPointerHashTable<QString, KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
173 	KviPointerHashTableIterator<QString, KviRegisteredUser> it(*d);
174 
175 	while(KviRegisteredUser * pUser = it.current())
176 	{
177 		QString szProp = pUser->getProperty("notify");
178 		if(!szProp.isEmpty() && szProp.split(',', QString::SkipEmptyParts).contains(szNick))
179 		{
180 			QString szComment = pUser->getProperty("comment");
181 			if(!szComment.isEmpty())
182 				szMsg = QString("%1 (%2), Group \"%3\" is on IRC as (%4)").arg(pUser->name(), szComment, pUser->group(), szWho);
183 			else
184 				szMsg = QString("%1, Group \"%2\" is on IRC as (%3)").arg(pUser->name(), pUser->group(), szWho);
185 			break;
186 		}
187 		++it;
188 	}
189 
190 	QString szFmt = __tr2qs("%1 is on IRC");
191 
192 	if(szMsg.isEmpty())
193 		szMsg = QString(szFmt).arg(szWho);
194 
195 	if((!szReason.isEmpty()) && (_OUTPUT_VERBOSE))
196 	{
197 		szMsg += '(';
198 		szMsg += szReason;
199 		szMsg += ')';
200 	}
201 
202 	pOut->outputNoFmt(KVI_OUT_NOTIFYONLINE, szMsg);
203 
204 	if(!(pOut->hasAttention(KviWindow::MainWindowIsVisible)))
205 	{
206 		if(KVI_OPTION_BOOL(KviOption_boolFlashWindowOnNotifyOnLine))
207 			pOut->demandAttention();
208 		if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNotifyOnLine))
209 		{
210 			szWho = "<b>";
211 			szWho += szNick;
212 			szWho += "</b>";
213 			szMsg = QString(szFmt).arg(szWho);
214 			g_pApp->notifierMessage(pOut, KVI_OPTION_MSGTYPE(KVI_OUT_NOTIFYONLINE).pixId(), szMsg, KVI_OPTION_UINT(KviOption_uintNotifierAutoHideTime));
215 		}
216 	}
217 }
218 
notifyOffLine(const QString & szNick,const QString & szUser,const QString & szHost,const QString & szReason)219 void KviNotifyListManager::notifyOffLine(const QString & szNick, const QString & szUser, const QString & szHost, const QString & szReason)
220 {
221 	KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolNotifyListChangesToActiveWindow) ? m_pConsole->activeWindow() : m_pConsole;
222 	if(!KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnNotifyOffLine, pOut, szNick))
223 	{
224 		QString szWho;
225 
226 		if(!(szUser.isEmpty() || szHost.isEmpty()))
227 			szWho = QString("\r!n\r%1\r [%2@\r!h\r%3\r]").arg(szNick, szUser, szHost);
228 		else
229 			szWho = QString("\r!n\r%1\r").arg(szNick);
230 
231 		QString szMsg;
232 
233 		KviPointerHashTable<QString, KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
234 		KviPointerHashTableIterator<QString, KviRegisteredUser> it(*d);
235 
236 		while(KviRegisteredUser * pUser = it.current())
237 		{
238 			QString szProp = pUser->getProperty("notify");
239 			if(!szProp.isEmpty() && szProp.split(',', QString::SkipEmptyParts).contains(szNick))
240 			{
241 				QString szComment = pUser->getProperty("comment");
242 				if(!szComment.isEmpty())
243 					szMsg = QString("%1 (%2), Group \"%3\" has left IRC as (%4)").arg(pUser->name(), szComment, pUser->group(), szWho);
244 				else
245 					szMsg = QString("%1, Group \"%2\" has left IRC as (%3)").arg(pUser->name(), pUser->group(), szWho);
246 				break;
247 			}
248 			++it;
249 		}
250 
251 		if(szMsg.isEmpty())
252 			szMsg = QString(__tr2qs("%1 has left IRC")).arg(szWho);
253 
254 		if((!szReason.isEmpty()) && (_OUTPUT_VERBOSE))
255 		{
256 			szMsg += '(';
257 			szMsg += szReason;
258 			szMsg += ')';
259 		}
260 
261 		pOut->outputNoFmt(KVI_OUT_NOTIFYOFFLINE, szMsg);
262 	}
263 
264 	m_pConsole->notifyListView()->part(szNick);
265 }
266 
267 //
268 //  INTELLIGENT NOTIFY LIST MANAGER: NOTIFY PROCESS:
269 //
270 //            start()                              stop()
271 //               |                                   ^
272 //         buildRegUserDict()                        |
273 //               |                                   |
274 //     m_pRegUserDict.empty() ? -- YES ------------->+
275 //                       |                           |
276 //                      NO                           |
277 //                       |                           |
278 //         newNotifySession()<------- TIMER ---------------- delayedNotifySession() --------------------------------+
279 //                       |    (can be stopped here)  |              ^                                               |
280 //                       |                           |              ^                                               |
281 //                  buildNotifyList()                |              |                                              YES
282 //                       |                           |              |                                               |
283 //                    m_NotifyList.empty() ? - YES ->+              |                                               |
284 //                       |                                          |                                               |
285 //                      NO                                          |                                               |
286 //                       |                                          |                                               |
287 //                      newIsOnSession()<------------- TIMER -------------------- delayedIsOnSession() -- NO - m_NotifyList.empty() ?
288 //                               |           (can be stopped here)  |                                               |
289 //                               |                                  |                                               |
290 //                            buildIsOnList()                       |                                               |
291 //                               |                                  |                                               |
292 //                           m_IsOnList.empty() ? -- YES ---------->+                                               |
293 //                               |                                                                                  |
294 //                              NO                                                                                  |
295 //                               |                                                                                  |
296 //                            sendIsOn() - - - - - - - - - - - -> handleIsOn()                                      |
297 //                                                                      |                                           |
298 //                                                             (build m_OnlineList)                                 |
299 //                                                                      |                                           |
300 //                                                             m_OnlineList.empty() ? - YES ----------------------->+
301 //                                                                      |                                           |
302 //                                                                     NO                                          YES
303 //                                                                      |                                           |
304 //                                                             delayedUserhostSession()<--------------- NO - m_OnlineList.empty() ?
305 //                                                                               |                                  ^
306 //                                                                             TIMER (can be stopped here)          |
307 //                                                                               |                                  |
308 //                                                                           newUserhostSession()                   |
309 //                                                                               |                                  |
310 //                                                                           buildUserhostList()                    |
311 //                                                                               |                                  |
312 //                                                                         m_UserhostList.empty()    ?    - YES --->+
313 //                                                                               |                          ^^^     |
314 //                                                                               |             (unexpected!)|||     |
315 //                                                                               NO                                 |
316 //                                                                               |                                  |
317 //                                                                           sendUserhost() - - - - - - - - > handleUserhost()
318 //
319 
KviIsOnNotifyListManager(KviIrcConnection * pConnection)320 KviIsOnNotifyListManager::KviIsOnNotifyListManager(KviIrcConnection * pConnection)
321     : KviNotifyListManager(pConnection)
322 {
323 	connect(&m_pDelayedNotifyTimer, SIGNAL(timeout()), this, SLOT(newNotifySession()));
324 	connect(&m_pDelayedIsOnTimer, SIGNAL(timeout()), this, SLOT(newIsOnSession()));
325 	connect(&m_pDelayedUserhostTimer, SIGNAL(timeout()), this, SLOT(newUserhostSession()));
326 }
327 
~KviIsOnNotifyListManager()328 KviIsOnNotifyListManager::~KviIsOnNotifyListManager()
329 {
330 	if(m_bRunning)
331 		stop();
332 }
333 
start()334 void KviIsOnNotifyListManager::start()
335 {
336 	if(m_bRunning)
337 		stop();
338 	m_bRunning = true;
339 	m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
340 
341 	m_bExpectingIsOn = false;
342 	m_bExpectingUserhost = false;
343 
344 	buildRegUserDict();
345 	if(m_pRegUserDict.empty())
346 	{
347 		if(_OUTPUT_VERBOSE)
348 			m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: No users to check for, quitting"));
349 		stop();
350 		return;
351 	}
352 	newNotifySession();
353 }
354 
buildRegUserDict()355 void KviIsOnNotifyListManager::buildRegUserDict()
356 {
357 	m_pRegUserDict.clear();
358 
359 	const KviPointerHashTable<QString, KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
360 	KviPointerHashTableIterator<QString, KviRegisteredUser> it(*d);
361 	while(KviRegisteredUser * u = it.current())
362 	{
363 		QString notify;
364 		if(u->getProperty("notify", notify))
365 		{
366 			for(const auto & single : notify.trimmed().split(' ', QString::SkipEmptyParts))
367 				m_pRegUserDict.emplace(single, u->name());
368 		}
369 		++it;
370 	}
371 }
372 
delayedNotifySession()373 void KviIsOnNotifyListManager::delayedNotifySession()
374 {
375 	unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs);
376 	if(iTimeout < 15)
377 	{
378 		// life first of all.
379 		// don't allow the user to suicide
380 		if(_OUTPUT_VERBOSE)
381 			m_pConsole->output(KVI_OUT_SYSTEMWARNING,
382 			    __tr2qs("Notify list: Timeout (%d sec) is too short, resetting to something more reasonable (15 sec)"),
383 			    iTimeout);
384 		iTimeout = 15;
385 		KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs) = 15;
386 	}
387 	m_pDelayedNotifyTimer.setInterval(iTimeout * 1000);
388 	m_pDelayedNotifyTimer.setSingleShot(true);
389 	m_pDelayedNotifyTimer.start();
390 }
391 
newNotifySession()392 void KviIsOnNotifyListManager::newNotifySession()
393 {
394 	buildNotifyList();
395 	if(m_NotifyList.empty())
396 	{
397 		if(_OUTPUT_VERBOSE)
398 			m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Notify list empty, quitting"));
399 		stop();
400 		return;
401 	}
402 	newIsOnSession();
403 }
404 
buildNotifyList()405 void KviIsOnNotifyListManager::buildNotifyList()
406 {
407 	m_NotifyList.clear();
408 	for(auto & it : m_pRegUserDict)
409 	{
410 		m_NotifyList.push_back(it.first);
411 	}
412 }
413 
delayedIsOnSession()414 void KviIsOnNotifyListManager::delayedIsOnSession()
415 {
416 	unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListIsOnDelayTimeInSecs);
417 	if(iTimeout < 5)
418 	{
419 		// life first of all.
420 		// don't allow the user to suicide
421 		if(_OUTPUT_VERBOSE)
422 			m_pConsole->output(KVI_OUT_SYSTEMWARNING,
423 			    __tr2qs("Notify list: ISON delay (%d sec) is too short, resetting to something more reasonable (5 sec)"),
424 			    iTimeout);
425 		iTimeout = 5;
426 		KVI_OPTION_UINT(KviOption_uintNotifyListIsOnDelayTimeInSecs) = 5;
427 	}
428 	m_pDelayedIsOnTimer.setInterval(iTimeout * 1000);
429 	m_pDelayedIsOnTimer.setSingleShot(true);
430 	m_pDelayedIsOnTimer.start();
431 }
432 
newIsOnSession()433 void KviIsOnNotifyListManager::newIsOnSession()
434 {
435 	buildIsOnList();
436 	if(m_IsOnList.empty())
437 		delayedNotifySession();
438 	else
439 		sendIsOn();
440 }
441 
buildIsOnList()442 void KviIsOnNotifyListManager::buildIsOnList()
443 {
444 	m_IsOnList.clear();
445 	m_szIsOnString = "";
446 	while(!m_NotifyList.empty())
447 	{
448 		const auto sIter = m_NotifyList.begin();
449 		const auto & s = *sIter;
450 
451 		if(((m_szIsOnString.length() + s.length()) + 1) >= 504)
452 			break;
453 
454 		if(!m_szIsOnString.isEmpty())
455 			m_szIsOnString.append(' ');
456 		m_szIsOnString.append(s);
457 		m_IsOnList.push_back(std::move(s));
458 		m_NotifyList.erase(sIter);
459 	}
460 }
461 
sendIsOn()462 void KviIsOnNotifyListManager::sendIsOn()
463 {
464 	if(_OUTPUT_PARANOIC)
465 		m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Checking for: %Q"), &m_szIsOnString);
466 	QByteArray szDec = m_pConnection->encodeText(m_szIsOnString);
467 	m_pConnection->sendFmtData("ISON %s", szDec.data());
468 	if(m_pConnection->lagMeter())
469 		m_pConnection->lagMeter()->lagCheckRegister("@notify_ison", 40); // not that reliable
470 	m_szIsOnString = "";
471 	m_bExpectingIsOn = true;
472 	// FIXME: #warning "And if can't send ?"
473 }
474 
handleIsOn(KviIrcMessage * msg)475 bool KviIsOnNotifyListManager::handleIsOn(KviIrcMessage * msg)
476 {
477 	if(!m_bExpectingIsOn)
478 		return false;
479 
480 	// Check if it is our ISON
481 	// all the nicks must be on the IsOnList
482 
483 	std::set<std::size_t> tmplist;
484 
485 	KviCString nk;
486 	const char * aux = msg->trailing();
487 
488 	while(*aux)
489 	{
490 		nk = "";
491 		aux = kvi_extractToken(nk, aux, ' ');
492 		if(nk.hasData())
493 		{
494 			bool bGotIt = false;
495 			QString dnk = m_pConnection->decodeText(nk.ptr());
496 
497 			std::size_t i = 0;
498 			for(const auto & s : m_IsOnList)
499 			{
500 				if(KviQString::equalCI(s, dnk))
501 				{
502 					tmplist.insert(i);
503 					bGotIt = true;
504 					break;
505 				}
506 				i++;
507 			}
508 			if(!bGotIt)
509 			{
510 				// oops... not my userhost!
511 				if(_OUTPUT_VERBOSE)
512 					m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Hey! You've used ISON behind my back? (I might be confused now...)"));
513 				return false;
514 			}
515 		}
516 	}
517 
518 	// Ok... looks to be my ison (still not sure at 100%, but can't do better)
519 	if(m_pConnection->lagMeter())
520 		m_pConnection->lagMeter()->lagCheckComplete("@notify_ison");
521 
522 	m_bExpectingIsOn = false;
523 
524 	m_OnlineList.clear();
525 
526 	// Ok... we have an IsOn reply here
527 	// The nicks in the IsOnList that are also in the reply are online, and go to the OnlineList
528 	// the remaining in the IsOnList are offline
529 	for(auto i = tmplist.rbegin(); i != tmplist.rend(); ++i)
530 	{
531 		m_OnlineList.push_back(std::move(m_IsOnList[*i]));
532 		m_IsOnList.erase(m_IsOnList.begin() + *i);
533 	}
534 
535 	// Ok... all the users that are online, are on the OnlineList
536 	// the remaining users are in the m_IsOnList, and are no longer online
537 
538 	// first the easy step: remove the users that have just left irc or have never been online
539 	// we're clearling the m_IsOnList
540 	for(const auto & s : m_IsOnList)
541 	{
542 		// has just left IRC... make him part
543 		if(m_pConsole->notifyListView()->findEntry(s))
544 			notifyOffLine(s);
545 	}
546 
547 	m_IsOnList.clear();
548 
549 	// ok... complex step now: the remaining users in the userhost list are online
550 	// if they have been online before, just remove them from the list
551 	// otherwise they must be matched for masks
552 	// and eventually inserted in the notify view later
553 
554 	KviIrcUserDataBase * db = console()->connection()->userDataBase();
555 
556 	std::set<std::size_t> l;
557 	std::size_t i = 0;
558 	for(const auto & ss : m_OnlineList)
559 	{
560 		if(KviUserListEntry * ent = m_pConsole->notifyListView()->findEntry(ss))
561 		{
562 			// the user was online from a previous notify session
563 			// might the mask have been changed ? (heh... this is tricky, maybe too much even)
564 			if(KVI_OPTION_BOOL(KviOption_boolNotifyListSendUserhostForOnlineUsers))
565 			{
566 				// user wants to be sure about online users....
567 				// check if he is on some channels
568 				if(ent->globalData()->nRefs() > 1)
569 				{
570 					// mmmh... we have more than one ref, so the user is at least in one query or channel
571 					// look him up on channels, if we find his entry, we can be sure that he is
572 					// still the right user
573 					std::vector<KviChannelWindow *> chlist = m_pConsole->connection()->channelList();
574 					for(auto ch : chlist)
575 					{
576 						if(KviUserListEntry * le = ch->findEntry(ss))
577 						{
578 							l.insert(i); // ok... found on a channel... we don't need a userhost to match him
579 							KviIrcMask mk(ss, le->globalData()->user(), le->globalData()->host());
580 							if(!doMatchUser(ss, mk))
581 								return true; // critical problems = have to restart!!!
582 							break;
583 						}
584 					}
585 				} // else Only one ref... we need a userhost to be sure (don't remove from the list)
586 			}
587 			else
588 			{
589 				// user wants no userhost for online users... we "hope" that everything will go ok.
590 				l.insert(i);
591 			}
592 			//l.insert(i); // we will remove him from the list
593 		}
594 		else
595 		{
596 			// the user was not online!
597 			// check if we have a cached mask
598 			if(db)
599 			{
600 				if(KviIrcUserEntry * ue = db->find(ss))
601 				{
602 					// already in the db... do we have a mask ?
603 					if(ue->hasUser() && ue->hasHost())
604 					{
605 						// yup! we have a complete mask to match on
606 						KviIrcMask mk(ss, ue->user(), ue->host());
607 						// lookup the user's name in the m_pRegUserDict
608 						if(!doMatchUser(ss, mk))
609 							return true; // critical problems = have to restart!!!
610 						l.insert(i);     // remove anyway
611 					}
612 				}
613 			}
614 		}
615 
616 		i++;
617 	}
618 
619 	for(auto i = l.rbegin(); i != l.rend(); ++i)
620 	{
621 		m_OnlineList.erase(m_OnlineList.begin() + *i);
622 	}
623 
624 	if(m_OnlineList.empty())
625 	{
626 		if(m_NotifyList.empty())
627 			delayedNotifySession();
628 		else
629 			delayedIsOnSession();
630 	}
631 	else
632 		delayedUserhostSession();
633 
634 	return true;
635 }
636 
637 // FIXME: #warning "Nickname escapes (links) in the notifylist messages!"
638 
doMatchUser(const QString & notifyString,const KviIrcMask & mask)639 bool KviIsOnNotifyListManager::doMatchUser(const QString & notifyString, const KviIrcMask & mask)
640 {
641 	const auto i = m_pRegUserDict.find(notifyString);
642 	if(i != m_pRegUserDict.end())
643 	{
644 		const QString & nam = i->second;
645 		// ok... find the user
646 		if(KviRegisteredUser * u = g_pRegisteredUserDataBase->findUserByName(nam))
647 		{
648 			// ok... match the user
649 			if(u->matchesFixed(mask))
650 			{
651 				// new user online
652 				if(!(m_pConsole->notifyListView()->findEntry(mask.nick())))
653 				{
654 					notifyOnLine(mask.nick(), mask.user(), mask.host());
655 				} // else already online, and matching... all ok
656 			}
657 			else
658 			{
659 				// not matched.... has he been online before ?
660 				if(m_pConsole->notifyListView()->findEntry(mask.nick()))
661 				{
662 					// has been online just a sec ago, but now the mask does not match
663 					// either reguserdb has changed, or the user went offline and another one got his nick
664 					// in the meantime... (ugly situation anyway)
665 					notifyOffLine(mask.nick(), mask.user(), mask.host(), __tr2qs("registration mask changed, or nickname is being used by someone else"));
666 				}
667 				else
668 				{
669 					// has never been online
670 					if(_OUTPUT_VERBOSE)
671 						m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: \r!n\r%Q\r appears to be online, but the mask [%Q@\r!h\r%Q\r] does not match (registration mask does not match, or nickname is being used by someone else)"), &(mask.nick()), &(mask.user()), &(mask.host()));
672 				}
673 			}
674 		}
675 		else
676 		{
677 			// oops... unexpected inconsistency.... reguser db modified ?
678 			m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Unexpected inconsistency, registered user DB modified? (restarting)"));
679 			stop();
680 			start();
681 			return false; // critical... exit from the call stack
682 		}
683 	}
684 	else
685 	{
686 		// oops... unexpected inconsistency
687 		m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Unexpected inconsistency, expected \r!n\r%Q\r in the registered user DB"), &notifyString);
688 	}
689 	return true;
690 }
691 
delayedUserhostSession()692 void KviIsOnNotifyListManager::delayedUserhostSession()
693 {
694 	unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListUserhostDelayTimeInSecs);
695 	if(iTimeout < 5)
696 	{
697 		// life first of all.
698 		// don't allow the user to suicide
699 		if(_OUTPUT_VERBOSE)
700 			m_pConsole->output(KVI_OUT_SYSTEMWARNING,
701 			    __tr2qs("Notify list: USERHOST delay (%d sec) is too short, resetting to something more reasonable (5 sec)"),
702 			    iTimeout);
703 		iTimeout = 5;
704 		KVI_OPTION_UINT(KviOption_uintNotifyListUserhostDelayTimeInSecs) = 5;
705 	}
706 	m_pDelayedUserhostTimer.setInterval(iTimeout * 1000);
707 	m_pDelayedUserhostTimer.setSingleShot(true);
708 	m_pDelayedUserhostTimer.start();
709 }
710 
newUserhostSession()711 void KviIsOnNotifyListManager::newUserhostSession()
712 {
713 	buildUserhostList();
714 	if(m_UserhostList.empty())
715 	{
716 		// this is unexpected!
717 		m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Unexpected inconsistency, userhost list is empty!"));
718 		if(m_OnlineList.empty())
719 		{
720 			if(m_NotifyList.empty())
721 				delayedNotifySession();
722 			else
723 				delayedIsOnSession();
724 		}
725 		else
726 			delayedUserhostSession();
727 		return;
728 	}
729 	sendUserhost();
730 }
731 
732 #define MAX_USERHOST_ENTRIES 5
733 
buildUserhostList()734 void KviIsOnNotifyListManager::buildUserhostList()
735 {
736 	m_szUserhostString = "";
737 	m_UserhostList.clear();
738 	for(std::size_t i = 0; i < MAX_USERHOST_ENTRIES && !m_OnlineList.empty(); ++i)
739 	{
740 		const auto sIter = m_OnlineList.begin();
741 		const QString & s = *sIter;
742 
743 		if(!m_szUserhostString.isEmpty())
744 			m_szUserhostString.append(' ');
745 		m_szUserhostString.append(s);
746 		m_UserhostList.push_back(std::move(s));
747 		m_OnlineList.erase(sIter);
748 	}
749 }
750 
sendUserhost()751 void KviIsOnNotifyListManager::sendUserhost()
752 {
753 	if(_OUTPUT_PARANOIC)
754 		m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Checking userhost for: %Q"), &m_szUserhostString);
755 	QByteArray ccc = m_pConnection->encodeText(m_szUserhostString);
756 	m_pConnection->sendFmtData("USERHOST %s", ccc.data());
757 	if(m_pConnection->lagMeter())
758 		m_pConnection->lagMeter()->lagCheckRegister("@notify_userhost", 50);
759 	m_szUserhostString = "";
760 	m_bExpectingUserhost = true;
761 	// FIXME: #warning "And if can't send ?"
762 }
763 
handleUserhost(KviIrcMessage * msg)764 bool KviIsOnNotifyListManager::handleUserhost(KviIrcMessage * msg)
765 {
766 	if(!m_bExpectingUserhost)
767 		return false;
768 	// first check for consistency: all the replies must be on the USERHOST list
769 
770 	std::map<std::size_t, std::unique_ptr<KviIrcMask>> tmplist;
771 
772 	KviCString nk;
773 	const char * aux = msg->trailing();
774 
775 	while(*aux)
776 	{
777 		nk = "";
778 		aux = kvi_extractToken(nk, aux, ' ');
779 		if(nk.hasData())
780 		{
781 			// split it in a mask
782 			KviCString nick;
783 			KviCString user;
784 			KviCString host;
785 
786 			int idx = nk.findFirstIdx('=');
787 			if(idx != -1)
788 			{
789 				nick = nk.left(idx);
790 				if(nick.lastCharIs('*'))
791 					nick.cutRight(1);
792 				nk.cutLeft(idx + 1);
793 				if(nk.firstCharIs('+') || nk.firstCharIs('-'))
794 					nk.cutLeft(1);
795 
796 				idx = nk.findFirstIdx('@');
797 				if(idx != -1)
798 				{
799 					user = nk.left(idx);
800 					nk.cutLeft(idx + 1);
801 					host = nk;
802 				}
803 				else
804 				{
805 					user = "*";
806 					host = nk;
807 				}
808 
809 				bool bGotIt = false;
810 				QString szNick = m_pConnection->decodeText(nick.ptr());
811 				QString szUser = m_pConnection->decodeText(user.ptr());
812 				QString szHost = m_pConnection->decodeText(host.ptr());
813 
814 				std::size_t i = 0;
815 				for(const auto & s : m_UserhostList)
816 				{
817 					if(KviQString::equalCI(s, szNick))
818 					{
819 						tmplist.emplace(i, std::make_unique<KviIrcMask>(szNick, szUser, szHost));
820 						bGotIt = true;
821 						break;
822 					}
823 				}
824 
825 				if(!bGotIt)
826 				{
827 					// oops... not my userhost!
828 					if(_OUTPUT_VERBOSE)
829 						m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Hey! You've used USERHOST behind my back? (I might be confused now...)"));
830 					return false;
831 				}
832 			}
833 			else
834 			{
835 				if(_OUTPUT_VERBOSE)
836 					m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Broken USERHOST reply from the server? (%s)"), nk.ptr());
837 			}
838 		}
839 	}
840 
841 	// Ok... looks to be my usershot (still not sure at 100%, but can't do better)
842 
843 	if(m_pConnection->lagMeter())
844 		m_pConnection->lagMeter()->lagCheckComplete("@notify_userhost");
845 
846 	m_bExpectingUserhost = false;
847 
848 	for(auto & pair : tmplist)
849 	{
850 		KviIrcMask * mk = pair.second.get();
851 
852 		if(!doMatchUser(mk->nick(), *mk))
853 			return true; // have to restart!!!
854 	}
855 
856 	for(auto i = tmplist.rbegin(); i != tmplist.rend(); ++i)
857 		m_UserhostList.erase(m_UserhostList.begin() + i->first);
858 
859 	for(const auto & s : m_UserhostList)
860 	{
861 		// oops... someone is no longer online ?
862 		if(_OUTPUT_VERBOSE)
863 			m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: \r!n\r%Q\r appears to have gone offline before USERHOST reply was received, will recheck in the next loop"), &s);
864 	}
865 
866 	m_UserhostList.clear();
867 
868 	if(m_OnlineList.empty())
869 	{
870 		if(m_NotifyList.empty())
871 			delayedNotifySession();
872 		else
873 			delayedIsOnSession();
874 	}
875 	else
876 	{
877 		delayedUserhostSession();
878 	}
879 
880 	return true;
881 }
882 
stop()883 void KviIsOnNotifyListManager::stop()
884 {
885 	if(!m_bRunning)
886 		return;
887 
888 	if(m_pConnection->lagMeter())
889 		m_pConnection->lagMeter()->lagCheckAbort("@notify_userhost");
890 	if(m_pConnection->lagMeter())
891 		m_pConnection->lagMeter()->lagCheckAbort("@notify_ison");
892 
893 	m_pDelayedNotifyTimer.stop();
894 	m_pDelayedIsOnTimer.stop();
895 	m_pDelayedUserhostTimer.stop();
896 	m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
897 	m_pRegUserDict.clear();
898 	m_NotifyList.clear();
899 	m_IsOnList.clear();
900 	m_OnlineList.clear();
901 	m_UserhostList.clear();
902 	m_szIsOnString = "";
903 	m_szUserhostString = "";
904 	m_bRunning = false;
905 }
906 
907 //
908 // Stupid notify list manager
909 //
910 
KviStupidNotifyListManager(KviIrcConnection * pConnection)911 KviStupidNotifyListManager::KviStupidNotifyListManager(KviIrcConnection * pConnection)
912     : KviNotifyListManager(pConnection)
913 {
914 	m_iRestartTimer = 0;
915 }
916 
~KviStupidNotifyListManager()917 KviStupidNotifyListManager::~KviStupidNotifyListManager()
918 {
919 	if(m_iRestartTimer)
920 	{
921 		killTimer(m_iRestartTimer);
922 		m_iRestartTimer = 0;
923 	}
924 }
925 
start()926 void KviStupidNotifyListManager::start()
927 {
928 	if(m_iRestartTimer)
929 	{
930 		killTimer(m_iRestartTimer);
931 		m_iRestartTimer = 0;
932 	}
933 	if(_OUTPUT_VERBOSE)
934 		m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Starting notify list"));
935 	buildNickList();
936 	if(m_pNickList.empty())
937 	{
938 		if(_OUTPUT_VERBOSE)
939 			m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("No users in the notify list"));
940 		return; // Ok... no nicknames in the list
941 	}
942 	m_iNextNickToCheck = 0;
943 	m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
944 	sendIsOn();
945 }
946 
sendIsOn()947 void KviStupidNotifyListManager::sendIsOn()
948 {
949 	m_szLastIsOnMsg = "";
950 	KVI_ASSERT(m_iNextNickToCheck < m_pNickList.size());
951 
952 	std::size_t i = m_iNextNickToCheck;
953 	for(; i < m_pNickList.size(); ++i)
954 	{
955 		const auto nick = m_pNickList[i];
956 		if((nick.length() + 5 + m_szLastIsOnMsg.length()) >= 510)
957 			break;
958 		KviQString::appendFormatted(m_szLastIsOnMsg, " %Q", &nick);
959 	}
960 	if(_OUTPUT_PARANOIC)
961 		m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Checking for: %Q"), &m_szLastIsOnMsg);
962 	QByteArray dat = m_pConnection->encodeText(m_szLastIsOnMsg);
963 	m_pConnection->sendFmtData("ISON%s", dat.data());
964 
965 	if(m_pConnection->lagMeter())
966 		m_pConnection->lagMeter()->lagCheckRegister("@notify_naive", 20);
967 
968 	m_iNextNickToCheck += i;
969 }
970 
handleIsOn(KviIrcMessage * msg)971 bool KviStupidNotifyListManager::handleIsOn(KviIrcMessage * msg)
972 {
973 	if(m_pConnection->lagMeter())
974 		m_pConnection->lagMeter()->lagCheckComplete("@notify_naive");
975 
976 	KviCString nk;
977 	const char * aux = msg->trailing();
978 	while(*aux)
979 	{
980 		nk = "";
981 		aux = kvi_extractToken(nk, aux, ' ');
982 		if(nk.hasData())
983 		{
984 			QString nkd = m_pConnection->decodeText(nk.ptr());
985 			QString nksp = " " + nkd;
986 			m_szLastIsOnMsg.replace(nksp, "", Qt::CaseInsensitive);
987 			if(!(m_pConsole->notifyListView()->findEntry(nkd)))
988 			{
989 				// not yet notified
990 				notifyOnLine(nkd);
991 			}
992 		}
993 	}
994 	// ok... check the users that have left irc now...
995 	QStringList sl = m_szLastIsOnMsg.isEmpty() ? QStringList() : m_szLastIsOnMsg.split(' ', QString::SkipEmptyParts);
996 
997 	for(auto & it : sl)
998 	{
999 		if(m_pConsole->notifyListView()->findEntry(it))
1000 		{
1001 			// has just left irc
1002 			notifyOffLine(it);
1003 		} // else has never been here...
1004 	}
1005 
1006 	if(((unsigned int)m_iNextNickToCheck) >= m_pNickList.size())
1007 	{
1008 		// have to restart
1009 		unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs);
1010 		if(iTimeout < 5)
1011 		{
1012 			// life first of all.
1013 			// don't allow the user to suicide
1014 			if(_OUTPUT_VERBOSE)
1015 				m_pConsole->output(KVI_OUT_SYSTEMWARNING,
1016 				    __tr2qs("Notify list: Timeout (%d sec) is too short, resetting to something more reasonable (5 sec)"),
1017 				    iTimeout);
1018 			iTimeout = 5;
1019 			KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs) = 5;
1020 		}
1021 		m_iRestartTimer = startTimer(iTimeout * 1000);
1022 	}
1023 	else
1024 		sendIsOn();
1025 	return true;
1026 }
1027 
timerEvent(QTimerEvent * e)1028 void KviStupidNotifyListManager::timerEvent(QTimerEvent * e)
1029 {
1030 	if(e->timerId() == m_iRestartTimer)
1031 	{
1032 		killTimer(m_iRestartTimer);
1033 		m_iRestartTimer = 0;
1034 		m_iNextNickToCheck = 0;
1035 		sendIsOn();
1036 		return;
1037 	}
1038 	QObject::timerEvent(e);
1039 }
1040 
stop()1041 void KviStupidNotifyListManager::stop()
1042 {
1043 	if(m_pConnection->lagMeter())
1044 		m_pConnection->lagMeter()->lagCheckAbort("@notify_naive");
1045 
1046 	if(m_iRestartTimer)
1047 	{
1048 		killTimer(m_iRestartTimer);
1049 		m_iRestartTimer = 0;
1050 	}
1051 	m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
1052 
1053 	// The ISON Method needs no stopping
1054 }
1055 
buildNickList()1056 void KviStupidNotifyListManager::buildNickList()
1057 {
1058 	const KviPointerHashTable<QString, KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
1059 	KviPointerHashTableIterator<QString, KviRegisteredUser> it(*d);
1060 	m_pNickList.clear();
1061 	while(it.current())
1062 	{
1063 		QString notify;
1064 		if(it.current()->getProperty("notify", notify))
1065 			m_pNickList.push_back(notify);
1066 		++it;
1067 	}
1068 }
1069 
1070 //
1071 // Watch notify list manager
1072 //
1073 
KviWatchNotifyListManager(KviIrcConnection * pConnection)1074 KviWatchNotifyListManager::KviWatchNotifyListManager(KviIrcConnection * pConnection)
1075     : KviNotifyListManager(pConnection)
1076 {
1077 }
1078 
buildRegUserDict()1079 void KviWatchNotifyListManager::buildRegUserDict()
1080 {
1081 	m_pRegUserDict.clear();
1082 
1083 	const KviPointerHashTable<QString, KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
1084 	KviPointerHashTableIterator<QString, KviRegisteredUser> it(*d);
1085 	while(KviRegisteredUser * u = it.current())
1086 	{
1087 		QString notify;
1088 		if(u->getProperty("notify", notify))
1089 		{
1090 			notify = notify.trimmed();
1091 			QStringList sl = notify.split(' ', QString::SkipEmptyParts);
1092 			for(auto & slit : sl)
1093 				m_pRegUserDict.emplace(slit, u->name());
1094 		}
1095 		++it;
1096 	}
1097 }
1098 
start()1099 void KviWatchNotifyListManager::start()
1100 {
1101 	m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
1102 
1103 	buildRegUserDict();
1104 
1105 	QString watchStr;
1106 
1107 	for(auto & it : m_pRegUserDict)
1108 	{
1109 		const QString & nk = it.first;
1110 		if(!nk.contains('*'))
1111 		{
1112 			if((watchStr.length() + nk.length() + 2) > 501)
1113 			{
1114 				QByteArray dat = m_pConnection->encodeText(watchStr);
1115 				m_pConnection->sendFmtData("WATCH%s", dat.data());
1116 				if(_OUTPUT_VERBOSE)
1117 					m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Adding watch entries for %Q"), &watchStr);
1118 				watchStr = "";
1119 			}
1120 			KviQString::appendFormatted(watchStr, " +%Q", &nk);
1121 		}
1122 	}
1123 
1124 	if(!watchStr.isEmpty())
1125 	{
1126 		QByteArray dat = m_pConnection->encodeText(watchStr);
1127 		m_pConnection->sendFmtData("WATCH%s", dat.data());
1128 		if(_OUTPUT_VERBOSE)
1129 			m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Adding watch entries for %Q"), &watchStr);
1130 	}
1131 }
stop()1132 void KviWatchNotifyListManager::stop()
1133 {
1134 	m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
1135 	m_pConnection->sendFmtData("WATCH c");
1136 	m_pRegUserDict.clear();
1137 }
1138 
doMatchUser(KviIrcMessage * msg,const QString & notifyString,const KviIrcMask & mask)1139 bool KviWatchNotifyListManager::doMatchUser(KviIrcMessage * msg, const QString & notifyString, const KviIrcMask & mask)
1140 {
1141 	const auto m = m_pRegUserDict.find(notifyString);
1142 	if(m != m_pRegUserDict.end())
1143 	{
1144 		const QString & nam = m->second;
1145 		// ok... find the user
1146 		if(KviRegisteredUser * u = g_pRegisteredUserDataBase->findUserByName(nam))
1147 		{
1148 			// ok... match the user
1149 			if(u->matchesFixed(mask))
1150 			{
1151 				// new user online
1152 				if(!(m_pConsole->notifyListView()->findEntry(mask.nick())))
1153 				{
1154 					notifyOnLine(mask.nick(), mask.user(), mask.host(), "watch");
1155 				}
1156 				else
1157 				{
1158 					// else already online, and matching... all ok
1159 					if(msg->numeric() == RPL_NOWON)
1160 					{
1161 						// This is a reply to a /watch +something (should not happen, unless the user is messing) or to /watch l (user requested)
1162 						notifyOnLine(mask.nick(), mask.user(), mask.host(),
1163 						    __tr2qs("watch entry listing requested by user"), false);
1164 					}
1165 					else
1166 					{
1167 						// This is a RPL_LOGON.... we're desynched ?
1168 						notifyOnLine(mask.nick(), mask.user(), mask.host(),
1169 						    __tr2qs("possible watch list desync"), false);
1170 					}
1171 				}
1172 			}
1173 			else
1174 			{
1175 				// not matched.... has he been online before ?
1176 				if(m_pConsole->notifyListView()->findEntry(mask.nick()))
1177 				{
1178 					// has been online just a sec ago, but now the mask does not match
1179 					// prolly the reguserdb has been changed
1180 					notifyOffLine(mask.nick(), mask.user(), mask.host(),
1181 					    __tr2qs("registration mask changed or desync with the watch service"));
1182 				}
1183 				else
1184 				{
1185 					// has never been online
1186 					if(_OUTPUT_VERBOSE)
1187 						m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,
1188 						    __tr("Notify list: \r!n\r%Q\r appears to be online, but the mask [%Q@\r!h\r%Q\r] does not match (watch: registration mask does not match, or nickname is being used by someone else)"),
1189 						    &(mask.nick()), &(mask.user()), &(mask.host()));
1190 				}
1191 			}
1192 		}
1193 		else
1194 		{
1195 			// oops... unexpected inconsistency.... reguser db modified ?
1196 			m_pConsole->output(KVI_OUT_SYSTEMWARNING,
1197 			    __tr2qs("Notify list: Unexpected inconsistency, registered user DB modified? (watch: restarting)"));
1198 			stop();
1199 			start();
1200 			return false; // critical... exit from the call stack
1201 		}
1202 	}
1203 	else
1204 	{
1205 		// not in our dictionary
1206 		// prolly someone used /WATCH behind our back... bad boy!
1207 		if(!(m_pConsole->notifyListView()->findEntry(mask.nick())))
1208 		{
1209 			notifyOnLine(mask.nick(), mask.user(), mask.host(), __tr2qs("watch entry added by user"));
1210 		}
1211 	}
1212 	return true;
1213 }
1214 
1215 // FIXME: #warning "DEDICATED WATCH LIST VERBOSITY FLAG ? (To allow the user to use /WATCH l and manual /WATCH)"
1216 
handleWatchReply(KviIrcMessage * msg)1217 bool KviWatchNotifyListManager::handleWatchReply(KviIrcMessage * msg)
1218 {
1219 	// 600: RPL_LOGON
1220 	// :prefix 600 <target> <nick> <user> <host> <logintime> :logged online
1221 	// 601: RPL_LOGOFF
1222 	// :prefix 601 <target> <nick> <user> <host> <logintime> :logged offline
1223 	// 604: RPL_NOWON
1224 	// :prefix 604 <target> <nick> <user> <host> <logintime> :is online
1225 	// 605: RPL_NOWOFF
1226 	// :prefix 605 <target> <nick> <user> <host> 0 :is offline
1227 
1228 	// FIXME: #warning "Use the logintime in some way ?"
1229 
1230 	const char * nk = msg->safeParam(1);
1231 	const char * us = msg->safeParam(2);
1232 	const char * ho = msg->safeParam(3);
1233 	QString dnk = m_pConnection->decodeText(nk);
1234 	QString dus = m_pConnection->decodeText(us);
1235 	QString dho = m_pConnection->decodeText(ho);
1236 
1237 	if((msg->numeric() == RPL_LOGON) || (msg->numeric() == RPL_NOWON))
1238 	{
1239 		KviIrcMask m(dnk, dus, dho);
1240 		doMatchUser(msg, dnk, m);
1241 		return true;
1242 	}
1243 	else if(msg->numeric() == RPL_WATCHOFF)
1244 	{
1245 		if(m_pConsole->notifyListView()->findEntry(dnk))
1246 		{
1247 			notifyOffLine(dnk, dus, dho, __tr2qs("removed from watch list"));
1248 		}
1249 		else
1250 		{
1251 			if(_OUTPUT_VERBOSE)
1252 				m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: Stopped watching for \r!n\r%Q\r"), &dnk);
1253 		}
1254 		if(m_pRegUserDict.count(dnk))
1255 			m_pRegUserDict.erase(dnk); // kill that
1256 
1257 		return true;
1258 	}
1259 	else if((msg->numeric() == RPL_LOGOFF) || (msg->numeric() == RPL_NOWOFF))
1260 	{
1261 		if(m_pConsole->notifyListView()->findEntry(dnk))
1262 		{
1263 			notifyOffLine(dnk, dus, dho, __tr2qs("watch"));
1264 		}
1265 		else
1266 		{
1267 			if(msg->numeric() == RPL_NOWOFF)
1268 			{
1269 				// This is a reply to a /watch +something
1270 				if(_OUTPUT_VERBOSE)
1271 					m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Notify list: \r!n\r%Q\r is offline (watch)"), &dnk);
1272 			}
1273 			else
1274 			{
1275 				// This is a RPL_LOGOFF for a user that has not matched the reg-mask
1276 				notifyOffLine(dnk, dus, dho, __tr2qs("unmatched watch list entry"));
1277 			}
1278 		}
1279 		return true;
1280 	}
1281 
1282 	return false;
1283 }
1284