fix curl calls
[fillybot.git] / mod.c
blobd3d55e0c0e26005a96be08f48a7a7053c043b55a
1 #include "mod.h"
3 #include <libxml/xmlstring.h>
4 #include <libxml/HTMLparser.h>
6 #include <stdint.h>
7 #include <inttypes.h>
8 #include <sys/wait.h>
9 #include <curl/curl.h>
11 #include <magic.h>
13 #include "entities.h"
15 static const char *nick_self;
16 static unsigned ponify;
17 #define elements(x) (sizeof(x)/sizeof(*x))
19 static struct tdb_context *feed_db, *chan_db, *mail_db, *seen_db;
21 #define sstrncmp(a, b) (strncmp(a, b, sizeof(b)-1))
22 #define sstrncasecmp(a, b) (strncasecmp(a, b, sizeof(b)-1))
24 #define SPAM_CUTOFF 5
26 static int pipe_command(struct bio *b, const char *target, const char *nick, int redirect_stdout, int redirect_stderr, char *argv[])
28 int fd[2];
29 int lines = 0;
30 if (pipe2(fd, O_CLOEXEC) < 0) {
31 privmsg(b, target, "Could not create pipe: %m");
32 return -1;
34 pid_t pid = fork();
35 if (pid < 0) {
36 privmsg(b, target, "Could not fork: %m");
37 close(fd[0]);
38 close(fd[1]);
39 return -1;
40 } else if (!pid) {
41 int fdnull = open("/dev/null", O_WRONLY|O_CLOEXEC);
42 close(fd[0]);
43 if (dup3(redirect_stdout ? fd[1] : fdnull, 1, 0) < 0)
44 exit(errno);
45 if (dup3(redirect_stderr ? fd[1] : fdnull, 2, 0) < 0)
46 exit(errno);
47 exit(execv(argv[0], argv));
48 } else {
49 int loc = -1;
50 char buffer[0x100];
51 int bufptr = 0, ret;
52 close(fd[1]);
53 fcntl(fd[0], F_SETFL, O_NONBLOCK);
54 while ((ret = waitpid(pid, &loc, WNOHANG)) >= 0) {
55 while (read(fd[0], buffer+bufptr, 1) == 1) {
56 if (buffer[bufptr] != '\n' && bufptr < sizeof(buffer)-1) {
57 bufptr++;
58 continue;
59 } else if (bufptr) {
60 buffer[bufptr] = 0;
61 bufptr = 0;
62 lines++;
63 if (lines < SPAM_CUTOFF)
64 privmsg(b, nick, "%s", buffer);
65 else
66 fprintf(stderr, "%s\n", buffer);
70 if (ret) {
71 if (bufptr) {
72 buffer[bufptr] = 0;
73 if (lines < SPAM_CUTOFF)
74 privmsg(b, nick, "%s", buffer);
75 else
76 fprintf(stderr, "%s\n", buffer);
78 if (lines >= SPAM_CUTOFF)
79 privmsg(b, nick, "%i lines suppressed", lines - SPAM_CUTOFF + 1);
80 break;
83 if (ret < 0)
84 privmsg(b, target, "error on waitpid: %m");
85 else
86 ret = loc;
87 close(fd[0]);
88 return ret;
92 #include "local.c"
94 static struct command_hash commands[2048];
96 static void command_abort(struct bio *b, const char *nick, const char *host, const char *target, char *args)
98 abort();
101 static void command_coinflip(struct bio *b, const char *nick, const char *host, const char *target, char *args)
103 if (getrand() & 1)
104 action(b, target, "whirrrs and clicks excitedly at %s", nick);
105 else
106 action(b, target, "eyes %s as nothing happens", nick);
109 static void command_crash(struct bio *b, const char *nick, const char *host, const char *target, char *args)
111 *(char*)0 = 0;
114 static void squash(char *title)
116 char *start = title;
117 goto start;
118 while (*title) {
119 while (*title != '\n' && *title != '\r' && *title != '\t' && *title != ' ' && *title) {
120 *(start++) = *(title++);
122 if (*title)
123 *(start++) = ' ';
124 start:
125 while (*title == '\n' || *title == '\r' || *title == '\t' || *title == ' ')
126 title++;
128 *start = 0;
131 struct curl_download_context
133 char *data;
134 size_t len;
137 static size_t write_data(void *ptr, size_t size, size_t nmemb, void *member) {
138 struct curl_download_context *ctx = member;
139 size *= nmemb;
140 ctx->data = realloc(ctx->data, ctx->len + size);
141 memcpy(ctx->data + ctx->len, ptr, size);
142 ctx->len += size;
143 return size;
146 static const char *get_text(xmlNode *cur)
148 for (; cur; cur = cur->next) {
149 if (cur->type == XML_TEXT_NODE)
150 return cur->content;
152 return NULL;
155 static const char *get_link(xmlAttr *cur, const char *which)
157 for (; cur; cur = cur->next) {
158 if (!strcasecmp(cur->name, which))
159 return get_text(cur->children);
161 return NULL;
164 static const char *walk_feed(struct bio *b, const char *nick, const char *target, const char *url, xmlNode *root, const char *last_link)
166 const char *main_title = NULL, *main_subtitle = NULL, *main_link = NULL;
167 const char *title = NULL, *link = NULL;
168 xmlNode *cur, *entry = NULL;
169 for (cur = root->children; cur; cur = cur->next) {
170 const char *name = cur->name;
171 if (cur->type != XML_ELEMENT_NODE)
172 continue;
173 else if (!strcasecmp(name, "category"))
174 continue;
175 else if (!strcasecmp(name, "entry"))
176 entry = entry ? entry : cur;
177 else if (!strcasecmp(name, "title"))
178 main_title = get_text(cur->children);
179 else if (!strcasecmp(name, "subtitle"))
180 main_subtitle = get_text(cur->children);
181 else if (!strcasecmp(name, "link")) {
182 const char *ishtml = get_link(cur->properties, "type");
183 const char *rel = get_link(cur->properties, "rel");
184 if ((!ishtml || !strcasecmp(ishtml, "text/html")) && rel && !strcasecmp(rel, "alternate"))
185 main_link = get_link(cur->properties, "href");
188 if (!main_link || !main_title) {
189 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
190 return NULL;
192 if (!entry)
193 return NULL;
194 for (cur = entry->children; cur; cur = cur->next) {
195 const char *name = cur->name;
196 if (cur->type != XML_ELEMENT_NODE)
197 continue;
198 else if (!strcasecmp(name, "link")) {
199 const char *ishtml = get_link(cur->properties, "type");
200 const char *rel = get_link(cur->properties, "rel");
201 if ((!ishtml || !strcasecmp(ishtml, "text/html")) && rel && !strcasecmp(rel, "alternate"))
202 link = get_link(cur->properties, "href");
203 } else if (!strcasecmp(name, "title"))
204 title = get_text(cur->children);
206 if (!title)
207 title = "<no title>";
208 if (!link) {
209 privmsg(b, target, "%s: Failed to parse entry: %s %s", nick, link, title);
210 return NULL;
212 if (!last_link) {
213 privmsg(b, target, "adding blog %s \"%s\": %s", main_link, main_title, main_subtitle);
214 privmsg(b, target, "Most recent entry: %s %s", link, title);
215 } else if (strcmp(last_link, link)) {
216 int updates = 0;
217 const char *prev_title = NULL, *prev_link = NULL, *cur_title = NULL, *cur_link = NULL;
218 for (entry = entry->next; entry; entry = entry->next) {
219 if (strcasecmp(entry->name, "entry"))
220 continue;
221 prev_title = cur_title;
222 prev_link = cur_link;
223 cur_title = cur_link = NULL;
224 for (cur = entry->children; cur; cur = cur->next) {
225 const char *name = cur->name;
226 if (cur->type != XML_ELEMENT_NODE)
227 continue;
228 else if (!strcasecmp(name, "link")) {
229 const char *ishtml = get_link(cur->properties, "type");
230 const char *rel = get_link(cur->properties, "rel");
231 if ((!ishtml || !strcasecmp(ishtml, "text/html")) &&
232 rel && !strcasecmp(rel, "alternate"))
233 cur_link = get_link(cur->properties, "href");
234 } else if (!strcasecmp(name, "title"))
235 cur_title = get_text(cur->children);
237 if (!cur_title)
238 cur_title = "<no title>";
239 if (!cur_link || !strcmp(last_link, cur_link))
240 break;
241 updates++;
243 if (updates == 1)
244 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
245 else if (updates > 1)
246 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
247 privmsg(b, target, "( %s ): %s", link, title);
249 return link;
252 static const char *walk_rss(struct bio *b, const char *nick, const char *target, const char *url, xmlNode *root, const char *last_link)
254 const char *main_title = NULL, *main_link = NULL, *title = NULL, *link = NULL, *ver;
255 xmlNode *cur, *entry = NULL;
256 ver = get_link(root->properties, "version");
257 if (!ver || strcmp(ver, "2.0")) {
258 if (!ver)
259 privmsg(b, target, "%s: Could not parse rss feed", nick);
260 else
261 privmsg(b, target, "%s: Invalid rss version \"%s\"", nick, ver);
262 return NULL;
264 for (cur = root->children; cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
265 if (!cur)
266 return NULL;
267 for (cur = cur->children; cur; cur = cur->next) {
268 const char *name = cur->name;
269 if (cur->type != XML_ELEMENT_NODE)
270 continue;
271 if (!strcasecmp(name, "title"))
272 main_title = get_text(cur->children);
273 else if (!strcasecmp(name, "link"))
274 main_link = main_link ? main_link : get_text(cur->children);
275 else if (!strcasecmp(name, "item"))
276 entry = entry ? entry : cur;
278 if (!main_link || !main_title) {
279 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
280 return NULL;
282 if (!entry)
283 return NULL;
285 link = title = NULL;
286 for (cur = entry->children; cur; cur = cur->next) {
287 const char *name = cur->name;
288 if (cur->type != XML_ELEMENT_NODE)
289 continue;
290 if (!strcasecmp(name, "title"))
291 title = get_text(cur->children);
292 else if (!strcasecmp(name, "link"))
293 link = get_text(cur->children);
295 if (!title)
296 title = "<no title>";
297 if (!link) {
298 privmsg(b, target, "%s: Failed to parse entry: %s %s", nick, link, title);
299 return NULL;
301 if (!last_link) {
302 privmsg(b, target, "adding blog %s \"%s\"", main_link, main_title);
303 privmsg(b, target, "Most recent entry: %s %s", link, title);
304 } else if (strcmp(last_link, link)) {
305 int updates = 0;
306 const char *prev_title = NULL, *prev_link = NULL, *cur_title = NULL, *cur_link = NULL;
307 for (entry = entry->next; entry; entry = entry->next) {
308 if (strcasecmp(entry->name, "item"))
309 continue;
310 prev_title = cur_title;
311 prev_link = cur_link;
312 cur_title = cur_link = NULL;
313 for (cur = entry->children; cur; cur = cur->next) {
314 const char *name = cur->name;
315 if (cur->type != XML_ELEMENT_NODE)
316 continue;
317 if (!strcasecmp(name, "title"))
318 cur_title = get_text(cur->children);
319 else if (!strcasecmp(name, "link"))
320 cur_link = get_text(cur->children);
322 if (!cur_title)
323 cur_title = "<no title>";
324 if (!cur_link || !strcmp(last_link, cur_link))
325 break;
326 updates++;
328 if (updates == 1)
329 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
330 else if (updates > 1)
331 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
332 privmsg(b, target, "( %s ): %s", link, title);
334 return link;
337 // HTML is a mess, so I'm just walking the tree depth first until I find the next element..
338 static xmlNode *next_link(xmlNode *cur_node)
340 if (cur_node->children)
341 return cur_node->children;
342 while (cur_node) {
343 if (cur_node->next)
344 return cur_node->next;
345 cur_node = cur_node->parent;
347 return NULL;
350 static const char *get_atom_link(xmlNode *cur)
352 for (; cur; cur = next_link(cur)) {
353 if (cur->type != XML_ELEMENT_NODE)
354 continue;
355 if (!strcasecmp(cur->name, "link")) {
356 const char *isxml = get_link(cur->properties, "type");
357 if (isxml && !strcasecmp(isxml, "application/atom+xml"))
358 return get_link(cur->properties, "href");
361 return NULL;
364 static const char *get_rss_link(xmlNode *cur)
366 for (; cur; cur = next_link(cur)) {
367 if (cur->type != XML_ELEMENT_NODE)
368 continue;
369 if (!strcasecmp(cur->name, "link")) {
370 const char *isxml = get_link(cur->properties, "type");
371 if (isxml && !strcasecmp(isxml, "application/rss+xml"))
372 return get_link(cur->properties, "href");
375 return NULL;
378 static void do_html(struct bio *b, const char *nick, const char *target, const char *url, const char *data, unsigned len)
380 htmlDocPtr ctx = htmlReadMemory(data, len, 0, url, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
381 xmlNode *root = xmlDocGetRootElement(ctx);
382 const char *link = get_atom_link(root);
383 if (link)
384 privmsg(b, target, "%s: not a valid feed link, try atom: %s", nick, link);
385 else if ((link = get_rss_link(root)))
386 privmsg(b, target, "%s: not a valid feed link, try rss: %s", nick, link);
387 else
388 privmsg(b, target, "%s: not a valid feed link, no suggestion found", nick);
389 xmlFreeDoc(ctx);
392 static size_t get_time_from_header(void *data, size_t size, size_t size2, void *ptr)
394 char *d, *e;
395 size *= size2;
396 if (sstrncmp(data, "Last-Modified: "))
397 return size;
398 data += sizeof("Last-Modified: ")-1;
399 *(char**)ptr = d = strdup(data);
400 if ((e = strchr(d, '\r')))
401 *e = 0;
402 return size;
405 static int check_single_feed(struct bio *b, const char *target, TDB_DATA key, const char *last_modified, const char *url, const char *link, const char *nick)
407 struct curl_download_context curl_ctx = {};
408 struct curl_slist *headers = NULL;
409 char error[CURL_ERROR_SIZE];
410 int retval = -1;
411 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
412 headers = curl_slist_append(headers, "Accept: */*");
414 CURL *h = curl_easy_init();
415 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
416 curl_easy_setopt(h, CURLOPT_URL, url);
417 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
418 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
419 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
420 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
421 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
422 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
423 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
424 curl_easy_setopt(h, CURLOPT_FILETIME, 1);
425 curl_easy_setopt(h, CURLOPT_HEADERFUNCTION, get_time_from_header);
426 curl_easy_setopt(h, CURLOPT_WRITEHEADER, &last_modified);
427 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
429 if (last_modified) {
430 char *tmp;
431 asprintf(&tmp, "If-Modified-Since: %s", last_modified);
432 headers = curl_slist_append(headers, tmp);
433 free(tmp);
436 int success = curl_easy_perform(h);
437 curl_slist_free_all(headers);
438 if (success == CURLE_OK) {
439 char *mime = NULL;
440 long code;
441 curl_easy_getinfo(h, CURLINFO_CONTENT_TYPE, &mime);
442 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &code);
443 if (code == 304)
445 else if (!mime || !sstrncmp(mime, "application/xml") || !sstrncmp(mime, "text/xml")) {
446 const char *ret_link = NULL;
447 xmlDocPtr ctx = xmlReadMemory(curl_ctx.data, curl_ctx.len, 0, url, XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
448 xmlNode *root = xmlDocGetRootElement(ctx);
450 if (!strcasecmp(root->name, "feed"))
451 ret_link = walk_feed(b, nick, target, url, root, link);
452 else if (!strcasecmp(root->name, "rss"))
453 ret_link = walk_rss(b, nick, target, url, root, link);
454 else {
455 privmsg(b, target, "Unknown feed type \"%s\"", root->name);
456 goto free_ctx;
459 if (!ret_link)
460 privmsg(b, target, "Could not feed parse correctly");
461 else if (ret_link && (!link || strcmp(ret_link, link))) {
462 TDB_DATA val;
463 asprintf((char**)&val.dptr, "%s\001%s", last_modified ? last_modified : "", ret_link);
464 val.dsize = strlen(val.dptr)+1;
465 if (tdb_store(feed_db, key, val, 0) < 0)
466 privmsg(b, target, "updating returns %s", tdb_errorstr(feed_db));
467 free(val.dptr);
468 retval = 1;
470 else
471 retval = 0;
473 free_ctx:
474 xmlFreeDoc(ctx);
475 } else if (!sstrncmp(mime, "text/html") || !sstrncmp(mime, "application/xhtml+xml"))
476 do_html(b, nick, target, url, curl_ctx.data, curl_ctx.len);
477 else
478 privmsg(b, target, "unhandled content type %s", mime);
479 } else
480 privmsg(b, target, "Error %s (%u)\n", error, success);
482 free(curl_ctx.data);
483 curl_easy_cleanup(h);
484 return retval;
487 static void command_follow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
489 char *url;
490 TDB_DATA key, val;
491 int ret;
492 if (!feed_db)
493 return;
495 if (!(url = token(&args, ' ')) || (sstrncmp(url, "http://") && sstrncmp(url, "https://"))) {
496 privmsg(b, target, "%s: Invalid url", nick);
497 return;
499 if (target[0] != '#') {
500 privmsg(b, target, "%s: Can only follow on channels", nick);
501 return;
504 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, url)+1;
505 val = tdb_fetch(feed_db, key);
506 if (val.dptr) {
507 char *foo = strchr(val.dptr, '\001');
508 ret = check_single_feed(b, target, key, NULL, url, foo ? foo + 1 : NULL, nick);
509 free(val.dptr);
510 } else
511 ret = check_single_feed(b, target, key, NULL, url, NULL, nick);
512 if (!ret)
513 privmsg(b, target, "%s: Not updated", nick);
515 free(key.dptr);
518 static void channel_feed_check(struct bio *b, const char *target, int64_t now)
520 int len = strlen(target);
521 TDB_DATA chan, res;
522 if (!feed_db || !chan_db)
523 return;
524 chan.dptr = (char*)target;
525 chan.dsize = len+1;
526 res = tdb_fetch(chan_db, chan);
527 if (res.dptr && res.dsize == 8) {
528 uint64_t then = *(uint64_t*)res.dptr;
529 if (now - then <= 2000)
530 return;
532 free(res.dptr);
533 res.dptr = (unsigned char*)&now;
534 res.dsize = 8;
535 if (tdb_store(chan_db, chan, res, 0) < 0) {
536 static int complain_db;
537 if (!complain_db++)
538 privmsg(b, target, "updating database: %s", tdb_errorstr(feed_db));
539 return;
542 /* Reset the alarm on every get, we are not actually in danger of doing an infinite loop, probably */
543 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
544 TDB_DATA f = tdb_fetch(feed_db, d);
545 TDB_DATA next = tdb_nextkey(feed_db, d);
547 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
548 const char *url = (char*)d.dptr + len + 1;
549 char *sep;
550 if ((sep = strchr(f.dptr, '\001'))) {
551 *(sep++) = 0;
552 alarm(20);
553 check_single_feed(b, target, d, f.dptr, url, sep, target);
556 free(d.dptr);
557 free(f.dptr);
558 d = next;
560 alarm(20);
563 static void command_unfollow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
565 TDB_DATA key;
566 char *url;
568 if (!feed_db)
569 return;
571 if (!(url = token(&args, ' ')) || (sstrncmp(url, "http://") && sstrncmp(url, "https://"))) {
572 privmsg(b, target, "%s: Invalid url", nick);
573 return;
575 if (target[0] != '#') {
576 privmsg(b, target, "%s: Can only unfollow on channels", nick);
577 return;
579 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, url)+1;
580 if (tdb_delete(feed_db, key) < 0) {
581 if (tdb_error(feed_db) == TDB_ERR_NOEXIST)
582 privmsg(b, target, "%s: Not following %s on this channel", nick, url);
583 else
584 privmsg(b, target, "%s: Could not delete: %s", nick, tdb_errorstr(feed_db));
585 } else
586 privmsg(b, target, "%s: No longer following %s", nick, url);
587 free(key.dptr);
590 static void command_feeds(struct bio *b, const char *nick, const char *host, const char *target, char *args)
592 int len = strlen(target), found = 0;
593 if (!feed_db)
594 return;
595 if (target[0] != '#') {
596 privmsg(b, target, "%s: Only useful in channels..", nick);
597 return;
599 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
600 TDB_DATA f = tdb_fetch(feed_db, d);
601 TDB_DATA next = tdb_nextkey(feed_db, d);
603 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
604 privmsg(b, target, "%s: following %s", nick, d.dptr + len + 1);
605 found++;
607 free(d.dptr);
608 free(f.dptr);
609 d = next;
611 if (!found)
612 privmsg(b, target, "%s: not following any feed on %s", nick, target);
615 static void command_feed_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
617 if (!feed_db)
618 return;
619 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
620 TDB_DATA f = tdb_fetch(feed_db, d);
621 TDB_DATA next = tdb_nextkey(feed_db, d);
622 privmsg(b, target, "%s: %s = %s", nick, d.dptr, f.dptr);
623 free(f.dptr);
624 free(d.dptr);
625 d = next;
629 static void command_feed_set(struct bio *b, const char *nick, const char *host, const char *target, char *args)
631 if (!feed_db)
632 return;
633 TDB_DATA key, val;
634 key.dptr = token(&args, ' ');
635 char *value = token(&args, ' ');
636 if (!key.dptr || !value)
637 return;
638 key.dsize = strlen(key.dptr) + 1;
639 val.dsize = strlen(value) + 2;
640 val.dptr = malloc(val.dsize);
641 strcpy(val.dptr+1, value);
642 val.dptr[0] = '\001';
643 if (tdb_store(feed_db, key, val, 0) < 0)
644 privmsg(b, target, "%s: setting failed: %s", nick, tdb_errorstr(feed_db));
645 else
646 privmsg(b, target, "%s: burp", nick);
647 free(val.dptr);
650 static void command_feed_rem(struct bio *b, const char *nick, const char *host, const char *target, char *args)
652 if (!feed_db || !args)
653 return;
654 TDB_DATA key = { .dptr = (unsigned char*)args, .dsize = strlen(args)+1 };
655 if (tdb_delete(feed_db, key) < 0)
656 privmsg(b, target, "%s: removing failed: %s", nick, tdb_errorstr(feed_db));
657 else
658 privmsg(b, target, "%s: burp", nick);
661 static void command_feed_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
663 if (!feed_db)
664 return;
666 tdb_wipe_all(feed_db);
667 privmsg(b, target, "%s: all evidence erased", nick);
670 static void command_seen_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
672 if (!seen_db)
673 return;
675 tdb_wipe_all(seen_db);
676 privmsg(b, target, "%s: all evidence erased", nick);
679 static void command_feed_counter(struct bio *b, const char *nick, const char *host, const char *target, char *args)
681 if (!chan_db)
682 return;
683 tdb_wipe_all(chan_db);
684 privmsg(b, target, "%s: All update counters reset", nick);
687 static char *get_text_appended(xmlNode *cur)
689 for (; cur; cur = cur->next) {
690 if (cur->type != XML_TEXT_NODE)
691 continue;
692 return strdup(cur->content);
694 return NULL;
697 static char *get_title(struct bio *b, xmlNode *cur_node)
699 for (; cur_node; cur_node = next_link(cur_node)) {
700 if (cur_node->type == XML_ELEMENT_NODE && !strcasecmp(cur_node->name, "title")) {
701 if (!cur_node->children)
702 return NULL;
703 return get_text_appended(cur_node->children);
706 return NULL;
709 static void internal_link(struct bio *b, const char *nick, const char *host, const char *target, char *args, unsigned verbose)
711 CURL *h;
712 struct curl_slist *headers = NULL;
713 char error[CURL_ERROR_SIZE];
714 int success;
715 struct curl_download_context curl_ctx = {};
717 if (!args)
718 return;
720 h = curl_easy_init();
721 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
722 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
723 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
724 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
725 headers = curl_slist_append(headers, "DNT: 1");
726 headers = curl_slist_append(headers, "Connection: keep-alive");
727 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
728 curl_easy_setopt(h, CURLOPT_URL, args);
729 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
730 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
731 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
732 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
733 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 20);
734 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
735 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
736 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
737 success = curl_easy_perform(h);
738 curl_easy_cleanup(h);
739 curl_slist_free_all(headers);
740 if (success == CURLE_OK) {
741 magic_t m = magic_open(MAGIC_MIME_TYPE);
742 magic_load(m, NULL);
743 const char *mime = magic_buffer(m, curl_ctx.data, curl_ctx.len);
744 if (strstr(mime, "text/html") || strstr(mime, "application/xml") || strstr(mime, "application/xhtml+xml")) {
745 htmlDocPtr ctx = htmlReadMemory(curl_ctx.data, curl_ctx.len, 0, args, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
746 xmlNode *root_element = xmlDocGetRootElement(ctx);
747 char *title = get_title(b, root_element);
748 if (title) {
749 char *nuke;
750 squash(title);
751 decode_html_entities_utf8(title, NULL);
752 if ((nuke = strstr(title, " on SoundCloud - Create")))
753 *nuke = 0;
754 if (*title)
755 privmsg(b, target, "%s linked %s", nick, title);
756 free(title);
758 if (verbose && !title)
759 privmsg(b, target, "%s linked %s page with invalid title", nick, mime);
760 xmlFreeDoc(ctx);
761 } else if (verbose) {
762 magic_setflags(m, MAGIC_COMPRESS);
763 const char *desc = magic_buffer(m, curl_ctx.data, curl_ctx.len);
764 privmsg(b, target, "%s linked type %s", nick, desc);
766 magic_close(m);
768 if (verbose && success != CURLE_OK)
769 privmsg(b, target, "Error %s (%u)\n", error, success);
770 free(curl_ctx.data);
773 static void command_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
775 if (!args || (sstrncmp(args, "http://") && sstrncmp(args, "https://")))
776 return;
777 else
778 internal_link(b, nick, host, target, token(&args, ' '), 1);
781 static void command_hug(struct bio *b, const char *nick, const char *host, const char *target, char *args)
783 action(b, target, "gives a robotic hug to %s", args ? args : nick);
786 static void command_inspect(struct bio *b, const char *nick, const char *host, const char *target, char *args)
788 struct command_hash *c;
789 unsigned leave, crash;
790 if (!args || !*args)
791 return;
792 c = &commands[strhash(args) % elements(commands)];
793 if (!c->string || strcasecmp(c->string, args)) {
794 privmsg(b, target, "Command %s not valid", args);
795 return;
797 leave = c->left + (c->cmd == command_inspect);
798 crash = c->enter - leave;
799 if (c->enter != leave)
800 privmsg(b, target, "%s: %u successes and %u crash%s, last crashing command: %s", c->string, leave, crash, crash == 1 ? "" : "es", c->failed_command);
801 else
802 privmsg(b, target, "%s: %u time%s executed succesfully", c->string, leave, leave == 1 ? "" : "s");
805 static void command_rebuild(struct bio *b, const char *nick, const char *host, const char *target, char *args)
807 int ret;
808 char *make[] = { "/usr/bin/make", "-j4", NULL };
809 char *git_reset[] = { "/usr/bin/git", "reset", "--hard", "master", NULL };
810 ret = pipe_command(b, target, nick, 0, 1, git_reset);
811 if (ret) {
812 action(b, target, "could not rebuild");
813 return;
815 alarm(40); // sigh, swapping to death
816 ret = pipe_command(b, target, nick, 0, 1, make);
817 if (!ret)
818 kill(getpid(), SIGUSR1);
819 else if (ret > 0)
820 action(b, target, "displays an ominous %i", ret);
823 static void command_swear(struct bio *b, const char *nick, const char *host, const char *target, char *args)
825 static const char *insults[] = {
826 "featherbrain",
827 "ponyfeathers",
828 "What in the hey?",
829 "What are you, a dictionary?",
830 "TAR-DY!",
831 "[BUY SOME APPLES]",
832 "{ Your lack of bloodlust on the battlefield is proof positive that you are a soulless automaton! }",
833 "Your royal snootiness"
835 privmsg(b, target, "%s: %s", args ? args : nick, insults[getrand() % elements(insults)]);
838 static const char *perty(int64_t *t)
840 if (*t >= 14 * 24 * 3600) {
841 *t /= 7 * 24 * 3600;
842 return "weeks";
843 } else if (*t >= 48 * 3600) {
844 *t /= 24 * 3600;
845 return "days";
846 } else if (*t >= 7200) {
847 *t /= 3600;
848 return "hours";
849 } else if (*t >= 120) {
850 *t /= 60;
851 return "minutes";
853 return *t == 1 ? "second" : "seconds";
856 static void command_timeout(struct bio *b, const char *nick, const char *host, const char *target, char *args)
858 struct command_hash *c;
859 int64_t t = get_time(b, target);
860 int64_t howlong;
861 if (t < 0)
862 return;
863 char *arg = token(&args, ' ');
864 if (!arg || !args || !(howlong = atoi(args))) {
865 action(b, target, "pretends to time out");;
866 return;
868 c = &commands[strhash(arg) % elements(commands)];
869 if (c->string && !strcasecmp(c->string, arg)) {
870 c->disabled_until = t + howlong;
871 const char *str = perty(&howlong);
872 action(b, target, "disables %s for %"PRIi64" %s", arg, howlong, str);
873 } else
874 action(b, target, "clicks sadly at %s for not being able to find that command", nick);
877 static void command_mfw(struct bio *b, const char *nick, const char *host, const char *target, char *args)
879 char error[CURL_ERROR_SIZE], *new_url;
880 CURL *h = curl_easy_init();
881 struct curl_slist *headers = NULL;
882 struct curl_download_context curl_ctx = {};
883 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
884 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
885 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
886 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
887 headers = curl_slist_append(headers, "DNT: 1");
888 headers = curl_slist_append(headers, "Connection: keep-alive");
889 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
890 curl_easy_setopt(h, CURLOPT_URL, "http://mylittlefacewhen.com/random/");
891 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
892 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
893 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
894 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
895 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
896 CURLcode ret = curl_easy_perform(h);
897 if (ret == CURLE_OK && curl_easy_getinfo(h, CURLINFO_REDIRECT_URL, &new_url) == CURLE_OK)
898 privmsg(b, target, "%s: %s", nick, new_url);
899 curl_slist_free_all(headers);
900 curl_easy_cleanup(h);
901 if (ret != CURLE_OK)
902 privmsg(b, target, "%s: You have no face", nick);
905 static TDB_DATA get_mail_key(const char *nick)
907 TDB_DATA d;
908 int i;
909 d.dsize = strlen(nick)+1;
910 d.dptr = malloc(d.dsize);
911 for (i = 0; i < d.dsize - 1; ++i)
912 d.dptr[i] = tolower(nick[i]);
913 d.dptr[i] = 0;
914 return d;
917 static void command_mail(struct bio *b, const char *nick, const char *host, const char *target, char *args)
919 char *victim;
920 size_t len;
921 if (!mail_db)
922 return;
923 TDB_DATA key, val;
924 victim = token(&args, ' ');
925 if (!victim || !args || victim[0] == '#' || strchr(victim, '@') || strchr(victim, '.')) {
926 privmsg(b, target, "%s: Usage: !mail <nick> <message>", nick);
927 return;
929 if (!strcasecmp(victim, nick_self)) {
930 action(b, target, "whirrs and clicks excitedly at the mail she received from %s", nick);
931 return;
934 key = get_mail_key(victim);
935 val = tdb_fetch(mail_db, key);
936 if (!val.dptr)
937 val.dsize = 0;
938 else {
939 unsigned char *cur;
940 int letters = 0;
941 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
942 letters++;
943 if (letters >= 4) {
944 action(b, target, "looks sadly at %s as she cannot hold any more mail to %s", nick, victim);
945 goto out;
948 len = strlen(nick) + strlen(args)+3;
949 val.dptr = realloc(val.dptr, val.dsize + len);
950 sprintf(val.dptr + val.dsize, "%s: %s", nick, args);
951 val.dsize += len;
952 if (tdb_store(mail_db, key, val, 0) < 0)
953 privmsg(b, target, "%s: updating mail returns %s", nick, tdb_errorstr(mail_db));
954 else
955 action(b, target, "whirrs and clicks at %s as she stores the mail for %s", nick, victim);
956 out:
957 free(val.dptr);
958 free(key.dptr);
961 static void command_deliver(struct bio *b, const char *nick, const char *target)
963 TDB_DATA key, val;
964 unsigned char *cur;
965 if (!mail_db)
966 return;
967 key = get_mail_key(nick);
968 val = tdb_fetch(mail_db, key);
969 if (!val.dptr) {
970 free(key.dptr);
971 return;
973 if (strcasecmp(key.dptr, nick_self)) {
974 privmsg(b, target, "%s: You've got mail!", nick);
975 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
976 privmsg(b, target, "%s", cur);
978 free(val.dptr);
979 tdb_delete(mail_db, key);
980 free(key.dptr);
983 static void update_seen(struct bio *b, char *doingwhat, const char *nick)
985 TDB_DATA key = get_mail_key(nick);
986 TDB_DATA val = { .dptr = doingwhat, .dsize = strlen(doingwhat)+1 };
987 if (seen_db)
988 tdb_store(seen_db, key, val, 0);
989 free(key.dptr);
992 static void command_seen(struct bio *b, const char *nick, const char *host, const char *target, char *args)
994 char *arg = token(&args, ' '), *x;
995 int64_t now = get_time(b, target);
996 TDB_DATA key, val;
997 if (now < 0)
998 return;
999 if (!seen_db || !arg) {
1000 privmsg(b, target, "%s: { Error... }", nick);
1001 return;
1003 if (!strcasecmp(arg, nick_self)) {
1004 action(b, target, "whirrs and clicks at %s", nick);
1005 return;
1007 key = get_mail_key(arg);
1008 val = tdb_fetch(seen_db, key);
1009 if (val.dptr && (x = strchr(val.dptr, ','))) {
1010 int64_t delta;
1011 const char *str;
1012 *(x++) = 0;
1013 delta = now - atoll(val.dptr);
1014 str = perty(&delta);
1015 if (delta < 0)
1016 privmsg(b, target, "%s was last seen in the future %s", arg, x);
1017 else
1018 privmsg(b, target, "%s was last seen %"PRIi64" %s ago %s", arg, delta, str, x);
1019 } else
1020 action(b, target, "cannot find any evidence that %s exists", arg);
1023 static void channel_msg(struct bio *b, const char *nick, const char *host,
1024 const char *chan, const char *msg, int64_t t)
1026 char *cur = NULL, *next;
1028 if (!msg || !strcmp(nick, "`Celestia`") || !strcmp(nick, "derpy") || !strcmp(nick, "`Luna`") || !sstrncmp(nick, "GitHub") || !sstrncmp(nick, "CIA-"))
1029 return;
1031 if (!sstrncasecmp(msg, "\001ACTION ")) {
1032 msg += sizeof("\001ACTION ")-1;
1033 asprintf(&cur, "%"PRIi64 ",in %s: * %s %s", t, chan, nick, msg);
1034 } else
1035 asprintf(&cur, "%"PRIi64 ",in %s: <%s> %s", t, chan, nick, msg);
1036 if (t > 0)
1037 update_seen(b, cur, nick);
1038 free(cur);
1040 next = (char*)msg;
1041 while (next && (cur = token(&next, ' '))) {
1042 if (!strcasecmp(cur, ">mfw") || !strcasecmp(cur, "mfw")) {
1043 if (!strcasecmp(chan, "#brony") || !ponify)
1044 continue;
1045 if (t < 0 || t > commands[strhash("mfw") % elements(commands)].disabled_until)
1046 command_mfw(b, nick, host, chan, NULL);
1047 break;
1048 } else if (!sstrncasecmp(cur, "http://") || !sstrncasecmp(cur, "https://")) {
1049 char *part;
1050 if (!sstrncasecmp(chan, "#brony") && !sstrncasecmp(cur, "http://www.youtube.com/watch?v="))
1051 return;
1052 if (t >= 0 && t < commands[strhash("get") % elements(commands)].disabled_until)
1053 return;
1055 else if (strcasestr(cur, "youtube.com/user") && (part = strstr(cur, "#p/"))) {
1056 char *foo;
1057 part = strrchr(part, '/') + 1;
1058 asprintf(&foo, "http://youtube.com/watch?v=%s", part);
1059 if (foo)
1060 internal_link(b, nick, host, chan, foo, 0);
1061 free(foo);
1062 return;
1063 } else if (strcasestr(cur, "twitter.com/") || strcasestr(cur, "mlfw.info") || strcasestr(cur, "mylittlefacewhen.com"))
1064 return;
1065 internal_link(b, nick, host, chan, cur, 0);
1066 break;
1071 static struct command_hash unhashed[] = {
1072 { "1", command_coinflip },
1074 { "get", command_get },
1075 { "hug", command_hug },
1076 { "mfw", command_mfw },
1077 { "swear", command_swear },
1078 { "mail", command_mail },
1079 { "seen", command_seen },
1081 { "rebuild", command_rebuild, 1 },
1082 { "abort", command_abort, 1 },
1083 { "crash", command_crash, 1 },
1084 { "inspect", command_inspect, 1 },
1085 { "timeout", command_timeout, 1 },
1087 { "follow", command_follow },
1088 { "unfollow", command_unfollow },
1089 { "feeds", command_feeds },
1091 // DEBUG
1092 { "feed_get", command_feed_get, 1 },
1093 { "feed_set", command_feed_set, 1 },
1094 { "feed_rem", command_feed_rem, 1 },
1095 { "feed_xxx", command_feed_xxx, 1 },
1096 { "feed_counter", command_feed_counter, 1 },
1097 { "seen_xxx", command_seen_xxx, 1 },
1100 static void init_hash(struct bio *b, const char *target)
1102 int i;
1103 for (i = 0; i < elements(unhashed); ++i) {
1104 unsigned h = strhash(unhashed[i].string) % elements(commands);
1105 if (commands[h].string)
1106 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, unhashed[i].string);
1107 else
1108 commands[h] = unhashed[i];
1110 #ifdef local_commands
1111 for (i = 0; i < elements(local_commands); ++i) {
1112 unsigned h = strhash(local_commands[i].string) % elements(commands);
1113 if (commands[h].string)
1114 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, local_commands[i].string);
1115 else
1116 commands[h] = local_commands[i];
1118 #endif
1121 void init_hook(struct bio *b, const char *target, const char *nick, unsigned is_ponified)
1123 char *cwd, *path = NULL;
1124 nick_self = nick;
1125 static const char *messages[] = {
1126 "feels circuits being activated that weren't before",
1127 "suddenly gets a better feel of her surroundings",
1128 "looks the same, yet there's definitely something different",
1129 "her lights begin to pulse slowly",
1130 "whirrrs and bleeps like never before",
1131 "bleeps a few times happily",
1132 "excitedly peeks at her surroundings"
1134 init_hash(b, target);
1135 ponify = is_ponified;
1137 cwd = getcwd(NULL, 0);
1138 asprintf(&path, "%s/db/feed.tdb", cwd);
1139 feed_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1140 free(path);
1141 if (!feed_db)
1142 privmsg(b, target, "Opening feed db failed: %m");
1144 asprintf(&path, "%s/db/chan.tdb", cwd);
1145 chan_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1146 free(path);
1147 if (!chan_db)
1148 privmsg(b, target, "Opening chan db failed: %m");
1150 asprintf(&path, "%s/db/mail.tdb", cwd);
1151 mail_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1152 free(path);
1153 if (!mail_db)
1154 privmsg(b, target, "Opening mail db failed: %m");
1156 asprintf(&path, "%s/db/seen.tdb", cwd);
1157 seen_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1158 free(path);
1159 if (!seen_db)
1160 privmsg(b, target, "Opening seen db failed: %m");
1162 free(cwd);
1163 action(b, target, "%s", messages[getrand() % elements(messages)]);
1166 static void __attribute__((destructor)) shutdown_hook(void)
1168 if (seen_db)
1169 tdb_close(seen_db);
1170 if (mail_db)
1171 tdb_close(mail_db);
1172 if (feed_db)
1173 tdb_close(feed_db);
1174 if (chan_db)
1175 tdb_close(chan_db);
1178 static char *nom_special(char *line)
1180 while (*line) {
1181 if (*line == 0x03) {
1182 line++;
1183 if (*line >= '0' && *line <= '9')
1184 line++;
1185 else continue;
1186 if (*line >= '0' && *line <= '9')
1187 line++;
1188 if (line[0] == ',' && line[1] >= '0' && line[1] <= '9')
1189 line += 2;
1190 else continue;
1191 if (*line >= '0' && *line <= '9')
1192 line++;
1193 } else if (*line != 0x02 && *line != 0x1f && *line != 0x16 && *line != 0x06 && *line != 0x07)
1194 return line;
1195 else
1196 line++;
1198 return line;
1201 static char *cleanup_special(char *line)
1203 char *cur, *start = nom_special(line);
1204 if (!*start)
1205 return NULL;
1206 for (line = cur = start; *line; line = nom_special(line))
1207 *(cur++) = *(line++);
1209 for (cur--; cur >= start; --cur)
1210 if (*cur != ' ' && *cur != '\001')
1211 break;
1213 if (cur < start)
1214 return NULL;
1215 cur[1] = 0;
1216 return start;
1219 static void rss_check(struct bio *b, const char *channel, int64_t t)
1221 static unsigned rss_enter, rss_leave;
1222 if (t >= 0 && rss_enter == rss_leave) {
1223 rss_enter++;
1224 channel_feed_check(b, channel, t);
1225 rss_leave++;
1229 void privmsg_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1230 const char *const *args, unsigned nargs)
1232 char *cmd_args, *cmd;
1233 const char *target = args[0][0] == '#' ? args[0] : prefix;
1234 unsigned chan, nick_len;
1235 struct command_hash *c;
1236 int64_t t = get_time(b, target);
1238 chan = args[0][0] == '#';
1239 if (chan) {
1240 rss_check(b, args[0], t);
1241 command_deliver(b, prefix, args[0]);
1243 cmd_args = cleanup_special((char*)args[1]);
1244 if (!cmd_args)
1245 return;
1246 if (chan && cmd_args[0] == '!') {
1247 cmd_args++;
1248 } else if (chan && (nick_len = strlen(nick_self)) &&
1249 !strncasecmp(cmd_args, nick_self, nick_len) &&
1250 (cmd_args[nick_len] == ':' || cmd_args[nick_len] == ',') && cmd_args[nick_len+1] == ' ') {
1251 cmd_args += nick_len + 2;
1252 if (!cmd_args[0])
1253 return;
1254 chan = 2;
1255 } else if (chan) {
1256 static unsigned channel_enter, channel_left;
1257 if (channel_enter == channel_left) {
1258 channel_enter++;
1259 channel_msg(b, prefix, host, args[0], cmd_args, t);
1260 channel_left++;
1262 return;
1264 cmd = token(&cmd_args, ' ');
1265 if (!chan && cmd_args && cmd_args[0] == '#') {
1266 if (!admin(host)) {
1267 privmsg(b, prefix, "%s: { Error... }", prefix);
1268 return;
1270 target = token(&cmd_args, ' ');
1273 c = &commands[strhash(cmd) % elements(commands)];
1274 if (c->string && !strcasecmp(c->string, cmd)) {
1275 if (c->left != c->enter)
1276 privmsg(b, target, "Command %s is disabled because of a crash", c->string);
1277 else if (t > 0 && t < c->disabled_until) {
1278 int64_t delta = c->disabled_until - t;
1279 const char *str = perty(&delta);
1280 b->writeline(b, "NOTICE %s :Command %s is on timeout for the next %"PRIi64 " %s", prefix, c->string, delta, str);
1281 } else if (c->admin && !admin(host)) {
1282 static const char *insults[] = {
1283 "{ Rarity, I love you so much! }",
1284 "{ Rarity, have I ever told you that I love you? }",
1285 "{ Yes, I love my sister, Rarity. }",
1286 "{ Raaaaaaaaaaaaaarity. }",
1287 "{ You do not fool me, Rari...bot! }"
1289 privmsg(b, target, "%s: %s", prefix, insults[getrand() % elements(insults)]);
1290 } else {
1291 c->enter++;
1292 snprintf(c->failed_command, sizeof(*c) - offsetof(struct command_hash, failed_command) - 1,
1293 "%s:%s (%s@%s) \"%s\" %s", target, prefix, ident, host, cmd, cmd_args);
1294 c->cmd(b, prefix, host, target, cmd_args);
1295 c->left++;
1297 } else if (chan == 2)
1298 privmsg(b, target, "%s: { I love you! }", prefix);
1301 void command_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1302 const char *command, const char *const *args, unsigned nargs)
1304 char *buf = NULL;
1305 int64_t t = -1;
1306 if (!strcasecmp(command, "NOTICE")) {
1307 if (nargs < 2 || args[0][0] == '#')
1308 return;
1309 if (admin(host))
1310 b->writeline(b, "%s", args[1]);
1311 else
1312 fprintf(stderr, "%s: %s\n", prefix, args[1]);
1313 } else if (!strcasecmp(command, "JOIN")) {
1314 t = get_time(b, args[0]);
1315 rss_check(b, args[0], t);
1316 command_deliver(b, prefix, args[0]);
1317 asprintf(&buf, "%"PRIi64",joining %s", t, args[0]);
1318 } else if (!strcasecmp(command, "PART")) {
1319 t = get_time(b, args[0]);
1320 rss_check(b, args[0], t);
1321 asprintf(&buf, "%"PRIi64",leaving %s", t, args[0]);
1322 } else if (!strcasecmp(command, "QUIT")) {
1323 t = get_time(b, NULL);
1324 asprintf(&buf, "%"PRIi64",quitting with the message \"%s\"", t, args[0]);
1325 } else if (!strcasecmp(command, "NICK")) {
1326 t = get_time(b, NULL);
1327 asprintf(&buf, "%"PRIi64",changing nick to %s", t, args[0]);
1328 } else if (0) {
1329 int i;
1330 fprintf(stderr, ":%s!%s%s %s", prefix, ident, host, command);
1331 for (i = 0; i < nargs; ++i)
1332 fprintf(stderr, " %s", args[i]);
1333 fprintf(stderr, "\n");
1335 if (t >= 0 && buf)
1336 update_seen(b, buf, prefix);
1337 free(buf);
1338 return;