diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | README.7 | 4 | ||||
-rw-r--r-- | catgirl.1 | 26 | ||||
-rw-r--r-- | chat.c | 4 | ||||
-rw-r--r-- | chat.h | 11 | ||||
-rw-r--r-- | command.c | 31 | ||||
-rw-r--r-- | handle.c | 16 | ||||
-rw-r--r-- | ignore.c | 73 |
8 files changed, 154 insertions, 12 deletions
diff --git a/Makefile b/Makefile index ec838f5..4a610b7 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ OBJS += complete.o OBJS += config.o OBJS += edit.o OBJS += handle.o +OBJS += ignore.o OBJS += irc.o OBJS += log.o OBJS += ui.o diff --git a/README.7 b/README.7 index 0362661..bfc00d4 100644 --- a/README.7 +++ b/README.7 @@ -1,4 +1,4 @@ -.Dd March 25, 2020 +.Dd March 31, 2020 .Dt README 7 .Os "Causal Agency" . @@ -132,6 +132,8 @@ line editing tab complete .It Pa url.c URL detection +.It Pa ignore.c +message filtering .It Pa log.c chat logging .It Pa config.c diff --git a/catgirl.1 b/catgirl.1 index 42569a3..4571162 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -1,4 +1,4 @@ -.Dd March 30, 2020 +.Dd March 31, 2020 .Dt CATGIRL 1 .Os . @@ -17,6 +17,7 @@ .Op Fl a Ar auth .Op Fl c Ar cert .Op Fl h Ar host +.Op Fl i Ar patt .Op Fl j Ar join .Op Fl k Ar priv .Op Fl n Ar nick @@ -147,6 +148,22 @@ and write it to Connect to .Ar host . . +.It Fl i Ar pattern, Cm ignore = Ar pattern +Add a case-insensitive message filtering pattern, +which may contain +.Ql * , +.Ql \&? +and +.Ql [] +wildcards as in +.Xr sh 1 . +The format of the pattern is as follows: +.Bd -ragged -offset indent +.Ar nick Ns Op Ar !user@host +.Op Ar command +.Op Ar channel +.Ed +. .It Fl j Ar join , Cm join = Ar join Join the comma-separated list of channels .Ar join . @@ -309,6 +326,11 @@ Type .Ic q to return to .Nm . +.It Ic /ignore Op Ar pattern +List message filtering patterns +or temporarily add a pattern. +To permanently add a pattern, use +.Fl i . .It Ic /move Oo Ar name Oc Ar num Move named window to number. .It Ic /open Op Ar count @@ -320,6 +342,8 @@ Open the most recent URL from .Ar nick or matching .Ar substring . +.It Ic /unignore Ar pattern +Temporarily remove a message filtering pattern. .It Ic /window Ar name Switch to window by name. .It Ic /window Ar num , Ic / Ns Ar num diff --git a/chat.c b/chat.c index b7bf91b..8ead5da 100644 --- a/chat.c +++ b/chat.c @@ -127,7 +127,7 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!C:H:N:O:RS:a:c:eg:h:j:k:ln:p:r:s:u:vw:"; + const char *Opts = "!C:H:N:O:RS:a:c:eg:h:i:j:k:ln:p:r:s:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, { "copy", required_argument, NULL, 'C' }, @@ -140,6 +140,7 @@ int main(int argc, char *argv[]) { { "cert", required_argument, NULL, 'c' }, { "sasl-external", no_argument, NULL, 'e' }, { "host", required_argument, NULL, 'h' }, + { "ignore", required_argument, NULL, 'i' }, { "join", required_argument, NULL, 'j' }, { "priv", required_argument, NULL, 'k' }, { "log", no_argument, NULL, 'l' }, @@ -168,6 +169,7 @@ int main(int argc, char *argv[]) { break; case 'e': sasl = true; break; case 'g': genCert(optarg); break; case 'h': host = optarg; + break; case 'i': ignoreAdd(optarg); break; case 'j': self.join = optarg; break; case 'k': priv = optarg; break; case 'l': logEnable = true; diff --git a/chat.h b/chat.h index 545be03..7bcac3b 100644 --- a/chat.h +++ b/chat.h @@ -200,7 +200,7 @@ const char *commandIsNotice(uint id, const char *input); const char *commandIsAction(uint id, const char *input); void commandCompleteAdd(void); -enum Heat { Cold, Warm, Hot }; +enum Heat { Ice, Cold, Warm, Hot }; extern struct Util uiNotifyUtil; void uiInit(void); void uiShow(void); @@ -261,6 +261,15 @@ void urlOpenCount(uint id, uint count); void urlOpenMatch(uint id, const char *str); void urlCopyMatch(uint id, const char *str); +enum { IgnoreCap = 256 }; +extern struct Ignore { + size_t len; + char *patterns[IgnoreCap]; +} ignore; +const char *ignoreAdd(const char *pattern); +bool ignoreRemove(const char *pattern); +enum Heat ignoreCheck(enum Heat heat, const struct Message *msg); + extern bool logEnable; void logFormat(uint id, const time_t *time, const char *format, ...) __attribute__((format(printf, 3, 4))); diff --git a/command.c b/command.c index 8782ee6..5872bdc 100644 --- a/command.c +++ b/command.c @@ -348,6 +348,35 @@ static void commandCopy(uint id, char *params) { urlCopyMatch(id, params); } +static void commandIgnore(uint id, char *params) { + if (params) { + const char *pattern = ignoreAdd(params); + uiFormat( + id, Cold, NULL, "Ignoring \3%02d%s\3", + Brown, pattern + ); + } else { + for (size_t i = 0; i < ignore.len; ++i) { + uiFormat( + Network, Warm, NULL, "Ignoring \3%02d%s\3", + Brown, ignore.patterns[i] + ); + } + } +} + +static void commandUnignore(uint id, char *params) { + if (!params) return; + if (ignoreRemove(params)) { + uiFormat( + id, Cold, NULL, "No longer ignoring \3%02d%s\3", + Brown, params + ); + } else { + uiFormat(id, Cold, NULL, "Not ignoring \3%02d%s\3", Brown, params); + } +} + static void commandExec(uint id, char *params) { execID = id; @@ -404,6 +433,7 @@ static const struct Handler { { "/except", commandExcept, 0 }, { "/exec", commandExec, Multiline | Restricted }, { "/help", commandHelp, 0 }, + { "/ignore", commandIgnore, 0 }, { "/invex", commandInvex, 0 }, { "/invite", commandInvite, 0 }, { "/join", commandJoin, Restricted }, @@ -428,6 +458,7 @@ static const struct Handler { { "/topic", commandTopic, 0 }, { "/unban", commandUnban, 0 }, { "/unexcept", commandUnexcept, 0 }, + { "/unignore", commandUnignore, 0 }, { "/uninvex", commandUninvex, 0 }, { "/voice", commandVoice, 0 }, { "/whois", commandWhois, 0 }, diff --git a/handle.c b/handle.c index 84a2927..a98c316 100644 --- a/handle.c +++ b/handle.c @@ -305,7 +305,7 @@ static void handleJoin(struct Message *msg) { msg->params[2] = NULL; } uiFormat( - id, Cold, tagTime(msg), + id, ignoreCheck(Cold, msg), tagTime(msg), "\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3", hash(msg->user), msg->nick, (msg->params[2] ? "(" : ""), @@ -337,7 +337,7 @@ static void handlePart(struct Message *msg) { completeRemove(id, msg->nick); urlScan(id, msg->nick, msg->params[1]); uiFormat( - id, Cold, tagTime(msg), + id, ignoreCheck(Cold, msg), tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], (msg->params[1] ? ": " : ""), @@ -388,7 +388,7 @@ static void handleNick(struct Message *msg) { set(&idNames[id], msg->params[0]); } uiFormat( - id, Cold, tagTime(msg), + id, ignoreCheck(Cold, msg), tagTime(msg), "\3%02d%s\3\tis now known as \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->user), msg->params[0] ); @@ -406,7 +406,7 @@ static void handleQuit(struct Message *msg) { for (uint id; (id = completeID(msg->nick));) { urlScan(id, msg->nick, msg->params[0]); uiFormat( - id, Cold, tagTime(msg), + id, ignoreCheck(Cold, msg), tagTime(msg), "\3%02d%s\3\tleaves%s%s", hash(msg->user), msg->nick, (msg->params[0] ? ": " : ""), @@ -427,7 +427,7 @@ static void handleInvite(struct Message *msg) { require(msg, true, 2); if (!strcmp(msg->params[0], self.nick)) { uiFormat( - Network, Hot, tagTime(msg), + Network, ignoreCheck(Hot, msg), tagTime(msg), "\3%02d%s\3\tinvites you to \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->params[1]), msg->params[1] ); @@ -1103,7 +1103,7 @@ static void handlePrivmsg(struct Message *msg) { logFormat(id, tagTime(msg), "-%s- %s", msg->nick, msg->params[1]); } uiFormat( - id, Warm, tagTime(msg), + id, ignoreCheck(Warm, msg), tagTime(msg), "\3%d-%s-\3%d\t%s", hash(msg->user), msg->nick, LightGray, msg->params[1] ); @@ -1111,7 +1111,7 @@ static void handlePrivmsg(struct Message *msg) { logFormat(id, tagTime(msg), "* %s %s", msg->nick, msg->params[1]); const char *mentions = colorMentions(id, msg); uiFormat( - id, (mention || query ? Hot : Warm), tagTime(msg), + id, ignoreCheck((mention || query ? Hot : Warm), msg), tagTime(msg), "%s\35\3%d* %s\17\35\t%s%s", (mention ? "\26" : ""), hash(msg->user), msg->nick, mentions, msg->params[1] @@ -1120,7 +1120,7 @@ static void handlePrivmsg(struct Message *msg) { logFormat(id, tagTime(msg), "<%s> %s", msg->nick, msg->params[1]); const char *mentions = colorMentions(id, msg); uiFormat( - id, (mention || query ? Hot : Warm), tagTime(msg), + id, ignoreCheck((mention || query ? Hot : Warm), msg), tagTime(msg), "%s\3%d<%s>\17\t%s%s", (mention ? "\26" : ""), hash(msg->user), msg->nick, mentions, msg->params[1] diff --git a/ignore.c b/ignore.c new file mode 100644 index 0000000..5c14b7d --- /dev/null +++ b/ignore.c @@ -0,0 +1,73 @@ +/* Copyright (C) 2020 C. McEnroe <june@causal.agency> + * + * 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 3 of the License, or + * (at your option) 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, see <https://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <fnmatch.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> + +#include "chat.h" + +struct Ignore ignore; + +const char *ignoreAdd(const char *pattern) { + if (ignore.len == IgnoreCap) errx(EX_CONFIG, "ignore limit exceeded"); + uint ex = 0, sp = 0; + for (const char *ch = pattern; *ch; ++ch) { + if (*ch == '!') ex++; + if (*ch == ' ') sp++; + } + char **dest = &ignore.patterns[ignore.len++]; + if (!ex && !sp) { + asprintf(dest, "%s!*@* * *", pattern); + } else if (sp < 1) { + asprintf(dest, "%s * *", pattern); + } else if (sp < 2) { + asprintf(dest, "%s *", pattern); + } else { + *dest = strdup(pattern); + } + if (!*dest) err(EX_OSERR, "strdup"); + return *dest; +} + +bool ignoreRemove(const char *pattern) { + bool found = false; + for (size_t i = 0; i < ignore.len; ++i) { + if (strcasecmp(ignore.patterns[i], pattern)) continue; + free(ignore.patterns[i]); + ignore.patterns[i] = ignore.patterns[--ignore.len]; + found = true; + } + return found; +} + +enum Heat ignoreCheck(enum Heat heat, const struct Message *msg) { + char match[512]; + snprintf( + match, sizeof(match), "%s!%s@%s %s %s", + msg->nick, msg->user, msg->host, msg->cmd, + (msg->params[0] ? msg->params[0] : "") + ); + for (size_t i = 0; i < ignore.len; ++i) { + if (fnmatch(ignore.patterns[i], match, FNM_CASEFOLD)) continue; + return Ice; + } + return heat; +} |