Mercurial > irccd
annotate plugins/links/links.c @ 988:2e02fbb8b32b
irccd: load metadata from native plugins
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 10 Feb 2021 17:45:55 +0100 |
parents | 685b85367c8e |
children | daaf92c097e6 |
rev | line source |
---|---|
987 | 1 /* |
2 * links.c -- links plugin | |
3 * | |
4 * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 #include <compat.h> | |
20 | |
21 #include <assert.h> | |
22 #include <ctype.h> | |
23 #include <err.h> | |
24 #include <errno.h> | |
25 #include <regex.h> | |
26 #include <stdio.h> | |
27 #include <stdlib.h> | |
28 #include <string.h> | |
29 | |
30 #include <curl/curl.h> | |
31 | |
32 #include <irccd/irccd.h> | |
33 #include <irccd/limits.h> | |
34 #include <irccd/server.h> | |
35 #include <irccd/subst.h> | |
36 #include <irccd/util.h> | |
37 | |
38 /* | |
39 * Since most websites are now bloated, we need a very large page size to | |
40 * analyse. Use 5MB for now. | |
41 */ | |
42 #define PAGE_MAX 5242880 | |
43 | |
44 struct req { | |
45 pthread_t thr; | |
46 CURL *curl; | |
47 struct irc_server *server; | |
48 int status; | |
49 char *link; | |
50 char *chan; | |
51 char *nickname; | |
52 char *origin; | |
53 FILE *fp; | |
54 char buf[]; | |
55 }; | |
56 | |
57 enum { | |
58 TPL_INFO | |
59 }; | |
60 | |
61 static char templates[][512] = { | |
62 [TPL_INFO] = "#{nickname}, voici le lien: #{title}" | |
63 }; | |
64 | |
65 static size_t | |
66 callback(char *ptr, size_t size, size_t nmemb, struct req *req) | |
67 { | |
68 fprintf(req->fp, "%.*s", (int)(size * nmemb), ptr); | |
69 | |
70 if (feof(req->fp) || ferror(req->fp)) | |
71 return 0; | |
72 | |
73 return size * nmemb; | |
74 } | |
75 | |
76 static const char * | |
77 parse(struct req *req) | |
78 { | |
79 regex_t regex; | |
80 regmatch_t match[2]; | |
81 char *ret = NULL; | |
82 | |
83 if (regcomp(®ex, "<title>([^<]+)<\\/title>", REG_EXTENDED | REG_ICASE) != 0) | |
84 return NULL; | |
85 | |
86 if (regexec(®ex, req->buf, 2, match, 0) == 0) { | |
87 ret = &req->buf[match[1].rm_so]; | |
88 ret[match[1].rm_eo - match[1].rm_so] = '\0'; | |
89 } | |
90 | |
91 regfree(®ex); | |
92 | |
93 return ret; | |
94 } | |
95 | |
96 static const char * | |
97 fmt(const struct req *req, const char *title) | |
98 { | |
99 static char line[IRC_MESSAGE_LEN]; | |
100 struct irc_subst subst = { | |
101 .time = time(NULL), | |
102 .flags = IRC_SUBST_DATE | IRC_SUBST_KEYWORDS | IRC_SUBST_IRC_ATTRS, | |
103 .keywords = (struct irc_subst_keyword []) { | |
104 { "channel", req->chan }, | |
105 { "nickname", req->nickname }, | |
106 { "origin", req->origin }, | |
107 { "server", req->server->name }, | |
108 { "title", title } | |
109 }, | |
110 .keywordsz = 5 | |
111 }; | |
112 | |
113 irc_subst(line, sizeof (line), templates[TPL_INFO], &subst); | |
114 | |
115 return line; | |
116 } | |
117 | |
118 static void | |
119 req_finish(struct req *); | |
120 | |
121 static void | |
122 complete(struct req *req) | |
123 { | |
124 const char *title; | |
125 | |
126 if (req->status && (title = parse(req))) | |
127 irc_server_message(req->server, req->chan, fmt(req, title)); | |
128 | |
129 pthread_join(req->thr, NULL); | |
130 req_finish(req); | |
131 } | |
132 | |
133 /* | |
134 * This function is running in a separate thread. | |
135 */ | |
136 static void * | |
137 routine(struct req *req) | |
138 { | |
139 typedef void (*func_t)(void *); | |
140 | |
141 if (curl_easy_perform(req->curl) == 0) | |
142 req->status = 1; | |
143 | |
144 irc_bot_post((func_t)complete, req); | |
145 | |
146 return NULL; | |
147 } | |
148 | |
149 static void | |
150 req_finish(struct req *req) | |
151 { | |
152 assert(req); | |
153 | |
154 if (req->server) | |
155 irc_server_decref(req->server); | |
156 if (req->curl) | |
157 curl_easy_cleanup(req->curl); | |
158 if (req->fp) | |
159 fclose(req->fp); | |
160 | |
161 free(req->chan); | |
162 free(req->nickname); | |
163 free(req->origin); | |
164 free(req); | |
165 } | |
166 | |
167 static struct req * | |
168 req_new(struct irc_server *server, const char *origin, const char *channel, char *link) | |
169 { | |
170 assert(link); | |
171 | |
172 struct req *req; | |
173 struct irc_server_user user; | |
174 | |
175 if (!(req = calloc(1, sizeof (*req) + PAGE_MAX + 1))) { | |
176 free(link); | |
177 return NULL; | |
178 } | |
179 if (!(req->curl = curl_easy_init())) | |
180 goto enomem; | |
181 if (!(req->fp = fmemopen(req->buf, PAGE_MAX, "w"))) | |
182 goto enomem; | |
183 | |
184 irc_server_incref(server); | |
185 irc_server_split(origin, &user); | |
186 req->link = link; | |
187 req->server = server; | |
188 req->chan = strdup(channel); | |
189 req->nickname = strdup(user.nickname); | |
190 req->origin = strdup(origin); | |
191 | |
192 curl_easy_setopt(req->curl, CURLOPT_URL, link); | |
193 curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L); | |
194 curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, callback); | |
195 curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req); | |
196 | |
197 return req; | |
198 | |
199 enomem: | |
200 errno = ENOMEM; | |
201 req_finish(req); | |
202 | |
203 return NULL; | |
204 } | |
205 | |
206 static void | |
207 req_start(struct req *req) | |
208 { | |
209 typedef void *(*func_t)(void *); | |
210 | |
211 if (pthread_create(&req->thr, NULL, (func_t)routine, req) != 0) | |
212 req_finish(req); | |
213 } | |
214 | |
215 void | |
216 links_event(const struct irc_event *ev) | |
217 { | |
218 struct req *req; | |
219 char *loc, *link, *end; | |
220 | |
221 if (ev->type != IRC_EVENT_MESSAGE) | |
222 return; | |
223 | |
224 /* Parse link. */ | |
225 if (!(loc = strstr(ev->message.message, "http://")) && | |
226 !(loc = strstr(ev->message.message, "https://"))) | |
227 return; | |
228 | |
229 /* Compute end to allocate only what's needed. */ | |
230 for (end = loc; *end && !isspace(*end); ) | |
231 ++end; | |
232 | |
233 link = irc_util_strndup(loc, end - loc); | |
234 | |
235 /* If the function fails, link is free'd anyway. */ | |
236 if (!(req = req_new(ev->server, ev->message.origin, ev->message.channel, link))) | |
237 return; | |
238 | |
239 req_start(req); | |
240 } | |
241 | |
242 void | |
243 links_set_template(const char *key, const char *value) | |
244 { | |
245 if (strcmp(key, "info") == 0) | |
246 strlcpy(templates[TPL_INFO], value, sizeof (templates[TPL_INFO])); | |
247 } | |
248 | |
249 const char * | |
250 links_get_template(const char *key) | |
251 { | |
252 if (strcmp(key, "info") == 0) | |
253 return templates[TPL_INFO]; | |
254 | |
255 return NULL; | |
256 } | |
257 | |
258 const char ** | |
259 links_get_templates(void) | |
260 { | |
261 static const char *keys[] = { "info", NULL }; | |
262 | |
263 return keys; | |
264 } | |
988
2e02fbb8b32b
irccd: load metadata from native plugins
David Demelier <markand@malikania.fr>
parents:
987
diff
changeset
|
265 |
2e02fbb8b32b
irccd: load metadata from native plugins
David Demelier <markand@malikania.fr>
parents:
987
diff
changeset
|
266 const char *links_description = "Parse links from messages"; |
2e02fbb8b32b
irccd: load metadata from native plugins
David Demelier <markand@malikania.fr>
parents:
987
diff
changeset
|
267 const char *links_version = IRCCD_VERSION; |
2e02fbb8b32b
irccd: load metadata from native plugins
David Demelier <markand@malikania.fr>
parents:
987
diff
changeset
|
268 const char *links_license = "ISC"; |
2e02fbb8b32b
irccd: load metadata from native plugins
David Demelier <markand@malikania.fr>
parents:
987
diff
changeset
|
269 const char *links_author = "David Demelier <markand@malikania.fr>"; |