1 /*************************************************************************** 2 * Copyright (C) 2005-2020 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 #pragma once 22 23 #include "common-export.h" 24 25 #include <utility> 26 27 #include <QString> 28 #include <QStringList> 29 #include <QVariantList> 30 #include <QVariantMap> 31 32 #include "expressionmatch.h" 33 #include "message.h" 34 #include "nickhighlightmatcher.h" 35 #include "syncableobject.h" 36 37 class COMMON_EXPORT HighlightRuleManager : public SyncableObject 38 { 39 Q_OBJECT 40 SYNCABLE_OBJECT 41 42 Q_PROPERTY(int highlightNick READ highlightNick WRITE setHighlightNick) 43 Q_PROPERTY(bool nicksCaseSensitive READ nicksCaseSensitive WRITE setNicksCaseSensitive) 44 45 public: 46 enum HighlightNickType 47 { 48 NoNick = 0x00, 49 CurrentNick = 0x01, 50 AllNicks = 0x02 51 }; 52 53 inline HighlightRuleManager(QObject* parent = nullptr) SyncableObject(parent)54 : SyncableObject(parent) 55 { 56 setAllowClientUpdates(true); 57 } 58 59 /** 60 * Individual highlight rule 61 */ 62 class COMMON_EXPORT HighlightRule 63 { 64 public: 65 /** 66 * Construct an empty highlight rule 67 */ 68 HighlightRule() = default; 69 70 /** 71 * Construct a highlight rule with the given parameters 72 * 73 * @param id Integer ID of the rule 74 * @param contents String representing a message contents expression to match 75 * @param isRegEx True if regular expression, otherwise false 76 * @param isCaseSensitive True if case sensitive, otherwise false 77 * @param isEnabled True if enabled, otherwise false 78 * @param isInverse True if rule is treated as highlight ignore, otherwise false 79 * @param sender String representing a message sender expression to match 80 * @param chanName String representing a channel name expression to match 81 */ HighlightRule(int id,QString contents,bool isRegEx,bool isCaseSensitive,bool isEnabled,bool isInverse,QString sender,QString chanName)82 HighlightRule( 83 int id, QString contents, bool isRegEx, bool isCaseSensitive, bool isEnabled, bool isInverse, QString sender, QString chanName) 84 : _id(id) 85 , _contents(std::move(contents)) 86 , _isRegEx(isRegEx) 87 , _isCaseSensitive(isCaseSensitive) 88 , _isEnabled(isEnabled) 89 , _isInverse(isInverse) 90 , _sender(std::move(sender)) 91 , _chanName(std::move(chanName)) 92 { 93 _cacheInvalid = true; 94 // Cache expression matches on construction 95 // 96 // This provides immediate feedback on errors when loading the rule. If profiling shows 97 // this as a performance bottleneck, this can be removed in deference to caching on 98 // first use. 99 // 100 // Inversely, if needed for validity checks, caching can be done on every update below 101 // instead of on first use. 102 determineExpressions(); 103 } 104 105 /** 106 * Gets the unique ID of this rule 107 * 108 * @return Integer ID of the rule 109 */ id()110 inline int id() const { return _id; } 111 /** 112 * Sets the ID of this rule 113 * 114 * CAUTION: IDs should be kept unique! 115 * 116 * @param id Integer ID of the rule 117 */ setId(int id)118 inline void setId(int id) { _id = id; } 119 120 /** 121 * Gets the message contents this rule matches 122 * 123 * NOTE: Use HighlightRule::contentsMatcher() for performing matches 124 * 125 * @return String representing a phrase or expression to match 126 */ contents()127 inline QString contents() const { return _contents; } 128 /** 129 * Sets the message contents this rule matches 130 * 131 * @param contents String representing a phrase or expression to match 132 */ setContents(const QString & contents)133 inline void setContents(const QString& contents) 134 { 135 _contents = contents; 136 _cacheInvalid = true; 137 } 138 139 /** 140 * Gets if this is a regular expression rule 141 * 142 * @return True if regular expression, otherwise false 143 */ isRegEx()144 inline bool isRegEx() const { return _isRegEx; } 145 /** 146 * Sets if this rule is a regular expression rule 147 * 148 * @param isRegEx True if regular expression, otherwise false 149 */ setIsRegEx(bool isRegEx)150 inline void setIsRegEx(bool isRegEx) 151 { 152 _isRegEx = isRegEx; 153 _cacheInvalid = true; 154 } 155 156 /** 157 * Gets if this rule is case sensitive 158 * 159 * @return True if case sensitive, otherwise false 160 */ isCaseSensitive()161 inline bool isCaseSensitive() const { return _isCaseSensitive; } 162 /** 163 * Sets if this rule is case sensitive 164 * 165 * @param isCaseSensitive True if case sensitive, otherwise false 166 */ setIsCaseSensitive(bool isCaseSensitive)167 inline void setIsCaseSensitive(bool isCaseSensitive) 168 { 169 _isCaseSensitive = isCaseSensitive; 170 _cacheInvalid = true; 171 } 172 173 /** 174 * Gets if this rule is enabled and active 175 * 176 * @return True if enabled, otherwise false 177 */ isEnabled()178 inline bool isEnabled() const { return _isEnabled; } 179 /** 180 * Sets if this rule is enabled and active 181 * 182 * @param isEnabled True if enabled, otherwise false 183 */ setIsEnabled(bool isEnabled)184 inline void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; } 185 186 /** 187 * Gets if this rule is a highlight ignore rule 188 * 189 * @return True if rule is treated as highlight ignore, otherwise false 190 */ isInverse()191 inline bool isInverse() const { return _isInverse; } 192 /** 193 * Sets if this rule is a highlight ignore rule 194 * 195 * @param isInverse True if rule is treated as highlight ignore, otherwise false 196 */ setIsInverse(bool isInverse)197 inline void setIsInverse(bool isInverse) { _isInverse = isInverse; } 198 199 /** 200 * Gets the message sender this rule matches 201 * 202 * NOTE: Use HighlightRule::senderMatcher() for performing matches 203 * 204 * @return String representing a phrase or expression to match 205 */ sender()206 inline QString sender() const { return _sender; } 207 /** 208 * Sets the message sender this rule matches 209 * 210 * @param sender String representing a phrase or expression to match 211 */ setSender(const QString & sender)212 inline void setSender(const QString& sender) 213 { 214 _sender = sender; 215 _cacheInvalid = true; 216 } 217 218 /** 219 * Gets the channel name this rule matches 220 * 221 * NOTE: Use HighlightRule::chanNameMatcher() for performing matches 222 * 223 * @return String representing a phrase or expression to match 224 */ chanName()225 inline QString chanName() const { return _chanName; } 226 /** 227 * Sets the channel name this rule matches 228 * 229 * @param chanName String representing a phrase or expression to match 230 */ setChanName(const QString & chanName)231 inline void setChanName(const QString& chanName) 232 { 233 _chanName = chanName; 234 _cacheInvalid = true; 235 } 236 237 /** 238 * Gets the expression matcher for the message contents, caching if needed 239 * 240 * @return Expression matcher to compare with message contents 241 */ contentsMatcher()242 inline ExpressionMatch contentsMatcher() const 243 { 244 if (_cacheInvalid) { 245 determineExpressions(); 246 } 247 return _contentsMatch; 248 } 249 250 /** 251 * Gets the expression matcher for the message sender, caching if needed 252 * 253 * @return Expression matcher to compare with message sender 254 */ senderMatcher()255 inline ExpressionMatch senderMatcher() const 256 { 257 if (_cacheInvalid) { 258 determineExpressions(); 259 } 260 return _senderMatch; 261 } 262 263 /** 264 * Gets the expression matcher for the channel name, caching if needed 265 * 266 * @return Expression matcher to compare with channel name 267 */ chanNameMatcher()268 inline ExpressionMatch chanNameMatcher() const 269 { 270 if (_cacheInvalid) { 271 determineExpressions(); 272 } 273 return _chanNameMatch; 274 } 275 276 bool operator!=(const HighlightRule& other) const; 277 278 private: 279 /** 280 * Update internal cache of expression matching if needed 281 */ 282 void determineExpressions() const; 283 284 int _id = -1; 285 QString _contents = {}; 286 bool _isRegEx = false; 287 bool _isCaseSensitive = false; 288 bool _isEnabled = true; 289 bool _isInverse = false; 290 QString _sender = {}; 291 QString _chanName = {}; 292 293 // These represent internal cache and should be safe to mutate in 'const' functions 294 // See https://stackoverflow.com/questions/3141087/what-is-meant-with-const-at-end-of-function-declaration 295 mutable bool _cacheInvalid = true; ///< If true, match cache needs redone 296 mutable ExpressionMatch _contentsMatch = {}; ///< Expression match cache for message content 297 mutable ExpressionMatch _senderMatch = {}; ///< Expression match cache for sender 298 mutable ExpressionMatch _chanNameMatch = {}; ///< Expression match cache for channel name 299 }; 300 301 using HighlightRuleList = QList<HighlightRule>; 302 303 int indexOf(int rule) const; contains(int rule)304 inline bool contains(int rule) const { return indexOf(rule) != -1; } isEmpty()305 inline bool isEmpty() const { return _highlightRuleList.isEmpty(); } count()306 inline int count() const { return _highlightRuleList.count(); } removeAt(int index)307 inline void removeAt(int index) { _highlightRuleList.removeAt(index); } clear()308 inline void clear() { _highlightRuleList.clear(); } 309 inline HighlightRule& operator[](int i) { return _highlightRuleList[i]; } 310 inline const HighlightRule& operator[](int i) const { return _highlightRuleList.at(i); } highlightRuleList()311 inline const HighlightRuleList& highlightRuleList() const { return _highlightRuleList; } 312 313 int nextId(); 314 highlightNick()315 inline int highlightNick() { return _highlightNick; } nicksCaseSensitive()316 inline bool nicksCaseSensitive() { return _nicksCaseSensitive; } 317 318 //! Check if a message matches the HighlightRule 319 /** This method checks if a message matches the users highlight rules. 320 * \param msg The Message that should be checked 321 */ 322 bool match(const Message& msg, const QString& currentNick, const QStringList& identityNicks); 323 324 public slots: 325 virtual QVariantMap initHighlightRuleList() const; 326 virtual void initSetHighlightRuleList(const QVariantMap& HighlightRuleList); 327 328 //! Request removal of an ignore rule based on the rule itself. 329 /** Use this method if you want to remove a single ignore rule 330 * and get that synced with the core immediately. 331 * \param highlightRule A valid ignore rule 332 */ requestRemoveHighlightRule(int highlightRule)333 virtual inline void requestRemoveHighlightRule(int highlightRule) { REQUEST(ARG(highlightRule)) } 334 virtual void removeHighlightRule(int highlightRule); 335 336 //! Request toggling of "isEnabled" flag of a given ignore rule. 337 /** Use this method if you want to toggle the "isEnabled" flag of a single ignore rule 338 * and get that synced with the core immediately. 339 * \param highlightRule A valid ignore rule 340 */ requestToggleHighlightRule(int highlightRule)341 virtual inline void requestToggleHighlightRule(int highlightRule) { REQUEST(ARG(highlightRule)) } 342 virtual void toggleHighlightRule(int highlightRule); 343 344 //! Request an HighlightRule to be added to the ignore list 345 /** Items added to the list with this method, get immediately synced with the core 346 * \param name The rule 347 * \param isRegEx If the rule should be interpreted as a nickname, or a regex 348 * \param isCaseSensitive If the rule should be interpreted as case-sensitive 349 * \param isEnabled If the rule is active 350 * @param chanName The channel in which the rule should apply 351 */ requestAddHighlightRule(int id,const QString & name,bool isRegEx,bool isCaseSensitive,bool isEnabled,bool isInverse,const QString & sender,const QString & chanName)352 virtual inline void requestAddHighlightRule(int id, 353 const QString& name, 354 bool isRegEx, 355 bool isCaseSensitive, 356 bool isEnabled, 357 bool isInverse, 358 const QString& sender, 359 const QString& chanName) 360 { 361 REQUEST(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isEnabled), ARG(isInverse), ARG(sender), ARG(chanName)) 362 } 363 364 virtual void addHighlightRule(int id, 365 const QString& name, 366 bool isRegEx, 367 bool isCaseSensitive, 368 bool isEnabled, 369 bool isInverse, 370 const QString& sender, 371 const QString& chanName); 372 requestSetHighlightNick(int highlightNick)373 virtual inline void requestSetHighlightNick(int highlightNick) { REQUEST(ARG(highlightNick)) } 374 setHighlightNick(int highlightNick)375 inline void setHighlightNick(int highlightNick) 376 { 377 _highlightNick = static_cast<HighlightNickType>(highlightNick); 378 // Convert from HighlightRuleManager::HighlightNickType to 379 // NickHighlightMatcher::HighlightNickType 380 _nickMatcher.setHighlightMode(static_cast<NickHighlightMatcher::HighlightNickType>(_highlightNick)); 381 } 382 requestSetNicksCaseSensitive(bool nicksCaseSensitive)383 virtual inline void requestSetNicksCaseSensitive(bool nicksCaseSensitive) { REQUEST(ARG(nicksCaseSensitive)) } 384 setNicksCaseSensitive(bool nicksCaseSensitive)385 inline void setNicksCaseSensitive(bool nicksCaseSensitive) 386 { 387 _nicksCaseSensitive = nicksCaseSensitive; 388 // Update nickname matcher, too 389 _nickMatcher.setCaseSensitive(nicksCaseSensitive); 390 } 391 392 /** 393 * Network removed from system 394 * 395 * Handles cleaning up cache from stale networks. 396 * 397 * @param id Network ID of removed network 398 */ networkRemoved(NetworkId id)399 inline void networkRemoved(NetworkId id) 400 { 401 // Clean up nickname matching cache 402 _nickMatcher.removeNetwork(id); 403 } 404 405 protected: setHighlightRuleList(const QList<HighlightRule> & HighlightRuleList)406 void setHighlightRuleList(const QList<HighlightRule>& HighlightRuleList) { _highlightRuleList = HighlightRuleList; } 407 408 bool match(const NetworkId& netId, 409 const QString& msgContents, 410 const QString& msgSender, 411 Message::Type msgType, 412 Message::Flags msgFlags, 413 const QString& bufferName, 414 const QString& currentNick, 415 const QStringList& identityNicks); 416 417 signals: 418 void ruleAdded(QString name, bool isRegEx, bool isCaseSensitive, bool isEnabled, bool isInverse, QString sender, QString chanName); 419 420 private: 421 HighlightRuleList _highlightRuleList = {}; ///< Custom highlight rule list 422 NickHighlightMatcher _nickMatcher = {}; ///< Nickname highlight matcher 423 424 /// Nickname highlighting mode 425 HighlightNickType _highlightNick = HighlightNickType::CurrentNick; 426 bool _nicksCaseSensitive = false; ///< If true, match nicknames with exact case 427 }; 428