Mercurial > irccd
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(®ex, "<title>([^<]+)<\\/title>", REG_EXTENDED | REG_ICASE) != 0) + return NULL; + + if (regexec(®ex, req->buf, 2, match, 0) == 0) { + ret = &req->buf[match[1].rm_so]; + ret[match[1].rm_eo - match[1].rm_so] = '\0'; + } + + regfree(®ex); + + 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; +}