fix mail command..
[fillybot.git] / mod.c
blob61a33e934bb708feea3ff35a46ddfbe16862cded
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 static const char *insults[] = {
112 "whirrrs and clicks excitedly at %s",
113 "eyes %s as nothing happens"
115 action(b, target, insults[getrand() % elements(insults)], nick);
118 static void squash(char *title)
120 char *start = title;
121 goto start;
122 while (*title) {
123 while (*title != '\n' && *title != '\r' && *title != '\t' && *title != ' ' && *title) {
124 *(start++) = *(title++);
126 if (*title)
127 *(start++) = ' ';
128 start:
129 while (*title == '\n' || *title == '\r' || *title == '\t' || *title == ' ')
130 title++;
132 *start = 0;
135 struct curl_download_context
137 char *data;
138 size_t len;
141 static size_t write_data(void *ptr, size_t size, size_t nmemb, void *member) {
142 struct curl_download_context *ctx = member;
143 size *= nmemb;
144 ctx->data = realloc(ctx->data, ctx->len + size);
145 memcpy(ctx->data + ctx->len, ptr, size);
146 ctx->len += size;
147 return size;
150 static const char *get_text(xmlNode *cur)
152 for (; cur; cur = cur->next) {
153 if (cur->type == XML_TEXT_NODE)
154 return cur->content;
156 return NULL;
159 static const char *get_link(xmlAttr *cur, const char *which)
161 for (; cur; cur = cur->next) {
162 if (!strcasecmp(cur->name, which))
163 return get_text(cur->children);
165 return NULL;
168 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)
170 const char *cur_title = NULL, *cur_link = NULL;
171 xmlNode *cur;
172 int cur_match = !category;
173 for (cur = entry->children; cur; cur = cur->next) {
174 const char *name = cur->name, *cur_cat;
175 if (cur->type != XML_ELEMENT_NODE)
176 continue;
177 else if (!strcasecmp(name, "link")) {
178 const char *ishtml = get_link(cur->properties, "type");
179 const char *rel = get_link(cur->properties, "rel");
180 if ((!ishtml || !strcasecmp(ishtml, "text/html")) &&
181 rel && !strcasecmp(rel, "alternate"))
182 cur_link = get_link(cur->properties, "href");
183 } else if (!strcasecmp(name, "title"))
184 cur_title = get_text(cur->children);
185 else if (!cur_match && !strcasecmp(name, "category") &&
186 (cur_cat = get_link(cur->properties, "term")) &&
187 strcasestr(cur_cat, category))
188 cur_match = 1;
191 if (cur_title)
192 *title = cur_title;
193 else
194 *title = "<no title>";
195 *link = cur_link;
196 return !cur_link ? -1 : cur_match;
199 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)
201 const char *main_title = NULL, *main_subtitle = NULL, *main_link = NULL, *title, *link, *prev_link = NULL, *prev_title = NULL;
202 int match, updates = 0;
203 xmlNode *cur, *entry = NULL;
204 for (cur = root->children; cur; cur = cur->next) {
205 const char *name = cur->name;
206 if (cur->type != XML_ELEMENT_NODE)
207 continue;
208 else if (!strcasecmp(name, "category"))
209 continue;
210 else if (!strcasecmp(name, "entry"))
211 entry = entry ? entry : cur;
212 else if (!strcasecmp(name, "title"))
213 main_title = get_text(cur->children);
214 else if (!strcasecmp(name, "subtitle"))
215 main_subtitle = get_text(cur->children);
216 else if (!strcasecmp(name, "link")) {
217 const char *ishtml = get_link(cur->properties, "type");
218 const char *rel = get_link(cur->properties, "rel");
219 if ((!ishtml || !strcasecmp(ishtml, "text/html")) && rel && !strcasecmp(rel, "alternate"))
220 main_link = get_link(cur->properties, "href");
223 if (!main_link || !main_title) {
224 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
225 return NULL;
227 if (!entry)
228 return NULL;
230 if (!last_link)
231 privmsg(b, target, "adding blog %s \"%s\": %s", main_link, main_title, main_subtitle);
233 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
234 if (match < 0)
235 return NULL;
237 for (; !match && entry; entry = entry->next) {
238 if (!strcasecmp(entry->name, "entry"))
239 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
242 if (match < 0)
243 return NULL;
244 if (!last_link) {
245 if (match > 0)
246 privmsg(b, target, "Most recent entry: %s %s", link, title);
247 else
248 privmsg(b, target, "Currently having no entries for this feed that matches the filter");
249 return link;
251 if (!strcmp(last_link, link) || !match || !entry)
252 return link;
254 for (entry = entry->next; entry; entry = entry->next) {
255 const char *cur_link, *cur_title;
256 if (strcasecmp(entry->name, "entry"))
257 continue;
258 match = get_feed_entry(b, nick, target, category, entry, &cur_title, &cur_link);
259 if (match < 0 || !strcmp(last_link, cur_link))
260 break;
261 if (match) {
262 prev_link = cur_link;
263 prev_title = cur_title;
264 updates++;
267 if (updates == 1)
268 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
269 else if (updates > 1)
270 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
271 privmsg(b, target, "( %s ): %s", link, title);
272 return link;
275 static const char *walk_rss(struct bio *b, const char *nick, const char *target, const char *url, xmlNode *root, const char *last_link)
277 const char *main_title = NULL, *main_link = NULL, *title = NULL, *link = NULL, *ver;
278 xmlNode *cur, *entry = NULL;
279 ver = get_link(root->properties, "version");
280 if (!ver || strcmp(ver, "2.0")) {
281 if (!ver)
282 privmsg(b, target, "%s: Could not parse rss feed", nick);
283 else
284 privmsg(b, target, "%s: Invalid rss version \"%s\"", nick, ver);
285 return NULL;
287 for (cur = root->children; cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
288 if (!cur)
289 return NULL;
290 for (cur = cur->children; cur; cur = cur->next) {
291 const char *name = cur->name;
292 if (cur->type != XML_ELEMENT_NODE)
293 continue;
294 if (!strcasecmp(name, "title"))
295 main_title = get_text(cur->children);
296 else if (!strcasecmp(name, "link"))
297 main_link = main_link ? main_link : get_text(cur->children);
298 else if (!strcasecmp(name, "item"))
299 entry = entry ? entry : cur;
301 if (!main_link || !main_title) {
302 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
303 return NULL;
305 if (!entry)
306 return NULL;
308 link = title = NULL;
309 for (cur = entry->children; cur; cur = cur->next) {
310 const char *name = cur->name;
311 if (cur->type != XML_ELEMENT_NODE)
312 continue;
313 if (!strcasecmp(name, "title"))
314 title = get_text(cur->children);
315 else if (!strcasecmp(name, "link"))
316 link = get_text(cur->children);
318 if (!title)
319 title = "<no title>";
320 if (!link) {
321 privmsg(b, target, "%s: Failed to parse entry: %s %s", nick, link, title);
322 return NULL;
324 if (!last_link) {
325 privmsg(b, target, "adding blog %s \"%s\"", main_link, main_title);
326 privmsg(b, target, "Most recent entry: %s %s", link, title);
327 } else if (strcmp(last_link, link)) {
328 int updates = 0;
329 const char *prev_title = NULL, *prev_link = NULL, *cur_title = NULL, *cur_link = NULL;
330 for (entry = entry->next; entry; entry = entry->next) {
331 if (strcasecmp(entry->name, "item"))
332 continue;
333 prev_title = cur_title;
334 prev_link = cur_link;
335 cur_title = cur_link = NULL;
336 for (cur = entry->children; cur; cur = cur->next) {
337 const char *name = cur->name;
338 if (cur->type != XML_ELEMENT_NODE)
339 continue;
340 if (!strcasecmp(name, "title"))
341 cur_title = get_text(cur->children);
342 else if (!strcasecmp(name, "link"))
343 cur_link = get_text(cur->children);
345 if (!cur_title)
346 cur_title = "<no title>";
347 if (!cur_link || !strcmp(last_link, cur_link))
348 break;
349 updates++;
351 if (updates == 1)
352 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
353 else if (updates > 1)
354 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
355 privmsg(b, target, "( %s ): %s", link, title);
357 return link;
360 // HTML is a mess, so I'm just walking the tree depth first until I find the next element..
361 static xmlNode *next_link(xmlNode *cur_node)
363 if (cur_node->children)
364 return cur_node->children;
365 while (cur_node) {
366 if (cur_node->next)
367 return cur_node->next;
368 cur_node = cur_node->parent;
370 return NULL;
373 static const char *get_atom_link(xmlNode *cur)
375 for (; cur; cur = next_link(cur)) {
376 if (cur->type != XML_ELEMENT_NODE)
377 continue;
378 if (!strcasecmp(cur->name, "link")) {
379 const char *isxml = get_link(cur->properties, "type");
380 if (isxml && !strcasecmp(isxml, "application/atom+xml"))
381 return get_link(cur->properties, "href");
384 return NULL;
387 static const char *get_rss_link(xmlNode *cur)
389 for (; cur; cur = next_link(cur)) {
390 if (cur->type != XML_ELEMENT_NODE)
391 continue;
392 if (!strcasecmp(cur->name, "link")) {
393 const char *isxml = get_link(cur->properties, "type");
394 if (isxml && !strcasecmp(isxml, "application/rss+xml"))
395 return get_link(cur->properties, "href");
398 return NULL;
401 static void do_html(struct bio *b, const char *nick, const char *target, const char *url, const char *data, unsigned len)
403 htmlDocPtr ctx = htmlReadMemory(data, len, 0, url, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
404 xmlNode *root = xmlDocGetRootElement(ctx);
405 const char *link = get_atom_link(root);
406 if (link)
407 privmsg(b, target, "%s: not a valid feed link, try atom: %s", nick, link);
408 else if ((link = get_rss_link(root)))
409 privmsg(b, target, "%s: not a valid feed link, try rss: %s", nick, link);
410 else
411 privmsg(b, target, "%s: not a valid feed link, no suggestion found", nick);
412 xmlFreeDoc(ctx);
415 static size_t get_time_from_header(void *data, size_t size, size_t size2, void *ptr)
417 char *d, *e;
418 size *= size2;
419 if (sstrncmp(data, "Last-Modified: "))
420 return size;
421 data += sizeof("Last-Modified: ")-1;
422 *(char**)ptr = d = strdup(data);
423 if ((e = strchr(d, '\r')))
424 *e = 0;
425 return size;
428 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)
430 struct curl_download_context curl_ctx = {};
431 struct curl_slist *headers = NULL;
432 char error[CURL_ERROR_SIZE], *category = strchr(url, '#');
433 int retval = -1;
434 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
435 headers = curl_slist_append(headers, "Accept: */*");
436 if (category)
437 *(category++) = 0;
439 CURL *h = curl_easy_init();
440 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
441 curl_easy_setopt(h, CURLOPT_URL, url);
442 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
443 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
444 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
445 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
446 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
447 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
448 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
449 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
450 curl_easy_setopt(h, CURLOPT_FILETIME, 1);
451 curl_easy_setopt(h, CURLOPT_HEADERFUNCTION, get_time_from_header);
452 curl_easy_setopt(h, CURLOPT_WRITEHEADER, &last_modified);
453 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
455 if (last_modified) {
456 char *tmp;
457 asprintf(&tmp, "If-Modified-Since: %s", last_modified);
458 headers = curl_slist_append(headers, tmp);
459 free(tmp);
462 int success = curl_easy_perform(h);
463 curl_slist_free_all(headers);
464 alarm(ALARM_TIME);
465 if (success == CURLE_OK) {
466 char *mime = NULL;
467 long code;
468 curl_easy_getinfo(h, CURLINFO_CONTENT_TYPE, &mime);
469 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &code);
470 if (code == 304)
472 else if (!mime || !sstrncmp(mime, "application/xml") || !sstrncmp(mime, "text/xml")) {
473 const char *ret_link = NULL;
474 xmlDocPtr ctx = xmlReadMemory(curl_ctx.data, curl_ctx.len, 0, url, XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
475 xmlNode *root = xmlDocGetRootElement(ctx);
477 if (!root || !root->name)
478 privmsg(b, "#derpy", "Failed to parse feed %s %p", url, root);
479 else if (!strcasecmp(root->name, "feed"))
480 ret_link = walk_feed(b, nick, target, url, category, root, link);
481 else if (!strcasecmp(root->name, "rss"))
482 ret_link = walk_rss(b, nick, target, url, root, link);
483 else {
484 privmsg(b, target, "Unknown feed type \"%s\"", root->name);
485 goto free_ctx;
487 if (category)
488 category[-1] = '#';
490 if (!ret_link)
491 privmsg(b, target, "Could not feed parse correctly");
492 else if (ret_link && (!link || strcmp(ret_link, link))) {
493 TDB_DATA val;
494 asprintf((char**)&val.dptr, "%s\001%s", last_modified ? last_modified : "", ret_link);
495 val.dsize = strlen(val.dptr)+1;
496 if (tdb_store(feed_db, key, val, 0) < 0)
497 privmsg(b, target, "updating returns %s", tdb_errorstr(feed_db));
498 free(val.dptr);
499 retval = 1;
501 else
502 retval = 0;
504 free_ctx:
505 xmlFreeDoc(ctx);
506 } else if (link)
508 else if (!sstrncmp(mime, "text/html") || !sstrncmp(mime, "application/xhtml+xml"))
509 do_html(b, nick, target, url, curl_ctx.data, curl_ctx.len);
510 else
511 privmsg(b, target, "unhandled content type %s", mime);
512 } else if (!link)
513 privmsg(b, target, "Error %s (%u)\n", error, success);
515 free(curl_ctx.data);
516 curl_easy_cleanup(h);
517 return retval;
520 static void command_follow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
522 char *space, *last_link = NULL;
523 TDB_DATA key, val;
524 int ret;
525 if (!feed_db)
526 return;
527 if (target[0] != '#') {
528 privmsg(b, target, "%s: Can only follow on channels", nick);
529 return;
531 if (!args || !*args) {
532 privmsg(b, target, "%s: Usage: !follow <url>", nick);
533 return;
536 if (((space = strchr(args, ' ')) && space < strchr(args, '#')) ||
537 (sstrncmp(args, "http://") && sstrncmp(args, "https://"))) {
538 privmsg(b, target, "%s: Invalid url", nick);
539 return;
542 alarm(0);
543 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, args)+1;
544 val = tdb_fetch(feed_db, key);
545 if (val.dptr)
546 last_link = strchr(val.dptr, '\001');
547 ret = check_single_feed(b, target, key, NULL, args, last_link ? last_link+1 : NULL, nick);
548 free(val.dptr);
549 if (!ret)
550 privmsg(b, target, "%s: Not updated", nick);
551 free(key.dptr);
554 static void channel_feed_check(struct bio *b, const char *target, int64_t now)
556 int len = strlen(target);
557 TDB_DATA chan, res;
558 if (!feed_db || !chan_db)
559 return;
560 chan.dptr = (char*)target;
561 chan.dsize = len+1;
562 res = tdb_fetch(chan_db, chan);
563 if (res.dptr && res.dsize == 8) {
564 uint64_t then = *(uint64_t*)res.dptr;
565 if (now - then <= 2000)
566 return;
568 alarm(0);
569 free(res.dptr);
570 res.dptr = (unsigned char*)&now;
571 res.dsize = 8;
572 if (tdb_store(chan_db, chan, res, 0) < 0) {
573 static int complain_db;
574 if (!complain_db++)
575 privmsg(b, target, "updating database: %s", tdb_errorstr(feed_db));
576 goto out;
579 /* Reset the alarm on every get, we are not actually in danger of doing an infinite loop, probably */
580 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
581 TDB_DATA f = tdb_fetch(feed_db, d);
582 TDB_DATA next = tdb_nextkey(feed_db, d);
584 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
585 const char *url = (char*)d.dptr + len + 1;
586 char *sep;
587 if ((sep = strchr(f.dptr, '\001'))) {
588 *(sep++) = 0;
589 check_single_feed(b, target, d, f.dptr, url, sep, target);
590 alarm(0);
593 free(d.dptr);
594 free(f.dptr);
595 d = next;
597 out:
598 alarm(ALARM_TIME);
601 static void command_unfollow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
603 TDB_DATA key;
604 char *url;
606 if (!feed_db)
607 return;
609 if (!(url = token(&args, ' ')) || (sstrncmp(url, "http://") && sstrncmp(url, "https://"))) {
610 privmsg(b, target, "%s: Invalid url", nick);
611 return;
613 if (target[0] != '#') {
614 privmsg(b, target, "%s: Can only unfollow on channels", nick);
615 return;
617 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, url)+1;
618 if (tdb_delete(feed_db, key) < 0) {
619 if (tdb_error(feed_db) == TDB_ERR_NOEXIST)
620 privmsg(b, target, "%s: Not following %s on this channel", nick, url);
621 else
622 privmsg(b, target, "%s: Could not delete: %s", nick, tdb_errorstr(feed_db));
623 } else
624 privmsg(b, target, "%s: No longer following %s", nick, url);
625 free(key.dptr);
628 static void command_feeds(struct bio *b, const char *nick, const char *host, const char *target, char *args)
630 int len = strlen(target), found = 0;
631 if (!feed_db)
632 return;
633 if (target[0] != '#') {
634 privmsg(b, target, "%s: Only useful in channels..", nick);
635 return;
637 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
638 TDB_DATA f = tdb_fetch(feed_db, d);
639 TDB_DATA next = tdb_nextkey(feed_db, d);
641 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
642 privmsg(b, target, "%s: following %s", nick, d.dptr + len + 1);
643 found++;
645 free(d.dptr);
646 free(f.dptr);
647 d = next;
649 if (!found)
650 privmsg(b, target, "%s: not following any feed on %s", nick, target);
653 static void command_feed_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
655 if (!feed_db)
656 return;
657 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
658 TDB_DATA next = tdb_nextkey(feed_db, d);
659 if (!args || strcasestr(d.dptr, args)) {
660 TDB_DATA f = tdb_fetch(feed_db, d);
661 privmsg(b, target, "%s: %s = %s", nick, d.dptr, f.dptr);
662 free(f.dptr);
664 if (strlen(d.dptr)+1 < d.dsize) {
665 privmsg(b, target, "%s: removed buggy entry", nick);
666 tdb_delete(feed_db, d);
668 free(d.dptr);
669 d = next;
673 static void command_feed_set(struct bio *b, const char *nick, const char *host, const char *target, char *args)
675 if (!feed_db)
676 return;
677 TDB_DATA key, val;
678 key.dptr = token(&args, ' ');
679 char *value = token(&args, ' ');
680 if (!key.dptr || !value)
681 return;
682 key.dsize = strlen(key.dptr) + 1;
683 val.dsize = strlen(value) + 2;
684 val.dptr = malloc(val.dsize);
685 strcpy(val.dptr+1, value);
686 val.dptr[0] = '\001';
687 if (tdb_store(feed_db, key, val, 0) < 0)
688 privmsg(b, target, "%s: setting failed: %s", nick, tdb_errorstr(feed_db));
689 else
690 privmsg(b, target, "%s: burp", nick);
691 free(val.dptr);
694 static void command_feed_rem(struct bio *b, const char *nick, const char *host, const char *target, char *args)
696 if (!feed_db || !args)
697 return;
698 TDB_DATA key = { .dptr = (unsigned char*)args, .dsize = strlen(args)+1 };
699 if (tdb_delete(feed_db, key) < 0)
700 privmsg(b, target, "%s: removing failed: %s", nick, tdb_errorstr(feed_db));
701 else
702 privmsg(b, target, "%s: burp", nick);
705 static void command_feed_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
707 if (!feed_db)
708 return;
710 tdb_wipe_all(feed_db);
711 privmsg(b, target, "%s: all evidence erased", nick);
714 static void command_seen_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
716 if (!seen_db)
717 return;
719 tdb_wipe_all(seen_db);
720 privmsg(b, target, "%s: all evidence erased", nick);
723 static void command_feed_counter(struct bio *b, const char *nick, const char *host, const char *target, char *args)
725 if (!chan_db)
726 return;
727 tdb_wipe_all(chan_db);
728 privmsg(b, target, "%s: All update counters reset", nick);
731 static char *get_text_appended(xmlNode *cur)
733 for (; cur; cur = cur->next) {
734 if (cur->type != XML_TEXT_NODE)
735 continue;
736 return strdup(cur->content);
738 return NULL;
741 static char *get_title(struct bio *b, xmlNode *cur_node)
743 for (; cur_node; cur_node = next_link(cur_node)) {
744 if (cur_node->type == XML_ELEMENT_NODE && !strcasecmp(cur_node->name, "title")) {
745 if (!cur_node->children)
746 return NULL;
747 return get_text_appended(cur_node->children);
750 return NULL;
753 static void internal_link(struct bio *b, const char *nick, const char *host, const char *target, char *args, unsigned verbose)
755 CURL *h;
756 struct curl_slist *headers = NULL;
757 char error[CURL_ERROR_SIZE];
758 int success, sent = verbose;
759 struct curl_download_context curl_ctx = {};
761 if (!args)
762 return;
763 int64_t stop, start = get_time(b, target);
765 alarm(0);
766 h = curl_easy_init();
767 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
768 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
769 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
770 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
771 headers = curl_slist_append(headers, "DNT: 1");
772 headers = curl_slist_append(headers, "Connection: keep-alive");
773 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
774 curl_easy_setopt(h, CURLOPT_URL, args);
775 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
776 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
777 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
778 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
779 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
780 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
781 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
782 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
783 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
784 success = curl_easy_perform(h);
785 curl_easy_cleanup(h);
786 curl_slist_free_all(headers);
787 alarm(ALARM_TIME);
788 if (success == CURLE_OK) {
789 magic_t m = magic_open(MAGIC_MIME_TYPE);
790 magic_load(m, NULL);
791 const char *mime = magic_buffer(m, curl_ctx.data, curl_ctx.len);
792 if (strstr(mime, "text/html") || strstr(mime, "application/xml") || strstr(mime, "application/xhtml+xml")) {
793 htmlDocPtr ctx = htmlReadMemory(curl_ctx.data, curl_ctx.len, 0, args, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
794 xmlNode *root_element = xmlDocGetRootElement(ctx);
795 char *title = get_title(b, root_element);
796 if (title) {
797 char *nuke;
798 squash(title);
799 decode_html_entities_utf8(title, NULL);
800 if ((nuke = strstr(title, " on SoundCloud - Create")))
801 *nuke = 0;
802 if (*title) {
803 privmsg(b, target, "%s linked %s", nick, title);
804 sent = 1;
806 free(title);
808 if (verbose && !title)
809 privmsg(b, target, "%s linked %s page with invalid title", nick, mime);
810 xmlFreeDoc(ctx);
811 } else if (verbose) {
812 magic_setflags(m, MAGIC_COMPRESS);
813 const char *desc = magic_buffer(m, curl_ctx.data, curl_ctx.len);
814 privmsg(b, target, "%s linked type %s", nick, desc);
816 magic_close(m);
818 if (verbose && success != CURLE_OK)
819 privmsg(b, target, "Error %s (%u)\n", error, success);
820 else if (!sent && (stop = get_time(b, target)) - start >= 15) {
821 privmsg(b, target, "Link (%s) by %s timed out, disabling links for 10 seconds", args, nick);
822 commands[strhash("get") % elements(commands)].disabled_until = stop + 10;
824 free(curl_ctx.data);
827 static void command_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
829 if (!args || (sstrncmp(args, "http://") && sstrncmp(args, "https://")))
830 return;
831 internal_link(b, nick, host, target, token(&args, ' '), 1);
834 static void command_hug(struct bio *b, const char *nick, const char *host, const char *target, char *args)
836 action(b, target, "gives a robotic hug to %s", args ? args : nick);
839 static void command_hugs(struct bio *b, const char *nick, const char *host, const char *target, char *args)
841 action(b, target, "gives a lunar hug to %s", args ? args : nick);
844 static void command_cookie(struct bio *b, const char *nick, const char *host, const char *target, char *args)
846 action(b, target, "hands a metallic looking cookie to %s", args ? args : nick);
849 static void command_derpy(struct bio *b, const char *nick, const char *host, const char *target, char *args)
851 static const char *insults[] = {
852 "accidentally shocks herself",
853 "tumbles down the stairs like a slinky",
854 "whirrrs and clicks in a screeching way",
855 "had problems executing this command",
856 "breaks down entirely",
857 "uses her magic to levitate herself off the ground, then hits it face first"
859 action(b, target, "%s", insults[getrand() % elements(insults)]);
862 static void command_inspect(struct bio *b, const char *nick, const char *host, const char *target, char *args)
864 struct command_hash *c;
865 unsigned leave, crash;
866 char *cmd;
867 if (!args || !(cmd = token(&args, ' ')))
868 return;
870 if (strcmp(cmd, "#")) {
871 c = &commands[strhash(cmd) % elements(commands)];
872 if (!c->string || strcasecmp(c->string, cmd)) {
873 privmsg(b, target, "Command %s not valid", cmd);
874 return;
876 } else
877 c = &command_channel;
879 leave = c->left + (c->cmd == command_inspect);
880 crash = c->enter - leave;
881 if (c->enter != leave)
882 privmsg(b, target, "%s: %u successes and %u crash%s, last crashing command: %s", c->string, leave, crash, crash == 1 ? "" : "es", c->failed_command);
883 else
884 privmsg(b, target, "%s: %u time%s executed succesfully", c->string, leave, leave == 1 ? "" : "s");
887 static void command_rebuild(struct bio *b, const char *nick, const char *host, const char *target, char *args)
889 int ret;
890 char *make[] = { "/usr/bin/make", "-j4", NULL };
891 char *git_reset[] = { "/usr/bin/git", "reset", "--hard", "master", NULL };
892 ret = pipe_command(b, target, nick, 0, 1, git_reset);
893 if (ret) {
894 action(b, target, "could not rebuild");
895 return;
897 alarm(2*ALARM_TIME); // sigh, swapping to death
898 ret = pipe_command(b, target, nick, 0, 1, make);
899 if (!ret)
900 kill(getpid(), SIGUSR1);
901 else if (ret > 0)
902 action(b, target, "displays an ominous %i", ret);
905 static void command_swear(struct bio *b, const char *nick, const char *host, const char *target, char *args)
907 static const char *insults[] = {
908 "featherbrain",
909 "ponyfeathers",
910 "What in the hey?",
911 "What are you, a dictionary?",
912 "TAR-DY!",
913 "[BUY SOME APPLES]",
914 "{ Your lack of bloodlust on the battlefield is proof positive that you are a soulless automaton! }",
915 "Your royal snootiness"
917 privmsg(b, target, "%s: %s", args ? args : nick, insults[getrand() % elements(insults)]);
920 static const char *perty(int64_t *t)
922 if (*t >= 14 * 24 * 3600) {
923 *t /= 7 * 24 * 3600;
924 return "weeks";
925 } else if (*t >= 48 * 3600) {
926 *t /= 24 * 3600;
927 return "days";
928 } else if (*t >= 7200) {
929 *t /= 3600;
930 return "hours";
931 } else if (*t >= 120) {
932 *t /= 60;
933 return "minutes";
935 return *t == 1 ? "second" : "seconds";
938 static void command_timeout(struct bio *b, const char *nick, const char *host, const char *target, char *args)
940 struct command_hash *c;
941 int64_t t = get_time(b, target);
942 int64_t howlong;
943 if (t < 0)
944 return;
945 char *arg = token(&args, ' ');
946 if (!arg || !args || !(howlong = atoi(args))) {
947 action(b, target, "pretends to time out");;
948 return;
950 c = &commands[strhash(arg) % elements(commands)];
951 if (c->string && !strcasecmp(c->string, arg)) {
952 c->disabled_until = t + howlong;
953 const char *str = perty(&howlong);
954 action(b, target, "disables %s for %"PRIi64" %s", arg, howlong, str);
955 } else
956 action(b, target, "clicks sadly at %s for not being able to find that command", nick);
959 static void command_mfw(struct bio *b, const char *nick, const char *host, const char *target, char *args)
961 char error[CURL_ERROR_SIZE], *new_url;
962 CURL *h = curl_easy_init();
963 struct curl_slist *headers = NULL;
964 struct curl_download_context curl_ctx = {};
965 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
966 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
967 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
968 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
969 headers = curl_slist_append(headers, "DNT: 1");
970 headers = curl_slist_append(headers, "Connection: keep-alive");
971 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
972 curl_easy_setopt(h, CURLOPT_URL, "http://mylittlefacewhen.com/random/");
973 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
974 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
975 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
976 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
977 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
978 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
979 alarm(ALARM_TIME);
980 CURLcode ret = curl_easy_perform(h);
981 if (ret == CURLE_OK && curl_easy_getinfo(h, CURLINFO_REDIRECT_URL, &new_url) == CURLE_OK)
982 privmsg(b, target, "%s: %s", nick, new_url);
983 curl_slist_free_all(headers);
984 curl_easy_cleanup(h);
985 if (ret != CURLE_OK)
986 privmsg(b, target, "%s: You have no face", nick);
989 static TDB_DATA get_mail_key(const char *nick)
991 TDB_DATA d;
992 int i;
993 d.dsize = strlen(nick)+1;
994 d.dptr = malloc(d.dsize);
995 for (i = 0; i < d.dsize - 1; ++i)
996 d.dptr[i] = tolower(nick[i]);
997 d.dptr[i] = 0;
998 return d;
1001 static void command_mail(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1003 char *victim, *x;
1004 size_t len;
1005 int override = 0;
1007 if (!mail_db || !seen_db)
1008 return;
1009 TDB_DATA key, val;
1010 victim = token(&args, ' ');
1011 if (victim && !strcasecmp(victim, "-seen")) {
1012 victim = token(&args, ' ');
1013 override = 1;
1015 if (!victim || !args || victim[0] == '#' || strchr(victim, '@') || strchr(victim, '.')) {
1016 privmsg(b, target, "%s: Usage: !mail <nick> <message>", nick);
1017 return;
1019 if (!strcasecmp(victim, nick_self)) {
1020 action(b, target, "whirrs and clicks excitedly at the mail she received from %s", nick);
1021 return;
1023 if (!strcasecmp(victim, nick)) {
1024 action(b, target, "echos the words from %s back to them: %s", nick, args);
1025 return;
1027 int64_t now = get_time(b, target);
1028 if (now < 0)
1029 return;
1031 key = get_mail_key(victim);
1033 val = tdb_fetch(seen_db, key);
1034 if (val.dptr && (x = strchr(val.dptr, ','))) {
1035 *(x++) = 0;
1036 if (!admin(host) && (now - atoll(val.dptr)) < 300 && (!sstrncasecmp(x, "in ") || !sstrncasecmp(x, "joining "))) {
1037 action(b, target, "would rather not store mail for someone active so recently");
1038 goto out;
1040 } else if (!override) {
1041 privmsg(b, target, "%s: I've never seen \"%s\" use !mail -seen %s <message> to override this check.", nick, victim, victim);
1042 return;
1045 val = tdb_fetch(mail_db, key);
1046 if (!val.dptr)
1047 val.dsize = 0;
1048 else {
1049 unsigned char *cur;
1050 int letters = 0;
1051 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1052 letters++;
1053 if (letters >= 4) {
1054 action(b, target, "looks sadly at %s as she cannot hold any more mail to %s", nick, victim);
1055 goto out;
1058 len = snprintf(NULL, 0, "%"PRIi64 ",%s: %s", now, nick, args) + 1;
1059 val.dptr = realloc(val.dptr, val.dsize + len);
1060 snprintf(val.dptr + val.dsize, len, "%"PRIi64 ",%s: %s", now, nick, args);
1061 val.dsize += len;
1062 if (tdb_store(mail_db, key, val, 0) < 0)
1063 privmsg(b, target, "%s: updating mail returns %s", nick, tdb_errorstr(mail_db));
1064 else
1065 action(b, target, "whirrs and clicks at %s as she stores the mail for %s", nick, victim);
1066 out:
1067 free(val.dptr);
1068 free(key.dptr);
1071 static void single_message(struct bio *b, const char *target, char *cur, int64_t now)
1073 char *endtime, *sep = strchr(cur, ':');
1074 int64_t delta = -1;
1075 if (sep && (endtime = strrchr(sep, ','))) {
1076 *endtime = 0;
1077 int64_t t = atoll(cur);
1078 if (t > 0)
1079 delta = now - t;
1080 cur = endtime + 1;
1082 if (delta >= 0) {
1083 const char *str = perty(&delta);
1084 privmsg(b, target, "%"PRIi64" %s ago from %s", delta, str, cur);
1085 } else
1086 privmsg(b, target, "From %s", cur);
1089 static void command_deliver(struct bio *b, const char *nick, const char *target)
1091 TDB_DATA key, val;
1092 unsigned char *cur;
1093 if (!mail_db)
1094 return;
1095 key = get_mail_key(nick);
1096 val = tdb_fetch(mail_db, key);
1097 if (!val.dptr) {
1098 free(key.dptr);
1099 return;
1101 int64_t now = get_time(b, NULL);
1102 if (strcasecmp(key.dptr, nick_self)) {
1103 privmsg(b, target, "%s: You've got mail!", nick);
1104 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1105 single_message(b, target, cur, now);
1107 free(val.dptr);
1108 tdb_delete(mail_db, key);
1109 free(key.dptr);
1112 static void update_seen(struct bio *b, char *doingwhat, const char *nick)
1114 TDB_DATA key;
1115 alarm(ALARM_TIME);
1116 key = get_mail_key(nick);
1117 TDB_DATA val = { .dptr = doingwhat, .dsize = strlen(doingwhat)+1 };
1118 if (seen_db)
1119 tdb_store(seen_db, key, val, 0);
1120 free(key.dptr);
1123 static void command_seen(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1125 char *arg = token(&args, ' '), *x;
1126 int64_t now = get_time(b, target);
1127 TDB_DATA key, val;
1128 if (now < 0)
1129 return;
1130 if (!seen_db || !arg) {
1131 privmsg(b, target, "%s: { Error... }", nick);
1132 return;
1134 if (!strcasecmp(arg, nick_self)) {
1135 action(b, target, "whirrs and clicks at %s", nick);
1136 return;
1138 key = get_mail_key(arg);
1139 val = tdb_fetch(seen_db, key);
1140 if (val.dptr && (x = strchr(val.dptr, ','))) {
1141 int64_t delta;
1142 const char *str;
1143 *(x++) = 0;
1144 delta = now - atoll(val.dptr);
1145 str = perty(&delta);
1146 if (delta < 0)
1147 privmsg(b, target, "%s was last seen in the future %s", arg, x);
1148 else
1149 privmsg(b, target, "%s was last seen %"PRIi64" %s ago %s", arg, delta, str, x);
1150 } else
1151 action(b, target, "cannot find any evidence that %s exists", arg);
1154 static void command_mailbag(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1156 char buffer[256];
1157 unsigned rem = sizeof(buffer)-1, first = 2;
1158 if (!mail_db)
1159 return;
1160 buffer[rem] = 0;
1162 for (TDB_DATA f = tdb_firstkey(mail_db); f.dptr;) {
1163 if (f.dsize + 4 > rem) {
1164 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1165 first = 2;
1166 rem = sizeof(buffer)-1;
1167 assert(f.dsize + 4 < rem);
1169 if (f.dptr) {
1170 if (first == 2) {
1171 first = 1;
1172 } else if (first) {
1173 rem -= 5;
1174 memcpy(&buffer[rem], " and ", 5);
1175 first = 0;
1176 } else {
1177 rem -= 2;
1178 memcpy(&buffer[rem], ", ", 2);
1180 rem -= f.dsize - 1;
1181 memcpy(&buffer[rem], f.dptr, f.dsize - 1);
1183 TDB_DATA next = tdb_nextkey(mail_db, f);
1184 free(f.dptr);
1185 f = next;
1187 if (first < 2)
1188 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1191 static void command_mailread(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1193 TDB_DATA key, val;
1194 char *victim;
1195 if (!mail_db || !(victim = token(&args, ' ')))
1196 return;
1197 key = get_mail_key(victim);
1198 val = tdb_fetch(mail_db, key);
1199 if (!val.dptr)
1200 action(b, target, "ponyshrugs as no mail for %s was found", victim);
1201 else {
1202 unsigned char *cur;
1203 int64_t now = get_time(b, NULL);
1204 action(b, target, "peeks through %s's mail", victim);
1205 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1206 single_message(b, target, cur, now);
1208 free(val.dptr);
1209 free(key.dptr);
1212 static void command_no_deliver(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1214 TDB_DATA key, val;
1215 char *cur;
1216 if (!mail_db || !(cur = token(&args, ' ')))
1217 return;
1218 key = get_mail_key(cur);
1219 val = tdb_fetch(mail_db, key);
1220 if (!val.dptr)
1221 action(b, target, "ponyshrugs as no mail for %s was found", cur);
1222 else {
1223 action(b, target, "deletes all evidence of %s's mail", cur);
1224 tdb_delete(mail_db, key);
1226 free(val.dptr);
1227 free(key.dptr);
1230 static void channel_msg(struct bio *b, const char *nick, const char *host,
1231 const char *chan, char *msg, int64_t t)
1233 char *cur = NULL, *next;
1234 int is_action = 0;
1236 if (!msg || !strcmp(nick, "`Daring_Do`") || !strcmp(nick, "`Celestia`") || !strcmp(nick, "derpy") || !strcmp(nick, "`Luna`") || !sstrncmp(nick, "GitHub") || !sstrncmp(nick, "CIA-") || !strcmp(nick, "Terminus-Bot") || !strcmp(nick, "r0m"))
1237 return;
1239 if (!sstrncasecmp(msg, "\001ACTION ")) {
1240 msg += sizeof("\001ACTION ")-1;
1241 is_action = 1;
1242 asprintf(&cur, "%"PRIi64 ",in %s: * %s %s", t, chan, nick, msg);
1243 } else
1244 asprintf(&cur, "%"PRIi64 ",in %s: <%s> %s", t, chan, nick, msg);
1245 if (t > 0)
1246 update_seen(b, cur, nick);
1247 alarm(ALARM_TIME);
1248 free(cur);
1249 (void)is_action;
1251 next = msg;
1252 while (next && (cur = token(&next, ' '))) {
1253 if (!strcasecmp(cur, ">mfw") || !strcasecmp(cur, "mfw")) {
1254 if (!strcasecmp(chan, "#brony") || !ponify)
1255 continue;
1256 if (t < 0 || t > commands[strhash("mfw") % elements(commands)].disabled_until)
1257 command_mfw(b, nick, host, chan, NULL);
1258 break;
1259 } else if (!sstrncasecmp(cur, "http://") || !sstrncasecmp(cur, "https://")) {
1260 static char last_url[512];
1261 char *part;
1262 if (!strcmp(cur, last_url))
1263 return;
1264 strncpy(last_url, cur, sizeof(last_url)-1);
1266 if (t >= 0 && t < commands[strhash("get") % elements(commands)].disabled_until)
1267 return;
1269 else if (strcasestr(cur, "youtube.com/user") && (part = strstr(cur, "#p/"))) {
1270 char *foo;
1271 part = strrchr(part, '/') + 1;
1272 asprintf(&foo, "http://youtube.com/watch?v=%s", part);
1273 if (foo)
1274 internal_link(b, nick, host, chan, foo, 0);
1275 free(foo);
1276 return;
1277 } else if (strcasestr(cur, "twitter.com/") || strcasestr(cur, "mlfw.info") || strcasestr(cur, "mylittlefacewhen.com"))
1278 return;
1279 internal_link(b, nick, host, chan, cur, 0);
1280 break;
1285 static struct command_hash unhashed[] = {
1286 { "1", command_coinflip },
1287 { "get", command_get },
1288 { "hug", command_hug },
1289 { "hugs", command_hugs },
1290 { "cookie", command_cookie },
1291 { "mfw", command_mfw },
1292 { "swear", command_swear },
1293 { "mail", command_mail },
1294 { "seen", command_seen },
1295 { "derpy", command_derpy },
1297 { "rebuild", command_rebuild, 1 },
1298 { "abort", command_abort, 1 },
1299 { "crash", command_crash, 1 },
1300 { "inspect", command_inspect, 1 },
1301 { "timeout", command_timeout, 1 },
1303 { "follow", command_follow },
1304 { "unfollow", command_unfollow },
1305 { "feeds", command_feeds },
1307 // DEBUG
1308 { "feed_get", command_feed_get, 1 },
1309 { "feed_set", command_feed_set, 1 },
1310 { "feed_rem", command_feed_rem, 1 },
1311 { "feed_xxx", command_feed_xxx, 1 },
1312 { "feed_counter", command_feed_counter, 1 },
1313 { "seen_xxx", command_seen_xxx, 1 },
1314 { "mailbag", command_mailbag },
1315 { "mailread", command_mailread, 1 },
1316 { "\"deliver\"", command_no_deliver, 1 },
1319 static void init_hash(struct bio *b, const char *target)
1321 int i;
1322 for (i = 0; i < elements(unhashed); ++i) {
1323 unsigned h = strhash(unhashed[i].string) % elements(commands);
1324 if (commands[h].string)
1325 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, unhashed[i].string);
1326 else
1327 commands[h] = unhashed[i];
1329 #ifdef local_commands
1330 for (i = 0; i < elements(local_commands); ++i) {
1331 unsigned h = strhash(local_commands[i].string) % elements(commands);
1332 if (commands[h].string)
1333 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, local_commands[i].string);
1334 else
1335 commands[h] = local_commands[i];
1337 #endif
1340 void init_hook(struct bio *b, const char *target, const char *nick, unsigned is_ponified)
1342 char *cwd, *path = NULL;
1343 nick_self = nick;
1344 static const char *messages[] = {
1345 "feels circuits being activated that weren't before",
1346 "suddenly gets a better feel of her surroundings",
1347 "looks the same, yet there's definitely something different",
1348 "emits a beep as her lights begin to pulse slowly",
1349 "whirrrs and bleeps like never before",
1350 "bleeps a few times happily",
1351 "excitedly peeks at her surroundings"
1353 init_hash(b, target);
1354 ponify = is_ponified;
1356 cwd = getcwd(NULL, 0);
1357 asprintf(&path, "%s/db/feed.tdb", cwd);
1358 feed_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1359 free(path);
1360 if (!feed_db)
1361 privmsg(b, target, "Opening feed db failed: %m");
1363 asprintf(&path, "%s/db/chan.tdb", cwd);
1364 chan_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1365 free(path);
1366 if (!chan_db)
1367 privmsg(b, target, "Opening chan db failed: %m");
1369 asprintf(&path, "%s/db/mail.tdb", cwd);
1370 mail_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1371 free(path);
1372 if (!mail_db)
1373 privmsg(b, target, "Opening mail db failed: %m");
1375 asprintf(&path, "%s/db/seen.tdb", cwd);
1376 seen_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1377 free(path);
1378 if (!seen_db)
1379 privmsg(b, target, "Opening seen db failed: %m");
1381 free(cwd);
1382 action(b, target, "%s", messages[getrand() % elements(messages)]);
1385 static void __attribute__((destructor)) shutdown_hook(void)
1387 if (seen_db)
1388 tdb_close(seen_db);
1389 if (mail_db)
1390 tdb_close(mail_db);
1391 if (feed_db)
1392 tdb_close(feed_db);
1393 if (chan_db)
1394 tdb_close(chan_db);
1397 static char *nom_special(char *line)
1399 while (*line) {
1400 if (*line == 0x03) {
1401 line++;
1402 if (*line >= '0' && *line <= '9')
1403 line++;
1404 else continue;
1405 if (*line >= '0' && *line <= '9')
1406 line++;
1407 if (line[0] == ',' && line[1] >= '0' && line[1] <= '9')
1408 line += 2;
1409 else continue;
1410 if (*line >= '0' && *line <= '9')
1411 line++;
1412 } else if (*line != 0x02 && /* BOLD */
1413 *line != 0x1f && /* UNDERLINE */
1414 *line != 0x16 && /* ITALIC */
1415 *line != 0x06 && /* NFI, is this even used? */
1416 *line != 0x07 && /* NFI, is this even used? */
1417 *line != 0x0f) /* NORMAL */
1418 return line;
1419 else
1420 line++;
1422 return line;
1425 static char *cleanup_special(char *line)
1427 char *cur, *start = nom_special(line);
1428 if (!*start)
1429 return NULL;
1430 for (line = cur = start; *line; line = nom_special(line))
1431 *(cur++) = *(line++);
1433 for (cur--; cur >= start; --cur)
1434 if (*cur != ' ' && *cur != '\001')
1435 break;
1437 if (cur < start)
1438 return NULL;
1439 cur[1] = 0;
1440 return start;
1443 static void rss_check(struct bio *b, const char *channel, int64_t t)
1445 static unsigned rss_enter, rss_leave;
1446 if (t >= 0 && rss_enter == rss_leave) {
1447 rss_enter++;
1448 channel_feed_check(b, channel, t);
1449 rss_leave++;
1453 static const char *privileged_command[] = {
1454 "{ Rarity, I love you so much! }",
1455 "{ Rarity, have I ever told you that I love you? }",
1456 "{ Yes, I love my sister, Rarity. }",
1457 "{ Raaaaaaaaaaaaaarity. }",
1458 "{ You do not fool me, Rari...bot! }"
1461 void privmsg_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1462 const char *const *args, unsigned nargs)
1464 char *cmd_args, *cmd;
1465 const char *target = args[0][0] == '#' ? args[0] : prefix;
1466 unsigned chan, nick_len;
1467 struct command_hash *c;
1468 int64_t t = get_time(b, target);
1469 int is_admin = admin(host);
1471 chan = args[0][0] == '#';
1472 if (chan) {
1473 rss_check(b, args[0], t);
1474 command_deliver(b, prefix, args[0]);
1476 cmd_args = cleanup_special((char*)args[1]);
1477 if (!cmd_args)
1478 return;
1480 if (ident && (!strcasecmp(ident, "Revolver") || !strcasecmp(ident, "Rev")))
1481 return;
1483 if (chan && cmd_args[0] == '!') {
1484 cmd_args++;
1485 } else if (chan && (nick_len = strlen(nick_self)) &&
1486 !strncasecmp(cmd_args, nick_self, nick_len) &&
1487 (cmd_args[nick_len] == ':' || cmd_args[nick_len] == ',') && cmd_args[nick_len+1] == ' ') {
1488 cmd_args += nick_len + 2;
1489 if (!cmd_args[0])
1490 return;
1491 chan = 2;
1492 } else if (chan) {
1493 if (command_channel.enter == command_channel.left) {
1494 command_channel.enter++;
1495 snprintf(command_channel.failed_command,
1496 sizeof(command_channel) - offsetof(struct command_hash, failed_command) - 1,
1497 "%s:%s (%s@%s) \"#\" %s", target, prefix, ident, host, cmd_args);
1498 channel_msg(b, prefix, host, args[0], cmd_args, t);
1499 command_channel.left++;
1501 return;
1503 cmd = token(&cmd_args, ' ');
1504 if (!chan && cmd_args && cmd_args[0] == '#') {
1505 if (!is_admin) {
1506 privmsg(b, target, "%s: %s", prefix, privileged_command[getrand() % elements(privileged_command)]);
1507 return;
1509 target = token(&cmd_args, ' ');
1512 c = &commands[strhash(cmd) % elements(commands)];
1513 if (c->string && !strcasecmp(c->string, cmd)) {
1514 if (c->left != c->enter)
1515 privmsg(b, target, "Command %s is disabled because of a crash", c->string);
1516 else if (t > 0 && t < c->disabled_until && !is_admin) {
1517 int64_t delta = c->disabled_until - t;
1518 const char *str = perty(&delta);
1519 b->writeline(b, "NOTICE %s :Command %s is on timeout for the next %"PRIi64 " %s", prefix, c->string, delta, str);
1520 } else if (!c->admin || is_admin) {
1521 c->enter++;
1522 snprintf(c->failed_command, sizeof(*c) - offsetof(struct command_hash, failed_command) - 1,
1523 "%s:%s (%s@%s) \"%s\" %s", target, prefix, ident, host, cmd, cmd_args);
1524 c->cmd(b, prefix, host, target, cmd_args);
1525 c->left++;
1526 } else
1527 privmsg(b, target, "%s: %s", prefix, privileged_command[getrand() % elements(privileged_command)]);
1528 } else if (chan == 2)
1529 privmsg(b, target, "%s: { I love you! }", prefix);
1532 void command_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1533 const char *command, const char *const *args, unsigned nargs)
1535 char *buf = NULL;
1536 int64_t t = -1;
1537 if (!strcasecmp(command, "NOTICE")) {
1538 if (nargs < 2 || args[0][0] == '#')
1539 return;
1540 if (admin(host))
1541 b->writeline(b, "%s", args[1]);
1542 else
1543 fprintf(stderr, "%s: %s\n", prefix, args[1]);
1544 } else if (!strcasecmp(command, "JOIN")) {
1545 t = get_time(b, args[0]);
1546 rss_check(b, args[0], t);
1547 command_deliver(b, prefix, args[0]);
1548 asprintf(&buf, "%"PRIi64",joining %s", t, args[0]);
1549 } else if (!strcasecmp(command, "PART")) {
1550 t = get_time(b, args[0]);
1551 rss_check(b, args[0], t);
1552 asprintf(&buf, "%"PRIi64",leaving %s", t, args[0]);
1553 } else if (!strcasecmp(command, "QUIT")) {
1554 t = get_time(b, NULL);
1555 asprintf(&buf, "%"PRIi64",quitting with the message \"%s\"", t, args[0]);
1556 } else if (!strcasecmp(command, "NICK")) {
1557 t = get_time(b, NULL);
1558 if (t >= 0) {
1559 asprintf(&buf, "%"PRIi64",changing nick from %s", t, prefix);
1560 if (buf)
1561 update_seen(b, buf, args[0]);
1562 free(buf);
1563 buf = NULL;
1565 asprintf(&buf, "%"PRIi64",changing nick to %s", t, args[0]);
1566 } else if (0) {
1567 int i;
1568 fprintf(stderr, ":%s!%s%s %s", prefix, ident, host, command);
1569 for (i = 0; i < nargs; ++i)
1570 fprintf(stderr, " %s", args[i]);
1571 fprintf(stderr, "\n");
1573 if (t >= 0 && buf)
1574 update_seen(b, buf, prefix);
1575 free(buf);
1576 return;