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