about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--catgirl.19
-rw-r--r--chat.h4
-rw-r--r--command.c11
-rw-r--r--handle.c15
-rw-r--r--url.c96
6 files changed, 133 insertions, 3 deletions
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 <debug>
 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 <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 <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#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
+}