Add /ignore message filtering patterns
parent
fcb6e2909f
commit
25f419465f
1
Makefile
1
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
|
||||
|
|
4
README.7
4
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
|
||||
|
|
26
catgirl.1
26
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
|
||||
|
|
4
chat.c
4
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;
|
||||
|
|
11
chat.h
11
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)));
|
||||
|
|
31
command.c
31
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 },
|
||||
|
|
16
handle.c
16
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]
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue