Implement sender matching for highlight rules
[quassel.git] / src / common / highlightrulemanager.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "highlightrulemanager.h"
22 #include "util.h"
23
24 #include <QtCore>
25 #include <QDebug>
26 #include <QStringList>
27
28 INIT_SYNCABLE_OBJECT(HighlightRuleManager)
29 HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager &other)
30 {
31     if (this == &other)
32         return *this;
33
34     SyncableObject::operator=(other);
35     _highlightRuleList = other._highlightRuleList;
36     _nicksCaseSensitive = other._nicksCaseSensitive;
37     _highlightNick = other._highlightNick;
38     return *this;
39 }
40
41
42 int HighlightRuleManager::indexOf(const QString &name) const
43 {
44     for (int i = 0; i < _highlightRuleList.count(); i++) {
45         if (_highlightRuleList[i].name == name)
46             return i;
47     }
48     return -1;
49 }
50
51
52 QVariantMap HighlightRuleManager::initHighlightRuleList() const
53 {
54     QVariantMap highlightRuleListMap;
55     QStringList name;
56     QVariantList isRegEx;
57     QVariantList isCaseSensitive;
58     QVariantList isActive;
59     QVariantList isInverse;
60     QStringList sender;
61     QStringList channel;
62
63     for (int i = 0; i < _highlightRuleList.count(); i++) {
64         name << _highlightRuleList[i].name;
65         isRegEx << _highlightRuleList[i].isRegEx;
66         isCaseSensitive << _highlightRuleList[i].isCaseSensitive;
67         isActive << _highlightRuleList[i].isEnabled;
68         isInverse << _highlightRuleList[i].isInverse;
69         sender << _highlightRuleList[i].sender;
70         channel << _highlightRuleList[i].chanName;
71     }
72
73     highlightRuleListMap["name"] = name;
74     highlightRuleListMap["isRegEx"] = isRegEx;
75     highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
76     highlightRuleListMap["isEnabled"] = isActive;
77     highlightRuleListMap["isInverse"] = isInverse;
78     highlightRuleListMap["sender"] = sender;
79     highlightRuleListMap["channel"] = channel;
80     highlightRuleListMap["highlightNick"] = _highlightNick;
81     highlightRuleListMap["nicksCaseSensitive"] = _nicksCaseSensitive;
82     return highlightRuleListMap;
83 }
84
85
86 void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlightRuleList)
87 {
88     QStringList name = highlightRuleList["name"].toStringList();
89     QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
90     QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
91     QVariantList isActive = highlightRuleList["isEnabled"].toList();
92     QVariantList isInverse = highlightRuleList["isInverse"].toList();
93     QStringList sender = highlightRuleList["sender"].toStringList();
94     QStringList channel = highlightRuleList["channel"].toStringList();
95
96     int count = name.count();
97     if (count != isRegEx.count() || count != isCaseSensitive.count() || count != isActive.count() ||
98         count != isInverse.count() || count != sender.count() || count != channel.count()) {
99         qWarning() << "Corrupted HighlightRuleList settings! (Count mismatch)";
100         return;
101     }
102
103     _highlightRuleList.clear();
104     for (int i = 0; i < name.count(); i++) {
105         _highlightRuleList << HighlightRule(name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
106                                             isActive[i].toBool(), isInverse[i].toBool(), sender[i], channel[i]);
107     }
108     _highlightNick = HighlightNickType(highlightRuleList["highlightNick"].toInt());
109     _nicksCaseSensitive = highlightRuleList["nicksCaseSensitive"].toBool();
110 }
111
112 void HighlightRuleManager::addHighlightRule(const QString &name, bool isRegEx, bool isCaseSensitive, bool isActive,
113                                             bool isInverse, const QString &sender, const QString &channel)
114 {
115     if (contains(name)) {
116         return;
117     }
118
119     HighlightRule newItem = HighlightRule(name, isRegEx, isCaseSensitive, isActive, isInverse, sender, channel);
120     _highlightRuleList << newItem;
121
122     SYNC(ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(sender), ARG(channel))
123 }
124
125
126 bool HighlightRuleManager::_match(const QString &msgContents, const QString &msgSender, Message::Type msgType, Message::Flags msgFlags, const QString &bufferName, const QString &currentNick, const QStringList identityNicks)
127 {
128     if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
129        return false;
130     }
131
132     bool matches = false;
133
134     for (int i = 0; i < _highlightRuleList.count(); i++) {
135         const HighlightRule &rule = _highlightRuleList.at(i);
136         if (!rule.isEnabled)
137             continue;
138
139         if (rule.chanName.size() > 0 && rule.chanName.compare(".*") != 0) {
140             if (rule.chanName.startsWith("!")) {
141                 QRegExp rx(rule.chanName.mid(1), Qt::CaseInsensitive);
142                 if (rx.exactMatch(bufferName))
143                     continue;
144             }
145             else {
146                 QRegExp rx(rule.chanName, Qt::CaseInsensitive);
147                 if (!rx.exactMatch(bufferName))
148                     continue;
149             }
150         }
151
152         QRegExp rx;
153         if (rule.isRegEx) {
154             rx = QRegExp(rule.name, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
155         } else {
156             rx = QRegExp("(^|\\W)" + QRegExp::escape(rule.name) + "(\\W|$)", rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
157         }
158         bool nameMatch = (rx.indexIn(stripFormatCodes(msgContents)) >= 0);
159
160         bool senderMatch;
161         if (rule.sender.isEmpty()) {
162             senderMatch = true;
163         } else {
164             if (rule.isRegEx) {
165                 rx = QRegExp(rule.sender, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
166             } else {
167                 rx = QRegExp(rule.sender, Qt::CaseInsensitive, QRegExp::Wildcard);
168             }
169             senderMatch = rx.exactMatch(msgSender);
170         }
171
172         if (nameMatch && senderMatch) {
173             // If an inverse rule matches, then we know that we never want to return a highlight.
174             if (rule.isInverse) {
175                 return false;
176             } else {
177                 matches = true;
178             }
179         }
180     }
181
182     if (matches)
183         return true;
184
185     if (!currentNick.isEmpty()) {
186         QStringList nickList;
187         if (_highlightNick == CurrentNick) {
188             nickList << currentNick;
189         }
190         else if (_highlightNick == AllNicks) {
191             nickList = identityNicks;
192             if (!nickList.contains(currentNick))
193                 nickList.prepend(currentNick);
194         }
195
196         for(const QString &nickname : nickList) {
197             QRegExp nickRegExp("(^|\\W)" + QRegExp::escape(nickname) + "(\\W|$)", _nicksCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
198             if (nickRegExp.indexIn(stripFormatCodes(msgContents)) >= 0) {
199                 return true;
200             }
201         }
202     }
203
204     return false;
205 }
206
207 void HighlightRuleManager::removeHighlightRule(const QString &highlightRule)
208 {
209     removeAt(indexOf(highlightRule));
210     SYNC(ARG(highlightRule))
211 }
212
213
214 void HighlightRuleManager::toggleHighlightRule(const QString &highlightRule)
215 {
216     int idx = indexOf(highlightRule);
217     if (idx == -1)
218         return;
219     _highlightRuleList[idx].isEnabled = !_highlightRuleList[idx].isEnabled;
220     SYNC(ARG(highlightRule))
221 }
222
223 bool HighlightRuleManager::match(const Message &msg, const QString &currentNick, const QStringList &identityNicks)
224 {
225     return _match(msg.contents(), msg.sender(), msg.type(), msg.flags(), msg.bufferInfo().bufferName(), currentNick, identityNicks);
226 }