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"), ¬ifyString);
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