That's what she really said
[fillybot.git] / mod.c
blobae992211edeb5d6861061ecde4b0f8d245eef440
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"
14 #include "quotes.h"
16 static const char *nick_self;
17 static unsigned ponify;
18 #define elements(x) (sizeof(x)/sizeof(*x))
20 static struct tdb_context *feed_db, *chan_db, *mail_db, *seen_db;
22 static struct command_hash command_channel = { "#", NULL };
24 #define sstrncmp(a, b) (strncmp(a, b, sizeof(b)-1))
25 #define sstrncasecmp(a, b) (strncasecmp(a, b, sizeof(b)-1))
27 #define SPAM_CUTOFF 5
29 static int pipe_command(struct bio *b, const char *target, const char *nick, int redirect_stdout, int redirect_stderr, char *argv[])
31 int fd[2];
32 int lines = 0;
33 if (pipe2(fd, O_CLOEXEC) < 0) {
34 privmsg(b, target, "Could not create pipe: %m");
35 return -1;
37 pid_t pid = fork();
38 if (pid < 0) {
39 privmsg(b, target, "Could not fork: %m");
40 close(fd[0]);
41 close(fd[1]);
42 return -1;
43 } else if (!pid) {
44 int fdnull = open("/dev/null", O_WRONLY|O_CLOEXEC);
45 close(fd[0]);
46 if (dup3(redirect_stdout ? fd[1] : fdnull, 1, 0) < 0)
47 exit(errno);
48 if (dup3(redirect_stderr ? fd[1] : fdnull, 2, 0) < 0)
49 exit(errno);
50 exit(execv(argv[0], argv));
51 } else {
52 int loc = -1;
53 char buffer[0x100];
54 int bufptr = 0, ret;
55 close(fd[1]);
56 fcntl(fd[0], F_SETFL, O_NONBLOCK);
57 while ((ret = waitpid(pid, &loc, WNOHANG)) >= 0) {
58 while (read(fd[0], buffer+bufptr, 1) == 1) {
59 if (buffer[bufptr] != '\n' && bufptr < sizeof(buffer)-1) {
60 bufptr++;
61 continue;
62 } else if (bufptr) {
63 buffer[bufptr] = 0;
64 bufptr = 0;
65 lines++;
66 if (lines < SPAM_CUTOFF)
67 privmsg(b, nick, "%s", buffer);
68 else
69 fprintf(stderr, "%s\n", buffer);
73 if (ret) {
74 if (bufptr) {
75 buffer[bufptr] = 0;
76 if (lines < SPAM_CUTOFF)
77 privmsg(b, nick, "%s", buffer);
78 else
79 fprintf(stderr, "%s\n", buffer);
81 if (lines >= SPAM_CUTOFF)
82 privmsg(b, nick, "%i lines suppressed", lines - SPAM_CUTOFF + 1);
83 break;
86 if (ret < 0)
87 privmsg(b, target, "error on waitpid: %m");
88 else
89 ret = loc;
90 close(fd[0]);
91 return ret;
95 #include "local.c"
97 static struct command_hash commands[2048];
99 static void command_abort(struct bio *b, const char *nick, const char *host, const char *target, char *args)
101 abort();
102 return;
105 static void command_crash(struct bio *b, const char *nick, const char *host, const char *target, char *args)
107 *(char*)0 = 0;
110 static void command_coinflip(struct bio *b, const char *nick, const char *host, const char *target, char *args)
112 static const char *insults[] = {
113 "whirrrs and clicks excitedly at %s",
114 "eyes %s as nothing happens"
116 action(b, target, insults[getrand() % elements(insults)], nick);
119 static void command_shesaid(struct bio *b, const char *nick, const char *host, const char *target, char *args)
121 privmsg(b, target, "%s: \"%s\"", args ? args : nick, women_quotes[getrand() % elements(women_quotes)]);
124 static void squash(char *title)
126 char *start = title;
127 goto start;
128 while (*title) {
129 while (*title != '\n' && *title != '\r' && *title != '\t' && *title != ' ' && *title) {
130 *(start++) = *(title++);
132 if (*title)
133 *(start++) = ' ';
134 start:
135 while (*title == '\n' || *title == '\r' || *title == '\t' || *title == ' ')
136 title++;
138 *start = 0;
141 struct curl_download_context
143 char *data;
144 size_t len;
147 static size_t write_data(void *ptr, size_t size, size_t nmemb, void *member) {
148 struct curl_download_context *ctx = member;
149 size *= nmemb;
150 ctx->data = realloc(ctx->data, ctx->len + size);
151 memcpy(ctx->data + ctx->len, ptr, size);
152 ctx->len += size;
153 return size;
156 static const char *get_text(xmlNode *cur)
158 for (; cur; cur = cur->next) {
159 if (cur->type == XML_TEXT_NODE)
160 return cur->content;
162 return NULL;
165 static const char *get_link(xmlAttr *cur, const char *which)
167 for (; cur; cur = cur->next) {
168 if (!strcasecmp(cur->name, which))
169 return get_text(cur->children);
171 return NULL;
174 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)
176 const char *cur_title = NULL, *cur_link = NULL;
177 xmlNode *cur;
178 int cur_match = !category;
179 for (cur = entry->children; cur; cur = cur->next) {
180 const char *name = cur->name, *cur_cat;
181 if (cur->type != XML_ELEMENT_NODE)
182 continue;
183 else if (!strcasecmp(name, "link")) {
184 const char *ishtml = get_link(cur->properties, "type");
185 const char *rel = get_link(cur->properties, "rel");
186 if ((!ishtml || !strcasecmp(ishtml, "text/html")) &&
187 rel && !strcasecmp(rel, "alternate"))
188 cur_link = get_link(cur->properties, "href");
189 } else if (!strcasecmp(name, "title"))
190 cur_title = get_text(cur->children);
191 else if (!cur_match && !strcasecmp(name, "category") &&
192 (cur_cat = get_link(cur->properties, "term")) &&
193 strcasestr(cur_cat, category))
194 cur_match = 1;
197 if (cur_title)
198 *title = cur_title;
199 else
200 *title = "<no title>";
201 *link = cur_link;
202 return !cur_link ? -1 : cur_match;
205 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)
207 const char *main_title = NULL, *main_subtitle = NULL, *main_link = NULL, *title, *link, *prev_link = NULL, *prev_title = NULL;
208 int match, updates = 0;
209 xmlNode *cur, *entry = NULL;
210 for (cur = root->children; cur; cur = cur->next) {
211 const char *name = cur->name;
212 if (cur->type != XML_ELEMENT_NODE)
213 continue;
214 else if (!strcasecmp(name, "category"))
215 continue;
216 else if (!strcasecmp(name, "entry"))
217 entry = entry ? entry : cur;
218 else if (!strcasecmp(name, "title"))
219 main_title = get_text(cur->children);
220 else if (!strcasecmp(name, "subtitle"))
221 main_subtitle = get_text(cur->children);
222 else if (!strcasecmp(name, "link")) {
223 const char *ishtml = get_link(cur->properties, "type");
224 const char *rel = get_link(cur->properties, "rel");
225 if ((!ishtml || !strcasecmp(ishtml, "text/html")) && rel && !strcasecmp(rel, "alternate"))
226 main_link = get_link(cur->properties, "href");
229 if (!main_link || !main_title) {
230 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
231 return NULL;
233 if (!entry)
234 return NULL;
236 if (!last_link)
237 privmsg(b, target, "adding blog %s \"%s\": %s", main_link, main_title, main_subtitle);
239 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
240 if (match < 0)
241 return NULL;
243 for (; !match && entry; entry = entry->next) {
244 if (!strcasecmp(entry->name, "entry"))
245 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
248 if (match < 0)
249 return NULL;
250 if (!last_link) {
251 if (match > 0)
252 privmsg(b, target, "Most recent entry: %s %s", link, title);
253 else
254 privmsg(b, target, "Currently having no entries for this feed that matches the filter");
255 return link;
257 if (!strcmp(last_link, link) || !match || !entry)
258 return link;
260 for (entry = entry->next; entry; entry = entry->next) {
261 const char *cur_link, *cur_title;
262 if (strcasecmp(entry->name, "entry"))
263 continue;
264 match = get_feed_entry(b, nick, target, category, entry, &cur_title, &cur_link);
265 if (match < 0 || !strcmp(last_link, cur_link))
266 break;
267 if (match) {
268 prev_link = cur_link;
269 prev_title = cur_title;
270 updates++;
273 if (updates == 1)
274 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
275 else if (updates > 1)
276 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
277 privmsg(b, target, "( %s ): %s", link, title);
278 return link;
281 static const char *walk_rss(struct bio *b, const char *nick, const char *target, const char *url, xmlNode *root, const char *last_link)
283 const char *main_title = NULL, *main_link = NULL, *title = NULL, *link = NULL, *ver;
284 xmlNode *cur, *entry = NULL;
285 ver = get_link(root->properties, "version");
286 if (!ver || strcmp(ver, "2.0")) {
287 if (!ver)
288 privmsg(b, target, "%s: Could not parse rss feed", nick);
289 else
290 privmsg(b, target, "%s: Invalid rss version \"%s\"", nick, ver);
291 return NULL;
293 for (cur = root->children; cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
294 if (!cur)
295 return NULL;
296 for (cur = cur->children; cur; cur = cur->next) {
297 const char *name = cur->name;
298 if (cur->type != XML_ELEMENT_NODE)
299 continue;
300 if (!strcasecmp(name, "title"))
301 main_title = get_text(cur->children);
302 else if (!strcasecmp(name, "link"))
303 main_link = main_link ? main_link : get_text(cur->children);
304 else if (!strcasecmp(name, "item"))
305 entry = entry ? entry : cur;
307 if (!main_link || !main_title) {
308 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
309 return NULL;
311 if (!entry)
312 return NULL;
314 link = title = NULL;
315 for (cur = entry->children; cur; cur = cur->next) {
316 const char *name = cur->name;
317 if (cur->type != XML_ELEMENT_NODE)
318 continue;
319 if (!strcasecmp(name, "title"))
320 title = get_text(cur->children);
321 else if (!strcasecmp(name, "link"))
322 link = get_text(cur->children);
324 if (!title)
325 title = "<no title>";
326 if (!link) {
327 privmsg(b, target, "%s: Failed to parse entry: %s %s", nick, link, title);
328 return NULL;
330 if (!last_link) {
331 privmsg(b, target, "adding blog %s \"%s\"", main_link, main_title);
332 privmsg(b, target, "Most recent entry: %s %s", link, title);
333 } else if (strcmp(last_link, link)) {
334 int updates = 0;
335 const char *prev_title = NULL, *prev_link = NULL, *cur_title = NULL, *cur_link = NULL;
336 for (entry = entry->next; entry; entry = entry->next) {
337 if (strcasecmp(entry->name, "item"))
338 continue;
339 prev_title = cur_title;
340 prev_link = cur_link;
341 cur_title = cur_link = NULL;
342 for (cur = entry->children; cur; cur = cur->next) {
343 const char *name = cur->name;
344 if (cur->type != XML_ELEMENT_NODE)
345 continue;
346 if (!strcasecmp(name, "title"))
347 cur_title = get_text(cur->children);
348 else if (!strcasecmp(name, "link"))
349 cur_link = get_text(cur->children);
351 if (!cur_title)
352 cur_title = "<no title>";
353 if (!cur_link || !strcmp(last_link, cur_link))
354 break;
355 updates++;
357 if (updates == 1)
358 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
359 else if (updates > 1)
360 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
361 privmsg(b, target, "( %s ): %s", link, title);
363 return link;
366 // HTML is a mess, so I'm just walking the tree depth first until I find the next element..
367 static xmlNode *next_link(xmlNode *cur_node)
369 if (cur_node->children)
370 return cur_node->children;
371 while (cur_node) {
372 if (cur_node->next)
373 return cur_node->next;
374 cur_node = cur_node->parent;
376 return NULL;
379 static const char *get_atom_link(xmlNode *cur)
381 for (; cur; cur = next_link(cur)) {
382 if (cur->type != XML_ELEMENT_NODE)
383 continue;
384 if (!strcasecmp(cur->name, "link")) {
385 const char *isxml = get_link(cur->properties, "type");
386 if (isxml && !strcasecmp(isxml, "application/atom+xml"))
387 return get_link(cur->properties, "href");
390 return NULL;
393 static const char *get_rss_link(xmlNode *cur)
395 for (; cur; cur = next_link(cur)) {
396 if (cur->type != XML_ELEMENT_NODE)
397 continue;
398 if (!strcasecmp(cur->name, "link")) {
399 const char *isxml = get_link(cur->properties, "type");
400 if (isxml && !strcasecmp(isxml, "application/rss+xml"))
401 return get_link(cur->properties, "href");
404 return NULL;
407 static void do_html(struct bio *b, const char *nick, const char *target, const char *url, const char *data, unsigned len)
409 htmlDocPtr ctx = htmlReadMemory(data, len, 0, url, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
410 xmlNode *root = xmlDocGetRootElement(ctx);
411 const char *link = get_atom_link(root);
412 if (link)
413 privmsg(b, target, "%s: not a valid feed link, try atom: %s", nick, link);
414 else if ((link = get_rss_link(root)))
415 privmsg(b, target, "%s: not a valid feed link, try rss: %s", nick, link);
416 else
417 privmsg(b, target, "%s: not a valid feed link, no suggestion found", nick);
418 xmlFreeDoc(ctx);
421 static size_t get_time_from_header(void *data, size_t size, size_t size2, void *ptr)
423 char *d, *e;
424 size *= size2;
425 if (sstrncmp(data, "Last-Modified: "))
426 return size;
427 data += sizeof("Last-Modified: ")-1;
428 *(char**)ptr = d = strdup(data);
429 if ((e = strchr(d, '\r')))
430 *e = 0;
431 return size;
434 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)
436 struct curl_download_context curl_ctx = {};
437 struct curl_slist *headers = NULL;
438 char error[CURL_ERROR_SIZE], *category = strchr(url, '#');
439 int retval = -1;
440 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
441 headers = curl_slist_append(headers, "Accept: */*");
442 if (category)
443 *(category++) = 0;
445 CURL *h = curl_easy_init();
446 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
447 curl_easy_setopt(h, CURLOPT_URL, url);
448 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
449 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
450 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
451 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
452 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
453 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
454 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
455 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
456 curl_easy_setopt(h, CURLOPT_FILETIME, 1);
457 curl_easy_setopt(h, CURLOPT_HEADERFUNCTION, get_time_from_header);
458 curl_easy_setopt(h, CURLOPT_WRITEHEADER, &last_modified);
459 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
461 if (last_modified) {
462 char *tmp;
463 asprintf(&tmp, "If-Modified-Since: %s", last_modified);
464 headers = curl_slist_append(headers, tmp);
465 free(tmp);
468 int success = curl_easy_perform(h);
469 curl_slist_free_all(headers);
470 if (success == CURLE_OK) {
471 char *mime = NULL;
472 long code;
473 curl_easy_getinfo(h, CURLINFO_CONTENT_TYPE, &mime);
474 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &code);
475 if (code == 304)
477 else if (!mime || !sstrncmp(mime, "application/xml") || !sstrncmp(mime, "text/xml")) {
478 const char *ret_link = NULL;
479 xmlDocPtr ctx = xmlReadMemory(curl_ctx.data, curl_ctx.len, 0, url, XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
480 xmlNode *root = xmlDocGetRootElement(ctx);
482 if (!root || !root->name)
483 fprintf(stderr, "Failed to parse feed %s %p", url, root);
484 else if (!strcasecmp(root->name, "feed"))
485 ret_link = walk_feed(b, nick, target, url, category, root, link);
486 else if (!strcasecmp(root->name, "rss"))
487 ret_link = walk_rss(b, nick, target, url, root, link);
488 else {
489 privmsg(b, target, "Unknown feed type \"%s\"", root->name);
490 goto free_ctx;
492 if (category)
493 category[-1] = '#';
495 if (!ret_link)
496 privmsg(b, target, "Could not feed parse correctly");
497 else if (ret_link && (!link || strcmp(ret_link, link))) {
498 TDB_DATA val;
499 asprintf((char**)&val.dptr, "%s\001%s", last_modified ? last_modified : "", ret_link);
500 val.dsize = strlen(val.dptr)+1;
501 if (tdb_store(feed_db, key, val, 0) < 0)
502 privmsg(b, target, "updating returns %s", tdb_errorstr(feed_db));
503 free(val.dptr);
504 retval = 1;
506 else
507 retval = 0;
509 free_ctx:
510 xmlFreeDoc(ctx);
511 } else if (link)
513 else if (!sstrncmp(mime, "text/html") || !sstrncmp(mime, "application/xhtml+xml"))
514 do_html(b, nick, target, url, curl_ctx.data, curl_ctx.len);
515 else
516 privmsg(b, target, "unhandled content type %s", mime);
517 } else if (!link)
518 privmsg(b, target, "Error %s (%u)\n", error, success);
520 free(curl_ctx.data);
521 curl_easy_cleanup(h);
522 return retval;
525 static void command_follow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
527 char *space, *last_link = NULL;
528 TDB_DATA key, val;
529 int ret;
530 if (!feed_db)
531 return;
532 if (target[0] != '#') {
533 privmsg(b, target, "%s: Can only follow on channels", nick);
534 return;
536 if (!args || !*args) {
537 privmsg(b, target, "%s: Usage: !follow <url>", nick);
538 return;
541 if (((space = strchr(args, ' ')) && space < strchr(args, '#')) ||
542 (sstrncmp(args, "http://") && sstrncmp(args, "https://"))) {
543 privmsg(b, target, "%s: Invalid url", nick);
544 return;
547 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, args)+1;
548 val = tdb_fetch(feed_db, key);
549 if (val.dptr)
550 last_link = strchr(val.dptr, '\001');
551 ret = check_single_feed(b, target, key, NULL, args, last_link ? last_link+1 : NULL, nick);
552 free(val.dptr);
553 if (!ret)
554 privmsg(b, target, "%s: Not updated", nick);
555 free(key.dptr);
558 static void channel_feed_check(struct bio *b, const char *target, int64_t now)
560 int len = strlen(target);
561 int64_t save[] = { now, 0, 0 };
563 TDB_DATA chan, res;
564 if (!feed_db || !chan_db)
565 return;
566 chan.dptr = (char*)target;
567 chan.dsize = len+1;
568 res = tdb_fetch(chan_db, chan);
569 if (res.dptr && res.dsize >= 8) {
570 uint64_t then = *(uint64_t*)res.dptr;
571 if (now - then <= 2000)
572 return;
573 if (res.dsize >= 16)
574 save[1] = ((uint64_t*)res.dptr)[1];
575 if (res.dsize >= 24)
576 save[2] = ((uint64_t*)res.dptr)[2];
578 free(res.dptr);
579 res.dptr = (unsigned char*)save;
580 res.dsize = sizeof(save);
581 if (tdb_store(chan_db, chan, res, 0) < 0) {
582 static int complain_db;
583 if (!complain_db++)
584 privmsg(b, target, "updating database: %s", tdb_errorstr(feed_db));
585 return;
588 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
589 TDB_DATA f = tdb_fetch(feed_db, d);
590 TDB_DATA next = tdb_nextkey(feed_db, d);
592 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
593 const char *url = (char*)d.dptr + len + 1;
594 char *sep;
595 if ((sep = strchr(f.dptr, '\001'))) {
596 *(sep++) = 0;
597 check_single_feed(b, target, d, f.dptr, url, sep, target);
600 free(d.dptr);
601 free(f.dptr);
602 d = next;
606 static void command_unfollow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
608 TDB_DATA key;
609 char *url;
611 if (!feed_db)
612 return;
614 if (!(url = token(&args, ' ')) || (sstrncmp(url, "http://") && sstrncmp(url, "https://"))) {
615 privmsg(b, target, "%s: Invalid url", nick);
616 return;
618 if (target[0] != '#') {
619 privmsg(b, target, "%s: Can only unfollow on channels", nick);
620 return;
622 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, url)+1;
623 if (tdb_delete(feed_db, key) < 0) {
624 if (tdb_error(feed_db) == TDB_ERR_NOEXIST)
625 privmsg(b, target, "%s: Not following %s on this channel", nick, url);
626 else
627 privmsg(b, target, "%s: Could not delete: %s", nick, tdb_errorstr(feed_db));
628 } else
629 privmsg(b, target, "%s: No longer following %s", nick, url);
630 free(key.dptr);
633 static void command_g(struct bio *b, const char *nick, const char *host, const char *target, char *args)
635 int ret = 0;
636 int64_t g = 0, g_total = 0;
637 TDB_DATA chan, res;
639 if (!chan_db || target[0] != '#')
640 return;
642 chan.dptr = (char*)target;
643 chan.dsize = strlen(chan.dptr)+1;
644 res = tdb_fetch(chan_db, chan);
645 if (res.dptr && res.dsize >= 16) {
646 g = ((int64_t*)res.dptr)[1];
647 ((int64_t*)res.dptr)[1] = 0;
648 if (res.dsize >= 24)
649 g_total = ((int64_t*)res.dptr)[2];
650 ret = tdb_store(chan_db, chan, res, 0);
652 free(res.dptr);
653 if (ret < 0)
654 fprintf(stderr, "updating database: %s", tdb_errorstr(feed_db));
655 else
656 privmsg(b, target, "%s: %"PRIi64" g's since last check, %"PRIi64" total\n", nick, g, g_total);
659 static void command_feeds(struct bio *b, const char *nick, const char *host, const char *target, char *args)
661 int len = strlen(target), found = 0;
662 if (!feed_db)
663 return;
664 if (target[0] != '#') {
665 privmsg(b, target, "%s: Only useful in channels..", nick);
666 return;
668 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
669 TDB_DATA f = tdb_fetch(feed_db, d);
670 TDB_DATA next = tdb_nextkey(feed_db, d);
672 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
673 privmsg(b, target, "%s: following %s", nick, d.dptr + len + 1);
674 found++;
676 free(d.dptr);
677 free(f.dptr);
678 d = next;
680 if (!found)
681 privmsg(b, target, "%s: not following any feed on %s", nick, target);
684 static void command_feed_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
686 if (!feed_db)
687 return;
688 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
689 TDB_DATA next = tdb_nextkey(feed_db, d);
690 if (!args || strcasestr(d.dptr, args)) {
691 TDB_DATA f = tdb_fetch(feed_db, d);
692 privmsg(b, target, "%s: %s = %s", nick, d.dptr, f.dptr);
693 free(f.dptr);
695 if (strlen(d.dptr)+1 < d.dsize) {
696 privmsg(b, target, "%s: removed buggy entry", nick);
697 tdb_delete(feed_db, d);
699 free(d.dptr);
700 d = next;
704 static void command_feed_set(struct bio *b, const char *nick, const char *host, const char *target, char *args)
706 if (!feed_db)
707 return;
708 TDB_DATA key, val;
709 key.dptr = token(&args, ' ');
710 char *value = token(&args, ' ');
711 if (!key.dptr || !value)
712 return;
713 key.dsize = strlen(key.dptr) + 1;
714 val.dsize = strlen(value) + 2;
715 val.dptr = malloc(val.dsize);
716 strcpy(val.dptr+1, value);
717 val.dptr[0] = '\001';
718 if (tdb_store(feed_db, key, val, 0) < 0)
719 privmsg(b, target, "%s: setting failed: %s", nick, tdb_errorstr(feed_db));
720 else
721 privmsg(b, target, "%s: burp", nick);
722 free(val.dptr);
725 static void command_feed_rem(struct bio *b, const char *nick, const char *host, const char *target, char *args)
727 if (!feed_db || !args)
728 return;
729 TDB_DATA key = { .dptr = (unsigned char*)args, .dsize = strlen(args)+1 };
730 if (tdb_delete(feed_db, key) < 0)
731 privmsg(b, target, "%s: removing failed: %s", nick, tdb_errorstr(feed_db));
732 else
733 privmsg(b, target, "%s: burp", nick);
736 static void command_feed_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
738 if (!feed_db)
739 return;
741 tdb_wipe_all(feed_db);
742 privmsg(b, target, "%s: all evidence erased", nick);
745 static void command_seen_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
747 if (!seen_db)
748 return;
750 tdb_wipe_all(seen_db);
751 privmsg(b, target, "%s: all evidence erased", nick);
754 static void command_feed_counter(struct bio *b, const char *nick, const char *host, const char *target, char *args)
756 if (!chan_db)
757 return;
758 tdb_wipe_all(chan_db);
759 privmsg(b, target, "%s: All update counters reset", nick);
762 static char *get_text_appended(xmlNode *cur)
764 for (; cur; cur = cur->next) {
765 if (cur->type != XML_TEXT_NODE)
766 continue;
767 return strdup(cur->content);
769 return NULL;
772 static char *get_title(struct bio *b, xmlNode *cur_node)
774 for (; cur_node; cur_node = next_link(cur_node)) {
775 if (cur_node->type == XML_ELEMENT_NODE && !strcasecmp(cur_node->name, "title")) {
776 if (!cur_node->children)
777 return NULL;
778 return get_text_appended(cur_node->children);
781 return NULL;
784 static void internal_link(struct bio *b, const char *nick, const char *host, const char *target, char *args, unsigned verbose)
786 CURL *h;
787 struct curl_slist *headers = NULL;
788 char error[CURL_ERROR_SIZE];
789 int success, sent = verbose;
790 struct curl_download_context curl_ctx = {};
792 if (!args)
793 return;
794 int64_t stop, start = get_time(b, target);
796 h = curl_easy_init();
797 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
798 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
799 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
800 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
801 headers = curl_slist_append(headers, "DNT: 1");
802 headers = curl_slist_append(headers, "Connection: keep-alive");
803 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
804 curl_easy_setopt(h, CURLOPT_URL, args);
805 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
806 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
807 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
808 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
809 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
810 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
811 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
812 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
813 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
814 success = curl_easy_perform(h);
815 curl_easy_cleanup(h);
816 curl_slist_free_all(headers);
817 if (success == CURLE_OK) {
818 magic_t m = magic_open(MAGIC_MIME_TYPE);
819 magic_load(m, NULL);
820 const char *mime = magic_buffer(m, curl_ctx.data, curl_ctx.len);
821 if (strstr(mime, "text/html") || strstr(mime, "application/xml") || strstr(mime, "application/xhtml+xml")) {
822 htmlDocPtr ctx = htmlReadMemory(curl_ctx.data, curl_ctx.len, 0, args, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
823 xmlNode *root_element = xmlDocGetRootElement(ctx);
824 char *title = get_title(b, root_element);
825 if (title) {
826 char *nuke;
827 squash(title);
828 decode_html_entities_utf8(title, NULL);
829 if ((nuke = strstr(title, " on SoundCloud - Create")))
830 *nuke = 0;
831 if (*title) {
832 privmsg(b, target, "%s linked %s", nick, title);
833 sent = 1;
835 free(title);
837 if (verbose && !title)
838 privmsg(b, target, "%s linked %s page with invalid title", nick, mime);
839 xmlFreeDoc(ctx);
840 } else if (verbose) {
841 magic_setflags(m, MAGIC_COMPRESS);
842 const char *desc = magic_buffer(m, curl_ctx.data, curl_ctx.len);
843 privmsg(b, target, "%s linked type %s", nick, desc);
845 magic_close(m);
847 if (verbose && success != CURLE_OK)
848 privmsg(b, target, "Error %s (%u)\n", error, success);
849 else if (!sent && (stop = get_time(b, target)) - start >= 15) {
850 privmsg(b, target, "Link (%s) by %s timed out, disabling links for 10 seconds", args, nick);
851 commands[strhash("get") % elements(commands)].disabled_until = stop + 10;
853 free(curl_ctx.data);
856 static void command_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
858 if (!args || (sstrncmp(args, "http://") && sstrncmp(args, "https://")))
859 return;
860 internal_link(b, nick, host, target, token(&args, ' '), 1);
863 static void command_hug(struct bio *b, const char *nick, const char *host, const char *target, char *args)
865 action(b, target, "gives a robotic hug to %s", args ? args : nick);
868 static void command_hugs(struct bio *b, const char *nick, const char *host, const char *target, char *args)
870 action(b, target, "gives a lunar hug to %s", args ? args : nick);
873 static void command_cookie(struct bio *b, const char *nick, const char *host, const char *target, char *args)
875 action(b, target, "hands a metallic looking cookie to %s", args ? args : nick);
878 static void command_derpy(struct bio *b, const char *nick, const char *host, const char *target, char *args)
880 static const char *insults[] = {
881 "accidentally shocks herself",
882 "tumbles down the stairs like a slinky",
883 "whirrrs and clicks in a screeching way",
884 "had problems executing this command",
885 "breaks down entirely",
886 "uses her magic to levitate herself off the ground, then hits it face first"
888 action(b, target, "%s", insults[getrand() % elements(insults)]);
891 static void command_inspect(struct bio *b, const char *nick, const char *host, const char *target, char *args)
893 struct command_hash *c;
894 unsigned leave, crash;
895 char *cmd;
896 if (!args || !(cmd = token(&args, ' ')))
897 return;
899 if (strcmp(cmd, "#")) {
900 c = &commands[strhash(cmd) % elements(commands)];
901 if (!c->string || strcasecmp(c->string, cmd)) {
902 privmsg(b, target, "Command %s not valid", cmd);
903 return;
905 } else
906 c = &command_channel;
908 leave = c->left + (c->cmd == command_inspect);
909 crash = c->enter - leave;
910 if (c->enter != leave)
911 privmsg(b, target, "%s: %u successes and %u crash%s, last crashing command: %s", c->string, leave, crash, crash == 1 ? "" : "es", c->failed_command);
912 else
913 privmsg(b, target, "%s: %u time%s executed succesfully", c->string, leave, leave == 1 ? "" : "s");
916 static void command_rebuild(struct bio *b, const char *nick, const char *host, const char *target, char *args)
918 int ret;
919 char *make[] = { "/usr/bin/make", "-j4", NULL };
920 char *git_reset[] = { "/usr/bin/git", "reset", "--hard", "master", NULL };
921 ret = pipe_command(b, target, nick, 0, 1, git_reset);
922 if (ret) {
923 action(b, target, "could not rebuild");
924 return;
926 ret = pipe_command(b, target, nick, 0, 1, make);
927 if (!ret)
928 kill(getpid(), SIGUSR1);
929 else if (ret > 0)
930 action(b, target, "displays an ominous %i", ret);
933 static void command_swear(struct bio *b, const char *nick, const char *host, const char *target, char *args)
935 static const char *insults[] = {
936 "featherbrain",
937 "ponyfeathers",
938 "What in the hey?",
939 "What are you, a dictionary?",
940 "TAR-DY!",
941 "[BUY SOME APPLES]",
942 "{ Your lack of bloodlust on the battlefield is proof positive that you are a soulless automaton! }",
943 "Your royal snootiness"
945 privmsg(b, target, "%s: %s", args ? args : nick, insults[getrand() % elements(insults)]);
948 static const char *perty(int64_t *t)
950 if (*t >= 14 * 24 * 3600) {
951 *t /= 7 * 24 * 3600;
952 return "weeks";
953 } else if (*t >= 48 * 3600) {
954 *t /= 24 * 3600;
955 return "days";
956 } else if (*t >= 7200) {
957 *t /= 3600;
958 return "hours";
959 } else if (*t >= 120) {
960 *t /= 60;
961 return "minutes";
963 return *t == 1 ? "second" : "seconds";
966 static void command_timeout(struct bio *b, const char *nick, const char *host, const char *target, char *args)
968 struct command_hash *c;
969 int64_t t = get_time(b, target);
970 int64_t howlong;
971 if (t < 0)
972 return;
973 char *arg = token(&args, ' ');
974 if (!arg || !args || !(howlong = atoi(args))) {
975 action(b, target, "pretends to time out");;
976 return;
978 c = &commands[strhash(arg) % elements(commands)];
979 if (c->string && !strcasecmp(c->string, arg)) {
980 c->disabled_until = t + howlong;
981 const char *str = perty(&howlong);
982 action(b, target, "disables %s for %"PRIi64" %s", arg, howlong, str);
983 } else
984 action(b, target, "clicks sadly at %s for not being able to find that command", nick);
987 static void command_mfw(struct bio *b, const char *nick, const char *host, const char *target, char *args)
989 char error[CURL_ERROR_SIZE], *new_url;
990 CURL *h = curl_easy_init();
991 struct curl_slist *headers = NULL;
992 struct curl_download_context curl_ctx = {};
993 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
994 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
995 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
996 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
997 headers = curl_slist_append(headers, "DNT: 1");
998 headers = curl_slist_append(headers, "Connection: keep-alive");
999 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
1000 curl_easy_setopt(h, CURLOPT_URL, "http://mylittlefacewhen.com/random/");
1001 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
1002 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
1003 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
1004 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
1005 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
1006 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
1007 CURLcode ret = curl_easy_perform(h);
1008 if (ret == CURLE_OK && curl_easy_getinfo(h, CURLINFO_REDIRECT_URL, &new_url) == CURLE_OK)
1009 privmsg(b, target, "%s: %s", nick, new_url);
1010 curl_slist_free_all(headers);
1011 curl_easy_cleanup(h);
1012 if (ret != CURLE_OK)
1013 privmsg(b, target, "%s: You have no face", nick);
1016 static TDB_DATA get_mail_key(const char *nick)
1018 TDB_DATA d;
1019 int i;
1020 d.dsize = strlen(nick)+1;
1021 d.dptr = malloc(d.dsize);
1022 for (i = 0; i < d.dsize - 1; ++i)
1023 d.dptr[i] = tolower(nick[i]);
1024 d.dptr[i] = 0;
1025 return d;
1028 static void command_mail(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1030 char *victim, *x;
1031 size_t len;
1032 int override = 0;
1034 if (!mail_db || !seen_db)
1035 return;
1036 TDB_DATA key, val;
1037 victim = token(&args, ' ');
1038 if (victim && !strcasecmp(victim, "-seen")) {
1039 victim = token(&args, ' ');
1040 override = 1;
1042 if (!victim || !args || victim[0] == '#' || strchr(victim, '@') || strchr(victim, '.')) {
1043 privmsg(b, target, "%s: Usage: !mail <nick> <message>", nick);
1044 return;
1046 if (!strcasecmp(victim, nick_self)) {
1047 action(b, target, "whirrs and clicks excitedly at the mail she received from %s", nick);
1048 return;
1050 if (!strcasecmp(victim, nick)) {
1051 action(b, target, "echos the words from %s back to them: %s", nick, args);
1052 return;
1054 int64_t now = get_time(b, target);
1055 if (now < 0)
1056 return;
1058 key = get_mail_key(victim);
1060 val = tdb_fetch(seen_db, key);
1061 if (val.dptr && (x = strchr(val.dptr, ','))) {
1062 *(x++) = 0;
1063 if (!admin(host) && (now - atoll(val.dptr)) < 300 && (!sstrncasecmp(x, "in ") || !sstrncasecmp(x, "joining "))) {
1064 action(b, target, "would rather not store mail for someone active so recently");
1065 goto out;
1067 } else if (!override) {
1068 privmsg(b, target, "%s: I've never seen \"%s\" use !mail -seen %s <message> to override this check.", nick, victim, victim);
1069 return;
1072 val = tdb_fetch(mail_db, key);
1073 if (!val.dptr)
1074 val.dsize = 0;
1075 else {
1076 unsigned char *cur;
1077 int letters = 0;
1078 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1079 letters++;
1080 if (letters >= 4) {
1081 action(b, target, "looks sadly at %s as she cannot hold any more mail to %s", nick, victim);
1082 goto out;
1085 len = snprintf(NULL, 0, "%"PRIi64 ",%s: %s", now, nick, args) + 1;
1086 val.dptr = realloc(val.dptr, val.dsize + len);
1087 snprintf(val.dptr + val.dsize, len, "%"PRIi64 ",%s: %s", now, nick, args);
1088 val.dsize += len;
1089 if (tdb_store(mail_db, key, val, 0) < 0)
1090 privmsg(b, target, "%s: updating mail returns %s", nick, tdb_errorstr(mail_db));
1091 else
1092 action(b, target, "whirrs and clicks at %s as she stores the mail for %s", nick, victim);
1093 out:
1094 free(val.dptr);
1095 free(key.dptr);
1098 static void single_message(struct bio *b, const char *target, char *cur, int64_t now)
1100 char *endtime, *sep = strchr(cur, ':');
1101 int64_t delta = -1;
1102 if (sep && (endtime = strchr(cur, ',')) && endtime < sep) {
1103 int64_t t = atoll(cur);
1104 if (t > 0)
1105 delta = now - t;
1106 cur = endtime + 1;
1108 if (delta >= 0) {
1109 const char *str = perty(&delta);
1110 privmsg(b, target, "%"PRIi64" %s ago from %s", delta, str, cur);
1111 } else
1112 privmsg(b, target, "From %s", cur);
1115 static void command_deliver(struct bio *b, const char *nick, const char *target)
1117 TDB_DATA key, val;
1118 unsigned char *cur;
1119 static unsigned mail_enter, mail_leave;
1120 if (mail_enter++ != mail_leave)
1121 return;
1123 if (!mail_db)
1124 return;
1125 key = get_mail_key(nick);
1126 val = tdb_fetch(mail_db, key);
1127 if (!val.dptr)
1128 goto end;
1129 int64_t now = get_time(b, NULL);
1130 if (strcasecmp(key.dptr, nick_self)) {
1131 privmsg(b, target, "%s: You've got mail!", nick);
1132 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1133 single_message(b, target, cur, now);
1135 free(val.dptr);
1136 tdb_delete(mail_db, key);
1137 end:
1138 free(key.dptr);
1139 mail_leave++;
1142 static void update_seen(struct bio *b, char *doingwhat, const char *nick)
1144 TDB_DATA key;
1145 key = get_mail_key(nick);
1146 TDB_DATA val = { .dptr = doingwhat, .dsize = strlen(doingwhat)+1 };
1147 if (seen_db)
1148 tdb_store(seen_db, key, val, 0);
1149 free(key.dptr);
1152 static void command_seen(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1154 char *arg = token(&args, ' '), *x;
1155 int64_t now = get_time(b, target);
1156 TDB_DATA key, val;
1157 if (now < 0)
1158 return;
1159 if (!seen_db || !arg) {
1160 privmsg(b, target, "%s: { Error... }", nick);
1161 return;
1163 if (!strcasecmp(arg, nick_self)) {
1164 action(b, target, "whirrs and clicks at %s", nick);
1165 return;
1167 key = get_mail_key(arg);
1168 val = tdb_fetch(seen_db, key);
1169 if (val.dptr && (x = strchr(val.dptr, ','))) {
1170 int64_t delta;
1171 const char *str;
1172 *(x++) = 0;
1173 delta = now - atoll(val.dptr);
1174 str = perty(&delta);
1175 if (delta < 0)
1176 privmsg(b, target, "%s was last seen in the future %s", arg, x);
1177 else
1178 privmsg(b, target, "%s was last seen %"PRIi64" %s ago %s", arg, delta, str, x);
1179 } else
1180 action(b, target, "cannot find any evidence that %s exists", arg);
1183 static void command_mailbag(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1185 char buffer[256];
1186 unsigned rem = sizeof(buffer)-1, first = 2;
1187 if (!mail_db)
1188 return;
1189 buffer[rem] = 0;
1191 for (TDB_DATA f = tdb_firstkey(mail_db); f.dptr;) {
1192 if (f.dsize + 4 > rem) {
1193 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1194 first = 2;
1195 rem = sizeof(buffer)-1;
1196 assert(f.dsize + 4 < rem);
1198 if (f.dptr) {
1199 if (first == 2) {
1200 first = 1;
1201 } else if (first) {
1202 rem -= 5;
1203 memcpy(&buffer[rem], " and ", 5);
1204 first = 0;
1205 } else {
1206 rem -= 2;
1207 memcpy(&buffer[rem], ", ", 2);
1209 rem -= f.dsize - 1;
1210 memcpy(&buffer[rem], f.dptr, f.dsize - 1);
1212 TDB_DATA next = tdb_nextkey(mail_db, f);
1213 free(f.dptr);
1214 f = next;
1216 if (first < 2)
1217 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1220 static void command_mailread(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1222 TDB_DATA key, val;
1223 char *victim;
1224 if (!mail_db || !(victim = token(&args, ' ')))
1225 return;
1226 key = get_mail_key(victim);
1227 val = tdb_fetch(mail_db, key);
1228 if (!val.dptr)
1229 action(b, target, "ponyshrugs as no mail for %s was found", victim);
1230 else {
1231 unsigned char *cur;
1232 int64_t now = get_time(b, NULL);
1233 action(b, target, "peeks through %s's mail", victim);
1234 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1235 single_message(b, target, cur, now);
1237 free(val.dptr);
1238 free(key.dptr);
1241 static void command_no_deliver(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1243 TDB_DATA key, val;
1244 char *cur;
1245 if (!mail_db || !(cur = token(&args, ' ')))
1246 return;
1247 key = get_mail_key(cur);
1248 val = tdb_fetch(mail_db, key);
1249 if (!val.dptr)
1250 action(b, target, "ponyshrugs as no mail for %s was found", cur);
1251 else {
1252 action(b, target, "deletes all evidence of %s's mail", cur);
1253 tdb_delete(mail_db, key);
1255 free(val.dptr);
1256 free(key.dptr);
1259 static void add_g(struct bio *b, const char *target, int g)
1261 int ret = 0;
1262 int64_t total_g = 0;
1263 TDB_DATA chan, res;
1265 if (!chan_db || target[0] != '#')
1266 return;
1268 chan.dptr = (char*)target;
1269 chan.dsize = strlen(chan.dptr)+1;
1270 res = tdb_fetch(chan_db, chan);
1271 if (res.dptr && res.dsize >= 16) {
1272 ((int64_t*)res.dptr)[1] += g;
1273 if (res.dsize >= 24)
1274 total_g = ((int64_t*)res.dptr)[2] += g;
1275 ret = tdb_store(chan_db, chan, res, 0);
1277 free(res.dptr);
1278 if (ret < 0)
1279 fprintf(stderr, "updating database: %s", tdb_errorstr(feed_db));
1280 else if (g < total_g) {
1281 int64_t last_g = total_g - g;
1282 last_g -= last_g % 1000;
1283 if (last_g + 1000 <= total_g)
1284 privmsg(b, target, "g has been said %"PRIi64" times!", total_g);
1288 static int parse_g(const char *cur, int *g)
1290 int this_g = 0;
1291 for (const unsigned char *ptr = cur; *ptr; ptr++) {
1292 if (*ptr >= 0x80)
1293 return 0;
1294 if (*ptr == 'g' || *ptr == 'G')
1295 this_g++;
1296 else if ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z'))
1297 return 0;
1298 else if (*ptr != '1' && *ptr >= '0' && *ptr <= '9')
1299 return 0;
1301 *g += this_g;
1302 return this_g;
1305 static void channel_msg(struct bio *b, const char *nick, const char *host,
1306 const char *chan, char *msg, int64_t t)
1308 char *cur = NULL, *next;
1309 int is_action = 0;
1310 int g = 0;
1312 if (!msg || !strcmp(nick, "`Daring_Do`") || !strcmp(nick, "`Celestia`") || !strcmp(nick, "derpy") || !strcmp(nick, "`Derpy`") || !strcmp(nick, "`Luna`") || !sstrncmp(nick, "GitHub") || !sstrncmp(nick, "CIA-") || !strcmp(nick, "Terminus-Bot") || !strcmp(nick, "r0m"))
1313 return;
1315 if (!sstrncasecmp(msg, "\001ACTION ")) {
1316 msg += sizeof("\001ACTION ")-1;
1317 is_action = 1;
1318 asprintf(&cur, "%"PRIi64 ",in %s: * %s %s", t, chan, nick, msg);
1319 } else
1320 asprintf(&cur, "%"PRIi64 ",in %s: <%s> %s", t, chan, nick, msg);
1321 if (t > 0)
1322 update_seen(b, cur, nick);
1323 free(cur);
1324 (void)is_action;
1326 next = msg;
1327 while ((cur = token(&next, ' '))) {
1328 if (!strcasecmp(cur, ">mfw") || !strcasecmp(cur, "mfw")) {
1329 if (!strcasecmp(chan, "#brony") || !ponify)
1330 continue;
1331 if (t < 0 || t > commands[strhash("mfw") % elements(commands)].disabled_until)
1332 command_mfw(b, nick, host, chan, NULL);
1333 return;
1334 } else if (!sstrncasecmp(cur, "http://") || !sstrncasecmp(cur, "https://")) {
1335 static char last_url[512];
1336 char *part;
1337 if (!strcmp(cur, last_url))
1338 return;
1339 strncpy(last_url, cur, sizeof(last_url)-1);
1341 if (t >= 0 && t < commands[strhash("get") % elements(commands)].disabled_until)
1342 return;
1344 else if (strcasestr(cur, "youtube.com/user") && (part = strstr(cur, "#p/"))) {
1345 char *foo;
1346 part = strrchr(part, '/') + 1;
1347 asprintf(&foo, "http://youtube.com/watch?v=%s", part);
1348 if (foo)
1349 internal_link(b, nick, host, chan, foo, 0);
1350 free(foo);
1351 return;
1352 } else if (strcasestr(cur, "twitter.com/") || strcasestr(cur, "mlfw.info") || strcasestr(cur, "mylittlefacewhen.com"))
1353 return;
1354 internal_link(b, nick, host, chan, cur, 0);
1355 return;
1357 else if (parse_g(cur, &g))
1358 continue;
1360 if (g)
1361 add_g(b, chan, g);
1364 static struct command_hash unhashed[] = {
1365 { "1", command_coinflip },
1366 { "get", command_get },
1367 { "hug", command_hug },
1368 { "hugs", command_hugs },
1369 { "cookie", command_cookie },
1370 { "mfw", command_mfw },
1371 { "swear", command_swear },
1372 { "mail", command_mail },
1373 { "seen", command_seen },
1374 { "derpy", command_derpy },
1375 { "g", command_g },
1376 { "shesaid", command_shesaid },
1378 { "rebuild", command_rebuild, 1 },
1379 { "abort", command_abort, 1 },
1380 { "crash", command_crash, 1 },
1381 { "inspect", command_inspect, 1 },
1382 { "timeout", command_timeout, 1 },
1384 { "follow", command_follow },
1385 { "unfollow", command_unfollow },
1386 { "feeds", command_feeds },
1388 // DEBUG
1389 { "feed_get", command_feed_get, 1 },
1390 { "feed_set", command_feed_set, 1 },
1391 { "feed_rem", command_feed_rem, 1 },
1392 { "feed_xxx", command_feed_xxx, 1 },
1393 { "feed_counter", command_feed_counter, 1 },
1394 { "seen_xxx", command_seen_xxx, 1 },
1395 { "mailbag", command_mailbag },
1396 { "mailread", command_mailread, 1 },
1397 { "\"deliver\"", command_no_deliver, 1 },
1400 static void init_hash(struct bio *b, const char *target)
1402 int i;
1403 for (i = 0; i < elements(unhashed); ++i) {
1404 unsigned h = strhash(unhashed[i].string) % elements(commands);
1405 if (commands[h].string)
1406 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, unhashed[i].string);
1407 else
1408 commands[h] = unhashed[i];
1410 #ifdef local_commands
1411 for (i = 0; i < elements(local_commands); ++i) {
1412 unsigned h = strhash(local_commands[i].string) % elements(commands);
1413 if (commands[h].string)
1414 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, local_commands[i].string);
1415 else
1416 commands[h] = local_commands[i];
1418 #endif
1421 void init_hook(struct bio *b, const char *target, const char *nick, unsigned is_ponified)
1423 char *cwd, *path = NULL;
1424 nick_self = nick;
1425 static const char *messages[] = {
1426 "feels circuits being activated that weren't before",
1427 "suddenly gets a better feel of her surroundings",
1428 "looks the same, yet there's definitely something different",
1429 "emits a beep as her lights begin to pulse slowly",
1430 "whirrrs and bleeps like never before",
1431 "bleeps a few times happily",
1432 "excitedly peeks at her surroundings"
1434 init_hash(b, target);
1435 ponify = is_ponified;
1437 cwd = getcwd(NULL, 0);
1438 asprintf(&path, "%s/db/feed.tdb", cwd);
1439 feed_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1440 free(path);
1441 if (!feed_db)
1442 privmsg(b, target, "Opening feed db failed: %m");
1444 asprintf(&path, "%s/db/chan.tdb", cwd);
1445 chan_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1446 free(path);
1447 if (!chan_db)
1448 privmsg(b, target, "Opening chan db failed: %m");
1450 asprintf(&path, "%s/db/mail.tdb", cwd);
1451 mail_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1452 free(path);
1453 if (!mail_db)
1454 privmsg(b, target, "Opening mail db failed: %m");
1456 asprintf(&path, "%s/db/seen.tdb", cwd);
1457 seen_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1458 free(path);
1459 if (!seen_db)
1460 privmsg(b, target, "Opening seen db failed: %m");
1462 free(cwd);
1463 action(b, target, "%s", messages[getrand() % elements(messages)]);
1466 static void __attribute__((destructor)) shutdown_hook(void)
1468 if (seen_db)
1469 tdb_close(seen_db);
1470 if (mail_db)
1471 tdb_close(mail_db);
1472 if (feed_db)
1473 tdb_close(feed_db);
1474 if (chan_db)
1475 tdb_close(chan_db);
1478 static char *nom_special(char *line)
1480 while (*line) {
1481 if (*line == 0x03) {
1482 line++;
1483 if (*line >= '0' && *line <= '9')
1484 line++;
1485 else continue;
1486 if (*line >= '0' && *line <= '9')
1487 line++;
1488 if (line[0] == ',' && line[1] >= '0' && line[1] <= '9')
1489 line += 2;
1490 else continue;
1491 if (*line >= '0' && *line <= '9')
1492 line++;
1493 } else if (*line != 0x02 && /* BOLD */
1494 *line != 0x1f && /* UNDERLINE */
1495 *line != 0x16 && /* ITALIC */
1496 *line != 0x06 && /* NFI, is this even used? */
1497 *line != 0x07 && /* NFI, is this even used? */
1498 *line != 0x0f) /* NORMAL */
1499 return line;
1500 else
1501 line++;
1503 return line;
1506 static char *cleanup_special(char *line)
1508 char *cur, *start = nom_special(line);
1509 if (!*start)
1510 return NULL;
1511 for (line = cur = start; *line; line = nom_special(line))
1512 *(cur++) = *(line++);
1514 for (cur--; cur >= start; --cur)
1515 if (*cur != ' ' && *cur != '\001')
1516 break;
1518 if (cur < start)
1519 return NULL;
1520 cur[1] = 0;
1521 return start;
1524 static void rss_check(struct bio *b, const char *channel, int64_t t)
1526 static unsigned rss_enter, rss_leave;
1527 if (t >= 0 && rss_enter == rss_leave) {
1528 rss_enter++;
1529 channel_feed_check(b, channel, t);
1530 rss_leave++;
1534 static const char *privileged_command[] = {
1535 "{ Rarity, I love you so much! }",
1536 "{ Rarity, have I ever told you that I love you? }",
1537 "{ Yes, I love my sister, Rarity. }",
1538 "{ Raaaaaaaaaaaaaarity. }",
1539 "{ You do not fool me, Rari...bot! }"
1542 void privmsg_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1543 const char *const *args, unsigned nargs)
1545 char *cmd_args, *cmd;
1546 const char *target = args[0][0] == '#' ? args[0] : prefix;
1547 unsigned chan, nick_len;
1548 struct command_hash *c;
1549 int64_t t = get_time(b, target);
1550 int is_admin = admin(host);
1552 chan = args[0][0] == '#';
1553 if (chan) {
1554 rss_check(b, args[0], t);
1555 command_deliver(b, prefix, args[0]);
1557 cmd_args = cleanup_special((char*)args[1]);
1558 if (!cmd_args)
1559 return;
1561 if (ident && (!strcasecmp(ident, "Revolver") || !strcasecmp(ident, "Rev")))
1562 return;
1564 if (chan && cmd_args[0] == '!') {
1565 cmd_args++;
1566 } else if (chan && (nick_len = strlen(nick_self)) &&
1567 !strncasecmp(cmd_args, nick_self, nick_len) &&
1568 (cmd_args[nick_len] == ':' || cmd_args[nick_len] == ',') && cmd_args[nick_len+1] == ' ') {
1569 cmd_args += nick_len + 2;
1570 if (!cmd_args[0])
1571 return;
1572 chan = 2;
1573 } else if (chan) {
1574 if (command_channel.enter == command_channel.left) {
1575 command_channel.enter++;
1576 snprintf(command_channel.failed_command,
1577 sizeof(command_channel) - offsetof(struct command_hash, failed_command) - 1,
1578 "%s:%s (%s@%s) \"#\" %s", target, prefix, ident, host, cmd_args);
1579 channel_msg(b, prefix, host, args[0], cmd_args, t);
1580 command_channel.left++;
1582 return;
1584 cmd = token(&cmd_args, ' ');
1585 if (!chan && cmd_args && cmd_args[0] == '#') {
1586 if (!is_admin) {
1587 privmsg(b, target, "%s: %s", prefix, privileged_command[getrand() % elements(privileged_command)]);
1588 return;
1590 target = token(&cmd_args, ' ');
1593 c = &commands[strhash(cmd) % elements(commands)];
1594 if (c->string && !strcasecmp(c->string, cmd)) {
1595 if (c->left != c->enter)
1596 privmsg(b, target, "Command %s is disabled because of a crash", c->string);
1597 else if (t > 0 && t < c->disabled_until && !is_admin) {
1598 int64_t delta = c->disabled_until - t;
1599 const char *str = perty(&delta);
1600 b->writeline(b, "NOTICE %s :Command %s is on timeout for the next %"PRIi64 " %s", prefix, c->string, delta, str);
1601 } else if (!c->admin || is_admin) {
1602 c->enter++;
1603 snprintf(c->failed_command, sizeof(*c) - offsetof(struct command_hash, failed_command) - 1,
1604 "%s:%s (%s@%s) \"%s\" %s", target, prefix, ident, host, cmd, cmd_args);
1605 c->cmd(b, prefix, host, target, cmd_args);
1606 c->left++;
1607 } else
1608 privmsg(b, target, "%s: %s", prefix, privileged_command[getrand() % elements(privileged_command)]);
1609 } else if (chan == 2)
1610 privmsg(b, target, "%s: { I love you! }", prefix);
1613 void command_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1614 const char *command, const char *const *args, unsigned nargs)
1616 char *buf = NULL;
1617 int64_t t = -1;
1618 if (!strcasecmp(command, "NOTICE")) {
1619 if (nargs < 2 || args[0][0] == '#')
1620 return;
1621 if (admin(host))
1622 b->writeline(b, "%s", args[1]);
1623 else
1624 fprintf(stderr, "%s: %s\n", prefix, args[1]);
1625 } else if (!strcasecmp(command, "JOIN")) {
1626 t = get_time(b, args[0]);
1627 rss_check(b, args[0], t);
1628 command_deliver(b, prefix, args[0]);
1629 asprintf(&buf, "%"PRIi64",joining %s", t, args[0]);
1630 } else if (!strcasecmp(command, "PART")) {
1631 t = get_time(b, args[0]);
1632 rss_check(b, args[0], t);
1633 asprintf(&buf, "%"PRIi64",leaving %s", t, args[0]);
1634 } else if (!strcasecmp(command, "QUIT")) {
1635 t = get_time(b, NULL);
1636 asprintf(&buf, "%"PRIi64",quitting with the message \"%s\"", t, args[0]);
1637 } else if (!strcasecmp(command, "NICK")) {
1638 t = get_time(b, NULL);
1639 if (t >= 0) {
1640 asprintf(&buf, "%"PRIi64",changing nick from %s", t, prefix);
1641 if (buf)
1642 update_seen(b, buf, args[0]);
1643 free(buf);
1644 buf = NULL;
1646 asprintf(&buf, "%"PRIi64",changing nick to %s", t, args[0]);
1647 } else if (0) {
1648 int i;
1649 fprintf(stderr, ":%s!%s%s %s", prefix, ident, host, command);
1650 for (i = 0; i < nargs; ++i)
1651 fprintf(stderr, " %s", args[i]);
1652 fprintf(stderr, "\n");
1654 if (t >= 0 && buf)
1655 update_seen(b, buf, prefix);
1656 free(buf);
1657 return;