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 += edit.o
OBJS += handle.o
OBJS += ignore.o
OBJS += irc.o
OBJS += log.o
OBJS += ui.o

View File

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

View File

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

4
chat.c
View File

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

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);
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)));

View File

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

View File

@ -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]

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