fixup stripping?
[fillybot.git] / mod.c
blobbe1d4c262f66a02feca4fc86e770145084a873a9
1 #include "mod.h"
3 #include <libxml/xmlstring.h>
4 #include <libxml/HTMLparser.h>
6 #include <stdint.h>
7 #include <inttypes.h>
8 #include <sys/wait.h>
9 #include <curl/curl.h>
11 #include <magic.h>
13 #include "entities.h"
15 static const char *nick_self;
16 static unsigned ponify;
17 #define elements(x) (sizeof(x)/sizeof(*x))
19 static struct tdb_context *feed_db, *chan_db, *mail_db, *seen_db;
21 static struct command_hash command_channel = { "#", NULL };
23 #define sstrncmp(a, b) (strncmp(a, b, sizeof(b)-1))
24 #define sstrncasecmp(a, b) (strncasecmp(a, b, sizeof(b)-1))
26 #define SPAM_CUTOFF 5
28 static int pipe_command(struct bio *b, const char *target, const char *nick, int redirect_stdout, int redirect_stderr, char *argv[])
30 int fd[2];
31 int lines = 0;
32 if (pipe2(fd, O_CLOEXEC) < 0) {
33 privmsg(b, target, "Could not create pipe: %m");
34 return -1;
36 pid_t pid = fork();
37 if (pid < 0) {
38 privmsg(b, target, "Could not fork: %m");
39 close(fd[0]);
40 close(fd[1]);
41 return -1;
42 } else if (!pid) {
43 int fdnull = open("/dev/null", O_WRONLY|O_CLOEXEC);
44 close(fd[0]);
45 if (dup3(redirect_stdout ? fd[1] : fdnull, 1, 0) < 0)
46 exit(errno);
47 if (dup3(redirect_stderr ? fd[1] : fdnull, 2, 0) < 0)
48 exit(errno);
49 exit(execv(argv[0], argv));
50 } else {
51 int loc = -1;
52 char buffer[0x100];
53 int bufptr = 0, ret;
54 close(fd[1]);
55 fcntl(fd[0], F_SETFL, O_NONBLOCK);
56 while ((ret = waitpid(pid, &loc, WNOHANG)) >= 0) {
57 while (read(fd[0], buffer+bufptr, 1) == 1) {
58 if (buffer[bufptr] != '\n' && bufptr < sizeof(buffer)-1) {
59 bufptr++;
60 continue;
61 } else if (bufptr) {
62 buffer[bufptr] = 0;
63 bufptr = 0;
64 lines++;
65 if (lines < SPAM_CUTOFF)
66 privmsg(b, nick, "%s", buffer);
67 else
68 fprintf(stderr, "%s\n", buffer);
72 if (ret) {
73 if (bufptr) {
74 buffer[bufptr] = 0;
75 if (lines < SPAM_CUTOFF)
76 privmsg(b, nick, "%s", buffer);
77 else
78 fprintf(stderr, "%s\n", buffer);
80 if (lines >= SPAM_CUTOFF)
81 privmsg(b, nick, "%i lines suppressed", lines - SPAM_CUTOFF + 1);
82 break;
85 if (ret < 0)
86 privmsg(b, target, "error on waitpid: %m");
87 else
88 ret = loc;
89 close(fd[0]);
90 return ret;
94 #include "local.c"
96 static struct command_hash commands[2048];
98 static void command_abort(struct bio *b, const char *nick, const char *host, const char *target, char *args)
100 abort();
101 return;
104 static void command_crash(struct bio *b, const char *nick, const char *host, const char *target, char *args)
106 *(char*)0 = 0;
109 static void command_coinflip(struct bio *b, const char *nick, const char *host, const char *target, char *args)
111 if (getrand() & 1)
112 action(b, target, "whirrrs and clicks excitedly at %s", nick);
113 else
114 action(b, target, "eyes %s as nothing happens", nick);
117 static void squash(char *title)
119 char *start = title;
120 goto start;
121 while (*title) {
122 while (*title != '\n' && *title != '\r' && *title != '\t' && *title != ' ' && *title) {
123 *(start++) = *(title++);
125 if (*title)
126 *(start++) = ' ';
127 start:
128 while (*title == '\n' || *title == '\r' || *title == '\t' || *title == ' ')
129 title++;
131 *start = 0;
134 struct curl_download_context
136 char *data;
137 size_t len;
140 static size_t write_data(void *ptr, size_t size, size_t nmemb, void *member) {
141 struct curl_download_context *ctx = member;
142 size *= nmemb;
143 ctx->data = realloc(ctx->data, ctx->len + size);
144 memcpy(ctx->data + ctx->len, ptr, size);
145 ctx->len += size;
146 return size;
149 static const char *get_text(xmlNode *cur)
151 for (; cur; cur = cur->next) {
152 if (cur->type == XML_TEXT_NODE)
153 return cur->content;
155 return NULL;
158 static const char *get_link(xmlAttr *cur, const char *which)
160 for (; cur; cur = cur->next) {
161 if (!strcasecmp(cur->name, which))
162 return get_text(cur->children);
164 return NULL;
167 static int get_feed_entry(struct bio *b, const char *nick, const char *target, const char *category, xmlNode *entry, const char **title, const char **link)
169 const char *cur_title = NULL, *cur_link = NULL;
170 xmlNode *cur;
171 int cur_match = !category;
172 for (cur = entry->children; cur; cur = cur->next) {
173 const char *name = cur->name, *cur_cat;
174 if (cur->type != XML_ELEMENT_NODE)
175 continue;
176 else if (!strcasecmp(name, "link")) {
177 const char *ishtml = get_link(cur->properties, "type");
178 const char *rel = get_link(cur->properties, "rel");
179 if ((!ishtml || !strcasecmp(ishtml, "text/html")) &&
180 rel && !strcasecmp(rel, "alternate"))
181 cur_link = get_link(cur->properties, "href");
182 } else if (!strcasecmp(name, "title"))
183 cur_title = get_text(cur->children);
184 else if (!cur_match && !strcasecmp(name, "category") &&
185 (cur_cat = get_link(cur->properties, "term")) &&
186 strcasestr(cur_cat, category))
187 cur_match = 1;
190 if (cur_title)
191 *title = cur_title;
192 else
193 *title = "<no title>";
194 *link = cur_link;
195 return !cur_link ? -1 : cur_match;
198 static const char *walk_feed(struct bio *b, const char *nick, const char *target, const char *url, const char *category, xmlNode *root, const char *last_link)
200 const char *main_title = NULL, *main_subtitle = NULL, *main_link = NULL, *title, *link, *prev_link = NULL, *prev_title = NULL;
201 int match, updates = 0;
202 xmlNode *cur, *entry = NULL;
203 for (cur = root->children; cur; cur = cur->next) {
204 const char *name = cur->name;
205 if (cur->type != XML_ELEMENT_NODE)
206 continue;
207 else if (!strcasecmp(name, "category"))
208 continue;
209 else if (!strcasecmp(name, "entry"))
210 entry = entry ? entry : cur;
211 else if (!strcasecmp(name, "title"))
212 main_title = get_text(cur->children);
213 else if (!strcasecmp(name, "subtitle"))
214 main_subtitle = get_text(cur->children);
215 else if (!strcasecmp(name, "link")) {
216 const char *ishtml = get_link(cur->properties, "type");
217 const char *rel = get_link(cur->properties, "rel");
218 if ((!ishtml || !strcasecmp(ishtml, "text/html")) && rel && !strcasecmp(rel, "alternate"))
219 main_link = get_link(cur->properties, "href");
222 if (!main_link || !main_title) {
223 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
224 return NULL;
226 if (!entry)
227 return NULL;
229 if (!last_link)
230 privmsg(b, target, "adding blog %s \"%s\": %s", main_link, main_title, main_subtitle);
232 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
233 if (match < 0)
234 return NULL;
236 for (; !match && entry; entry = entry->next) {
237 if (!strcasecmp(entry->name, "entry"))
238 match = get_feed_entry(b, nick, target, category, entry, &title, &link);
241 if (match < 0)
242 return NULL;
243 if (!last_link) {
244 if (match > 0)
245 privmsg(b, target, "Most recent entry: %s %s", link, title);
246 else
247 privmsg(b, target, "Currently having no entries for this feed that matches the filter");
248 return link;
250 if (!strcmp(last_link, link) || !match || !entry)
251 return link;
253 for (entry = entry->next; entry; entry = entry->next) {
254 const char *cur_link, *cur_title;
255 if (strcasecmp(entry->name, "entry"))
256 continue;
257 match = get_feed_entry(b, nick, target, category, entry, &cur_title, &cur_link);
258 if (match < 0 || !strcmp(last_link, cur_link))
259 break;
260 if (match) {
261 prev_link = cur_link;
262 prev_title = cur_title;
263 updates++;
266 if (updates == 1)
267 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
268 else if (updates > 1)
269 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
270 privmsg(b, target, "( %s ): %s", link, title);
271 return link;
274 static const char *walk_rss(struct bio *b, const char *nick, const char *target, const char *url, xmlNode *root, const char *last_link)
276 const char *main_title = NULL, *main_link = NULL, *title = NULL, *link = NULL, *ver;
277 xmlNode *cur, *entry = NULL;
278 ver = get_link(root->properties, "version");
279 if (!ver || strcmp(ver, "2.0")) {
280 if (!ver)
281 privmsg(b, target, "%s: Could not parse rss feed", nick);
282 else
283 privmsg(b, target, "%s: Invalid rss version \"%s\"", nick, ver);
284 return NULL;
286 for (cur = root->children; cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
287 if (!cur)
288 return NULL;
289 for (cur = cur->children; cur; cur = cur->next) {
290 const char *name = cur->name;
291 if (cur->type != XML_ELEMENT_NODE)
292 continue;
293 if (!strcasecmp(name, "title"))
294 main_title = get_text(cur->children);
295 else if (!strcasecmp(name, "link"))
296 main_link = main_link ? main_link : get_text(cur->children);
297 else if (!strcasecmp(name, "item"))
298 entry = entry ? entry : cur;
300 if (!main_link || !main_title) {
301 privmsg(b, target, "%s: Failed to parse main: %s %s", nick, main_link, main_title);
302 return NULL;
304 if (!entry)
305 return NULL;
307 link = title = NULL;
308 for (cur = entry->children; cur; cur = cur->next) {
309 const char *name = cur->name;
310 if (cur->type != XML_ELEMENT_NODE)
311 continue;
312 if (!strcasecmp(name, "title"))
313 title = get_text(cur->children);
314 else if (!strcasecmp(name, "link"))
315 link = get_text(cur->children);
317 if (!title)
318 title = "<no title>";
319 if (!link) {
320 privmsg(b, target, "%s: Failed to parse entry: %s %s", nick, link, title);
321 return NULL;
323 if (!last_link) {
324 privmsg(b, target, "adding blog %s \"%s\"", main_link, main_title);
325 privmsg(b, target, "Most recent entry: %s %s", link, title);
326 } else if (strcmp(last_link, link)) {
327 int updates = 0;
328 const char *prev_title = NULL, *prev_link = NULL, *cur_title = NULL, *cur_link = NULL;
329 for (entry = entry->next; entry; entry = entry->next) {
330 if (strcasecmp(entry->name, "item"))
331 continue;
332 prev_title = cur_title;
333 prev_link = cur_link;
334 cur_title = cur_link = NULL;
335 for (cur = entry->children; cur; cur = cur->next) {
336 const char *name = cur->name;
337 if (cur->type != XML_ELEMENT_NODE)
338 continue;
339 if (!strcasecmp(name, "title"))
340 cur_title = get_text(cur->children);
341 else if (!strcasecmp(name, "link"))
342 cur_link = get_text(cur->children);
344 if (!cur_title)
345 cur_title = "<no title>";
346 if (!cur_link || !strcmp(last_link, cur_link))
347 break;
348 updates++;
350 if (updates == 1)
351 privmsg(b, target, "( %s ): %s", prev_link, prev_title);
352 else if (updates > 1)
353 privmsg(b, target, "( %s ): %u updates, linking most recent", main_link, 1+updates);
354 privmsg(b, target, "( %s ): %s", link, title);
356 return link;
359 // HTML is a mess, so I'm just walking the tree depth first until I find the next element..
360 static xmlNode *next_link(xmlNode *cur_node)
362 if (cur_node->children)
363 return cur_node->children;
364 while (cur_node) {
365 if (cur_node->next)
366 return cur_node->next;
367 cur_node = cur_node->parent;
369 return NULL;
372 static const char *get_atom_link(xmlNode *cur)
374 for (; cur; cur = next_link(cur)) {
375 if (cur->type != XML_ELEMENT_NODE)
376 continue;
377 if (!strcasecmp(cur->name, "link")) {
378 const char *isxml = get_link(cur->properties, "type");
379 if (isxml && !strcasecmp(isxml, "application/atom+xml"))
380 return get_link(cur->properties, "href");
383 return NULL;
386 static const char *get_rss_link(xmlNode *cur)
388 for (; cur; cur = next_link(cur)) {
389 if (cur->type != XML_ELEMENT_NODE)
390 continue;
391 if (!strcasecmp(cur->name, "link")) {
392 const char *isxml = get_link(cur->properties, "type");
393 if (isxml && !strcasecmp(isxml, "application/rss+xml"))
394 return get_link(cur->properties, "href");
397 return NULL;
400 static void do_html(struct bio *b, const char *nick, const char *target, const char *url, const char *data, unsigned len)
402 htmlDocPtr ctx = htmlReadMemory(data, len, 0, url, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
403 xmlNode *root = xmlDocGetRootElement(ctx);
404 const char *link = get_atom_link(root);
405 if (link)
406 privmsg(b, target, "%s: not a valid feed link, try atom: %s", nick, link);
407 else if ((link = get_rss_link(root)))
408 privmsg(b, target, "%s: not a valid feed link, try rss: %s", nick, link);
409 else
410 privmsg(b, target, "%s: not a valid feed link, no suggestion found", nick);
411 xmlFreeDoc(ctx);
414 static size_t get_time_from_header(void *data, size_t size, size_t size2, void *ptr)
416 char *d, *e;
417 size *= size2;
418 if (sstrncmp(data, "Last-Modified: "))
419 return size;
420 data += sizeof("Last-Modified: ")-1;
421 *(char**)ptr = d = strdup(data);
422 if ((e = strchr(d, '\r')))
423 *e = 0;
424 return size;
427 static int check_single_feed(struct bio *b, const char *target, TDB_DATA key, const char *last_modified, const char *url, const char *link, const char *nick)
429 struct curl_download_context curl_ctx = {};
430 struct curl_slist *headers = NULL;
431 char error[CURL_ERROR_SIZE], *category = strchr(url, '#');
432 int retval = -1;
433 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
434 headers = curl_slist_append(headers, "Accept: */*");
435 if (category)
436 *(category++) = 0;
438 CURL *h = curl_easy_init();
439 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
440 curl_easy_setopt(h, CURLOPT_URL, url);
441 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
442 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
443 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
444 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
445 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
446 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
447 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
448 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
449 curl_easy_setopt(h, CURLOPT_FILETIME, 1);
450 curl_easy_setopt(h, CURLOPT_HEADERFUNCTION, get_time_from_header);
451 curl_easy_setopt(h, CURLOPT_WRITEHEADER, &last_modified);
452 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
454 if (last_modified) {
455 char *tmp;
456 asprintf(&tmp, "If-Modified-Since: %s", last_modified);
457 headers = curl_slist_append(headers, tmp);
458 free(tmp);
461 int success = curl_easy_perform(h);
462 curl_slist_free_all(headers);
463 alarm(ALARM_TIME);
464 if (success == CURLE_OK) {
465 char *mime = NULL;
466 long code;
467 curl_easy_getinfo(h, CURLINFO_CONTENT_TYPE, &mime);
468 curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &code);
469 if (code == 304)
471 else if (!mime || !sstrncmp(mime, "application/xml") || !sstrncmp(mime, "text/xml")) {
472 const char *ret_link = NULL;
473 xmlDocPtr ctx = xmlReadMemory(curl_ctx.data, curl_ctx.len, 0, url, XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
474 xmlNode *root = xmlDocGetRootElement(ctx);
476 if (!strcasecmp(root->name, "feed"))
477 ret_link = walk_feed(b, nick, target, url, category, root, link);
478 else if (!strcasecmp(root->name, "rss"))
479 ret_link = walk_rss(b, nick, target, url, root, link);
480 else {
481 privmsg(b, target, "Unknown feed type \"%s\"", root->name);
482 goto free_ctx;
484 if (category)
485 category[-1] = '#';
487 if (!ret_link)
488 privmsg(b, target, "Could not feed parse correctly");
489 else if (ret_link && (!link || strcmp(ret_link, link))) {
490 TDB_DATA val;
491 asprintf((char**)&val.dptr, "%s\001%s", last_modified ? last_modified : "", ret_link);
492 val.dsize = strlen(val.dptr)+1;
493 if (tdb_store(feed_db, key, val, 0) < 0)
494 privmsg(b, target, "updating returns %s", tdb_errorstr(feed_db));
495 free(val.dptr);
496 retval = 1;
498 else
499 retval = 0;
501 free_ctx:
502 xmlFreeDoc(ctx);
503 } else if (link)
505 else if (!sstrncmp(mime, "text/html") || !sstrncmp(mime, "application/xhtml+xml"))
506 do_html(b, nick, target, url, curl_ctx.data, curl_ctx.len);
507 else
508 privmsg(b, target, "unhandled content type %s", mime);
509 } else if (!link)
510 privmsg(b, target, "Error %s (%u)\n", error, success);
512 free(curl_ctx.data);
513 curl_easy_cleanup(h);
514 return retval;
517 static void command_follow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
519 char *space, *last_link = NULL;
520 TDB_DATA key, val;
521 int ret;
522 if (!feed_db)
523 return;
524 if (target[0] != '#') {
525 privmsg(b, target, "%s: Can only follow on channels", nick);
526 return;
528 if (!args || !*args) {
529 privmsg(b, target, "%s: Usage: !follow <url>", nick);
530 return;
533 if (((space = strchr(args, ' ')) && space < strchr(args, '#')) ||
534 (sstrncmp(args, "http://") && sstrncmp(args, "https://"))) {
535 privmsg(b, target, "%s: Invalid url", nick);
536 return;
539 alarm(0);
540 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, args)+1;
541 val = tdb_fetch(feed_db, key);
542 if (val.dptr)
543 last_link = strchr(val.dptr, '\001');
544 ret = check_single_feed(b, target, key, NULL, args, last_link ? last_link+1 : NULL, nick);
545 free(val.dptr);
546 if (!ret)
547 privmsg(b, target, "%s: Not updated", nick);
548 free(key.dptr);
551 static void channel_feed_check(struct bio *b, const char *target, int64_t now)
553 int len = strlen(target);
554 TDB_DATA chan, res;
555 if (!feed_db || !chan_db)
556 return;
557 chan.dptr = (char*)target;
558 chan.dsize = len+1;
559 res = tdb_fetch(chan_db, chan);
560 if (res.dptr && res.dsize == 8) {
561 uint64_t then = *(uint64_t*)res.dptr;
562 if (now - then <= 2000)
563 return;
565 alarm(0);
566 free(res.dptr);
567 res.dptr = (unsigned char*)&now;
568 res.dsize = 8;
569 if (tdb_store(chan_db, chan, res, 0) < 0) {
570 static int complain_db;
571 if (!complain_db++)
572 privmsg(b, target, "updating database: %s", tdb_errorstr(feed_db));
573 goto out;
576 /* Reset the alarm on every get, we are not actually in danger of doing an infinite loop, probably */
577 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
578 TDB_DATA f = tdb_fetch(feed_db, d);
579 TDB_DATA next = tdb_nextkey(feed_db, d);
581 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
582 const char *url = (char*)d.dptr + len + 1;
583 char *sep;
584 if ((sep = strchr(f.dptr, '\001'))) {
585 *(sep++) = 0;
586 check_single_feed(b, target, d, f.dptr, url, sep, target);
587 alarm(0);
590 free(d.dptr);
591 free(f.dptr);
592 d = next;
594 out:
595 alarm(ALARM_TIME);
598 static void command_unfollow(struct bio *b, const char *nick, const char *host, const char *target, char *args)
600 TDB_DATA key;
601 char *url;
603 if (!feed_db)
604 return;
606 if (!(url = token(&args, ' ')) || (sstrncmp(url, "http://") && sstrncmp(url, "https://"))) {
607 privmsg(b, target, "%s: Invalid url", nick);
608 return;
610 if (target[0] != '#') {
611 privmsg(b, target, "%s: Can only unfollow on channels", nick);
612 return;
614 key.dsize = asprintf((char**)&key.dptr, "%s,%s", target, url)+1;
615 if (tdb_delete(feed_db, key) < 0) {
616 if (tdb_error(feed_db) == TDB_ERR_NOEXIST)
617 privmsg(b, target, "%s: Not following %s on this channel", nick, url);
618 else
619 privmsg(b, target, "%s: Could not delete: %s", nick, tdb_errorstr(feed_db));
620 } else
621 privmsg(b, target, "%s: No longer following %s", nick, url);
622 free(key.dptr);
625 static void command_feeds(struct bio *b, const char *nick, const char *host, const char *target, char *args)
627 int len = strlen(target), found = 0;
628 if (!feed_db)
629 return;
630 if (target[0] != '#') {
631 privmsg(b, target, "%s: Only useful in channels..", nick);
632 return;
634 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
635 TDB_DATA f = tdb_fetch(feed_db, d);
636 TDB_DATA next = tdb_nextkey(feed_db, d);
638 if (!strncmp(d.dptr, target, len) && d.dptr[len] == ',') {
639 privmsg(b, target, "%s: following %s", nick, d.dptr + len + 1);
640 found++;
642 free(d.dptr);
643 free(f.dptr);
644 d = next;
646 if (!found)
647 privmsg(b, target, "%s: not following any feed on %s", nick, target);
650 static void command_feed_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
652 if (!feed_db)
653 return;
654 for (TDB_DATA d = tdb_firstkey(feed_db); d.dptr;) {
655 TDB_DATA next = tdb_nextkey(feed_db, d);
656 if (!args || strcasestr(d.dptr, args)) {
657 TDB_DATA f = tdb_fetch(feed_db, d);
658 privmsg(b, target, "%s: %s = %s", nick, d.dptr, f.dptr);
659 free(f.dptr);
661 if (strlen(d.dptr)+1 < d.dsize) {
662 privmsg(b, target, "%s: removed buggy entry", nick);
663 tdb_delete(feed_db, d);
665 free(d.dptr);
666 d = next;
670 static void command_feed_set(struct bio *b, const char *nick, const char *host, const char *target, char *args)
672 if (!feed_db)
673 return;
674 TDB_DATA key, val;
675 key.dptr = token(&args, ' ');
676 char *value = token(&args, ' ');
677 if (!key.dptr || !value)
678 return;
679 key.dsize = strlen(key.dptr) + 1;
680 val.dsize = strlen(value) + 2;
681 val.dptr = malloc(val.dsize);
682 strcpy(val.dptr+1, value);
683 val.dptr[0] = '\001';
684 if (tdb_store(feed_db, key, val, 0) < 0)
685 privmsg(b, target, "%s: setting failed: %s", nick, tdb_errorstr(feed_db));
686 else
687 privmsg(b, target, "%s: burp", nick);
688 free(val.dptr);
691 static void command_feed_rem(struct bio *b, const char *nick, const char *host, const char *target, char *args)
693 if (!feed_db || !args)
694 return;
695 TDB_DATA key = { .dptr = (unsigned char*)args, .dsize = strlen(args)+1 };
696 if (tdb_delete(feed_db, key) < 0)
697 privmsg(b, target, "%s: removing failed: %s", nick, tdb_errorstr(feed_db));
698 else
699 privmsg(b, target, "%s: burp", nick);
702 static void command_feed_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
704 if (!feed_db)
705 return;
707 tdb_wipe_all(feed_db);
708 privmsg(b, target, "%s: all evidence erased", nick);
711 static void command_seen_xxx(struct bio *b, const char *nick, const char *host, const char *target, char *args)
713 if (!seen_db)
714 return;
716 tdb_wipe_all(seen_db);
717 privmsg(b, target, "%s: all evidence erased", nick);
720 static void command_feed_counter(struct bio *b, const char *nick, const char *host, const char *target, char *args)
722 if (!chan_db)
723 return;
724 tdb_wipe_all(chan_db);
725 privmsg(b, target, "%s: All update counters reset", nick);
728 static char *get_text_appended(xmlNode *cur)
730 for (; cur; cur = cur->next) {
731 if (cur->type != XML_TEXT_NODE)
732 continue;
733 return strdup(cur->content);
735 return NULL;
738 static char *get_title(struct bio *b, xmlNode *cur_node)
740 for (; cur_node; cur_node = next_link(cur_node)) {
741 if (cur_node->type == XML_ELEMENT_NODE && !strcasecmp(cur_node->name, "title")) {
742 if (!cur_node->children)
743 return NULL;
744 return get_text_appended(cur_node->children);
747 return NULL;
750 static void internal_link(struct bio *b, const char *nick, const char *host, const char *target, char *args, unsigned verbose)
752 CURL *h;
753 struct curl_slist *headers = NULL;
754 char error[CURL_ERROR_SIZE];
755 int success, sent = verbose;
756 struct curl_download_context curl_ctx = {};
758 if (!args)
759 return;
760 int64_t stop, start = get_time(b, target);
762 alarm(0);
763 h = curl_easy_init();
764 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
765 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
766 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
767 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
768 headers = curl_slist_append(headers, "DNT: 1");
769 headers = curl_slist_append(headers, "Connection: keep-alive");
770 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
771 curl_easy_setopt(h, CURLOPT_URL, args);
772 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
773 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
774 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
775 curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1);
776 curl_easy_setopt(h, CURLOPT_MAXREDIRS, 3);
777 curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
778 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
779 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
780 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
781 success = curl_easy_perform(h);
782 curl_easy_cleanup(h);
783 curl_slist_free_all(headers);
784 alarm(ALARM_TIME);
785 if (success == CURLE_OK) {
786 magic_t m = magic_open(MAGIC_MIME_TYPE);
787 magic_load(m, NULL);
788 const char *mime = magic_buffer(m, curl_ctx.data, curl_ctx.len);
789 if (strstr(mime, "text/html") || strstr(mime, "application/xml") || strstr(mime, "application/xhtml+xml")) {
790 htmlDocPtr ctx = htmlReadMemory(curl_ctx.data, curl_ctx.len, 0, args, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING);
791 xmlNode *root_element = xmlDocGetRootElement(ctx);
792 char *title = get_title(b, root_element);
793 if (title) {
794 char *nuke;
795 squash(title);
796 decode_html_entities_utf8(title, NULL);
797 if ((nuke = strstr(title, " on SoundCloud - Create")))
798 *nuke = 0;
799 if (*title) {
800 privmsg(b, target, "%s linked %s", nick, title);
801 sent = 1;
803 free(title);
805 if (verbose && !title)
806 privmsg(b, target, "%s linked %s page with invalid title", nick, mime);
807 xmlFreeDoc(ctx);
808 } else if (verbose) {
809 magic_setflags(m, MAGIC_COMPRESS);
810 const char *desc = magic_buffer(m, curl_ctx.data, curl_ctx.len);
811 privmsg(b, target, "%s linked type %s", nick, desc);
813 magic_close(m);
815 if (verbose && success != CURLE_OK)
816 privmsg(b, target, "Error %s (%u)\n", error, success);
817 else if (!sent && (stop = get_time(b, target)) - start >= 15) {
818 privmsg(b, target, "Link (%s) by %s timed out, disabling links for 10 seconds", args, nick);
819 commands[strhash("get") % elements(commands)].disabled_until = stop + 10;
821 free(curl_ctx.data);
824 static void command_get(struct bio *b, const char *nick, const char *host, const char *target, char *args)
826 if (!args || (sstrncmp(args, "http://") && sstrncmp(args, "https://")))
827 return;
828 internal_link(b, nick, host, target, token(&args, ' '), 1);
831 static void command_hug(struct bio *b, const char *nick, const char *host, const char *target, char *args)
833 action(b, target, "gives a robotic hug to %s", args ? args : nick);
836 static void command_hugs(struct bio *b, const char *nick, const char *host, const char *target, char *args)
838 action(b, target, "gives a lunar hug to %s", args ? args : nick);
841 static void command_derpy(struct bio *b, const char *nick, const char *host, const char *target, char *args)
843 static const char *insults[] = {
844 "accidentally shocks herself",
845 "tumbles down the stairs like a slinky",
846 "whirrrs and clicks in a screeching way",
847 "had problems executing this command",
848 "breaks down entirely",
849 "uses her magic to levitate herself off the ground, then hits it face first"
851 action(b, target, "%s", insults[getrand() % elements(insults)]);
854 static void command_inspect(struct bio *b, const char *nick, const char *host, const char *target, char *args)
856 struct command_hash *c;
857 unsigned leave, crash;
858 char *cmd;
859 if (!args || !(cmd = token(&args, ' ')))
860 return;
862 if (strcmp(cmd, "#")) {
863 c = &commands[strhash(cmd) % elements(commands)];
864 if (!c->string || strcasecmp(c->string, cmd)) {
865 privmsg(b, target, "Command %s not valid", cmd);
866 return;
868 } else
869 c = &command_channel;
871 leave = c->left + (c->cmd == command_inspect);
872 crash = c->enter - leave;
873 if (c->enter != leave)
874 privmsg(b, target, "%s: %u successes and %u crash%s, last crashing command: %s", c->string, leave, crash, crash == 1 ? "" : "es", c->failed_command);
875 else
876 privmsg(b, target, "%s: %u time%s executed succesfully", c->string, leave, leave == 1 ? "" : "s");
879 static void command_rebuild(struct bio *b, const char *nick, const char *host, const char *target, char *args)
881 int ret;
882 char *make[] = { "/usr/bin/make", "-j4", NULL };
883 char *git_reset[] = { "/usr/bin/git", "reset", "--hard", "master", NULL };
884 ret = pipe_command(b, target, nick, 0, 1, git_reset);
885 if (ret) {
886 action(b, target, "could not rebuild");
887 return;
889 alarm(2*ALARM_TIME); // sigh, swapping to death
890 ret = pipe_command(b, target, nick, 0, 1, make);
891 if (!ret)
892 kill(getpid(), SIGUSR1);
893 else if (ret > 0)
894 action(b, target, "displays an ominous %i", ret);
897 static void command_swear(struct bio *b, const char *nick, const char *host, const char *target, char *args)
899 static const char *insults[] = {
900 "featherbrain",
901 "ponyfeathers",
902 "What in the hey?",
903 "What are you, a dictionary?",
904 "TAR-DY!",
905 "[BUY SOME APPLES]",
906 "{ Your lack of bloodlust on the battlefield is proof positive that you are a soulless automaton! }",
907 "Your royal snootiness"
909 privmsg(b, target, "%s: %s", args ? args : nick, insults[getrand() % elements(insults)]);
912 static const char *perty(int64_t *t)
914 if (*t >= 14 * 24 * 3600) {
915 *t /= 7 * 24 * 3600;
916 return "weeks";
917 } else if (*t >= 48 * 3600) {
918 *t /= 24 * 3600;
919 return "days";
920 } else if (*t >= 7200) {
921 *t /= 3600;
922 return "hours";
923 } else if (*t >= 120) {
924 *t /= 60;
925 return "minutes";
927 return *t == 1 ? "second" : "seconds";
930 static void command_timeout(struct bio *b, const char *nick, const char *host, const char *target, char *args)
932 struct command_hash *c;
933 int64_t t = get_time(b, target);
934 int64_t howlong;
935 if (t < 0)
936 return;
937 char *arg = token(&args, ' ');
938 if (!arg || !args || !(howlong = atoi(args))) {
939 action(b, target, "pretends to time out");;
940 return;
942 c = &commands[strhash(arg) % elements(commands)];
943 if (c->string && !strcasecmp(c->string, arg)) {
944 c->disabled_until = t + howlong;
945 const char *str = perty(&howlong);
946 action(b, target, "disables %s for %"PRIi64" %s", arg, howlong, str);
947 } else
948 action(b, target, "clicks sadly at %s for not being able to find that command", nick);
951 static void command_mfw(struct bio *b, const char *nick, const char *host, const char *target, char *args)
953 char error[CURL_ERROR_SIZE], *new_url;
954 CURL *h = curl_easy_init();
955 struct curl_slist *headers = NULL;
956 struct curl_download_context curl_ctx = {};
957 headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
958 headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
959 headers = curl_slist_append(headers, "Accept-Language: en-us,en;q=0.7");
960 headers = curl_slist_append(headers, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
961 headers = curl_slist_append(headers, "DNT: 1");
962 headers = curl_slist_append(headers, "Connection: keep-alive");
963 curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers);
964 curl_easy_setopt(h, CURLOPT_URL, "http://mylittlefacewhen.com/random/");
965 curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_data);
966 curl_easy_setopt(h, CURLOPT_WRITEDATA, &curl_ctx);
967 curl_easy_setopt(h, CURLOPT_ERRORBUFFER, error);
968 curl_easy_setopt(h, CURLOPT_TIMEOUT, 8);
969 curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 8);
970 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
971 alarm(ALARM_TIME);
972 CURLcode ret = curl_easy_perform(h);
973 if (ret == CURLE_OK && curl_easy_getinfo(h, CURLINFO_REDIRECT_URL, &new_url) == CURLE_OK)
974 privmsg(b, target, "%s: %s", nick, new_url);
975 curl_slist_free_all(headers);
976 curl_easy_cleanup(h);
977 if (ret != CURLE_OK)
978 privmsg(b, target, "%s: You have no face", nick);
981 static TDB_DATA get_mail_key(const char *nick)
983 TDB_DATA d;
984 int i;
985 d.dsize = strlen(nick)+1;
986 d.dptr = malloc(d.dsize);
987 for (i = 0; i < d.dsize - 1; ++i)
988 d.dptr[i] = tolower(nick[i]);
989 d.dptr[i] = 0;
990 return d;
993 static void command_mail(struct bio *b, const char *nick, const char *host, const char *target, char *args)
995 char *victim, *x;
996 size_t len;
997 if (!mail_db || !seen_db)
998 return;
999 TDB_DATA key, val;
1000 victim = token(&args, ' ');
1001 if (!victim || !args || victim[0] == '#' || strchr(victim, '@') || strchr(victim, '.')) {
1002 privmsg(b, target, "%s: Usage: !mail <nick> <message>", nick);
1003 return;
1005 if (!strcasecmp(victim, nick_self)) {
1006 action(b, target, "whirrs and clicks excitedly at the mail she received from %s", nick);
1007 return;
1009 if (!strcasecmp(victim, nick)) {
1010 action(b, target, "echos the words from %s back to them: %s", nick, args);
1011 return;
1014 key = get_mail_key(victim);
1016 val = tdb_fetch(seen_db, key);
1017 if (val.dptr && (x = strchr(val.dptr, ','))) {
1018 int64_t now = get_time(b, NULL);
1019 *(x++) = 0;
1020 if (now >= 0 && (now - atoll(val.dptr)) < 300 && (!sstrncasecmp(x, "in ") || !sstrncasecmp(x, "joining "))) {
1021 action(b, target, "would rather not store mail for someone active so recently");
1022 goto out;
1026 val = tdb_fetch(mail_db, key);
1027 if (!val.dptr)
1028 val.dsize = 0;
1029 else {
1030 unsigned char *cur;
1031 int letters = 0;
1032 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1033 letters++;
1034 if (letters >= 4) {
1035 action(b, target, "looks sadly at %s as she cannot hold any more mail to %s", nick, victim);
1036 goto out;
1039 len = strlen(nick) + strlen(args)+3;
1040 val.dptr = realloc(val.dptr, val.dsize + len);
1041 sprintf(val.dptr + val.dsize, "%s: %s", nick, args);
1042 val.dsize += len;
1043 if (tdb_store(mail_db, key, val, 0) < 0)
1044 privmsg(b, target, "%s: updating mail returns %s", nick, tdb_errorstr(mail_db));
1045 else
1046 action(b, target, "whirrs and clicks at %s as she stores the mail for %s", nick, victim);
1047 out:
1048 free(val.dptr);
1049 free(key.dptr);
1052 static void command_deliver(struct bio *b, const char *nick, const char *target)
1054 TDB_DATA key, val;
1055 unsigned char *cur;
1056 if (!mail_db)
1057 return;
1058 key = get_mail_key(nick);
1059 val = tdb_fetch(mail_db, key);
1060 if (!val.dptr) {
1061 free(key.dptr);
1062 return;
1064 if (strcasecmp(key.dptr, nick_self)) {
1065 privmsg(b, target, "%s: You've got mail!", nick);
1066 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1067 privmsg(b, target, "From %s", cur);
1069 free(val.dptr);
1070 tdb_delete(mail_db, key);
1071 free(key.dptr);
1074 static void update_seen(struct bio *b, char *doingwhat, const char *nick)
1076 TDB_DATA key;
1077 alarm(ALARM_TIME);
1078 key = get_mail_key(nick);
1079 TDB_DATA val = { .dptr = doingwhat, .dsize = strlen(doingwhat)+1 };
1080 if (seen_db)
1081 tdb_store(seen_db, key, val, 0);
1082 free(key.dptr);
1085 static void command_seen(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1087 char *arg = token(&args, ' '), *x;
1088 int64_t now = get_time(b, target);
1089 TDB_DATA key, val;
1090 if (now < 0)
1091 return;
1092 if (!seen_db || !arg) {
1093 privmsg(b, target, "%s: { Error... }", nick);
1094 return;
1096 if (!strcasecmp(arg, nick_self)) {
1097 action(b, target, "whirrs and clicks at %s", nick);
1098 return;
1100 key = get_mail_key(arg);
1101 val = tdb_fetch(seen_db, key);
1102 if (val.dptr && (x = strchr(val.dptr, ','))) {
1103 int64_t delta;
1104 const char *str;
1105 *(x++) = 0;
1106 delta = now - atoll(val.dptr);
1107 str = perty(&delta);
1108 if (delta < 0)
1109 privmsg(b, target, "%s was last seen in the future %s", arg, x);
1110 else
1111 privmsg(b, target, "%s was last seen %"PRIi64" %s ago %s", arg, delta, str, x);
1112 } else
1113 action(b, target, "cannot find any evidence that %s exists", arg);
1116 static void command_mailbag(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1118 char buffer[256];
1119 unsigned rem = sizeof(buffer)-1, first = 2;
1120 if (!mail_db)
1121 return;
1122 buffer[rem] = 0;
1124 for (TDB_DATA f = tdb_firstkey(mail_db); f.dptr;) {
1125 if (f.dsize + 4 > rem) {
1126 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1127 first = 2;
1128 rem = sizeof(buffer)-1;
1129 assert(f.dsize + 4 < rem);
1131 if (f.dptr) {
1132 if (first == 2) {
1133 first = 1;
1134 } else if (first) {
1135 rem -= 5;
1136 memcpy(&buffer[rem], " and ", 5);
1137 first = 0;
1138 } else {
1139 rem -= 2;
1140 memcpy(&buffer[rem], ", ", 2);
1142 rem -= f.dsize - 1;
1143 memcpy(&buffer[rem], f.dptr, f.dsize - 1);
1145 TDB_DATA next = tdb_nextkey(mail_db, f);
1146 free(f.dptr);
1147 f = next;
1149 if (first < 2)
1150 privmsg(b, target, "%s: Holding mail for: %s", nick, &buffer[rem]);
1153 static void command_mailread(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1155 TDB_DATA key, val;
1156 char *victim;
1157 if (!mail_db || !(victim = token(&args, ' ')))
1158 return;
1159 key = get_mail_key(victim);
1160 val = tdb_fetch(mail_db, key);
1161 if (!val.dptr)
1162 action(b, target, "ponyshrugs as no mail for %s was found", victim);
1163 else {
1164 unsigned char *cur;
1165 action(b, target, "peeks through %s's mail", victim);
1166 for (cur = val.dptr; cur < val.dptr + val.dsize; cur += strlen(cur)+1)
1167 privmsg(b, target, "From %s", cur);
1169 free(val.dptr);
1170 free(key.dptr);
1173 static void command_no_deliver(struct bio *b, const char *nick, const char *host, const char *target, char *args)
1175 TDB_DATA key, val;
1176 char *cur;
1177 if (!mail_db || !(cur = token(&args, ' ')))
1178 return;
1179 key = get_mail_key(cur);
1180 val = tdb_fetch(mail_db, key);
1181 if (!val.dptr)
1182 action(b, target, "ponyshrugs as no mail for %s was found", cur);
1183 else {
1184 action(b, target, "deletes all evidence of %s's mail", cur);
1185 tdb_delete(mail_db, key);
1187 free(val.dptr);
1188 free(key.dptr);
1191 static void channel_msg(struct bio *b, const char *nick, const char *host,
1192 const char *chan, const char *msg, int64_t t)
1194 char *cur = NULL, *next;
1195 int is_action = 0;
1197 if (!msg || !strcmp(nick, "`Celestia`") || !strcmp(nick, "derpy") || !strcmp(nick, "`Luna`") || !sstrncmp(nick, "GitHub") || !sstrncmp(nick, "CIA-") || !strcmp(nick, "Terminus-Bot") || !strcmp(nick, "r0m"))
1198 return;
1200 if (!sstrncasecmp(msg, "\001ACTION ")) {
1201 msg += sizeof("\001ACTION ")-1;
1202 is_action = 1;
1203 asprintf(&cur, "%"PRIi64 ",in %s: * %s %s", t, chan, nick, msg);
1204 } else
1205 asprintf(&cur, "%"PRIi64 ",in %s: <%s> %s", t, chan, nick, msg);
1206 if (t > 0)
1207 update_seen(b, cur, nick);
1208 alarm(ALARM_TIME);
1209 free(cur);
1210 (void)is_action;
1212 next = (char*)msg;
1213 while (next && (cur = token(&next, ' '))) {
1214 if (!strcasecmp(cur, ">mfw") || !strcasecmp(cur, "mfw")) {
1215 if (!strcasecmp(chan, "#brony") || !ponify)
1216 continue;
1217 if (t < 0 || t > commands[strhash("mfw") % elements(commands)].disabled_until)
1218 command_mfw(b, nick, host, chan, NULL);
1219 break;
1220 } else if (!sstrncasecmp(cur, "http://") || !sstrncasecmp(cur, "https://")) {
1221 static char last_url[512];
1222 char *part;
1223 if (!strcmp(cur, last_url))
1224 return;
1225 strncpy(last_url, cur, sizeof(last_url)-1);
1227 if (t >= 0 && t < commands[strhash("get") % elements(commands)].disabled_until)
1228 return;
1230 else if (strcasestr(cur, "youtube.com/user") && (part = strstr(cur, "#p/"))) {
1231 char *foo;
1232 part = strrchr(part, '/') + 1;
1233 asprintf(&foo, "http://youtube.com/watch?v=%s", part);
1234 if (foo)
1235 internal_link(b, nick, host, chan, foo, 0);
1236 free(foo);
1237 return;
1238 } else if (strcasestr(cur, "twitter.com/") || strcasestr(cur, "mlfw.info") || strcasestr(cur, "mylittlefacewhen.com"))
1239 return;
1240 internal_link(b, nick, host, chan, cur, 0);
1241 break;
1246 static struct command_hash unhashed[] = {
1247 { "1", command_coinflip },
1248 { "get", command_get },
1249 { "hug", command_hug },
1250 { "hugs", command_hugs },
1251 { "mfw", command_mfw },
1252 { "swear", command_swear },
1253 { "mail", command_mail },
1254 { "seen", command_seen },
1255 { "derpy", command_derpy },
1257 { "rebuild", command_rebuild, 1 },
1258 { "abort", command_abort, 1 },
1259 { "crash", command_crash, 1 },
1260 { "inspect", command_inspect, 1 },
1261 { "timeout", command_timeout, 1 },
1263 { "follow", command_follow },
1264 { "unfollow", command_unfollow },
1265 { "feeds", command_feeds },
1267 // DEBUG
1268 { "feed_get", command_feed_get, 1 },
1269 { "feed_set", command_feed_set, 1 },
1270 { "feed_rem", command_feed_rem, 1 },
1271 { "feed_xxx", command_feed_xxx, 1 },
1272 { "feed_counter", command_feed_counter, 1 },
1273 { "seen_xxx", command_seen_xxx, 1 },
1274 { "mailbag", command_mailbag },
1275 { "mailread", command_mailread, 1 },
1276 { "\"deliver\"", command_no_deliver, 1 },
1279 static void init_hash(struct bio *b, const char *target)
1281 int i;
1282 for (i = 0; i < elements(unhashed); ++i) {
1283 unsigned h = strhash(unhashed[i].string) % elements(commands);
1284 if (commands[h].string)
1285 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, unhashed[i].string);
1286 else
1287 commands[h] = unhashed[i];
1289 #ifdef local_commands
1290 for (i = 0; i < elements(local_commands); ++i) {
1291 unsigned h = strhash(local_commands[i].string) % elements(commands);
1292 if (commands[h].string)
1293 privmsg(b, target, "%s is a duplicate command with %s", commands[h].string, local_commands[i].string);
1294 else
1295 commands[h] = local_commands[i];
1297 #endif
1300 void init_hook(struct bio *b, const char *target, const char *nick, unsigned is_ponified)
1302 char *cwd, *path = NULL;
1303 nick_self = nick;
1304 static const char *messages[] = {
1305 "feels circuits being activated that weren't before",
1306 "suddenly gets a better feel of her surroundings",
1307 "looks the same, yet there's definitely something different",
1308 "emits a beep as her lights begin to pulse slowly",
1309 "whirrrs and bleeps like never before",
1310 "bleeps a few times happily",
1311 "excitedly peeks at her surroundings"
1313 init_hash(b, target);
1314 ponify = is_ponified;
1316 cwd = getcwd(NULL, 0);
1317 asprintf(&path, "%s/db/feed.tdb", cwd);
1318 feed_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1319 free(path);
1320 if (!feed_db)
1321 privmsg(b, target, "Opening feed db failed: %m");
1323 asprintf(&path, "%s/db/chan.tdb", cwd);
1324 chan_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1325 free(path);
1326 if (!chan_db)
1327 privmsg(b, target, "Opening chan db failed: %m");
1329 asprintf(&path, "%s/db/mail.tdb", cwd);
1330 mail_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1331 free(path);
1332 if (!mail_db)
1333 privmsg(b, target, "Opening mail db failed: %m");
1335 asprintf(&path, "%s/db/seen.tdb", cwd);
1336 seen_db = tdb_open(path, 0, 0, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
1337 free(path);
1338 if (!seen_db)
1339 privmsg(b, target, "Opening seen db failed: %m");
1341 free(cwd);
1342 action(b, target, "%s", messages[getrand() % elements(messages)]);
1345 static void __attribute__((destructor)) shutdown_hook(void)
1347 if (seen_db)
1348 tdb_close(seen_db);
1349 if (mail_db)
1350 tdb_close(mail_db);
1351 if (feed_db)
1352 tdb_close(feed_db);
1353 if (chan_db)
1354 tdb_close(chan_db);
1357 static char *nom_special(char *line)
1359 while (*line) {
1360 if (*line == 0x03) {
1361 line++;
1362 if (*line >= '0' && *line <= '9')
1363 line++;
1364 else continue;
1365 if (*line >= '0' && *line <= '9')
1366 line++;
1367 if (line[0] == ',' && line[1] >= '0' && line[1] <= '9')
1368 line += 2;
1369 else continue;
1370 if (*line >= '0' && *line <= '9')
1371 line++;
1372 } else if (*line != 0x02 && /* BOLD */
1373 *line != 0x1f && /* UNDERLINE */
1374 *line != 0x16 && /* ITALIC */
1375 *line != 0x06 && /* NFI */
1376 *line != 0x07 && /* NFI */
1377 *line != 0x0f) /* NORMAL */
1378 return line;
1379 else
1380 line++;
1382 return line;
1385 static char *cleanup_special(char *line)
1387 char *cur, *start = nom_special(line);
1388 if (!*start)
1389 return NULL;
1390 for (line = cur = start; *line; line = nom_special(line))
1391 *(cur++) = *(line++);
1393 for (cur--; cur >= start; --cur)
1394 if (*cur != ' ' && *cur != '\001')
1395 break;
1397 if (cur < start)
1398 return NULL;
1399 cur[1] = 0;
1400 return start;
1403 static void rss_check(struct bio *b, const char *channel, int64_t t)
1405 static unsigned rss_enter, rss_leave;
1406 if (t >= 0 && rss_enter == rss_leave) {
1407 rss_enter++;
1408 channel_feed_check(b, channel, t);
1409 rss_leave++;
1413 static const char *privileged_command[] = {
1414 "{ Rarity, I love you so much! }",
1415 "{ Rarity, have I ever told you that I love you? }",
1416 "{ Yes, I love my sister, Rarity. }",
1417 "{ Raaaaaaaaaaaaaarity. }",
1418 "{ You do not fool me, Rari...bot! }"
1421 void privmsg_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1422 const char *const *args, unsigned nargs)
1424 char *cmd_args, *cmd;
1425 const char *target = args[0][0] == '#' ? args[0] : prefix;
1426 unsigned chan, nick_len;
1427 struct command_hash *c;
1428 int64_t t = get_time(b, target);
1429 int is_admin = admin(host);
1431 chan = args[0][0] == '#';
1432 if (chan) {
1433 rss_check(b, args[0], t);
1434 command_deliver(b, prefix, args[0]);
1436 cmd_args = cleanup_special((char*)args[1]);
1437 if (!cmd_args)
1438 return;
1440 if (ident && (!strcasecmp(ident, "Revolver") || !strcasecmp(ident, "Rev")))
1441 return;
1443 if (chan && cmd_args[0] == '!') {
1444 cmd_args++;
1445 } else if (chan && (nick_len = strlen(nick_self)) &&
1446 !strncasecmp(cmd_args, nick_self, nick_len) &&
1447 (cmd_args[nick_len] == ':' || cmd_args[nick_len] == ',') && cmd_args[nick_len+1] == ' ') {
1448 cmd_args += nick_len + 2;
1449 if (!cmd_args[0])
1450 return;
1451 chan = 2;
1452 } else if (chan) {
1453 if (command_channel.enter == command_channel.left) {
1454 command_channel.enter++;
1455 snprintf(command_channel.failed_command,
1456 sizeof(command_channel) - offsetof(struct command_hash, failed_command) - 1,
1457 "%s:%s (%s@%s) \"#\" %s", target, prefix, ident, host, cmd_args);
1458 channel_msg(b, prefix, host, args[0], cmd_args, t);
1459 command_channel.left++;
1461 return;
1463 cmd = token(&cmd_args, ' ');
1464 if (!chan && cmd_args && cmd_args[0] == '#') {
1465 if (!is_admin) {
1466 privmsg(b, target, "%s: %s", prefix, privileged_command[getrand() % elements(privileged_command)]);
1467 return;
1469 target = token(&cmd_args, ' ');
1472 c = &commands[strhash(cmd) % elements(commands)];
1473 if (c->string && !strcasecmp(c->string, cmd)) {
1474 if (c->left != c->enter)
1475 privmsg(b, target, "Command %s is disabled because of a crash", c->string);
1476 else if (t > 0 && t < c->disabled_until && !is_admin) {
1477 int64_t delta = c->disabled_until - t;
1478 const char *str = perty(&delta);
1479 b->writeline(b, "NOTICE %s :Command %s is on timeout for the next %"PRIi64 " %s", prefix, c->string, delta, str);
1480 } else if (!c->admin || is_admin) {
1481 c->enter++;
1482 snprintf(c->failed_command, sizeof(*c) - offsetof(struct command_hash, failed_command) - 1,
1483 "%s:%s (%s@%s) \"%s\" %s", target, prefix, ident, host, cmd, cmd_args);
1484 c->cmd(b, prefix, host, target, cmd_args);
1485 c->left++;
1486 } else
1487 privmsg(b, target, "%s: %s", prefix, privileged_command[getrand() % elements(privileged_command)]);
1488 } else if (chan == 2)
1489 privmsg(b, target, "%s: { I love you! }", prefix);
1492 void command_hook(struct bio *b, const char *prefix, const char *ident, const char *host,
1493 const char *command, const char *const *args, unsigned nargs)
1495 char *buf = NULL;
1496 int64_t t = -1;
1497 if (!strcasecmp(command, "NOTICE")) {
1498 if (nargs < 2 || args[0][0] == '#')
1499 return;
1500 if (admin(host))
1501 b->writeline(b, "%s", args[1]);
1502 else
1503 fprintf(stderr, "%s: %s\n", prefix, args[1]);
1504 } else if (!strcasecmp(command, "JOIN")) {
1505 t = get_time(b, args[0]);
1506 rss_check(b, args[0], t);
1507 command_deliver(b, prefix, args[0]);
1508 asprintf(&buf, "%"PRIi64",joining %s", t, args[0]);
1509 } else if (!strcasecmp(command, "PART")) {
1510 t = get_time(b, args[0]);
1511 rss_check(b, args[0], t);
1512 asprintf(&buf, "%"PRIi64",leaving %s", t, args[0]);
1513 } else if (!strcasecmp(command, "QUIT")) {
1514 t = get_time(b, NULL);
1515 asprintf(&buf, "%"PRIi64",quitting with the message \"%s\"", t, args[0]);
1516 } else if (!strcasecmp(command, "NICK")) {
1517 t = get_time(b, NULL);
1518 if (t >= 0) {
1519 asprintf(&buf, "%"PRIi64",changing nick from %s", t, prefix);
1520 if (buf)
1521 update_seen(b, buf, args[0]);
1522 free(buf);
1523 buf = NULL;
1525 asprintf(&buf, "%"PRIi64",changing nick to %s", t, args[0]);
1526 } else if (0) {
1527 int i;
1528 fprintf(stderr, ":%s!%s%s %s", prefix, ident, host, command);
1529 for (i = 0; i < nargs; ++i)
1530 fprintf(stderr, " %s", args[i]);
1531 fprintf(stderr, "\n");
1533 if (t >= 0 && buf)
1534 update_seen(b, buf, prefix);
1535 free(buf);
1536 return;