From 33b381eb8763db415177f5a842c02d841b936cdb Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 14 Feb 2011 14:27:18 -0500 Subject: [PATCH] Implemented custom expression evaluation options This option allows customization of the boolean logic used to determine how conditional values are used to filter notifications for channel messages. It evaluates as a full boolean logic expression, including the use of sub-expressions. The default value of "all" will bypass this evaluation and simply require all conditions to be true. The expression consists of space-separated tokens in the following grammar: * expression = expression operator expression | "(" expression ")" | value * operator = "and" | "or" * value = "true" | "false" | condition * condition = Also implemented EVAL command for testing purposes. --- README.md | 38 +++++++++++++++++ notifo.cpp | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 05db963..90d5a7e 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,11 @@ Commands Manually trigger a notification with the given message. Useful for testing to validate credentials, etc. +* `eval ` + + Evaluate the given expression in an empty context. Useful for testing to validate that + a given expression is properly formatted and does not contain invalid tokens. + Configuration ------------- @@ -196,6 +201,39 @@ Configuration URI that will be sent with the notification to Notifo. This could be a web address or a local scheme to access a mobile application. +### Advanced + +* `channel_conditions = "all"` + + This option allows customization of the boolean logic used to determine how conditional + values are used to filter notifications for channel messages. It evaluates as a full + boolean logic expression, including the use of sub-expressions. The default value of + "all" will bypass this evaluation and simply require all conditions to be true. + + The expression consists of space-separated tokens in the following grammar: + + * `expression = expression operator expression | "(" expression ")" | value` + * `operator = "and" | "or"` + * `value = "true" | "false" | condition` + * `condition = ` + + As a simple example, to replicate the default "all" value, would be the value of + `away_only and client_count_less_than and highlight and idle and last_active and + last_notification and nick_blacklist and replied`. + + Alternately, setting a value of `true` would send a notification for *every* message, + while a value of `false` would *never* send a notification. + + For a more complicated example, the value of `client_count_less_than and highlight and + (last_active or last_notification or replied) and nick_blacklist` would send a + notification if any of the three conditions in the sub-expression are met, while still + requiring all of the conditions outside of the parentheses to also be met. + +* `query_conditions = "all"` + + This option is more or less identical to `channel_conditions`, except that it is used + to filter notifications for private messages. + Roadmap ------- diff --git a/notifo.cpp b/notifo.cpp index 3fe3ada..f94fe17 100644 --- a/notifo.cpp +++ b/notifo.cpp @@ -83,6 +83,10 @@ class CNotifoMod : public CModule defaults["username"] = ""; defaults["secret"] = ""; + // Condition strings + defaults["channel_conditions"] = "all"; + defaults["query_conditions"] = "all"; + // Notification conditions #ifdef NOTIFO_AWAY defaults["away_only"] = "no"; @@ -171,6 +175,91 @@ class CNotifoMod : public CModule AddSocket(sock); } + /** + * Evaluate a boolean expression using condition values. + * All tokens must be separated by spaces, using "and" and "or" for + * boolean operators, "(" and ")" to enclose sub-expressions, and + * condition option names to evaluate each condition. + * + * @param expression Boolean expression string + * @param context Notification context + * @param nick Sender nick + * @param message Message contents + * @return Result of boolean evaluation + */ + bool eval(const CString& expression, const CString& context=CString(""), const CNick& nick=CNick(""), const CString& message=" ") + { + CString padded = expression.Replace_n("(", " ( "); + padded.Replace(")", " ) "); + + VCString tokens; + padded.Split(" ", tokens, false); + + return eval_tokens(tokens.begin(), tokens.end(), context, nick, message); + } + +#define expr(x, y) else if (token == x) { value = oper ? value && y : value || y; } + + /** + * Evaluate a tokenized boolean expression, or sub-expression. + * + * @param pos Token vector iterator current position + * @param end Token vector iterator end position + * @param context Notification context + * @param nick Sender nick + * @param message Message contents + * @return Result of boolean expression + */ + bool eval_tokens(VCString::iterator pos, VCString::iterator end, const CString& context, const CNick& nick, const CString& message) + { + bool oper = true; + bool value = true; + + for(; pos != end; pos++) + { + CString token = pos->AsLower(); + + if (token == "(") + { + bool inner = eval_tokens(++pos, end, context, nick, message); + value = oper ? value && inner : value || inner; + } + else if (token == ")") + { + pos++; + return value; + } + else if (token == "and") + { + oper = true; + } + else if (token == "or") + { + oper = false; + } + + expr("true", true) + expr("false", false) + expr("away_only", away_only()) + expr("client_count_less_than", client_count_less_than()) + expr("highlight", highlight(message)) + expr("idle", idle()) + expr("last_active", last_active(context)) + expr("last_notification", last_notification(context)) + expr("nick_blacklist", nick_blacklist(nick)) + expr("replied", replied(context)) + + else + { + PutModule("Error: Unexpected token \"" + token + "\""); + } + } + + return value; + } + +#undef expr + protected: /** @@ -348,6 +437,13 @@ class CNotifoMod : public CModule bool notify_channel(const CNick& nick, const CChan& channel, const CString& message) { CString context = channel.GetName(); + + CString expression = options["channel_conditions"].AsLower(); + if (expression != "all") + { + return eval(expression, context, nick, message); + } + return away_only() && client_count_less_than() && highlight(message) @@ -365,9 +461,16 @@ class CNotifoMod : public CModule * @param nick Nick that sent the message * @return Notification should be sent */ - bool notify_pm(const CNick& nick) + bool notify_pm(const CNick& nick, const CString& message) { CString context = nick.GetNick(); + + CString expression = options["query_conditions"].AsLower(); + if (expression != "all") + { + return eval(expression, context, nick, message); + } + return away_only() && client_count_less_than() && idle() @@ -458,7 +561,7 @@ class CNotifoMod : public CModule */ EModRet OnPrivMsg(CNick& nick, CString& message) { - if (notify_pm(nick)) + if (notify_pm(nick, message)) { CString title = "Private Message"; CString msg = "From " + nick.GetNick(); @@ -478,7 +581,7 @@ class CNotifoMod : public CModule */ EModRet OnPrivAction(CNick& nick, CString& message) { - if (notify_pm(nick)) + if (notify_pm(nick, message)) { CString title = "Private Message"; CString msg = "* " + nick.GetNick(); @@ -597,6 +700,14 @@ class CNotifoMod : public CModule } else { + if (option == "channel_conditions" || option == "query_conditions") + { + if (value != "all") + { + eval(value); + } + } + options[option] = value; options[option].Trim(); SetNV(option, options[option]); @@ -792,6 +903,12 @@ class CNotifoMod : public CModule { PutModule("View the detailed documentation at https://github.com/jreese/znc-notifo/blob/master/README.md"); } + // EVAL command + else if (action == "eval") + { + CString value = command.Token(1, true, " "); + PutModule(eval(value) ? "true" : "false"); + } else { PutModule("Error: invalid command, try `help`");