Mercurial > irccd
diff plugins/links/links.c @ 987:685b85367c8e
plugin logger: resurrect
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 10 Feb 2021 17:21:45 +0100 |
parents | |
children | 2e02fbb8b32b |
line wrap: on
line diff
--- /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; +}