//============================================================================= // // File : kvi_notifylist.cpp // Creation date : Fri Oct 27 2000 23:41:01 CEST by Szymon Stefanek // // This file is part of the KVirc irc client distribution // Copyright (C) 2000-2004 Szymon Stefanek (pragma at kvirc dot net) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your opinion) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #define __KVIRC__ #include "kvi_debug.h" #include "kvi_notifylist.h" #include "kvi_console.h" #include "kvi_ircsocket.h" #include "kvi_regusersdb.h" #include "kvi_userlistview.h" #include "kvi_channel.h" #include "kvi_options.h" #include "kvi_window.h" #include "kvi_locale.h" #include "kvi_out.h" #include "kvi_sparser.h" #include "kvi_ircmask.h" #include "kvi_numeric.h" #include "kvi_parameterlist.h" #include "kvi_ircconnection.h" #include "kvi_app.h" #include "kvi_qstring.h" #include "kvi_lagmeter.h" #include "kvi_kvs_eventtriggers.h" #include "kvi_qcstring.h" #include // FIXME: #warning "Finish this doc!" /* @doc: notify_list @title: Notify lists @short: Tracking users on IRC @keyterms: notify property, watch property, notify lists @body: The notify list is a means of keeping track of users on IRC.[br] Once connected to an IRC server, you can tell KVIrc to check periodically if your friends are online.[br] This is basically achieved by setting a property in the [doc:registered_users]registered users database[/doc] entry.[br] The property is called "notify", and you have to set it to the nickname that you want to look for.[br] So for example, assume to register a frend of yours like Szymon:[br] [example] [cmd:reguser.add]reguser.add[/cmd] Szymon [cmd:reguser.addmask]reguser.addmask[/cmd] Szymon Pragma!*@*.it [/example] And then want it in the notify list; nothing easier, just set hist "notify" property to the nickname that you want him to be "looked for":[br] [example] [cmd:reguser.setproperty]reguser.setproperty[/cmd] Szymon notify Pragma [/example] In this way, once in a while, KVIrc will send to the server an ISON message with the nickname Pragma. If Szymon is online, you will be notified with a message:[br] "Pragma [someuser@somehost.it] is on IRC".[br] If Szymon uses often "[Pragma]" as his secondary nickname , you can do the following:[br] [example] [cmd:reguser.addmask]reguser.addmask[/cmd] Szymon [Pragma]*@*.it [cmd:reguser.setproperty]reguser.setproperty[/cmd] Szymon notify "Pragma [Pragma]" [/example] KVIrc will then look for both nicknames getting online.[br] KVIrc supports three notify lists management methods:[br] The "stupid ISON method", the "intelligent ISON method" and the "WATCH method".[br] The "stupid ISON method" will assume that Szymon is online if any user with nickname Pragma (or [Pragma] in the second example) gets online; this means that also Pragma!someuser@somehost.com will be assumed to be "Szymon" and will be shown in the notify list.[br] This might be a false assumption (since somehod.com does not even match *.it), but it is the best result that the "stupid ISON method" can achieve.[br] The "intelligent ISON method" will also check the Pragma's username and hostname and match it in the registered masks; so in the example above, you will be notified if any user that matches Pragma!*@*.it gets online; (but you will NOT be notified if (for example) Pragma!someuser@somehost.com gets online).[br] So what's the point in including a stupid method? :) Well...the intelligent method "eats" some of your IRC bandwidth; it has to send USERHOST messages for every group of 5 users in the notify list. If you have a lot of users in the notify list, it might become slow and eventually cause a client to server flood.[br] So finally, the intelligent method is the default. If you have "flood" problems, or if you think that the notify list is quite slow , try the "stupid" method: it is not that bad after all.[br] The third notify list management method is the "WATCH method".[br] It uses a totally different (and better) approach to the notify lists management, and can be used only on the networks that support the WATCH notify method (DALnet, WebNet, etc.).[br] KVIrc will attempt to guess if the server you're currently using supports the WATCH command and eventually use this last method.[br] The WATCH method uses the "notify" property to get the nicknames that have to be sent to the server in the /WATCH commands. */ // Basic NotifyListManager: this does completely nothing KviNotifyListManager::KviNotifyListManager(KviIrcConnection * pConnection) : TQObject(0,"notify_list_manager") { m_pConnection = pConnection; m_pConsole = pConnection->console(); } KviNotifyListManager::~KviNotifyListManager() { } void KviNotifyListManager::start() { } void KviNotifyListManager::stop() { } bool KviNotifyListManager::handleUserhost(KviIrcMessage *) { return false; } bool KviNotifyListManager::handleIsOn(KviIrcMessage *) { return false; } bool KviNotifyListManager::handleWatchReply(KviIrcMessage *) { return false; } void KviNotifyListManager::notifyOnLine(const TQString &nick,const TQString &user,const TQString &host,const TQString &szReason,bool bJoin) { if(bJoin) m_pConsole->notifyListView()->join(nick,user,host); KviWindow * out = KVI_OPTION_BOOL(KviOption_boolNotifyListChangesToActiveWindow) ? m_pConsole->activeWindow() : m_pConsole; if(KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnNotifyOnLine,out,nick))return; TQString szWho; TQString szMsg; if(!(user.isEmpty() || host.isEmpty())) KviTQString::sprintf(szWho,"\r!n\r%Q\r [%Q@\r!h\r%Q\r]",&nick,&user,&host); else KviTQString::sprintf(szWho,"\r!n\r%Q\r",&nick); KviPointerHashTable * d = g_pRegisteredUserDataBase->userDict(); KviPointerHashTableIterator it(*d); TQString szNotify; while(KviRegisteredUser * u = it.current()) { if(TQStringList::split(",",u->getProperty("notify")).findIndex(nick)!=-1) { TQString szComment=u->getProperty("comment"); if(!szComment.isEmpty()) KviTQString::sprintf(szMsg,"%Q (%Q), Group \"%Q\" is on IRC as (%Q)",&(u->name()),&szComment,&(u->group()),&szWho); else KviTQString::sprintf(szMsg,"%Q, Group \"%Q\" is on IRC as (%Q)",&(u->name()),&(u->group()),&szWho); break; } ++it; } TQString szFmt = __tr2qs("%Q is on IRC"); if(szMsg.isEmpty()) KviTQString::sprintf(szMsg,szFmt,&szWho); if((!szReason.isEmpty()) && (_OUTPUT_VERBOSE)) { szMsg += "("; szMsg += szReason; szMsg += ")"; } out->outputNoFmt(KVI_OUT_NOTIFYONLINE,szMsg); if(!(out->hasAttention())) { if(KVI_OPTION_BOOL(KviOption_boolFlashWindowOnNotifyOnLine)) out->demandAttention(); if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNotifyOnLine)) { szWho = ""; szWho += nick; szWho += ""; KviTQString::sprintf(szMsg,szFmt,&szWho); g_pApp->notifierMessage(0,KVI_OPTION_MSGTYPE(KVI_OUT_NOTIFYONLINE).pixId(),szMsg,15); } } } void KviNotifyListManager::notifyOffLine(const TQString &nick,const TQString &user,const TQString &host,const TQString &szReason) { KviWindow * out = KVI_OPTION_BOOL(KviOption_boolNotifyListChangesToActiveWindow) ? m_pConsole->activeWindow() : m_pConsole; if(!KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnNotifyOffLine,out,nick)) { TQString szWho; if(!(user.isEmpty() || host.isEmpty())) KviTQString::sprintf(szWho,"\r!n\r%Q\r [%Q@\r!h\r%Q\r]",&nick,&user,&host); else KviTQString::sprintf(szWho,"\r!n\r%Q\r",&nick); TQString szMsg; KviPointerHashTable * d = g_pRegisteredUserDataBase->userDict(); KviPointerHashTableIterator it(*d); TQString szNotify; while(KviRegisteredUser * u = it.current()) { if(TQStringList::split(",",u->getProperty("notify")).findIndex(nick)!=-1) { TQString szComment=u->getProperty("comment"); if(!szComment.isEmpty()) KviTQString::sprintf(szMsg,"%Q (%Q), Group \"%Q\" has left IRC as (%Q)",&(u->name()),&szComment,&(u->group()),&szWho); else KviTQString::sprintf(szMsg,"%Q, Group \"%Q\" has left IRC as (%Q)",&(u->name()),&(u->group()),&szWho); break; } ++it; } if(szMsg.isEmpty()) KviTQString::sprintf(szMsg,__tr2qs("%Q has left IRC"),&szWho); if((!szReason.isEmpty()) && (_OUTPUT_VERBOSE)) { szMsg += "("; szMsg += szReason; szMsg += ")"; } out->outputNoFmt(KVI_OUT_NOTIFYOFFLINE,szMsg); } m_pConsole->notifyListView()->part(nick); } // // INTELLIGENT NOTIFY LIST MANAGER: NOTIFY PROCESS: // // start() stop() // | ^ // buildRegUserDict() | // | | // m_pRegUserDict->isEmpty() ? -- YES ---------->+ // | | // NO | // | | // newNotifySession()<------- TIMER ---------------- delayedNotifySession() --------------------------------+ // | (can be stopped here) | ^ | // | | ^ | // buildNotifyList() | | YES // | | | | // m_pNotifyList->isEmpty() ? - YES ->+ | | // | | | // NO | | // | | | // newIsOnSession()<------------- TIMER -------------------- delayedIsOnSession() -- NO - m_pNotifyList->isEmpty() ? // | (can be stopped here) | | // | | | // buildIsOnList() | | // | | | // m_pIsOnList->isEmpty() ? -- YES ---------->+ | // | | // NO | // | | // sendIsOn() - - - - - - - - - - - -> handleIsOn() | // | | // (build m_pOnlineList) | // | | // m_pOnlineList->isEmpty() ? - YES ----------------------->+ // | | // NO YES // | | // delayedUserhostSession()<--------------- NO - m_pOnlineList->isEmpty() ? // | ^ // TIMER (can be stopped here) | // | | // newUserhostSession() | // | | // buildUserhostList() | // | | // m_pUserhostList->isEmpty() ? - YES --->+ // | ^^^ | // | (unexpected!)||| | // NO | // | | // sendUserhost() - - - - - - - - > handleUserhost() // KviIsOnNotifyListManager::KviIsOnNotifyListManager(KviIrcConnection * pConnection) : KviNotifyListManager(pConnection) { m_pRegUserDict = new KviPointerHashTable(17,false); // case insensitive , copy keys m_pRegUserDict->setAutoDelete(true); m_pNotifyList = new KviPointerList; m_pNotifyList->setAutoDelete(true); m_pIsOnList = new KviPointerList; m_pIsOnList->setAutoDelete(true); m_pOnlineList = new KviPointerList; m_pOnlineList->setAutoDelete(true); m_pUserhostList = new KviPointerList; m_pUserhostList->setAutoDelete(true); m_pDelayedNotifyTimer = new TQTimer(); connect(m_pDelayedNotifyTimer,TQ_SIGNAL(timeout()),this,TQ_SLOT(newNotifySession())); m_pDelayedIsOnTimer = new TQTimer(); connect(m_pDelayedIsOnTimer,TQ_SIGNAL(timeout()),this,TQ_SLOT(newIsOnSession())); m_pDelayedUserhostTimer = new TQTimer(); connect(m_pDelayedUserhostTimer,TQ_SIGNAL(timeout()),this,TQ_SLOT(newUserhostSession())); m_bRunning = false; } KviIsOnNotifyListManager::~KviIsOnNotifyListManager() { if(m_bRunning)stop(); delete m_pDelayedNotifyTimer; delete m_pDelayedIsOnTimer; delete m_pDelayedUserhostTimer; delete m_pRegUserDict; delete m_pOnlineList; delete m_pNotifyList; delete m_pIsOnList; delete m_pUserhostList; } void KviIsOnNotifyListManager::start() { if(m_bRunning)stop(); m_bRunning = true; m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName()); m_bExpectingIsOn = false; m_bExpectingUserhost = false; buildRegUserDict(); if(m_pRegUserDict->isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: No users to check for, quitting")); stop(); return; } newNotifySession(); } void KviIsOnNotifyListManager::buildRegUserDict() { m_pRegUserDict->clear(); const KviPointerHashTable * d = g_pRegisteredUserDataBase->userDict(); KviPointerHashTableIterator it(*d); while(KviRegisteredUser * u = it.current()) { TQString notify; if(u->getProperty("notify",notify)) { notify.stripWhiteSpace(); while(!notify.isEmpty()) { int idx = notify.find(' '); if(idx > 0) { TQString single = notify.left(idx); m_pRegUserDict->replace(single,new TQString(u->name())); notify.remove(0,idx+1); } else { m_pRegUserDict->replace(notify,new TQString(u->name())); notify = ""; } } } ++it; } } void KviIsOnNotifyListManager::delayedNotifySession() { unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs); if(iTimeout < 15) { // life first of all. // don't allow the user to suicide if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Timeout (%d sec) is too short, resetting to something more reasonable (15 sec)"), iTimeout); iTimeout = 15; KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs) = 15; } m_pDelayedNotifyTimer->start(iTimeout * 1000,true); } void KviIsOnNotifyListManager::newNotifySession() { buildNotifyList(); if(m_pNotifyList->isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Notify list empty, quitting")); stop(); return; } newIsOnSession(); } void KviIsOnNotifyListManager::buildNotifyList() { m_pNotifyList->clear(); KviPointerHashTableIterator it(*m_pRegUserDict); while(it.current()) { m_pNotifyList->append(new TQString(it.currentKey())); ++it; } } void KviIsOnNotifyListManager::delayedIsOnSession() { unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListIsOnDelayTimeInSecs); if(iTimeout < 5) { // life first of all. // don't allow the user to suicide if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: ISON delay (%d sec) is too short, resetting to something more reasonable (5 sec)"), iTimeout); iTimeout = 5; KVI_OPTION_UINT(KviOption_uintNotifyListIsOnDelayTimeInSecs) = 5; } m_pDelayedIsOnTimer->start(iTimeout * 1000,true); } void KviIsOnNotifyListManager::newIsOnSession() { buildIsOnList(); if(m_pIsOnList->isEmpty())delayedNotifySession(); else sendIsOn(); } void KviIsOnNotifyListManager::buildIsOnList() { m_pIsOnList->clear(); m_szIsOnString = ""; m_pNotifyList->setAutoDelete(false); while(TQString * s = m_pNotifyList->first()) { if(((m_szIsOnString.length() + s->length()) + 1) < 504) { if(!m_szIsOnString.isEmpty())m_szIsOnString.append(' '); m_szIsOnString.append(*s); m_pIsOnList->append(s); m_pNotifyList->removeFirst(); } else break; } m_pNotifyList->setAutoDelete(true); } void KviIsOnNotifyListManager::sendIsOn() { if(_OUTPUT_PARANOIC) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Checking for: %Q"),&m_szIsOnString); KviTQCString szDec = m_pConnection->encodeText(m_szIsOnString); m_pConnection->sendFmtData("ISON %s",szDec.data()); if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckRegister("@notify_ison",40); // not that reliable m_szIsOnString = ""; m_bExpectingIsOn = true; // FIXME: #warning "And if can't send ?" } bool KviIsOnNotifyListManager::handleIsOn(KviIrcMessage *msg) { if(!m_bExpectingIsOn)return false; // Check if it is our ISON // all the nicks must be on the IsOnList KviPointerList tmplist; tmplist.setAutoDelete(false); KviStr nk; const char * aux = msg->trailing(); while(*aux) { nk = ""; aux = kvi_extractToken(nk,aux,' '); if(nk.hasData()) { bool bGotIt = false; TQString dnk = m_pConnection->decodeText(nk.ptr()); for(TQString * s = m_pIsOnList->first();s && (!bGotIt);s = m_pIsOnList->next()) { if(KviTQString::equalCI(*s,dnk)) { tmplist.append(s); bGotIt = true; } } if(!bGotIt) { // ops...not my userhost! if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Hey! You've used ISON behind my back? (I might be confused now...)")); return false; } } } // Ok...looks to be my ison (still not sure at 100% , but can't do better) if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckComplete("@notify_ison"); m_bExpectingIsOn = false; m_pOnlineList->clear(); m_pIsOnList->setAutoDelete(false); // Ok...we have an IsOn reply here // The nicks in the IsOnList that are also in the reply are online , and go to the OnlineList // the remaining in the IsOnList are offline TQString * s; for(s = tmplist.first();s;s = tmplist.next()) { m_pIsOnList->removeRef(s); m_pOnlineList->append(s); } m_pIsOnList->setAutoDelete(true); // Ok...all the users that are online , are on the OnlineList // the remaining users are in the m_pIsOnList , and are no longer online // first the easy step: remove the users that have just left irc or have never been online // we're clearling the m_pIsOnList while((s = m_pIsOnList->first())) { if(m_pConsole->notifyListView()->findEntry(*s)) { // has just left IRC... make him part notifyOffLine(*s); } // else has never been here m_pIsOnList->removeFirst(); // autodelete is true } // ok... complex step now: the remaining users in the userhost list are online // if they have been online before, just remove them from the list // otherwise they must be matched for masks // and eventually inserted in the notify view later KviIrcUserDataBase * db = console()->connection()->userDataBase(); KviPointerList l; l.setAutoDelete(false); for(s = m_pOnlineList->first();s;s = m_pOnlineList->next()) { if(KviUserListEntry * ent = m_pConsole->notifyListView()->findEntry(*s)) { // the user was online from a previous notify session // might the mask have been changed ? (heh...this is tricky, maybe too much even) if(KVI_OPTION_BOOL(KviOption_boolNotifyListSendUserhostForOnlineUsers)) { // user wants to be sure about online users.... // check if he is on some channels if(ent->globalData()->nRefs() > 1) { // mmmh...we have more than one ref , so the user is at least in one query or channel // look him up on channels , if we find his entry , we can be sure that he is // still the right user KviPointerList * chlist = m_pConsole->connection()->channelList(); for(KviChannel * ch = chlist->first();ch;ch = chlist->next()) { if(KviUserListEntry * le = ch->findEntry(*s)) { l.append(s); // ok...found on a channel...we don't need an userhost to match him KviIrcMask mk(*s,le->globalData()->user(),le->globalData()->host()); if(!doMatchUser(*s,mk))return true; // critical problems = have to restart!!! break; } } } // else Only one ref...we need an userhost to be sure (don't remove from the list) } else { // user wants no userhost for online users...we "hope" that everything will go ok. l.append(s); } //l.append(s); // we will remove him from the list } else { // the user was not online! // check if we have a cached mask if(db) { if(KviIrcUserEntry * ue = db->find(*s)) { // already in the db... do we have a mask ? if(ue->hasUser() && ue->hasHost()) { // yup! we have a complete mask to match on KviIrcMask mk(*s,ue->user(),ue->host()); // lookup the user's name in the m_pRegUserDict if(!doMatchUser(*s,mk))return true; // critical problems = have to restart!!! l.append(s); // remove anyway } } } } } for(s = l.first();s;s = l.next()) { m_pOnlineList->removeRef(s); // autodelete is true } if(m_pOnlineList->isEmpty()) { if(m_pNotifyList->isEmpty())delayedNotifySession(); else delayedIsOnSession(); } else delayedUserhostSession(); return true; } // FIXME: #warning "Nickname escapes (links) in the notifylist messages!" bool KviIsOnNotifyListManager::doMatchUser(const TQString ¬ifyString,const KviIrcMask & mask) { TQString * nam = m_pRegUserDict->find(notifyString); if(nam) { // ok...find the user if(KviRegisteredUser * u = g_pRegisteredUserDataBase->findUserByName(*nam)) { // ok ... match the user if(u->matchesFixed(mask)) { // new user online if(!(m_pConsole->notifyListView()->findEntry(mask.nick()))) { notifyOnLine(mask.nick(),mask.user(),mask.host()); } // else already online , and matching...all ok } else { // not matched.... has he been online before ? if(m_pConsole->notifyListView()->findEntry(mask.nick())) { // has been online just a sec ago , but now the mask does not match // either reguserdb has changed , or the user went offline and another one got his nick // in the meantime... (ugly situation anyway) notifyOffLine(mask.nick(),mask.user(),mask.host(),__tr2qs("registration mask changed, or nickname is being used by someone else")); } else { // has never been online if(_OUTPUT_VERBOSE) 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())); } } } else { // ops... unexpected inconsistency .... reguser db modified ? m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Unexpected inconsistency, registered user DB modified? (restarting)")); stop(); start(); return false; // critical ... exit from the call stack } } else { // ops...unexpected inconsistency m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Unexpected inconsistency, expected \r!n\r%Q\r in the registered user DB"),¬ifyString); } return true; } void KviIsOnNotifyListManager::delayedUserhostSession() { unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListUserhostDelayTimeInSecs); if(iTimeout < 5) { // life first of all. // don't allow the user to suicide if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: USERHOST delay (%d sec) is too short, resetting to something more reasonable (5 sec)"), iTimeout); iTimeout = 5; KVI_OPTION_UINT(KviOption_uintNotifyListUserhostDelayTimeInSecs) = 5; } m_pDelayedUserhostTimer->start(iTimeout * 1000,true); } void KviIsOnNotifyListManager::newUserhostSession() { buildUserhostList(); if(m_pUserhostList->isEmpty()) { // this is unexpected! m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Unexpected inconsistency, userhost list is empty!")); if(m_pOnlineList->isEmpty()) { if(m_pNotifyList->isEmpty())delayedNotifySession(); else delayedIsOnSession(); } else delayedUserhostSession(); return; } sendUserhost(); } #define MAX_USERHOST_ENTRIES 5 void KviIsOnNotifyListManager::buildUserhostList() { m_szUserhostString = ""; m_pUserhostList->clear(); m_pOnlineList->setAutoDelete(false); int i = 0; TQString * s; while((s = m_pOnlineList->first()) && (i < MAX_USERHOST_ENTRIES)) { if(!m_szUserhostString.isEmpty())m_szUserhostString.append(' '); m_szUserhostString.append(*s); m_pUserhostList->append(s); m_pOnlineList->removeFirst(); i++; } m_pOnlineList->setAutoDelete(true); } void KviIsOnNotifyListManager::sendUserhost() { if(_OUTPUT_PARANOIC) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Checking userhost for: %Q"),&m_szUserhostString); KviTQCString ccc = m_pConnection->encodeText(m_szUserhostString); m_pConnection->sendFmtData("USERHOST %s",ccc.data()); if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckRegister("@notify_userhost",50); m_szUserhostString = ""; m_bExpectingUserhost = true; // FIXME: #warning "And if can't send ?" } bool KviIsOnNotifyListManager::handleUserhost(KviIrcMessage *msg) { if(!m_bExpectingUserhost)return false; // first check for consistency: all the replies must be on the USERHOST list KviPointerList tmplist; tmplist.setAutoDelete(true); KviStr nk; const char * aux = msg->trailing(); while(*aux) { nk = ""; aux = kvi_extractToken(nk,aux,' '); if(nk.hasData()) { // split it in a mask KviStr nick; KviStr user; KviStr host; int idx = nk.findFirstIdx('='); if(idx != -1) { nick = nk.left(idx); if(nick.lastCharIs('*'))nick.cutRight(1); nk.cutLeft(idx + 1); if(nk.firstCharIs('+') || nk.firstCharIs('-'))nk.cutLeft(1); idx = nk.findFirstIdx('@'); if(idx != -1) { user = nk.left(idx); nk.cutLeft(idx + 1); host = nk; } else { user = "*"; host = nk; } bool bGotIt = false; TQString szNick = m_pConnection->decodeText(nick.ptr()); TQString szUser = m_pConnection->decodeText(user.ptr()); TQString szHost = m_pConnection->decodeText(host.ptr()); for(TQString * s = m_pUserhostList->first();s && (!bGotIt);s = m_pUserhostList->next()) { if(KviTQString::equalCI(*s,szNick)) { KviIrcMask * mk = new KviIrcMask(szNick,szUser,szHost); tmplist.append(mk); bGotIt = true; m_pUserhostList->removeRef(s); } } if(!bGotIt) { // ops...not my userhost! if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Hey! You've used USERHOST behind my back? (I might be confused now...)")); return false; } } else { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Broken USERHOST reply from the server? (%s)"),nk.ptr()); } } } // Ok...looks to be my usershot (still not sure at 100% , but can't do better) if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckComplete("@notify_userhost"); m_bExpectingUserhost = false; for(KviIrcMask * mk = tmplist.first();mk;mk = tmplist.next()) { if(!doMatchUser(mk->nick(),*mk))return true; // have to restart!!! } if(!(m_pUserhostList->isEmpty())) { // ops...someone is no longer online ? while(TQString * s = m_pUserhostList->first()) { if(_OUTPUT_VERBOSE) 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); m_pUserhostList->removeFirst(); } } if(m_pOnlineList->isEmpty()) { if(m_pNotifyList->isEmpty())delayedNotifySession(); else delayedIsOnSession(); } else delayedUserhostSession(); return true; } void KviIsOnNotifyListManager::stop() { if(!m_bRunning)return; if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckAbort("@notify_userhost"); if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckAbort("@notify_ison"); m_pDelayedNotifyTimer->stop(); m_pDelayedIsOnTimer->stop(); m_pDelayedUserhostTimer->stop(); m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName()); m_pRegUserDict->clear(); m_pNotifyList->clear(); m_pIsOnList->clear(); m_pOnlineList->clear(); m_pUserhostList->clear(); m_szIsOnString = ""; m_szUserhostString = ""; m_bRunning = false; } /////////////////////////////////////////////////////////////////////////////////////////////////// // // Stupid notify list manager // /////////////////////////////////////////////////////////////////////////////////////////////////// KviStupidNotifyListManager::KviStupidNotifyListManager(KviIrcConnection * pConnection) : KviNotifyListManager(pConnection) { m_pNickList = new KviPointerList; m_pNickList->setAutoDelete(true); m_iRestartTimer = 0; } KviStupidNotifyListManager::~KviStupidNotifyListManager() { if(m_iRestartTimer) { killTimer(m_iRestartTimer); m_iRestartTimer = 0; } delete m_pNickList; } void KviStupidNotifyListManager::start() { if(m_iRestartTimer) { killTimer(m_iRestartTimer); m_iRestartTimer = 0; } if(_OUTPUT_VERBOSE) m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Starting notify list")); buildNickList(); if(m_pNickList->isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("No users in the notify list")); return; // Ok...no nicknames in the list } m_iNextNickToCheck = 0; m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName()); sendIsOn(); } void KviStupidNotifyListManager::sendIsOn() { m_szLastIsOnMsg = ""; TQString * nick = m_pNickList->at(m_iNextNickToCheck); __range_valid(nick); int i = 0; while(nick && ((nick->length() + 5 + m_szLastIsOnMsg.length()) < 510)) { KviTQString::appendFormatted(m_szLastIsOnMsg," %Q",nick); nick = m_pNickList->next(); i++; } if(_OUTPUT_PARANOIC) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Checking for:%Q"),&m_szLastIsOnMsg); KviTQCString dat = m_pConnection->encodeText(m_szLastIsOnMsg); m_pConnection->sendFmtData("ISON%s",dat.data()); if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckRegister("@notify_naive",20); m_iNextNickToCheck += i; } bool KviStupidNotifyListManager::handleIsOn(KviIrcMessage * msg) { if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckComplete("@notify_naive"); KviStr nk; const char * aux = msg->trailing(); while(*aux) { nk = ""; aux = kvi_extractToken(nk,aux,' '); if(nk.hasData()) { TQString nkd = m_pConnection->decodeText(nk.ptr()); TQString nksp = " " + nkd; m_szLastIsOnMsg.replace(nksp,"",false); if(!(m_pConsole->notifyListView()->findEntry(nkd))) { // not yet notified notifyOnLine(nkd); } } } // ok...check the users that have left irc now... TQStringList sl = TQStringList::split(' ',m_szLastIsOnMsg); for(TQStringList::Iterator it = sl.begin();it != sl.end();++it) { if(m_pConsole->notifyListView()->findEntry(*it)) { // has just left irc notifyOffLine(*it); } // else has never been here... } if(((unsigned int)m_iNextNickToCheck) >= m_pNickList->count()) { // have to restart unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs); if(iTimeout < 5) { // life first of all. // don't allow the user to suicide if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Timeout (%d sec) is too short, resetting to something more reasonable (5 sec)"), iTimeout); iTimeout = 5; KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs) = 5; } m_iRestartTimer = startTimer(iTimeout * 1000); } else sendIsOn(); return true; } void KviStupidNotifyListManager::timerEvent(TQTimerEvent *e) { if(e->timerId() == m_iRestartTimer) { killTimer(m_iRestartTimer); m_iRestartTimer = 0; m_iNextNickToCheck = 0; sendIsOn(); return; } TQObject::timerEvent(e); } void KviStupidNotifyListManager::stop() { if(m_pConnection->lagMeter()) m_pConnection->lagMeter()->lagCheckAbort("@notify_naive"); if(m_iRestartTimer) { killTimer(m_iRestartTimer); m_iRestartTimer = 0; } m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName()); // The ISON Method needs no stopping } void KviStupidNotifyListManager::buildNickList() { const KviPointerHashTable * d = g_pRegisteredUserDataBase->userDict(); KviPointerHashTableIterator it(*d); m_pNickList->clear(); while(it.current()) { TQString notify; if(it.current()->getProperty("notify",notify)) { m_pNickList->append(new TQString(notify)); } ++it; } } /////////////////////////////////////////////////////////////////////////////////////////////////// // // Watch notify list manager // /////////////////////////////////////////////////////////////////////////////////////////////////// KviWatchNotifyListManager::KviWatchNotifyListManager(KviIrcConnection * pConnection) : KviNotifyListManager(pConnection) { m_pRegUserDict = new KviPointerHashTable(17,false); m_pRegUserDict->setAutoDelete(true); } KviWatchNotifyListManager::~KviWatchNotifyListManager() { delete m_pRegUserDict; } void KviWatchNotifyListManager::buildRegUserDict() { m_pRegUserDict->clear(); const KviPointerHashTable * d = g_pRegisteredUserDataBase->userDict(); KviPointerHashTableIterator it(*d); while(KviRegisteredUser * u = it.current()) { TQString notify; if(u->getProperty("notify",notify)) { notify.stripWhiteSpace(); TQStringList sl = TQStringList::split(' ',notify); for(TQStringList::Iterator it = sl.begin();it != sl.end();++it) { m_pRegUserDict->replace(*it,new TQString(u->name())); } } ++it; } } void KviWatchNotifyListManager::start() { m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName()); buildRegUserDict(); TQString watchStr; KviPointerHashTableIterator it(*m_pRegUserDict); while(it.current()) { TQString nk = it.currentKey(); if(nk.find('*') == -1) { if((watchStr.length() + nk.length() + 2) > 501) { KviTQCString dat = m_pConnection->encodeText(watchStr); m_pConnection->sendFmtData("WATCH%s",dat.data()); if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Adding watch entries for%Q"),&watchStr); watchStr = ""; } KviTQString::appendFormatted(watchStr," +%Q",&nk); } ++it; } if(!watchStr.isEmpty()) { KviTQCString dat = m_pConnection->encodeText(watchStr); m_pConnection->sendFmtData("WATCH%s",dat.data()); if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Adding watch entries for%Q"),&watchStr); } } void KviWatchNotifyListManager::stop() { m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName()); m_pConnection->sendFmtData("WATCH clear"); m_pRegUserDict->clear(); } bool KviWatchNotifyListManager::doMatchUser(KviIrcMessage * msg,const TQString ¬ifyString,const KviIrcMask & mask) { TQString * nam = m_pRegUserDict->find(notifyString); if(nam) { // ok...find the user if(KviRegisteredUser * u = g_pRegisteredUserDataBase->findUserByName(*nam)) { // ok ... match the user if(u->matchesFixed(mask)) { // new user online if(!(m_pConsole->notifyListView()->findEntry(mask.nick()))) { notifyOnLine(mask.nick(),mask.user(),mask.host(),"watch"); } else { // else already online , and matching...all ok if(msg->numeric() == RPL_NOWON) { // This is a reply to a /watch +something (should not happen, unless the user is messing) or to /watch l (user requested) notifyOnLine(mask.nick(),mask.user(),mask.host(), __tr2qs("watch entry listing requested by user"),false); } else { // This is a RPL_LOGON....we're desynched ? notifyOnLine(mask.nick(),mask.user(),mask.host(), __tr2qs("possible watch list desync"),false); } } } else { // not matched.... has he been online before ? if(m_pConsole->notifyListView()->findEntry(mask.nick())) { // has been online just a sec ago , but now the mask does not match // prolly the reguserdb has been changed notifyOffLine(mask.nick(),mask.user(),mask.host(), __tr2qs("registration mask changed or desync with the watch service")); } else { // has never been online if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __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)"), &(mask.nick()),&(mask.user()),&(mask.host())); } } } else { // ops... unexpected inconsistency .... reguser db modified ? m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Notify list: Unexpected inconsistency, registered user DB modified? (watch: restarting)")); stop(); start(); return false; // critical ... exit from the call stack } } else { // not in our dictionary // prolly someone used /WATCH behind our back... bad boy! if(!(m_pConsole->notifyListView()->findEntry(mask.nick()))) { notifyOnLine(mask.nick(),mask.user(),mask.host(),__tr2qs("watch entry added by user")); } } return true; } // FIXME: #warning "DEDICATED WATCH LIST VERBOSITY FLAG ? (To allow the user to use /WATCH l and manual /WATCH)" bool KviWatchNotifyListManager::handleWatchReply(KviIrcMessage *msg) { // 600: RPL_LOGON // :prefix 600 :logged online // 601: RPL_LOGON // :prefix 601 :logged offline // 604: PRL_NOWON // :prefix 604 :is online // 605: PRL_NOWOFF // :prefix 605 0 :is offline // FIXME: #warning "Use the logintime in some way ?" const char * nk = msg->safeParam(1); const char * us = msg->safeParam(2); const char * ho = msg->safeParam(3); TQString dnk = m_pConnection->decodeText(nk); TQString dus = m_pConnection->decodeText(us); TQString dho = m_pConnection->decodeText(ho); if((msg->numeric() == RPL_LOGON) || (msg->numeric() == RPL_NOWON)) { KviIrcMask m(dnk,dus,dho); doMatchUser(msg,dnk,m); return true; } else if(msg->numeric() == RPL_WATCHOFF) { if(m_pConsole->notifyListView()->findEntry(dnk)) { notifyOffLine(dnk,dus,dho,__tr2qs("removed from watch list")); } else { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Stopped watching for \r!n\r%Q\r"),&dnk); } if(m_pRegUserDict->find(dnk))m_pRegUserDict->remove(dnk); // kill that return true; } else if((msg->numeric() == RPL_LOGOFF) || (msg->numeric() == RPL_NOWOFF)) { if(m_pConsole->notifyListView()->findEntry(dnk)) { notifyOffLine(dnk,dus,dho,__tr2qs("watch")); } else { if(msg->numeric() == RPL_NOWOFF) { // This is a reply to a /watch +something if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: \r!n\r%Q\r is offline (watch)"),&dnk); } else { // This is a RPL_LOGOFF for an user that has not matched the reg-mask notifyOffLine(dnk,dus,dho,__tr2qs("unmatched watch list entry")); } } return true; } return false; } #include "kvi_notifylist.moc"