Add /ignore message filtering patterns

master
C. McEnroe 2020-03-31 14:30:42 -04:00
parent fcb6e2909f
commit 25f419465f
8 changed files with 154 additions and 12 deletions

View File

@ -12,6 +12,7 @@ OBJS += complete.o
OBJS += config.o OBJS += config.o
OBJS += edit.o OBJS += edit.o
OBJS += handle.o OBJS += handle.o
OBJS += ignore.o
OBJS += irc.o OBJS += irc.o
OBJS += log.o OBJS += log.o
OBJS += ui.o OBJS += ui.o

View File

@ -1,4 +1,4 @@
.Dd March 25, 2020 .Dd March 31, 2020
.Dt README 7 .Dt README 7
.Os "Causal Agency" .Os "Causal Agency"
. .
@ -132,6 +132,8 @@ line editing
tab complete tab complete
.It Pa url.c .It Pa url.c
URL detection URL detection
.It Pa ignore.c
message filtering
.It Pa log.c .It Pa log.c
chat logging chat logging
.It Pa config.c .It Pa config.c

View File

@ -1,4 +1,4 @@
.Dd March 30, 2020 .Dd March 31, 2020
.Dt CATGIRL 1 .Dt CATGIRL 1
.Os .Os
. .
@ -17,6 +17,7 @@
.Op Fl a Ar auth .Op Fl a Ar auth
.Op Fl c Ar cert .Op Fl c Ar cert
.Op Fl h Ar host .Op Fl h Ar host
.Op Fl i Ar patt
.Op Fl j Ar join .Op Fl j Ar join
.Op Fl k Ar priv .Op Fl k Ar priv
.Op Fl n Ar nick .Op Fl n Ar nick
@ -147,6 +148,22 @@ and write it to
Connect to Connect to
.Ar host . .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 .It Fl j Ar join , Cm join = Ar join
Join the comma-separated list of channels Join the comma-separated list of channels
.Ar join . .Ar join .
@ -309,6 +326,11 @@ Type
.Ic q .Ic q
to return to to return to
.Nm . .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 .It Ic /move Oo Ar name Oc Ar num
Move named window to number. Move named window to number.
.It Ic /open Op Ar count .It Ic /open Op Ar count
@ -320,6 +342,8 @@ Open the most recent URL from
.Ar nick .Ar nick
or matching or matching
.Ar substring . .Ar substring .
.It Ic /unignore Ar pattern
Temporarily remove a message filtering pattern.
.It Ic /window Ar name .It Ic /window Ar name
Switch to window by name. Switch to window by name.
.It Ic /window Ar num , Ic / Ns Ar num .It Ic /window Ar num , Ic / Ns Ar num

4
chat.c
View File

@ -127,7 +127,7 @@ int main(int argc, char *argv[]) {
const char *user = NULL; const char *user = NULL;
const char *real = 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[] = { const struct option LongOpts[] = {
{ "insecure", no_argument, NULL, '!' }, { "insecure", no_argument, NULL, '!' },
{ "copy", required_argument, NULL, 'C' }, { "copy", required_argument, NULL, 'C' },
@ -140,6 +140,7 @@ int main(int argc, char *argv[]) {
{ "cert", required_argument, NULL, 'c' }, { "cert", required_argument, NULL, 'c' },
{ "sasl-external", no_argument, NULL, 'e' }, { "sasl-external", no_argument, NULL, 'e' },
{ "host", required_argument, NULL, 'h' }, { "host", required_argument, NULL, 'h' },
{ "ignore", required_argument, NULL, 'i' },
{ "join", required_argument, NULL, 'j' }, { "join", required_argument, NULL, 'j' },
{ "priv", required_argument, NULL, 'k' }, { "priv", required_argument, NULL, 'k' },
{ "log", no_argument, NULL, 'l' }, { "log", no_argument, NULL, 'l' },
@ -168,6 +169,7 @@ int main(int argc, char *argv[]) {
break; case 'e': sasl = true; break; case 'e': sasl = true;
break; case 'g': genCert(optarg); break; case 'g': genCert(optarg);
break; case 'h': host = optarg; break; case 'h': host = optarg;
break; case 'i': ignoreAdd(optarg);
break; case 'j': self.join = optarg; break; case 'j': self.join = optarg;
break; case 'k': priv = optarg; break; case 'k': priv = optarg;
break; case 'l': logEnable = true; break; case 'l': logEnable = true;

11
chat.h
View File

@ -200,7 +200,7 @@ const char *commandIsNotice(uint id, const char *input);
const char *commandIsAction(uint id, const char *input); const char *commandIsAction(uint id, const char *input);
void commandCompleteAdd(void); void commandCompleteAdd(void);
enum Heat { Cold, Warm, Hot }; enum Heat { Ice, Cold, Warm, Hot };
extern struct Util uiNotifyUtil; extern struct Util uiNotifyUtil;
void uiInit(void); void uiInit(void);
void uiShow(void); void uiShow(void);
@ -261,6 +261,15 @@ void urlOpenCount(uint id, uint count);
void urlOpenMatch(uint id, const char *str); void urlOpenMatch(uint id, const char *str);
void urlCopyMatch(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; extern bool logEnable;
void logFormat(uint id, const time_t *time, const char *format, ...) void logFormat(uint id, const time_t *time, const char *format, ...)
__attribute__((format(printf, 3, 4))); __attribute__((format(printf, 3, 4)));

View File

@ -348,6 +348,35 @@ static void commandCopy(uint id, char *params) {
urlCopyMatch(id, 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) { static void commandExec(uint id, char *params) {
execID = id; execID = id;
@ -404,6 +433,7 @@ static const struct Handler {
{ "/except", commandExcept, 0 }, { "/except", commandExcept, 0 },
{ "/exec", commandExec, Multiline | Restricted }, { "/exec", commandExec, Multiline | Restricted },
{ "/help", commandHelp, 0 }, { "/help", commandHelp, 0 },
{ "/ignore", commandIgnore, 0 },
{ "/invex", commandInvex, 0 }, { "/invex", commandInvex, 0 },
{ "/invite", commandInvite, 0 }, { "/invite", commandInvite, 0 },
{ "/join", commandJoin, Restricted }, { "/join", commandJoin, Restricted },
@ -428,6 +458,7 @@ static const struct Handler {
{ "/topic", commandTopic, 0 }, { "/topic", commandTopic, 0 },
{ "/unban", commandUnban, 0 }, { "/unban", commandUnban, 0 },
{ "/unexcept", commandUnexcept, 0 }, { "/unexcept", commandUnexcept, 0 },
{ "/unignore", commandUnignore, 0 },
{ "/uninvex", commandUninvex, 0 }, { "/uninvex", commandUninvex, 0 },
{ "/voice", commandVoice, 0 }, { "/voice", commandVoice, 0 },
{ "/whois", commandWhois, 0 }, { "/whois", commandWhois, 0 },

View File

@ -305,7 +305,7 @@ static void handleJoin(struct Message *msg) {
msg->params[2] = NULL; msg->params[2] = NULL;
} }
uiFormat( uiFormat(
id, Cold, tagTime(msg), id, ignoreCheck(Cold, msg), tagTime(msg),
"\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3", "\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3",
hash(msg->user), msg->nick, hash(msg->user), msg->nick,
(msg->params[2] ? "(" : ""), (msg->params[2] ? "(" : ""),
@ -337,7 +337,7 @@ static void handlePart(struct Message *msg) {
completeRemove(id, msg->nick); completeRemove(id, msg->nick);
urlScan(id, msg->nick, msg->params[1]); urlScan(id, msg->nick, msg->params[1]);
uiFormat( uiFormat(
id, Cold, tagTime(msg), id, ignoreCheck(Cold, msg), tagTime(msg),
"\3%02d%s\3\tleaves \3%02d%s\3%s%s", "\3%02d%s\3\tleaves \3%02d%s\3%s%s",
hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0],
(msg->params[1] ? ": " : ""), (msg->params[1] ? ": " : ""),
@ -388,7 +388,7 @@ static void handleNick(struct Message *msg) {
set(&idNames[id], msg->params[0]); set(&idNames[id], msg->params[0]);
} }
uiFormat( uiFormat(
id, Cold, tagTime(msg), id, ignoreCheck(Cold, msg), tagTime(msg),
"\3%02d%s\3\tis now known as \3%02d%s\3", "\3%02d%s\3\tis now known as \3%02d%s\3",
hash(msg->user), msg->nick, hash(msg->user), msg->params[0] 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));) { for (uint id; (id = completeID(msg->nick));) {
urlScan(id, msg->nick, msg->params[0]); urlScan(id, msg->nick, msg->params[0]);
uiFormat( uiFormat(
id, Cold, tagTime(msg), id, ignoreCheck(Cold, msg), tagTime(msg),
"\3%02d%s\3\tleaves%s%s", "\3%02d%s\3\tleaves%s%s",
hash(msg->user), msg->nick, hash(msg->user), msg->nick,
(msg->params[0] ? ": " : ""), (msg->params[0] ? ": " : ""),
@ -427,7 +427,7 @@ static void handleInvite(struct Message *msg) {
require(msg, true, 2); require(msg, true, 2);
if (!strcmp(msg->params[0], self.nick)) { if (!strcmp(msg->params[0], self.nick)) {
uiFormat( uiFormat(
Network, Hot, tagTime(msg), Network, ignoreCheck(Hot, msg), tagTime(msg),
"\3%02d%s\3\tinvites you to \3%02d%s\3", "\3%02d%s\3\tinvites you to \3%02d%s\3",
hash(msg->user), msg->nick, hash(msg->params[1]), msg->params[1] 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]); logFormat(id, tagTime(msg), "-%s- %s", msg->nick, msg->params[1]);
} }
uiFormat( uiFormat(
id, Warm, tagTime(msg), id, ignoreCheck(Warm, msg), tagTime(msg),
"\3%d-%s-\3%d\t%s", "\3%d-%s-\3%d\t%s",
hash(msg->user), msg->nick, LightGray, msg->params[1] 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]); logFormat(id, tagTime(msg), "* %s %s", msg->nick, msg->params[1]);
const char *mentions = colorMentions(id, msg); const char *mentions = colorMentions(id, msg);
uiFormat( 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", "%s\35\3%d* %s\17\35\t%s%s",
(mention ? "\26" : ""), hash(msg->user), msg->nick, (mention ? "\26" : ""), hash(msg->user), msg->nick,
mentions, msg->params[1] 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]); logFormat(id, tagTime(msg), "<%s> %s", msg->nick, msg->params[1]);
const char *mentions = colorMentions(id, msg); const char *mentions = colorMentions(id, msg);
uiFormat( 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", "%s\3%d<%s>\17\t%s%s",
(mention ? "\26" : ""), hash(msg->user), msg->nick, (mention ? "\26" : ""), hash(msg->user), msg->nick,
mentions, msg->params[1] mentions, msg->params[1]

73
ignore.c 100644
View File

@ -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;
}