changeset 987:685b85367c8e

plugin logger: resurrect
author David Demelier <markand@malikania.fr>
date Wed, 10 Feb 2021 17:21:45 +0100
parents 583d9f83e9f5
children 2e02fbb8b32b
files cmake/IrccdDefinePlugin.cmake extern/libcompat/src/strndup.c irccd/dl-plugin.c lib/irccd/irccd.c lib/irccd/util.c lib/irccd/util.h plugins/CMakeLists.txt plugins/links/CMakeLists.txt plugins/links/links.7 plugins/links/links.c
diffstat 10 files changed, 446 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/cmake/IrccdDefinePlugin.cmake	Wed Feb 10 13:20:39 2021 +0100
+++ b/cmake/IrccdDefinePlugin.cmake	Wed Feb 10 17:21:45 2021 +0100
@@ -18,8 +18,8 @@
 
 function(irccd_define_js_plugin)
 	set(options "")
-	set(oneValueArgs NAME MAN)
-	set(multiValueArgs SCRIPT)
+	set(oneValueArgs MAN NAME SCRIPT)
+	set(multiValueArgs "")
 
 	cmake_parse_arguments(PLG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
@@ -50,3 +50,49 @@
 		)
 	endif ()
 endfunction()
+
+function(irccd_define_c_plugin)
+	set(options "")
+	set(oneValueArgs NAME MAN)
+	set(multiValueArgs LIBRARIES SOURCES)
+
+	cmake_parse_arguments(PLG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+	if (NOT PLG_NAME)
+		message(FATAL_ERROR "Missing NAME argument")
+	elseif (NOT PLG_SOURCES)
+		message(FATAL_ERROR "Missing SOURCES argument")
+	endif ()
+
+	add_library(${PLG_NAME} MODULE ${PLG_SOURCES})
+	get_target_property(LIBIRCCD_INCLUDES libirccd INCLUDE_DIRECTORIES)
+	get_target_property(LIBCOMPAT_INCLUDES libirccd-compat INCLUDE_DIRECTORIES)
+	target_include_directories(
+		${PLG_NAME}
+		PRIVATE
+			${LIBIRCCD_INCLUDES}
+			${LIBCOMPAT_INCLUDES}
+			${OPENSSL_INCLUDE_DIR}
+	)
+	target_link_libraries(${PLG_NAME} ${PLG_LIBRARIES})
+	set_target_properties(${PLG_NAME} PROPERTIES PREFIX "")
+
+	#
+	# This is required but not enabled by default, otherwise we get
+	# undefined errors from any libirccd functions.
+	#
+	if (APPLE)
+		set_target_properties(${PLG_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
+	endif ()
+
+	if (PLG_MAN)
+		get_filename_component(basename ${PLG_MAN} NAME)
+		configure_file(${PLG_MAN} ${CMAKE_CURRENT_BINARY_DIR}/${basename})
+
+		install(
+			FILES ${CMAKE_CURRENT_BINARY_DIR}/${basename}
+			DESTINATION ${CMAKE_INSTALL_MANDIR}/man7
+			RENAME irccd-plugin-${basename}
+		)
+	endif ()
+endfunction()
--- a/extern/libcompat/src/strndup.c	Wed Feb 10 13:20:39 2021 +0100
+++ b/extern/libcompat/src/strndup.c	Wed Feb 10 17:21:45 2021 +0100
@@ -20,7 +20,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-/* Requires strnlen. */
 #include "compat.h"
 
 char *
@@ -36,7 +35,8 @@
 	if (!(ret = malloc(length + 1)))
 		return NULL;
 
+	memcpy(ret, src, length);
 	ret[length] = '\0';
 
-	return memcpy(ret, src, length);
+	return ret;
 }
--- a/irccd/dl-plugin.c	Wed Feb 10 13:20:39 2021 +0100
+++ b/irccd/dl-plugin.c	Wed Feb 10 17:21:45 2021 +0100
@@ -67,7 +67,7 @@
 {
 	static char sym[128];
 
-	snprintf(sym, sizeof (sym), "%s_%s",self->prefix, func);
+	snprintf(sym, sizeof (sym), "%s_%s", self->prefix, func);
 
 	return sym;
 }
@@ -180,6 +180,7 @@
 	struct self self;
 	struct stat st;
 
+	memset(&self, 0, sizeof (self));
 	strlcpy(self.plugin.name, name, sizeof (self.plugin.name));
 
 	/*
--- a/lib/irccd/irccd.c	Wed Feb 10 13:20:39 2021 +0100
+++ b/lib/irccd/irccd.c	Wed Feb 10 17:21:45 2021 +0100
@@ -361,7 +361,7 @@
 		if (path) {
 			if (!is_extension_valid(ldr, path))
 				continue;
-	
+
 			p = open_plugin(ldr, name, path);
 		} else {
 			/* Copy the paths to tokenize it. */
--- a/lib/irccd/util.c	Wed Feb 10 13:20:39 2021 +0100
+++ b/lib/irccd/util.c	Wed Feb 10 17:21:45 2021 +0100
@@ -95,6 +95,19 @@
 }
 
 char *
+irc_util_strndup(const char *src, size_t n)
+{
+	assert(src);
+
+	char *ret;
+
+	if (!(ret = strndup(src, n)))
+		err(1, "strndup");
+
+	return ret;
+}
+
+char *
 irc_util_basename(const char *str)
 {
 	static char ret[PATH_MAX];
--- a/lib/irccd/util.h	Wed Feb 10 13:20:39 2021 +0100
+++ b/lib/irccd/util.h	Wed Feb 10 17:21:45 2021 +0100
@@ -45,6 +45,9 @@
 irc_util_strdup(const char *);
 
 char *
+irc_util_strndup(const char *, size_t);
+
+char *
 irc_util_basename(const char *);
 
 char *
--- a/plugins/CMakeLists.txt	Wed Feb 10 13:20:39 2021 +0100
+++ b/plugins/CMakeLists.txt	Wed Feb 10 17:21:45 2021 +0100
@@ -27,3 +27,5 @@
 	add_subdirectory(roulette)
 	add_subdirectory(tictactoe)
 endif ()
+
+add_subdirectory(links)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/links/CMakeLists.txt	Wed Feb 10 17:21:45 2021 +0100
@@ -0,0 +1,28 @@
+#
+# CMakeLists.txt -- CMake build for irccd
+#
+# Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(links)
+
+find_package(CURL REQUIRED)
+
+irccd_define_c_plugin(
+	NAME links
+	MAN ${links_SOURCE_DIR}/links.7
+	SOURCES ${links_SOURCE_DIR}/links.c
+	LIBRARIES CURL::libcurl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/links/links.7	Wed Feb 10 17:21:45 2021 +0100
@@ -0,0 +1,83 @@
+.\"
+.\" Copyright (c) 2013-2020 David Demelier <markand@malikania.fr>
+.\"
+.\" Permission to use, copy, modify, and/or distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd @IRCCD_MAN_DATE@
+.Dt IRCCD-PLUGIN-LINKS 7
+.Os
+.\" NAME
+.Sh NAME
+.Nm links
+.Nd irccd links plugin
+.\" DESCRIPTION
+.Sh DESCRIPTION
+The
+.Nm
+is used to analyze links sent on channels. It will load the web page and extract
+its title on the same channel.
+.\" INSTALLATION
+.Sh INSTALLATION
+The plugin
+.Nm
+is distributed with irccd. To enable it add the following to your plugins
+section:
+.Pp
+.Bd -literal
+[plugins]
+links = ""
+.Ed
+.\" USAGE
+.Sh USAGE
+The plugin will automatically fetch web page titles on message that contains
+either http://something or https://something.
+.Pp
+Example of possible output:
+.Bd -literal -offset Ds
+markand: http://example.org
+irccd: Example Domain
+.Ed
+.\" CONFIGURATION
+.Sh CONFIGURATION
+The following options are available under the
+.Va [plugin.links]
+section:
+.Bl -tag -width 14n -offset Ds
+.It Va timeout No (int)
+Timeout in seconds before dropping a request (default: 30).
+.El
+.\" TEMPLATES
+.Sh TEMPLATES
+The
+.Nm
+plugin supports the following templates in
+.Va [templates.links]
+section:
+.Bl -tag -width 14n -offset Ds
+.It Va info
+Message to be written when title has been retrieved successfully. Keywords:
+.Em channel , nickname , origin , server , title .
+.El
+.\" BUGS
+.Sh BUGS
+As HTML pages are not always correct and not openable with a XML parser,
+.Nm
+actually searches for a <title></title> tag using a regular expression. While
+this is not ideal, it usually works in most situations.
+.Pp
+Only a few set of HTML entity characters are converted to the human readable
+format.
+.\" SEE ALSO
+.Sh SEE ALSO
+.Xr irccd 1 ,
+.Xr irccd-templates 7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/links/links.c	Wed Feb 10 17:21:45 2021 +0100
@@ -0,0 +1,264 @@
+/*
+ * links.c -- links plugin
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <compat.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+#include <irccd/irccd.h>
+#include <irccd/limits.h>
+#include <irccd/server.h>
+#include <irccd/subst.h>
+#include <irccd/util.h>
+
+/*
+ * Since most websites are now bloated, we need a very large page size to
+ * analyse. Use 5MB for now.
+ */
+#define PAGE_MAX 5242880
+
+struct req {
+	pthread_t thr;
+	CURL *curl;
+	struct irc_server *server;
+	int status;
+	char *link;
+	char *chan;
+	char *nickname;
+	char *origin;
+	FILE *fp;
+	char buf[];
+};
+
+enum {
+	TPL_INFO
+};
+
+static char templates[][512] = {
+	[TPL_INFO] = "#{nickname}, voici le lien: #{title}"
+};
+
+static size_t
+callback(char *ptr, size_t size, size_t nmemb, struct req *req)
+{
+	fprintf(req->fp, "%.*s", (int)(size * nmemb), ptr);
+
+	if (feof(req->fp) || ferror(req->fp))
+		return 0;
+
+	return size * nmemb;
+}
+
+static const char *
+parse(struct req *req)
+{
+	regex_t regex;
+	regmatch_t match[2];
+	char *ret = NULL;
+
+	if (regcomp(&regex, "<title>([^<]+)<\\/title>",	REG_EXTENDED | REG_ICASE) != 0)
+		return NULL;
+
+	if (regexec(&regex, req->buf, 2, match, 0) == 0) {
+		ret = &req->buf[match[1].rm_so];
+		ret[match[1].rm_eo - match[1].rm_so] = '\0';
+	}
+
+	regfree(&regex);
+
+	return ret;
+}
+
+static const char *
+fmt(const struct req *req, const char *title)
+{
+	static char line[IRC_MESSAGE_LEN];
+	struct irc_subst subst = {
+		.time = time(NULL),
+		.flags = IRC_SUBST_DATE | IRC_SUBST_KEYWORDS | IRC_SUBST_IRC_ATTRS,
+		.keywords = (struct irc_subst_keyword []) {
+			{ "channel",    req->chan               },
+			{ "nickname",   req->nickname           },
+			{ "origin",     req->origin             },
+			{ "server",     req->server->name       },
+			{ "title",      title                   }
+		},
+		.keywordsz = 5
+	};
+
+	irc_subst(line, sizeof (line), templates[TPL_INFO], &subst);
+
+	return line;
+}
+
+static void
+req_finish(struct req *);
+
+static void
+complete(struct req *req)
+{
+	const char *title;
+
+	if (req->status && (title = parse(req)))
+		irc_server_message(req->server, req->chan, fmt(req, title));
+
+	pthread_join(req->thr, NULL);
+	req_finish(req);
+}
+
+/*
+ * This function is running in a separate thread.
+ */
+static void *
+routine(struct req *req)
+{
+	typedef void (*func_t)(void *);
+
+	if (curl_easy_perform(req->curl) == 0)
+		req->status = 1;
+
+	irc_bot_post((func_t)complete, req);
+
+	return NULL;
+}
+
+static void
+req_finish(struct req *req)
+{
+	assert(req);
+
+	if (req->server)
+		irc_server_decref(req->server);
+	if (req->curl)
+		curl_easy_cleanup(req->curl);
+	if (req->fp)
+		fclose(req->fp);
+
+	free(req->chan);
+	free(req->nickname);
+	free(req->origin);
+	free(req);
+}
+
+static struct req *
+req_new(struct irc_server *server, const char *origin, const char *channel, char *link)
+{
+	assert(link);
+
+	struct req *req;
+	struct irc_server_user user;
+
+	if (!(req = calloc(1, sizeof (*req) + PAGE_MAX + 1))) {
+		free(link);
+		return NULL;
+	}
+	if (!(req->curl = curl_easy_init()))
+		goto enomem;
+	if (!(req->fp = fmemopen(req->buf, PAGE_MAX, "w")))
+		goto enomem;
+
+	irc_server_incref(server);
+	irc_server_split(origin, &user);
+	req->link = link;
+	req->server = server;
+	req->chan = strdup(channel);
+	req->nickname = strdup(user.nickname);
+	req->origin = strdup(origin);
+
+	curl_easy_setopt(req->curl, CURLOPT_URL, link);
+	curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L);
+	curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, callback);
+	curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req);
+
+	return req;
+
+enomem:
+	errno = ENOMEM;
+	req_finish(req);
+
+	return NULL;
+}
+
+static void
+req_start(struct req *req)
+{
+	typedef void *(*func_t)(void *);
+
+	if (pthread_create(&req->thr, NULL, (func_t)routine, req) != 0)
+		req_finish(req);
+}
+
+void
+links_event(const struct irc_event *ev)
+{
+	struct req *req;
+	char *loc, *link, *end;
+
+	if (ev->type != IRC_EVENT_MESSAGE)
+		return;
+
+	/* Parse link. */
+	if (!(loc = strstr(ev->message.message, "http://")) &&
+	    !(loc = strstr(ev->message.message, "https://")))
+	    return;
+
+	/* Compute end to allocate only what's needed. */
+	for (end = loc; *end && !isspace(*end); )
+		++end;
+
+	link = irc_util_strndup(loc, end - loc);
+
+	/* If the function fails, link is free'd anyway. */
+	if (!(req = req_new(ev->server, ev->message.origin, ev->message.channel, link)))
+		return;
+
+	req_start(req);
+}
+
+void
+links_set_template(const char *key, const char *value)
+{
+	if (strcmp(key, "info") == 0)
+		strlcpy(templates[TPL_INFO], value, sizeof (templates[TPL_INFO]));
+}
+
+const char *
+links_get_template(const char *key)
+{
+	if (strcmp(key, "info") == 0)
+		return templates[TPL_INFO];
+
+	return NULL;
+}
+
+const char **
+links_get_templates(void)
+{
+	static const char *keys[] = { "info", NULL };
+
+	return keys;
+}