summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--README.74
-rw-r--r--catgirl.126
-rw-r--r--chat.c4
-rw-r--r--chat.h11
-rw-r--r--command.c31
-rw-r--r--handle.c16
-rw-r--r--ignore.c73
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;
+}