have fun
[fillybot.git] / mod.c
blob9f88206808b462b123e6e41b5d6995fbd8a1cae1
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 static struct command_hash command_channel = { "#", NULL };
23 #define sstrncmp(a, b) (strncmp(a, b, sizeof(b)-1))
24 #define sstrncasecmp(a, b) (strncasecmp(a, b, sizeof(b)-1))
26 #define SPAM_CUTOFF 5
28 static int pipe_command(struct bio *b, const char *target, const char *nick, int redirect_stdout, int redirect_stderr, char *argv[])
30 int fd[2];
31 int lines = 0;
32 if (pipe2(fd, O_CLOEXEC) < 0) {
33 privmsg(b, target, "Could not create pipe: %m");
34 return -1;
36 pid_t pid = fork();
37 if (pid < 0) {
38 privmsg(b, target, "Could not fork: %m");
39 close(fd[0]);
40 close(fd[1]);
41 return -1;
42 } else if (!pid) {
43 int fdnull = open("/dev/null", O_WRONLY|O_CLOEXEC);
44 close(fd[0]);
45 if (dup3(redirect_stdout ? fd[1] : fdnull, 1, 0) < 0)
46 exit(errno);
47 if (dup3(redirect_stderr ? fd[1] : fdnull, 2, 0) < 0)
48 exit(errno);
49 exit(execv(argv[0], argv));
50 } else {
51 int loc = -1;
52 char buffer[0x100];
53 int bufptr = 0, ret;
54 close(fd[1]);
55 fcntl(fd[0], F_SETFL, O_NONBLOCK);
56 while ((ret = waitpid(pid, &loc, WNOHANG)) >= 0) {
57 while (read(fd[0], buffer+bufptr, 1) == 1) {
58 if (buffer[bufptr] != '\n' && bufptr < sizeof(buffer)-1) {
59 bufptr++;
60 continue;
61 } else if (bufptr) {
62 buffer[bufptr] = 0;
63 bufptr = 0;
64 lines++;
65 if (lines < SPAM_CUTOFF)
66 privmsg(b, nick, "%s", buffer);
67 else
68 fprintf(stderr, "%s\n", buffer);
72 if (ret) {
73 if (bufptr) {
74 buffer[bufptr] = 0;
75 if (lines < SPAM_CUTOFF)
76 privmsg(b, nick, "%s", buffer);
77 else
78 fprintf(stderr, "%s\n", buffer);
80 if (lines >= SPAM_CUTOFF)
81 privmsg(b, nick, "%i lines suppressed", lines - SPAM_CUTOFF + 1);
82 break;
85 if (ret < 0)
86 privmsg(b, target, "error on waitpid: %m");
87 else
88 ret = loc;
89 close(fd[0]);
90 return ret;
94 #include "local.c"
96 static struct command_hash commands[2048];
98 static void command_abort(struct bio *b, const char *nick, const char *host, const char *target, char *args)
100 abort();
101 return;
104 static void command_crash(struct bio *b, const char *nick, const char *host, const char *target, char *args)
106 *(char*)0 = 0;
109 static void command_coinflip(struct bio *b, const char *nick, const char *host, const char *target, char *args)
111 if (getrand() & 1)
112 action(b, target, "whirrrs and clicks excitedly at %s", nick);
113 else
114 action(b, target, "eyes %s as nothing happens", nick);
117 static void squash(char *title)
119 char *start = title;
120 goto start;
121 while (*title) {
122 while (*title != '\n' && *title != '\r' && *title != '\t' && *title != ' ' && *title) {
123 *(start++) = *(title++);
125 if (*title)
126 *(start++) = ' ';
127 start:
128 while (*title == '\n' || *title == '\r' || *title == '\t' || *title == ' ')
129 title++;
131 *start = 0;
134 struct curl_download_context
136 char *data;
137 size_t len;
140 static size_t write_data(void *ptr, size_t size, size_t nmemb, void *member) {
141 struct curl_download_context *ctx = member;
142 size *= nmemb;
143 ctx->data = realloc(ctx->data, ctx->len + size);
144 memcpy(ctx->data + ctx->len, ptr, size);
145 ctx->len += size;
146 return size;
149 static const char *get_text(xmlNode *cur)
151 for (; cur; cur = cur->next) {
152 if (cur->type == XML_TEXT_NODE)
153 return cur->content;
155 return NULL;
158 static const char *get_link(xmlAttr *cur, const char *which)
160 for (; cur; cur = cur->next) {
161 if (!strcasecmp(cur->name, which))
162 return get_text(cur->children);
164 return NULL;
167 static int get_feed_entry(struct bio *b, const char *nick, const char *target, const char *category, xmlNode *entry, const char **title, const char **link)
169 const char *cur_title = NULL, *cur_link = NULL;
170 xmlNode *cur;
171 int cur_match = !category;
172 for (cur = entry->children; cur; cur = cur->next) {
173 const char *name = cur->name, *cur_cat;
174 if (cur->type != XML_ELEMENT_NODE)
175 continue;
176 else if (!strcasecmp(name, "link")) {
177 const char *ishtml = get_link(cur->properties, "type");
178 const char *rel = get_link(cur->properties, "rel");
179 if ((!ishtml || !strcasecmp(ishtml, "text/html")) &&
180 rel && !strcasecmp(rel, "alternate"))
181 cur_link = get_link(cur->properties, "href");
182 } else if (!strcasecmp(name, "title"))
183 cur_title = get_text(cur->children);
184 else if (!cur_match && !strcasecmp(name, "category") &&
185 (cur_cat = get_link(cur->properties, "term")) &&
186 strcasestr(cur_cat, category))
187 cur_match = 1;
190 if (cur_title)
191 *title = cur_title;
192 else
193 *title = "<no title>";
194 *link = cur_link;
195 return !cur_link ? -1 : cur_match;
198 static const char *walk_feed(struct bio *b, const char *nick, const char *target, const char *url, const char *category, xmlNode *root, const char *last_link)
200 const char *main_title = NULL, *main_subtitle = NULL, *main_link = NULL, *title, *link, *prev_link = NULL, *prev_title = NULL;
201 int match, updates = 0;
202 xmlNode *cur, *entry = NULL;
203 for (cur = root->children; cur; cur = cur->next) {
204 const char *name = cur->name;
205 if (cur->type != XML_ELEMENT_NODE)
206 continue;
207 else if (!strcasecmp(name, "category"))
208 continue;
209 else if (!strcasecmp(name, "entry"))
210 entry = entry ? entry : cur;
211 else if (!strcasecmp(name, "title"))
212 main_title = get_text(cur->children);
213 else if (!strcasecmp(name, "subtitle"))
214 main_subtitle = get_text(cur->children);
215 else if (!strcasecmp(name, "link")) {
216 const char *ishtml = get_link(cur->properties, "type");
217 const char *rel = get_link(cur->properties, "rel");
218 if ((!ishtml || !strcasecmp(ishtml, "text/html")) && rel && !strcasecmp(rel, "alternate"))
219 main_link = get_link(cur->properties, "href");
222 if (!main_link || !main_title) {
223 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
224 return NULL;
226 if (!entry)
227 return NULL;
229 if (!last_link)
230 privmsg(b, target, "adding blog %s \"%s\": %s", main_link, main_title, main_subtitle);
232 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
233 if (match < 0)
234 return NULL;
236 for (; !match && entry; entry = entry->next) {
237 if (!strcasecmp(entry->name, "entry"))
238 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
241 if (match < 0)
242 return NULL;
243 if (!last_link) {
244 if (match > 0)
245 privmsg(b, target, "Most recent entry: %s %s", link, title);
246 else
247 privmsg(b, target, "Currently having no entries for this feed that matches the filter");
248 return link;
250 if (!strcmp(last_link, link) || !match || !entry)
251 return link;
253 for (entry = entry->next; entry; entry = entry->next) {
254 const char *cur_link, *cur_title;
255 if (strcasecmp(entry->name, "entry"))
256 continue;
257 match = get_feed_entry(b, nick, target, category, entry, &cur_title, &cur_link);
258 if (match < 0 || !strcmp(last_link, cur_link))
259 break;
260 if (match) {
261 prev_link = cur_link;
262 prev_title = cur_title;
263 updates++;
266 if (updates == 1)
267 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
268 else if (updates > 1)
269 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
270 privmsg(b, target, "( %s ): %s", link, title);
271 return link;
274 static const char *walk_rss(struct bio *b, const char *nick, const char *target, const char *url, xmlNode *root, const char *last_link)
276 const char *main_title = NULL, *main_link = NULL, *title = NULL, *link = NULL, *ver;
277 xmlNode *cur, *entry = NULL;
278 ver = get_link(root->properties, "version");
279 if (!ver || strcmp(ver, "2.0")) {
280 if (!ver)
281 privmsg(b, target, "%s: Could not parse rss feed", nick);
282 else
283 privmsg(b, target, "%s: Invalid rss version \"%s\"", nick, ver);
284 return NULL;
286 for (cur = root->children; cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
287 if (!cur)
288 return NULL;
289 for (cur = cur->children; cur; cur = cur->next) {
290 const char *name = cur->name;
291 if (cur->type != XML_ELEMENT_NODE)
292 continue;
293 if (!strcasecmp(name, "title"))
294 main_title = get_text(cur->children);
295 else if (!strcasecmp(name, "link"))
296 main_link = main_link ? main_link : get_text(cur->children);
297 else if (!strcasecmp(name, "item"))
298 entry = entry ? entry : cur;
300 if (!main_link || !main_title) {
301 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
302 return NULL;
304 if (!entry)
305 return NULL;
307 link = title = NULL;
308 for (cur = entry->children; cur; cur = cur->next) {
309 const char *name = cur->name;
310 if (cur->type != XML_ELEMENT_NODE)
311 continue;
312 if (!strcasecmp(name, "title"))
313 title = get_text(cur->children);
314 else if (!strcasecmp(name, "link"))
315 link = get_text(cur->children);
317 if (!title)
318 title = "<no title>";
319 if (!link) {
320 privmsg(b, target, "%s: Failed to parse entry: %s %s", nick, link, title);
321 return NULL;
323 if (!last_link) {
324 privmsg(b, target, "adding blog %s \"%s\"", main_link, main_title);
325 privmsg(b, target, "Most recent entry: %s %s", link, title);
326 } else if (strcmp(last_link, link)) {
327 int updates = 0;
328 const char *prev_title = NULL, *prev_link = NULL, *cur_title = NULL, *cur_link = NULL;
329 for (entry = entry->next; entry; entry = entry->next) {
330 if (strcasecmp(entry->name, "item"))
331 continue;
332 prev_title = cur_title;
333 prev_link = cur_link;
334 cur_title = cur_link = NULL;
335 for (cur = entry->children; cur; cur = cur->next) {
336 const char *name = cur->name;
337 if (cur->type != XML_ELEMENT_NODE)
338 continue;
339 if (!strcasecmp(name, "title"))
340 cur_title = get_text(cur->children);
341 else if (!strcasecmp(name, "link"))
342 cur_link = get_text(cur->children);
344 if (!cur_title)
345 cur_title = "<no title>";
346 if (!cur_link || !strcmp(last_link, cur_link))
347 break;
348 updates++;
350 if (updates == 1)
351 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
352 else if (updates > 1)
353 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
354 privmsg(b, target, "( %s ): %s", link, title);
356 return link;
359 // HTML is a mess, so I'm just walking the tree depth first until I find the next element..
360 static xmlNode *next_link(xmlNode *cur_node)
362 if (cur_node->children)
363 return cur_node->children;
364 while (cur_node) {
365 if (cur_node->next)
366 return cur_node->next;
367 cur_node = cur_node->parent;
369 return NULL;
372 static const char *get_atom_link(xmlNode *cur)
374 for (; cur; cur = next_link(cur)) {
375 if (cur->type != XML_ELEMENT_NODE)
376 continue;
377 if (!strcasecmp(cur->name, "link")) {
378 const char *isxml = get_link(cur->properties, "type");
379 if (isxml && !strcasecmp(isxml, "application/atom+xml"))
380 return get_link(cur->properties, "href");
383 return NULL;
386 static const char *get_rss_link(xmlNode *cur)
388 for (; cur; cur = next_link(cur)) {
389 if (cur->type != XML_ELEMENT_NODE)
390 continue;
391 if (!strcasecmp(cur->name, "link")) {
392 const char *isxml = get_link(cur->properties, "type");
393 if (isxml && !strcasecmp(isxml, "application/rss+xml"))
394 return get_link(cur->properties, "href");
397 return NULL;
400 static void do_html(struct bio *b, const char *nick, const char *target, const char *url, const char *data, unsigned len)
402 htmlDocPtr ctx = htmlReadMemory(data, len, 0, url, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
403 xmlNode *root = xmlDocGetRootElement(ctx);
404 const char *link = get_atom_link(root);
405 if (link)
406 privmsg(b, target, "%s: not a valid feed link, try atom: %s", nick, link);
407 else if ((link = get_rss_link(root)))
408 privmsg(b, target, "%s: not a valid feed link, try rss: %s", nick, link);
409 else
410 privmsg(b, target, "%s: not a valid feed link, no suggestion found", nick);
411 xmlFreeDoc(ctx);
414 static size_t get_time_from_header(void *data, size_t size, size_t size2, void *ptr)
416 char *d, *e;
417 size *= size2;
418 if (sstrncmp(data, "Last-Modified: "))
419 return size;
420 data += sizeof("Last-Modified: ")-1;
421 *(char**)ptr = d = strdup(data);
422 if ((e = strchr(d, '\r')))
423 *e = 0;
424 return size;
427 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)
429 struct curl_download_context curl_ctx = {};
430 struct curl_slist *headers = NULL;
431 char error[CURL_ERROR_SIZE], *category = strchr(url, '#');
432 int retval = -1;
433 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
434 headers = curl_slist_append(headers, "Accept: */*");
435 if (category)
436 *(category++) = 0;
438 CURL *h = curl_easy_init();
439 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
440 curl_easy_setopt(h, CURLOPT_URL, url);
441 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
442 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
443 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
444 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
445 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
446 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
447 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
448 curl_easy_setopt(h, CURLOPT_FILETIME, 1);
449 curl_easy_setopt(h, CURLOPT_HEADERFUNCTION, get_time_from_header);
450 curl_easy_setopt(h, CURLOPT_WRITEHEADER, &last_modified);
451 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
453 if (last_modified) {
454 char *tmp;
455 asprintf(&tmp, "If-Modified-Since: %s", last_modified);
456 headers = curl_slist_append(headers, tmp);
457 free(tmp);
460 int success = curl_easy_perform(h);
461 curl_slist_free_all(headers);
462 alarm(ALARM_TIME);
463 if (success == CURLE_OK) {
464 char *mime = NULL;
465 long code;
466 curl_easy_getinfo(h, CURLINFO_CONTENT_TYPE, &mime);
467 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &code);
468 if (code == 304)
470 else if (!mime || !sstrncmp(mime, "application/xml") || !sstrncmp(mime, "text/xml")) {
471 const char *ret_link = NULL;
472 xmlDocPtr ctx = xmlReadMemory(curl_ctx.data, curl_ctx.len, 0, url, XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
473 xmlNode *root = xmlDocGetRootElement(ctx);
475 if (!strcasecmp(root->name, "feed"))
476 ret_link = walk_feed(b, nick, target, url, category, root, link);
477 else if (!strcasecmp(root->name, "rss"))
478 ret_link = walk_rss(b, nick, target, url, root, link);
479 else {
480 privmsg(b, target, "Unknown feed type \"%s\"", root->name);
481 goto free_ctx;
483 if (category)
484 category[-1] = '#';
486 if (!ret_link)
487 privmsg(b, target, "Could not feed parse correctly");
488 else if (ret_link && (!link || strcmp(ret_link, link))) {
489 TDB_DATA val;
490 asprintf((char**)&val.dptr, "%s\001%s", last_modified ? last_modified : "", ret_link);
491 val.dsize = strlen(val.dptr)+1;
492 if (tdb_store(feed_db, key, val, 0) < 0)
493 privmsg(b, target, "updating returns %s", tdb_errorstr(feed_db));
494 free(val.dptr);
495 retval = 1;
497 else
498 retval = 0;
500 free_ctx:
501 xmlFreeDoc(ctx);
502 } else if (link)
504 else if (!sstrncmp(mime, "text/html") || !sstrncmp(mime, "application/xhtml+xml"))
505 do_html(b, nick, target, url, curl_ctx.data, curl_ctx.len);
506 else
507 privmsg(b, target, "unhandled content type %s", mime);
508 } else if (!link)
509 privmsg(b, target, "Error %s (%u)\n", error, success);
511 free(curl_ctx.data);
512 curl_easy_cleanup(h);
513 return retval;
516 static void command_follow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
518 char *space, *last_link = NULL;
519 TDB_DATA key, val;
520 int ret;
521 if (!feed_db)
522 return;
523 if (target[0] != '#') {
524 privmsg(b, target, "%s: Can only follow on channels", nick);
525 return;
527 if (!args || !*args) {
528 privmsg(b, target, "%s: Usage: !follow <url>", nick);
529 return;
532 if (((space = strchr(args, ' ')) && space < strchr(args, '#')) ||
533 (sstrncmp(args, "http://") && sstrncmp(args, "https://"))) {
534 privmsg(b, target, "%s: Invalid url", nick);
535 return;
538 alarm(0);
539 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, args)+1;
540 val = tdb_fetch(feed_db, key);
541 if (val.dptr)
542 last_link = strchr(val.dptr, '\001');
543 ret = check_single_feed(b, target, key, NULL, args, last_link ? last_link+1 : NULL, nick);
544 free(val.dptr);
545 if (!ret)
546 privmsg(b, target, "%s: Not updated", nick);
547 free(key.dptr);
550 static void channel_feed_check(struct bio *b, const char *target, int64_t now)
552 int len = strlen(target);
553 TDB_DATA chan, res;
554 if (!feed_db || !chan_db)
555 return;
556 chan.dptr = (char*)target;
557 chan.dsize = len+1;
558 res = tdb_fetch(chan_db, chan);
559 if (res.dptr && res.dsize == 8) {
560 uint64_t then = *(uint64_t*)res.dptr;
561 if (now - then <= 2000)
562 return;
564 alarm(0);
565 free(res.dptr);
566 res.dptr = (unsigned char*)&now;
567 res.dsize = 8;
568 if (tdb_store(chan_db, chan, res, 0) < 0) {
569 static int complain_db;
570 if (!complain_db++)
571 privmsg(b, target, "updating database: %s", tdb_errorstr(feed_db));
572 goto out;
575 /* Reset the alarm on every get, we are not actually in danger of doing an infinite loop, probably */
576 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
577 TDB_DATA f = tdb_fetch(feed_db, d);
578 TDB_DATA next = tdb_nextkey(feed_db, d);
580 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
581 const char *url = (char*)d.dptr + len + 1;
582 char *sep;
583 if ((sep = strchr(f.dptr, '\001'))) {
584 *(sep++) = 0;
585 check_single_feed(b, target, d, f.dptr, url, sep, target);
586 alarm(0);
589 free(d.dptr);
590 free(f.dptr);
591 d = next;
593 out:
594 alarm(ALARM_TIME);
597 static void command_unfollow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
599 TDB_DATA key;
600 char *url;
602 if (!feed_db)
603 return;
605 if (!(url = token(&args, ' ')) || (sstrncmp(url, "http://") && sstrncmp(url, "https://"))) {
606 privmsg(b, target, "%s: Invalid url", nick);
607 return;
609 if (target[0] != '#') {
610 privmsg(b, target, "%s: Can only unfollow on channels", nick);
611 return;
613 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, url)+1;
614 if (tdb_delete(feed_db, key) < 0) {
615 if (tdb_error(feed_db) == TDB_ERR_NOEXIST)
616 privmsg(b, target, "%s: Not following %s on this channel", nick, url);
617 else
618 privmsg(b, target, "%s: Could not delete: %s", nick, tdb_errorstr(feed_db));
619 } else
620 privmsg(b, target, "%s: No longer following %s", nick, url);
621 free(key.dptr);
624 static void command_feeds(struct bio *b, const char *nick, const char *host, const char *target, char *args)
626 int len = strlen(target), found = 0;
627 if (!feed_db)
628 return;
629 if (target[0] != '#') {
630 privmsg(b, target, "%s: Only useful in channels..", nick);
631 return;
633 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
634 TDB_DATA f = tdb_fetch(feed_db, d);
635 TDB_DATA next = tdb_nextkey(feed_db, d);
637 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
638 privmsg(b, target, "%s: following %s", nick, d.dptr + len + 1);
639 found++;
641 free(d.dptr);
642 free(f.dptr);
643 d = next;
645 if (!found)
646 privmsg(b, target, "%s: not following any feed on %s", nick, target);
649 static void command_feed_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
651 if (!feed_db)
652 return;
653 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
654 TDB_DATA next = tdb_nextkey(feed_db, d);
655 if (!args || strcasestr(d.dptr, args)) {
656 TDB_DATA f = tdb_fetch(feed_db, d);
657 privmsg(b, target, "%s: %s = %s", nick, d.dptr, f.dptr);
658 free(f.dptr);
660 if (strlen(d.dptr)+1 < d.dsize) {
661 privmsg(b, target, "%s: removed buggy entry", nick);
662 tdb_delete(feed_db, d);
664 free(d.dptr);
665 d = next;
669 static void command_feed_set(struct bio *b, const char *nick, const char *host, const char *target, char *args)
671 if (!feed_db)
672 return;
673 TDB_DATA key, val;
674 key.dptr = token(&args, ' ');
675 char *value = token(&args, ' ');
676 if (!key.dptr || !value)
677 return;
678 key.dsize = strlen(key.dptr) + 1;
679 val.dsize = strlen(value) + 2;
680 val.dptr = malloc(val.dsize);
681 strcpy(val.dptr+1, value);
682 val.dptr[0] = '\001';
683 if (tdb_store(feed_db, key, val, 0) < 0)
684 privmsg(b, target, "%s: setting failed: %s", nick, tdb_errorstr(feed_db));
685 else
686 privmsg(b, target, "%s: burp", nick);
687 free(val.dptr);
690 static void command_feed_rem(struct bio *b, const char *nick, const char *host, const char *target, char *args)
692 if (!feed_db || !args)
693 return;
694 TDB_DATA key = { .dptr = (unsigned char*)args, .dsize = strlen(args)+1 };
695 if (tdb_delete(feed_db, key) < 0)
696 privmsg(b, target, "%s: removing failed: %s", nick, tdb_errorstr(feed_db));
697 else
698 privmsg(b, target, "%s: burp", nick);
701 static void command_feed_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
703 if (!feed_db)
704 return;
706 tdb_wipe_all(feed_db);
707 privmsg(b, target, "%s: all evidence erased", nick);
710 static void command_seen_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
712 if (!seen_db)
713 return;
715 tdb_wipe_all(seen_db);
716 privmsg(b, target, "%s: all evidence erased", nick);
719 static void command_feed_counter(struct bio *b, const char *nick, const char *host, const char *target, char *args)
721 if (!chan_db)
722 return;
723 tdb_wipe_all(chan_db);
724 privmsg(b, target, "%s: All update counters reset", nick);
727 static char *get_text_appended(xmlNode *cur)
729 for (; cur; cur = cur->next) {
730 if (cur->type != XML_TEXT_NODE)
731 continue;
732 return strdup(cur->content);
734 return NULL;
737 static char *get_title(struct bio *b, xmlNode *cur_node)
739 for (; cur_node; cur_node = next_link(cur_node)) {
740 if (cur_node->type == XML_ELEMENT_NODE && !strcasecmp(cur_node->name, "title")) {
741 if (!cur_node->children)
742 return NULL;
743 return get_text_appended(cur_node->children);
746 return NULL;
749 static void internal_link(struct bio *b, const char *nick, const char *host, const char *target, char *args, unsigned verbose)
751 CURL *h;
752 struct curl_slist *headers = NULL;
753 char error[CURL_ERROR_SIZE];
754 int success;
755 struct curl_download_context curl_ctx = {};
757 if (!args)
758 return;
760 alarm(0);
761 h = curl_easy_init();
762 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
763 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
764 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
765 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
766 headers = curl_slist_append(headers, "DNT: 1");
767 headers = curl_slist_append(headers, "Connection: keep-alive");
768 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
769 curl_easy_setopt(h, CURLOPT_URL, args);
770 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
771 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
772 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
773 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
774 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 20);
775 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
776 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
777 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
778 success = curl_easy_perform(h);
779 curl_easy_cleanup(h);
780 curl_slist_free_all(headers);
781 alarm(ALARM_TIME);
782 if (success == CURLE_OK) {
783 magic_t m = magic_open(MAGIC_MIME_TYPE);
784 magic_load(m, NULL);
785 const char *mime = magic_buffer(m, curl_ctx.data, curl_ctx.len);
786 if (strstr(mime, "text/html") || strstr(mime, "application/xml") || strstr(mime, "application/xhtml+xml")) {
787 htmlDocPtr ctx = htmlReadMemory(curl_ctx.data, curl_ctx.len, 0, args, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
788 xmlNode *root_element = xmlDocGetRootElement(ctx);
789 char *title = get_title(b, root_element);
790 if (title) {
791 char *nuke;
792 squash(title);
793 decode_html_entities_utf8(title, NULL);
794 if ((nuke = strstr(title, " on SoundCloud - Create")))
795 *nuke = 0;
796 if (*title)
797 privmsg(b, target, "%s linked %s", nick, title);
798 free(title);
800 if (verbose && !title)
801 privmsg(b, target, "%s linked %s page with invalid title", nick, mime);
802 xmlFreeDoc(ctx);
803 } else if (verbose) {
804 magic_setflags(m, MAGIC_COMPRESS);
805 const char *desc = magic_buffer(m, curl_ctx.data, curl_ctx.len);
806 privmsg(b, target, "%s linked type %s", nick, desc);
808 magic_close(m);
810 if (verbose && success != CURLE_OK)
811 privmsg(b, target, "Error %s (%u)\n", error, success);
812 free(curl_ctx.data);
815 static void command_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
817 if (!args || (sstrncmp(args, "http://") && sstrncmp(args, "https://")))
818 return;
819 internal_link(b, nick, host, target, token(&args, ' '), 1);
822 static void command_hug(struct bio *b, const char *nick, const char *host, const char *target, char *args)
824 action(b, target, "gives a robotic hug to %s", args ? args : nick);
827 static void command_hugs(struct bio *b, const char *nick, const char *host, const char *target, char *args)
829 action(b, target, "gives a lunar hug to %s", args ? args : nick);
832 static void command_derpy(struct bio *b, const char *nick, const char *host, const char *target, char *args)
834 static const char *insults[] = {
835 "accidentally shocks herself",
836 "tumbles down the stairs like a slinky",
837 "whirrrs and clicks in a screeching way",
838 "had problems executing this command",
839 "breaks down entirely",
840 "uses her magic to levitate herself off the ground, then hits it face first"
842 action(b, target, "%s", insults[getrand() % elements(insults)]);
845 static void command_inspect(struct bio *b, const char *nick, const char *host, const char *target, char *args)
847 struct command_hash *c;
848 unsigned leave, crash;
849 char *cmd;
850 if (!args || !(cmd = token(&args, ' ')))
851 return;
853 if (strcmp(cmd, "#")) {
854 c = &commands[strhash(cmd) % elements(commands)];
855 if (!c->string || strcasecmp(c->string, cmd)) {
856 privmsg(b, target, "Command %s not valid", cmd);
857 return;
859 } else
860 c = &command_channel;
862 leave = c->left + (c->cmd == command_inspect);
863 crash = c->enter - leave;
864 if (c->enter != leave)
865 privmsg(b, target, "%s: %u successes and %u crash%s, last crashing command: %s", c->string, leave, crash, crash == 1 ? "" : "es", c->failed_command);
866 else
867 privmsg(b, target, "%s: %u time%s executed succesfully", c->string, leave, leave == 1 ? "" : "s");
870 static void command_rebuild(struct bio *b, const char *nick, const char *host, const char *target, char *args)
872 int ret;
873 char *make[] = { "/usr/bin/make", "-j4", NULL };
874 char *git_reset[] = { "/usr/bin/git", "reset", "--hard", "master", NULL };
875 ret = pipe_command(b, target, nick, 0, 1, git_reset);
876 if (ret) {
877 action(b, target, "could not rebuild");
878 return;
880 alarm(2*ALARM_TIME); // sigh, swapping to death
881 ret = pipe_command(b, target, nick, 0, 1, make);
882 if (!ret)
883 kill(getpid(), SIGUSR1);
884 else if (ret > 0)
885 action(b, target, "displays an ominous %i", ret);
888 static void command_swear(struct bio *b, const char *nick, const char *host, const char *target, char *args)
890 static const char *insults[] = {
891 "featherbrain",
892 "ponyfeathers",
893 "What in the hey?",
894 "What are you, a dictionary?",
895 "TAR-DY!",
896 "[BUY SOME APPLES]",
897 "{ Your lack of bloodlust on the battlefield is proof positive that you are a soulless automaton! }",
898 "Your royal snootiness"
900 privmsg(b, target, "%s: %s", args ? args : nick, insults[getrand() % elements(insults)]);
903 static const char *perty(int64_t *t)
905 if (*t >= 14 * 24 * 3600) {
906 *t /= 7 * 24 * 3600;
907 return "weeks";
908 } else if (*t >= 48 * 3600) {
909 *t /= 24 * 3600;
910 return "days";
911 } else if (*t >= 7200) {
912 *t /= 3600;
913 return "hours";
914 } else if (*t >= 120) {
915 *t /= 60;
916 return "minutes";
918 return *t == 1 ? "second" : "seconds";
921 static void command_timeout(struct bio *b, const char *nick, const char *host, const char *target, char *args)
923 struct command_hash *c;
924 int64_t t = get_time(b, target);
925 int64_t howlong;
926 if (t < 0)
927 return;
928 char *arg = token(&args, ' ');
929 if (!arg || !args || !(howlong = atoi(args))) {
930 action(b, target, "pretends to time out");;
931 return;
933 c = &commands[strhash(arg) % elements(commands)];
934 if (c->string && !strcasecmp(c->string, arg)) {
935 c->disabled_until = t + howlong;
936 const char *str = perty(&howlong);
937 action(b, target, "disables %s for %"PRIi64" %s", arg, howlong, str);
938 } else
939 action(b, target, "clicks sadly at %s for not being able to find that command", nick);
942 static void command_mfw(struct bio *b, const char *nick, const char *host, const char *target, char *args)
944 char error[CURL_ERROR_SIZE], *new_url;
945 CURL *h = curl_easy_init();
946 struct curl_slist *headers = NULL;
947 struct curl_download_context curl_ctx = {};
948 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
949 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
950 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
951 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
952 headers = curl_slist_append(headers, "DNT: 1");
953 headers = curl_slist_append(headers, "Connection: keep-alive");
954 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
955 curl_easy_setopt(h, CURLOPT_URL, "http://mylittlefacewhen.com/random/");
956 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
957 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
958 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
959 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
960 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
961 alarm(ALARM_TIME);
962 CURLcode ret = curl_easy_perform(h);
963 if (ret == CURLE_OK && curl_easy_getinfo(h, CURLINFO_REDIRECT_URL, &new_url) == CURLE_OK)
964 privmsg(b, target, "%s: %s", nick, new_url);
965 curl_slist_free_all(headers);
966 curl_easy_cleanup(h);
967 if (ret != CURLE_OK)
968 privmsg(b, target, "%s: You have no face", nick);
971 static TDB_DATA get_mail_key(const char *nick)
973 TDB_DATA d;
974 int i;
975 d.dsize = strlen(nick)+1;
976 d.dptr = malloc(d.dsize);
977 for (i = 0; i < d.dsize - 1; ++i)
978 d.dptr[i] = tolower(nick[i]);
979 d.dptr[i] = 0;
980 return d;
983 static void command_mail(struct bio *b, const char *nick, const char *host, const char *target, char *args)
985 char *victim, *x;
986 size_t len;
987 if (!mail_db || !seen_db)
988 return;
989 TDB_DATA key, val;
990 victim = token(&args, ' ');
991 if (!victim || !args || victim[0] == '#' || strchr(victim, '@') || strchr(victim, '.')) {
992 privmsg(b, target, "%s: Usage: !mail <nick> <message>", nick);
993 return;
995 if (!strcasecmp(victim, nick_self)) {
996 action(b, target, "whirrs and clicks excitedly at the mail she received from %s", nick);
997 return;
999 if (!strcasecmp(victim, nick)) {
1000 action(b, target, "echos the words from %s back to them: %s", nick, args);
1001 return;
1004 key = get_mail_key(victim);
1006 val = tdb_fetch(seen_db, key);
1007 if (val.dptr && (x = strchr(val.dptr, ','))) {
1008 int64_t now = get_time(b, NULL);
1009 *(x++) = 0;
1010 if (now >= 0 && (now - atoll(val.dptr)) < 300 && (!sstrncasecmp(x, "in ") || !sstrncasecmp(x, "joining "))) {
1011 action(b, target, "would rather not store mail for someone active so recently");
1012 goto out;
1016 val = tdb_fetch(mail_db, key);
1017 if (!val.dptr)
1018 val.dsize = 0;
1019 else {
1020 unsigned char *cur;
1021 int letters = 0;
1022 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1023 letters++;
1024 if (letters >= 4) {
1025 action(b, target, "looks sadly at %s as she cannot hold any more mail to %s", nick, victim);
1026 goto out;
1029 len = strlen(nick) + strlen(args)+3;
1030 val.dptr = realloc(val.dptr, val.dsize + len);
1031 sprintf(val.dptr + val.dsize, "%s: %s", nick, args);
1032 val.dsize += len;
1033 if (tdb_store(mail_db, key, val, 0) < 0)
1034 privmsg(b, target, "%s: updating mail returns %s", nick, tdb_errorstr(mail_db));
1035 else
1036 action(b, target, "whirrs and clicks at %s as she stores the mail for %s", nick, victim);
1037 out:
1038 free(val.dptr);
1039 free(key.dptr);
1042 static void command_deliver(struct bio *b, const char *nick, const char *target)
1044 TDB_DATA key, val;
1045 unsigned char *cur;
1046 if (!mail_db)
1047 return;
1048 key = get_mail_key(nick);
1049 val = tdb_fetch(mail_db, key);
1050 if (!val.dptr) {
1051 free(key.dptr);
1052 return;
1054 if (strcasecmp(key.dptr, nick_self)) {
1055 privmsg(b, target, "%s: You've got mail!", nick);
1056 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1057 privmsg(b, target, "From %s", cur);
1059 free(val.dptr);
1060 tdb_delete(mail_db, key);
1061 free(key.dptr);
1064 static void update_seen(struct bio *b, char *doingwhat, const char *nick)
1066 TDB_DATA key;
1067 alarm(ALARM_TIME);
1068 key = get_mail_key(nick);
1069 TDB_DATA val = { .dptr = doingwhat, .dsize = strlen(doingwhat)+1 };
1070 if (seen_db)
1071 tdb_store(seen_db, key, val, 0);
1072 free(key.dptr);
1075 static void command_seen(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1077 char *arg = token(&args, ' '), *x;
1078 int64_t now = get_time(b, target);
1079 TDB_DATA key, val;
1080 if (now < 0)
1081 return;
1082 if (!seen_db || !arg) {
1083 privmsg(b, target, "%s: { Error... }", nick);
1084 return;
1086 if (!strcasecmp(arg, nick_self)) {
1087 action(b, target, "whirrs and clicks at %s", nick);
1088 return;
1090 key = get_mail_key(arg);
1091 val = tdb_fetch(seen_db, key);
1092 if (val.dptr && (x = strchr(val.dptr, ','))) {
1093 int64_t delta;
1094 const char *str;
1095 *(x++) = 0;
1096 delta = now - atoll(val.dptr);
1097 str = perty(&delta);
1098 if (delta < 0)
1099 privmsg(b, target, "%s was last seen in the future %s", arg, x);
1100 else
1101 privmsg(b, target, "%s was last seen %"PRIi64" %s ago %s", arg, delta, str, x);
1102 } else
1103 action(b, target, "cannot find any evidence that %s exists", arg);
1106 static void command_mailbag(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1108 char buffer[256];
1109 unsigned rem = sizeof(buffer)-1, first = 2;
1110 if (!mail_db)
1111 return;
1112 buffer[rem] = 0;
1114 for (TDB_DATA f = tdb_firstkey(mail_db); f.dptr;) {
1115 if (f.dsize + 4 > rem) {
1116 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1117 first = 2;
1118 rem = sizeof(buffer)-1;
1119 assert(f.dsize + 4 < rem);
1121 if (f.dptr) {
1122 if (first == 2) {
1123 first = 1;
1124 } else if (first) {
1125 rem -= 5;
1126 memcpy(&buffer[rem], " and ", 5);
1127 first = 0;
1128 } else {
1129 rem -= 2;
1130 memcpy(&buffer[rem], ", ", 2);
1132 rem -= f.dsize - 1;
1133 memcpy(&buffer[rem], f.dptr, f.dsize - 1);
1135 TDB_DATA next = tdb_nextkey(mail_db, f);
1136 free(f.dptr);
1137 f = next;
1139 if (first < 2)
1140 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1143 static void command_no_deliver(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1145 TDB_DATA key, val;
1146 char *cur;
1147 if (!mail_db || !(cur = token(&args, ' ')))
1148 return;
1149 key = get_mail_key(cur);
1150 val = tdb_fetch(mail_db, key);
1151 if (!val.dptr)
1152 action(b, target, "ponyshrugs as no mail for %s was found", cur);
1153 else {
1154 action(b, target, "deletes all evidence of %s's mail", cur);
1155 tdb_delete(mail_db, key);
1157 free(val.dptr);
1158 free(key.dptr);
1161 static void channel_msg(struct bio *b, const char *nick, const char *host,
1162 const char *chan, const char *msg, int64_t t)
1164 char *cur = NULL, *next;
1165 int is_action = 0;
1167 if (!msg || !strcmp(nick, "`Celestia`") || !strcmp(nick, "derpy") || !strcmp(nick, "`Luna`") || !sstrncmp(nick, "GitHub") || !sstrncmp(nick, "CIA-") || !strcmp(nick, "Terminus-Bot") || !strcmp(nick, "r0m"))
1168 return;
1170 if (!sstrncasecmp(msg, "\001ACTION ")) {
1171 msg += sizeof("\001ACTION ")-1;
1172 is_action = 1;
1173 asprintf(&cur, "%"PRIi64 ",in %s: * %s %s", t, chan, nick, msg);
1174 } else
1175 asprintf(&cur, "%"PRIi64 ",in %s: <%s> %s", t, chan, nick, msg);
1176 if (t > 0)
1177 update_seen(b, cur, nick);
1178 alarm(ALARM_TIME);
1179 free(cur);
1180 (void)is_action;
1182 next = (char*)msg;
1183 while (next && (cur = token(&next, ' '))) {
1184 if (!strcasecmp(cur, ">mfw") || !strcasecmp(cur, "mfw")) {
1185 if (!strcasecmp(chan, "#brony") || !ponify)
1186 continue;
1187 if (t < 0 || t > commands[strhash("mfw") % elements(commands)].disabled_until)
1188 command_mfw(b, nick, host, chan, NULL);
1189 break;
1190 } else if (!sstrncasecmp(cur, "http://") || !sstrncasecmp(cur, "https://")) {
1191 static char last_url[512];
1192 char *part;
1193 if (!strcmp(cur, last_url))
1194 return;
1195 strncpy(last_url, cur, sizeof(last_url)-1);
1197 if (t >= 0 && t < commands[strhash("get") % elements(commands)].disabled_until)
1198 return;
1200 else if (strcasestr(cur, "youtube.com/user") && (part = strstr(cur, "#p/"))) {
1201 char *foo;
1202 part = strrchr(part, '/') + 1;
1203 asprintf(&foo, "http://youtube.com/watch?v=%s", part);
1204 if (foo)
1205 internal_link(b, nick, host, chan, foo, 0);
1206 free(foo);
1207 return;
1208 } else if (strcasestr(cur, "twitter.com/") || strcasestr(cur, "mlfw.info") || strcasestr(cur, "mylittlefacewhen.com"))
1209 return;
1210 internal_link(b, nick, host, chan, cur, 0);
1211 break;
1216 static struct command_hash unhashed[] = {
1217 { "1", command_coinflip },
1218 { "get", command_get },
1219 { "hug", command_hug },
1220 { "hugs", command_hugs },
1221 { "mfw", command_mfw },
1222 { "swear", command_swear },
1223 { "mail", command_mail },
1224 { "seen", command_seen },
1225 { "derpy", command_derpy },
1227 { "rebuild", command_rebuild, 1 },
1228 { "abort", command_abort, 1 },
1229 { "crash", command_crash, 1 },
1230 { "inspect", command_inspect, 1 },
1231 { "timeout", command_timeout, 1 },
1233 { "follow", command_follow },
1234 { "unfollow", command_unfollow },
1235 { "feeds", command_feeds },
1237 // DEBUG
1238 { "feed_get", command_feed_get, 1 },
1239 { "feed_set", command_feed_set, 1 },
1240 { "feed_rem", command_feed_rem, 1 },
1241 { "feed_xxx", command_feed_xxx, 1 },
1242 { "feed_counter", command_feed_counter, 1 },
1243 { "seen_xxx", command_seen_xxx, 1 },
1244 { "mailbag", command_mailbag, 1 },
1245 { "\"deliver\"", command_no_deliver, 1 },
1248 static void init_hash(struct bio *b, const char *target)
1250 int i;
1251 for (i = 0; i < elements(unhashed); ++i) {
1252 unsigned h = strhash(unhashed[i].string) % elements(commands);
1253 if (commands[h].string)
1254 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, unhashed[i].string);
1255 else
1256 commands[h] = unhashed[i];
1258 #ifdef local_commands
1259 for (i = 0; i < elements(local_commands); ++i) {
1260 unsigned h = strhash(local_commands[i].string) % elements(commands);
1261 if (commands[h].string)
1262 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, local_commands[i].string);
1263 else
1264 commands[h] = local_commands[i];
1266 #endif
1269 void init_hook(struct bio *b, const char *target, const char *nick, unsigned is_ponified)
1271 char *cwd, *path = NULL;
1272 nick_self = nick;
1273 static const char *messages[] = {
1274 "feels circuits being activated that weren't before",
1275 "suddenly gets a better feel of her surroundings",
1276 "looks the same, yet there's definitely something different",
1277 "emits a beep as her lights begin to pulse slowly",
1278 "whirrrs and bleeps like never before",
1279 "bleeps a few times happily",
1280 "excitedly peeks at her surroundings"
1282 init_hash(b, target);
1283 ponify = is_ponified;
1285 cwd = getcwd(NULL, 0);
1286 asprintf(&path, "%s/db/feed.tdb", cwd);
1287 feed_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1288 free(path);
1289 if (!feed_db)
1290 privmsg(b, target, "Opening feed db failed: %m");
1292 asprintf(&path, "%s/db/chan.tdb", cwd);
1293 chan_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1294 free(path);
1295 if (!chan_db)
1296 privmsg(b, target, "Opening chan db failed: %m");
1298 asprintf(&path, "%s/db/mail.tdb", cwd);
1299 mail_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1300 free(path);
1301 if (!mail_db)
1302 privmsg(b, target, "Opening mail db failed: %m");
1304 asprintf(&path, "%s/db/seen.tdb", cwd);
1305 seen_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1306 free(path);
1307 if (!seen_db)
1308 privmsg(b, target, "Opening seen db failed: %m");
1310 free(cwd);
1311 action(b, target, "%s", messages[getrand() % elements(messages)]);
1314 static void __attribute__((destructor)) shutdown_hook(void)
1316 if (seen_db)
1317 tdb_close(seen_db);
1318 if (mail_db)
1319 tdb_close(mail_db);
1320 if (feed_db)
1321 tdb_close(feed_db);
1322 if (chan_db)
1323 tdb_close(chan_db);
1326 static char *nom_special(char *line)
1328 while (*line) {
1329 if (*line == 0x03) {
1330 line++;
1331 if (*line >= '0' && *line <= '9')
1332 line++;
1333 else continue;
1334 if (*line >= '0' && *line <= '9')
1335 line++;
1336 if (line[0] == ',' && line[1] >= '0' && line[1] <= '9')
1337 line += 2;
1338 else continue;
1339 if (*line >= '0' && *line <= '9')
1340 line++;
1341 } else if (*line != 0x02 && *line != 0x1f && *line != 0x16 && *line != 0x06 && *line != 0x07)
1342 return line;
1343 else
1344 line++;
1346 return line;
1349 static char *cleanup_special(char *line)
1351 char *cur, *start = nom_special(line);
1352 if (!*start)
1353 return NULL;
1354 for (line = cur = start; *line; line = nom_special(line))
1355 *(cur++) = *(line++);
1357 for (cur--; cur >= start; --cur)
1358 if (*cur != ' ' && *cur != '\001')
1359 break;
1361 if (cur < start)
1362 return NULL;
1363 cur[1] = 0;
1364 return start;
1367 static void rss_check(struct bio *b, const char *channel, int64_t t)
1369 static unsigned rss_enter, rss_leave;
1370 if (t >= 0 && rss_enter == rss_leave) {
1371 rss_enter++;
1372 channel_feed_check(b, channel, t);
1373 rss_leave++;
1377 void privmsg_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1378 const char *const *args, unsigned nargs)
1380 char *cmd_args, *cmd;
1381 const char *target = args[0][0] == '#' ? args[0] : prefix;
1382 unsigned chan, nick_len;
1383 struct command_hash *c;
1384 int64_t t = get_time(b, target);
1385 int is_admin = admin(host);
1387 chan = args[0][0] == '#';
1388 if (chan) {
1389 rss_check(b, args[0], t);
1390 command_deliver(b, prefix, args[0]);
1392 cmd_args = cleanup_special((char*)args[1]);
1393 if (!cmd_args)
1394 return;
1396 if (ident && (!strcasecmp(ident, "Revolver") || !strcasecmp(ident, "Rev")))
1397 return;
1399 if (chan && cmd_args[0] == '!') {
1400 cmd_args++;
1401 } else if (chan && (nick_len = strlen(nick_self)) &&
1402 !strncasecmp(cmd_args, nick_self, nick_len) &&
1403 (cmd_args[nick_len] == ':' || cmd_args[nick_len] == ',') && cmd_args[nick_len+1] == ' ') {
1404 cmd_args += nick_len + 2;
1405 if (!cmd_args[0])
1406 return;
1407 chan = 2;
1408 } else if (chan) {
1409 if (command_channel.enter == command_channel.left) {
1410 command_channel.enter++;
1411 snprintf(command_channel.failed_command,
1412 sizeof(command_channel) - offsetof(struct command_hash, failed_command) - 1,
1413 "%s:%s (%s@%s) \"#\" %s", target, prefix, ident, host, cmd_args);
1414 channel_msg(b, prefix, host, args[0], cmd_args, t);
1415 command_channel.left++;
1417 return;
1419 cmd = token(&cmd_args, ' ');
1420 if (!chan && cmd_args && cmd_args[0] == '#') {
1421 if (!is_admin) {
1422 privmsg(b, prefix, "%s: { Error... }", prefix);
1423 return;
1425 target = token(&cmd_args, ' ');
1428 c = &commands[strhash(cmd) % elements(commands)];
1429 if (c->string && !strcasecmp(c->string, cmd)) {
1430 if (c->left != c->enter)
1431 privmsg(b, target, "Command %s is disabled because of a crash", c->string);
1432 else if (t > 0 && t < c->disabled_until && !is_admin) {
1433 int64_t delta = c->disabled_until - t;
1434 const char *str = perty(&delta);
1435 b->writeline(b, "NOTICE %s :Command %s is on timeout for the next %"PRIi64 " %s", prefix, c->string, delta, str);
1436 } else if (c->admin && !is_admin) {
1437 static const char *insults[] = {
1438 "{ Rarity, I love you so much! }",
1439 "{ Rarity, have I ever told you that I love you? }",
1440 "{ Yes, I love my sister, Rarity. }",
1441 "{ Raaaaaaaaaaaaaarity. }",
1442 "{ You do not fool me, Rari...bot! }"
1444 privmsg(b, target, "%s: %s", prefix, insults[getrand() % elements(insults)]);
1445 } else {
1446 c->enter++;
1447 snprintf(c->failed_command, sizeof(*c) - offsetof(struct command_hash, failed_command) - 1,
1448 "%s:%s (%s@%s) \"%s\" %s", target, prefix, ident, host, cmd, cmd_args);
1449 c->cmd(b, prefix, host, target, cmd_args);
1450 c->left++;
1452 } else if (chan == 2)
1453 privmsg(b, target, "%s: { I love you! }", prefix);
1456 void command_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1457 const char *command, const char *const *args, unsigned nargs)
1459 char *buf = NULL;
1460 int64_t t = -1;
1461 if (!strcasecmp(command, "NOTICE")) {
1462 if (nargs < 2 || args[0][0] == '#')
1463 return;
1464 if (admin(host))
1465 b->writeline(b, "%s", args[1]);
1466 else
1467 fprintf(stderr, "%s: %s\n", prefix, args[1]);
1468 } else if (!strcasecmp(command, "JOIN")) {
1469 t = get_time(b, args[0]);
1470 rss_check(b, args[0], t);
1471 command_deliver(b, prefix, args[0]);
1472 asprintf(&buf, "%"PRIi64",joining %s", t, args[0]);
1473 } else if (!strcasecmp(command, "PART")) {
1474 t = get_time(b, args[0]);
1475 rss_check(b, args[0], t);
1476 asprintf(&buf, "%"PRIi64",leaving %s", t, args[0]);
1477 } else if (!strcasecmp(command, "QUIT")) {
1478 t = get_time(b, NULL);
1479 asprintf(&buf, "%"PRIi64",quitting with the message \"%s\"", t, args[0]);
1480 } else if (!strcasecmp(command, "NICK")) {
1481 t = get_time(b, NULL);
1482 if (t >= 0) {
1483 asprintf(&buf, "%"PRIi64",changing nick from %s", t, prefix);
1484 if (buf)
1485 update_seen(b, buf, args[0]);
1486 free(buf);
1487 buf = NULL;
1489 asprintf(&buf, "%"PRIi64",changing nick to %s", t, args[0]);
1490 } else if (0) {
1491 int i;
1492 fprintf(stderr, ":%s!%s%s %s", prefix, ident, host, command);
1493 for (i = 0; i < nargs; ++i)
1494 fprintf(stderr, " %s", args[i]);
1495 fprintf(stderr, "\n");
1497 if (t >= 0 && buf)
1498 update_seen(b, buf, prefix);
1499 free(buf);
1500 return;