diff --git a/Makefile b/Makefile index 48aba7b..bcbb0d8 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ OBJS += edit.o OBJS += handle.o OBJS += irc.o OBJS += ui.o +OBJS += url.o dev: tags all diff --git a/catgirl.1 b/catgirl.1 index 5394d33..f489d07 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -156,6 +156,15 @@ Close the named, numbered or current window. Toggle logging in the .Sy window. +.It Ic /open Op Ar count +Open each of +.Ar count +most recent URLs. +.It Ic /open Ar nick | substring +Open the most recent URL from +.Ar nick +or matching +.Ar substring . .It Ic /window Ar name Switch to window by name. .It Ic /window Ar num , Ic / Ns Ar num diff --git a/chat.h b/chat.h index 909527e..583107a 100644 --- a/chat.h +++ b/chat.h @@ -169,6 +169,10 @@ void completeClear(size_t id); size_t completeID(const char *str); enum Color completeColor(size_t id, const char *str); +void urlScan(size_t id, const char *nick, const char *mesg); +void urlOpenCount(size_t id, size_t count); +void urlOpenMatch(size_t id, const char *str); + FILE *configOpen(const char *path, const char *mode); int getopt_config( int argc, char *const *argv, diff --git a/command.c b/command.c index eaabc9c..4100928 100644 --- a/command.c +++ b/command.c @@ -144,6 +144,16 @@ static void commandClose(size_t id, char *params) { } } +static void commandOpen(size_t id, char *params) { + if (!params) { + urlOpenCount(id, 1); + } else if (isdigit(params[0])) { + urlOpenCount(id, strtoul(params, NULL, 10)); + } else { + urlOpenMatch(id, params); + } +} + static const struct Handler { const char *cmd; Command *fn; @@ -155,6 +165,7 @@ static const struct Handler { { "/names", commandNames }, { "/nick", commandNick }, { "/notice", commandNotice }, + { "/open", commandOpen }, { "/part", commandPart }, { "/query", commandQuery }, { "/quit", commandQuit }, diff --git a/handle.c b/handle.c index 0780767..f919fcb 100644 --- a/handle.c +++ b/handle.c @@ -193,6 +193,7 @@ static void handleReplyISupport(struct Message *msg) { static void handleReplyMOTD(struct Message *msg) { require(msg, false, 2); char *line = msg->params[1]; + urlScan(Network, msg->nick, line); if (!strncmp(line, "- ", 2)) { uiFormat(Network, Cold, tagTime(msg), "\3%d-\3\t%s", Gray, &line[2]); } else { @@ -227,6 +228,7 @@ static void handlePart(struct Message *msg) { completeClear(id); } completeRemove(id, msg->nick); + urlScan(id, msg->nick, msg->params[1]); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", @@ -241,6 +243,7 @@ static void handleKick(struct Message *msg) { size_t id = idFor(msg->params[0]); bool kicked = self.nick && !strcmp(msg->params[1], self.nick); completeTouch(id, msg->nick, hash(msg->user)); + urlScan(id, msg->nick, msg->params[2]); uiFormat( id, (kicked ? Hot : Cold), tagTime(msg), "%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s", @@ -275,6 +278,7 @@ static void handleQuit(struct Message *msg) { require(msg, true, 0); size_t id; while (None != (id = completeID(msg->nick))) { + urlScan(id, msg->nick, msg->params[0]); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tleaves%s%s", @@ -333,8 +337,10 @@ static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); if (!replies.topic) return; replies.topic--; + size_t id = idFor(msg->params[1]); + urlScan(id, NULL, msg->params[2]); uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + id, Cold, tagTime(msg), "The sign in \3%02d%s\3 reads: %s", hash(msg->params[1]), msg->params[1], msg->params[2] ); @@ -342,16 +348,18 @@ static void handleReplyTopic(struct Message *msg) { static void handleTopic(struct Message *msg) { require(msg, true, 2); + size_t id = idFor(msg->params[0]); if (msg->params[1][0]) { + urlScan(id, msg->nick, msg->params[1]); uiFormat( - idFor(msg->params[0]), Warm, tagTime(msg), + id, Warm, tagTime(msg), "\3%02d%s\3\tplaces a new sign in \3%02d%s\3: %s", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], msg->params[1] ); } else { uiFormat( - idFor(msg->params[0]), Warm, tagTime(msg), + id, Warm, tagTime(msg), "\3%02d%s\3\tremoves the sign in \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] ); @@ -400,6 +408,7 @@ static void handlePrivmsg(struct Message *msg) { bool action = isAction(msg); bool mention = !mine && isMention(msg); if (!notice && !mine) completeTouch(id, msg->nick, hash(msg->user)); + urlScan(id, msg->nick, msg->params[1]); if (notice) { uiFormat( id, Warm, tagTime(msg), diff --git a/url.c b/url.c new file mode 100644 index 0000000..7790461 --- /dev/null +++ b/url.c @@ -0,0 +1,96 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +static const char *Pattern = { + "(" + "cvs|" + "ftp|" + "git|" + "gopher|" + "http|" + "https|" + "irc|" + "ircs|" + "magnet|" + "sftp|" + "ssh|" + "svn|" + "telnet|" + "vnc" + ")" + ":[^[:space:]>\"]+" +}; +static regex_t Regex; + +static void compile(void) { + static bool compiled; + if (compiled) return; + compiled = true; + int error = regcomp(&Regex, Pattern, REG_EXTENDED); + if (!error) return; + char buf[256]; + regerror(error, &Regex, buf, sizeof(buf)); + errx(EX_SOFTWARE, "regcomp: %s: %s", buf, Pattern); +} + +enum { Cap = 32 }; +static struct { + size_t ids[Cap]; + char *nicks[Cap]; + char *urls[Cap]; + size_t len; +} ring; + +static void push(size_t id, const char *nick, const char *url, size_t len) { + size_t i = ring.len++ % Cap; + free(ring.nicks[i]); + free(ring.urls[i]); + ring.ids[i] = id; + ring.nicks[i] = NULL; + if (nick) { + ring.nicks[i] = strdup(nick); + if (!ring.nicks[i]) err(EX_OSERR, "strdup"); + } + ring.urls[i] = strndup(url, len); + if (!ring.urls[i]) err(EX_OSERR, "strndup"); +} + +void urlScan(size_t id, const char *nick, const char *mesg) { + if (!mesg) return; + compile(); + regmatch_t match = {0}; + for (const char *ptr = mesg; *ptr; ptr += match.rm_eo) { + if (regexec(&Regex, ptr, 1, &match, 0)) break; + push(id, nick, &ptr[match.rm_so], match.rm_eo - match.rm_so); + } +} + +void urlOpenCount(size_t id, size_t count) { + // TODO +} + +void urlOpenMatch(size_t id, const char *str) { + // TODO +}