3 #include <libxml/xmlstring.h>
4 #include <libxml/HTMLparser.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))
28 static int pipe_command(struct bio
*b
, const char *target
, const char *nick
, int redirect_stdout
, int redirect_stderr
, char *argv
[])
32 if (pipe2(fd
, O_CLOEXEC
) < 0) {
33 privmsg(b
, target
, "Could not create pipe: %m");
38 privmsg(b
, target
, "Could not fork: %m");
43 int fdnull
= open("/dev/null", O_WRONLY
|O_CLOEXEC
);
45 if (dup3(redirect_stdout
? fd
[1] : fdnull
, 1, 0) < 0)
47 if (dup3(redirect_stderr
? fd
[1] : fdnull
, 2, 0) < 0)
49 exit(execv(argv
[0], argv
));
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) {
65 if (lines
< SPAM_CUTOFF
)
66 privmsg(b
, nick
, "%s", buffer
);
68 fprintf(stderr
, "%s\n", buffer
);
75 if (lines
< SPAM_CUTOFF
)
76 privmsg(b
, nick
, "%s", buffer
);
78 fprintf(stderr
, "%s\n", buffer
);
80 if (lines
>= SPAM_CUTOFF
)
81 privmsg(b
, nick
, "%i lines suppressed", lines
- SPAM_CUTOFF
+ 1);
86 privmsg(b
, target
, "error on waitpid: %m");
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
)
104 static void command_crash(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
109 static void command_coinflip(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
112 action(b
, target
, "whirrrs and clicks excitedly at %s", nick
);
114 action(b
, target
, "eyes %s as nothing happens", nick
);
117 static void squash(char *title
)
122 while (*title
!= '\n' && *title
!= '\r' && *title
!= '\t' && *title
!= ' ' && *title
) {
123 *(start
++) = *(title
++);
128 while (*title
== '\n' || *title
== '\r' || *title
== '\t' || *title
== ' ')
134 struct curl_download_context
140 static size_t write_data(void *ptr
, size_t size
, size_t nmemb
, void *member
) {
141 struct curl_download_context
*ctx
= member
;
143 ctx
->data
= realloc(ctx
->data
, ctx
->len
+ size
);
144 memcpy(ctx
->data
+ ctx
->len
, ptr
, size
);
149 static const char *get_text(xmlNode
*cur
)
151 for (; cur
; cur
= cur
->next
) {
152 if (cur
->type
== XML_TEXT_NODE
)
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
);
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
;
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
)
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
))
193 *title
= "<no title>";
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
)
207 else if (!strcasecmp(name
, "category"))
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
);
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
);
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
);
245 privmsg(b
, target
, "Most recent entry: %s %s", link
, title
);
247 privmsg(b
, target
, "Currently having no entries for this feed that matches the filter");
250 if (!strcmp(last_link
, link
) || !match
|| !entry
)
253 for (entry
= entry
->next
; entry
; entry
= entry
->next
) {
254 const char *cur_link
, *cur_title
;
255 if (strcasecmp(entry
->name
, "entry"))
257 match
= get_feed_entry(b
, nick
, target
, category
, entry
, &cur_title
, &cur_link
);
258 if (match
< 0 || !strcmp(last_link
, cur_link
))
261 prev_link
= cur_link
;
262 prev_title
= cur_title
;
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
);
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")) {
281 privmsg(b
, target
, "%s: Could not parse rss feed", nick
);
283 privmsg(b
, target
, "%s: Invalid rss version \"%s\"", nick
, ver
);
286 for (cur
= root
->children
; cur
&& cur
->type
!= XML_ELEMENT_NODE
; cur
= cur
->next
);
289 for (cur
= cur
->children
; cur
; cur
= cur
->next
) {
290 const char *name
= cur
->name
;
291 if (cur
->type
!= XML_ELEMENT_NODE
)
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
);
308 for (cur
= entry
->children
; cur
; cur
= cur
->next
) {
309 const char *name
= cur
->name
;
310 if (cur
->type
!= XML_ELEMENT_NODE
)
312 if (!strcasecmp(name
, "title"))
313 title
= get_text(cur
->children
);
314 else if (!strcasecmp(name
, "link"))
315 link
= get_text(cur
->children
);
318 title
= "<no title>";
320 privmsg(b
, target
, "%s: Failed to parse entry: %s %s", nick
, link
, title
);
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
)) {
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"))
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
)
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
);
345 cur_title
= "<no title>";
346 if (!cur_link
|| !strcmp(last_link
, cur_link
))
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
);
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
;
366 return cur_node
->next
;
367 cur_node
= cur_node
->parent
;
372 static const char *get_atom_link(xmlNode
*cur
)
374 for (; cur
; cur
= next_link(cur
)) {
375 if (cur
->type
!= XML_ELEMENT_NODE
)
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");
386 static const char *get_rss_link(xmlNode
*cur
)
388 for (; cur
; cur
= next_link(cur
)) {
389 if (cur
->type
!= XML_ELEMENT_NODE
)
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");
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
);
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
);
410 privmsg(b
, target
, "%s: not a valid feed link, no suggestion found", nick
);
414 static size_t get_time_from_header(void *data
, size_t size
, size_t size2
, void *ptr
)
418 if (sstrncmp(data
, "Last-Modified: "))
420 data
+= sizeof("Last-Modified: ")-1;
421 *(char**)ptr
= d
= strdup(data
);
422 if ((e
= strchr(d
, '\r')))
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
, '#');
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: */*");
438 CURL
*h
= curl_easy_init();
439 curl_easy_setopt(h
, CURLOPT_HTTPHEADER
, headers
);
440 curl_easy_setopt(h
, CURLOPT_URL
, url
);
441 curl_easy_setopt(h
, CURLOPT_WRITEFUNCTION
, write_data
);
442 curl_easy_setopt(h
, CURLOPT_WRITEDATA
, &curl_ctx
);
443 curl_easy_setopt(h
, CURLOPT_ERRORBUFFER
, error
);
444 curl_easy_setopt(h
, CURLOPT_FOLLOWLOCATION
, 1);
445 curl_easy_setopt(h
, CURLOPT_MAXREDIRS
, 3);
446 curl_easy_setopt(h
, CURLOPT_SSL_VERIFYPEER
, 0);
447 curl_easy_setopt(h
, CURLOPT_TIMEOUT
, 8);
448 curl_easy_setopt(h
, CURLOPT_FILETIME
, 1);
449 curl_easy_setopt(h
, CURLOPT_HEADERFUNCTION
, get_time_from_header
);
450 curl_easy_setopt(h
, CURLOPT_WRITEHEADER
, &last_modified
);
451 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
455 asprintf(&tmp
, "If-Modified-Since: %s", last_modified
);
456 headers
= curl_slist_append(headers
, tmp
);
460 int success
= curl_easy_perform(h
);
461 curl_slist_free_all(headers
);
463 if (success
== CURLE_OK
) {
466 curl_easy_getinfo(h
, CURLINFO_CONTENT_TYPE
, &mime
);
467 curl_easy_getinfo(h
, CURLINFO_RESPONSE_CODE
, &code
);
470 else if (!mime
|| !sstrncmp(mime
, "application/xml") || !sstrncmp(mime
, "text/xml")) {
471 const char *ret_link
= NULL
;
472 xmlDocPtr ctx
= xmlReadMemory(curl_ctx
.data
, curl_ctx
.len
, 0, url
, XML_PARSE_NOWARNING
| XML_PARSE_NOERROR
);
473 xmlNode
*root
= xmlDocGetRootElement(ctx
);
475 if (!strcasecmp(root
->name
, "feed"))
476 ret_link
= walk_feed(b
, nick
, target
, url
, category
, root
, link
);
477 else if (!strcasecmp(root
->name
, "rss"))
478 ret_link
= walk_rss(b
, nick
, target
, url
, root
, link
);
480 privmsg(b
, target
, "Unknown feed type \"%s\"", root
->name
);
487 privmsg(b
, target
, "Could not feed parse correctly");
488 else if (ret_link
&& (!link
|| strcmp(ret_link
, link
))) {
490 asprintf((char**)&val
.dptr
, "%s\001%s", last_modified
? last_modified
: "", ret_link
);
491 val
.dsize
= strlen(val
.dptr
)+1;
492 if (tdb_store(feed_db
, key
, val
, 0) < 0)
493 privmsg(b
, target
, "updating returns %s", tdb_errorstr(feed_db
));
504 else if (!sstrncmp(mime
, "text/html") || !sstrncmp(mime
, "application/xhtml+xml"))
505 do_html(b
, nick
, target
, url
, curl_ctx
.data
, curl_ctx
.len
);
507 privmsg(b
, target
, "unhandled content type %s", mime
);
509 privmsg(b
, target
, "Error %s (%u)\n", error
, success
);
512 curl_easy_cleanup(h
);
516 static void command_follow(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
518 char *space
, *last_link
= NULL
;
523 if (target
[0] != '#') {
524 privmsg(b
, target
, "%s: Can only follow on channels", nick
);
527 if (!args
|| !*args
) {
528 privmsg(b
, target
, "%s: Usage: !follow <url>", nick
);
532 if (((space
= strchr(args
, ' ')) && space
< strchr(args
, '#')) ||
533 (sstrncmp(args
, "http://") && sstrncmp(args
, "https://"))) {
534 privmsg(b
, target
, "%s: Invalid url", nick
);
539 key
.dsize
= asprintf((char**)&key
.dptr
, "%s,%s", target
, args
)+1;
540 val
= tdb_fetch(feed_db
, key
);
542 last_link
= strchr(val
.dptr
, '\001');
543 ret
= check_single_feed(b
, target
, key
, NULL
, args
, last_link
? last_link
+1 : NULL
, nick
);
546 privmsg(b
, target
, "%s: Not updated", nick
);
550 static void channel_feed_check(struct bio
*b
, const char *target
, int64_t now
)
552 int len
= strlen(target
);
554 if (!feed_db
|| !chan_db
)
556 chan
.dptr
= (char*)target
;
558 res
= tdb_fetch(chan_db
, chan
);
559 if (res
.dptr
&& res
.dsize
== 8) {
560 uint64_t then
= *(uint64_t*)res
.dptr
;
561 if (now
- then
<= 2000)
566 res
.dptr
= (unsigned char*)&now
;
568 if (tdb_store(chan_db
, chan
, res
, 0) < 0) {
569 static int complain_db
;
571 privmsg(b
, target
, "updating database: %s", tdb_errorstr(feed_db
));
575 /* Reset the alarm on every get, we are not actually in danger of doing an infinite loop, probably */
576 for (TDB_DATA d
= tdb_firstkey(feed_db
); d
.dptr
;) {
577 TDB_DATA f
= tdb_fetch(feed_db
, d
);
578 TDB_DATA next
= tdb_nextkey(feed_db
, d
);
580 if (!strncmp(d
.dptr
, target
, len
) && d
.dptr
[len
] == ',') {
581 const char *url
= (char*)d
.dptr
+ len
+ 1;
583 if ((sep
= strchr(f
.dptr
, '\001'))) {
585 check_single_feed(b
, target
, d
, f
.dptr
, url
, sep
, target
);
597 static void command_unfollow(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
605 if (!(url
= token(&args
, ' ')) || (sstrncmp(url
, "http://") && sstrncmp(url
, "https://"))) {
606 privmsg(b
, target
, "%s: Invalid url", nick
);
609 if (target
[0] != '#') {
610 privmsg(b
, target
, "%s: Can only unfollow on channels", nick
);
613 key
.dsize
= asprintf((char**)&key
.dptr
, "%s,%s", target
, url
)+1;
614 if (tdb_delete(feed_db
, key
) < 0) {
615 if (tdb_error(feed_db
) == TDB_ERR_NOEXIST
)
616 privmsg(b
, target
, "%s: Not following %s on this channel", nick
, url
);
618 privmsg(b
, target
, "%s: Could not delete: %s", nick
, tdb_errorstr(feed_db
));
620 privmsg(b
, target
, "%s: No longer following %s", nick
, url
);
624 static void command_feeds(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
626 int len
= strlen(target
), found
= 0;
629 if (target
[0] != '#') {
630 privmsg(b
, target
, "%s: Only useful in channels..", nick
);
633 for (TDB_DATA d
= tdb_firstkey(feed_db
); d
.dptr
;) {
634 TDB_DATA f
= tdb_fetch(feed_db
, d
);
635 TDB_DATA next
= tdb_nextkey(feed_db
, d
);
637 if (!strncmp(d
.dptr
, target
, len
) && d
.dptr
[len
] == ',') {
638 privmsg(b
, target
, "%s: following %s", nick
, d
.dptr
+ len
+ 1);
646 privmsg(b
, target
, "%s: not following any feed on %s", nick
, target
);
649 static void command_feed_get(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
653 for (TDB_DATA d
= tdb_firstkey(feed_db
); d
.dptr
;) {
654 TDB_DATA next
= tdb_nextkey(feed_db
, d
);
655 if (!args
|| strcasestr(d
.dptr
, args
)) {
656 TDB_DATA f
= tdb_fetch(feed_db
, d
);
657 privmsg(b
, target
, "%s: %s = %s", nick
, d
.dptr
, f
.dptr
);
660 if (strlen(d
.dptr
)+1 < d
.dsize
) {
661 privmsg(b
, target
, "%s: removed buggy entry", nick
);
662 tdb_delete(feed_db
, d
);
669 static void command_feed_set(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
674 key
.dptr
= token(&args
, ' ');
675 char *value
= token(&args
, ' ');
676 if (!key
.dptr
|| !value
)
678 key
.dsize
= strlen(key
.dptr
) + 1;
679 val
.dsize
= strlen(value
) + 2;
680 val
.dptr
= malloc(val
.dsize
);
681 strcpy(val
.dptr
+1, value
);
682 val
.dptr
[0] = '\001';
683 if (tdb_store(feed_db
, key
, val
, 0) < 0)
684 privmsg(b
, target
, "%s: setting failed: %s", nick
, tdb_errorstr(feed_db
));
686 privmsg(b
, target
, "%s: burp", nick
);
690 static void command_feed_rem(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
692 if (!feed_db
|| !args
)
694 TDB_DATA key
= { .dptr
= (unsigned char*)args
, .dsize
= strlen(args
)+1 };
695 if (tdb_delete(feed_db
, key
) < 0)
696 privmsg(b
, target
, "%s: removing failed: %s", nick
, tdb_errorstr(feed_db
));
698 privmsg(b
, target
, "%s: burp", nick
);
701 static void command_feed_xxx(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
706 tdb_wipe_all(feed_db
);
707 privmsg(b
, target
, "%s: all evidence erased", nick
);
710 static void command_seen_xxx(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
715 tdb_wipe_all(seen_db
);
716 privmsg(b
, target
, "%s: all evidence erased", nick
);
719 static void command_feed_counter(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
723 tdb_wipe_all(chan_db
);
724 privmsg(b
, target
, "%s: All update counters reset", nick
);
727 static char *get_text_appended(xmlNode
*cur
)
729 for (; cur
; cur
= cur
->next
) {
730 if (cur
->type
!= XML_TEXT_NODE
)
732 return strdup(cur
->content
);
737 static char *get_title(struct bio
*b
, xmlNode
*cur_node
)
739 for (; cur_node
; cur_node
= next_link(cur_node
)) {
740 if (cur_node
->type
== XML_ELEMENT_NODE
&& !strcasecmp(cur_node
->name
, "title")) {
741 if (!cur_node
->children
)
743 return get_text_appended(cur_node
->children
);
749 static void internal_link(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
, unsigned verbose
)
752 struct curl_slist
*headers
= NULL
;
753 char error
[CURL_ERROR_SIZE
];
755 struct curl_download_context curl_ctx
= {};
761 h
= curl_easy_init();
762 headers
= curl_slist_append(headers
, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
763 headers
= curl_slist_append(headers
, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
764 headers
= curl_slist_append(headers
, "Accept-Language: en-us,en;q=0.7");
765 headers
= curl_slist_append(headers
, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
766 headers
= curl_slist_append(headers
, "DNT: 1");
767 headers
= curl_slist_append(headers
, "Connection: keep-alive");
768 curl_easy_setopt(h
, CURLOPT_HTTPHEADER
, headers
);
769 curl_easy_setopt(h
, CURLOPT_URL
, args
);
770 curl_easy_setopt(h
, CURLOPT_WRITEFUNCTION
, write_data
);
771 curl_easy_setopt(h
, CURLOPT_WRITEDATA
, &curl_ctx
);
772 curl_easy_setopt(h
, CURLOPT_ERRORBUFFER
, error
);
773 curl_easy_setopt(h
, CURLOPT_FOLLOWLOCATION
, 1);
774 curl_easy_setopt(h
, CURLOPT_MAXREDIRS
, 20);
775 curl_easy_setopt(h
, CURLOPT_SSL_VERIFYPEER
, 0);
776 curl_easy_setopt(h
, CURLOPT_TIMEOUT
, 8);
777 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
778 success
= curl_easy_perform(h
);
779 curl_easy_cleanup(h
);
780 curl_slist_free_all(headers
);
782 if (success
== CURLE_OK
) {
783 magic_t m
= magic_open(MAGIC_MIME_TYPE
);
785 const char *mime
= magic_buffer(m
, curl_ctx
.data
, curl_ctx
.len
);
786 if (strstr(mime
, "text/html") || strstr(mime
, "application/xml") || strstr(mime
, "application/xhtml+xml")) {
787 htmlDocPtr ctx
= htmlReadMemory(curl_ctx
.data
, curl_ctx
.len
, 0, args
, HTML_PARSE_RECOVER
|HTML_PARSE_NOERROR
|HTML_PARSE_NOWARNING
);
788 xmlNode
*root_element
= xmlDocGetRootElement(ctx
);
789 char *title
= get_title(b
, root_element
);
793 decode_html_entities_utf8(title
, NULL
);
794 if ((nuke
= strstr(title
, " on SoundCloud - Create")))
797 privmsg(b
, target
, "%s linked %s", nick
, title
);
800 if (verbose
&& !title
)
801 privmsg(b
, target
, "%s linked %s page with invalid title", nick
, mime
);
803 } else if (verbose
) {
804 magic_setflags(m
, MAGIC_COMPRESS
);
805 const char *desc
= magic_buffer(m
, curl_ctx
.data
, curl_ctx
.len
);
806 privmsg(b
, target
, "%s linked type %s", nick
, desc
);
810 if (verbose
&& success
!= CURLE_OK
)
811 privmsg(b
, target
, "Error %s (%u)\n", error
, success
);
815 static void command_get(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
817 if (!args
|| (sstrncmp(args
, "http://") && sstrncmp(args
, "https://")))
819 internal_link(b
, nick
, host
, target
, token(&args
, ' '), 1);
822 static void command_hug(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
824 action(b
, target
, "gives a robotic hug to %s", args
? args
: nick
);
827 static void command_hugs(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
829 action(b
, target
, "gives a lunar hug to %s", args
? args
: nick
);
832 static void command_derpy(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
834 static const char *insults
[] = {
835 "accidentally shocks herself",
836 "tumbles down the stairs like a slinky",
837 "whirrrs and clicks in a screeching way",
838 "had problems executing this command",
839 "breaks down entirely",
840 "uses her magic to levitate herself off the ground, then hits it face first"
842 action(b
, target
, "%s", insults
[getrand() % elements(insults
)]);
845 static void command_inspect(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
847 struct command_hash
*c
;
848 unsigned leave
, crash
;
850 if (!args
|| !(cmd
= token(&args
, ' ')))
853 if (strcmp(cmd
, "#")) {
854 c
= &commands
[strhash(cmd
) % elements(commands
)];
855 if (!c
->string
|| strcasecmp(c
->string
, cmd
)) {
856 privmsg(b
, target
, "Command %s not valid", cmd
);
860 c
= &command_channel
;
862 leave
= c
->left
+ (c
->cmd
== command_inspect
);
863 crash
= c
->enter
- leave
;
864 if (c
->enter
!= leave
)
865 privmsg(b
, target
, "%s: %u successes and %u crash%s, last crashing command: %s", c
->string
, leave
, crash
, crash
== 1 ? "" : "es", c
->failed_command
);
867 privmsg(b
, target
, "%s: %u time%s executed succesfully", c
->string
, leave
, leave
== 1 ? "" : "s");
870 static void command_rebuild(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
873 char *make
[] = { "/usr/bin/make", "-j4", NULL
};
874 char *git_reset
[] = { "/usr/bin/git", "reset", "--hard", "master", NULL
};
875 ret
= pipe_command(b
, target
, nick
, 0, 1, git_reset
);
877 action(b
, target
, "could not rebuild");
880 alarm(2*ALARM_TIME
); // sigh, swapping to death
881 ret
= pipe_command(b
, target
, nick
, 0, 1, make
);
883 kill(getpid(), SIGUSR1
);
885 action(b
, target
, "displays an ominous %i", ret
);
888 static void command_swear(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
890 static const char *insults
[] = {
894 "What are you, a dictionary?",
897 "{ Your lack of bloodlust on the battlefield is proof positive that you are a soulless automaton! }",
898 "Your royal snootiness"
900 privmsg(b
, target
, "%s: %s", args
? args
: nick
, insults
[getrand() % elements(insults
)]);
903 static const char *perty(int64_t *t
)
905 if (*t
>= 14 * 24 * 3600) {
908 } else if (*t
>= 48 * 3600) {
911 } else if (*t
>= 7200) {
914 } else if (*t
>= 120) {
918 return *t
== 1 ? "second" : "seconds";
921 static void command_timeout(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
923 struct command_hash
*c
;
924 int64_t t
= get_time(b
, target
);
928 char *arg
= token(&args
, ' ');
929 if (!arg
|| !args
|| !(howlong
= atoi(args
))) {
930 action(b
, target
, "pretends to time out");;
933 c
= &commands
[strhash(arg
) % elements(commands
)];
934 if (c
->string
&& !strcasecmp(c
->string
, arg
)) {
935 c
->disabled_until
= t
+ howlong
;
936 const char *str
= perty(&howlong
);
937 action(b
, target
, "disables %s for %"PRIi64
" %s", arg
, howlong
, str
);
939 action(b
, target
, "clicks sadly at %s for not being able to find that command", nick
);
942 static void command_mfw(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
944 char error
[CURL_ERROR_SIZE
], *new_url
;
945 CURL
*h
= curl_easy_init();
946 struct curl_slist
*headers
= NULL
;
947 struct curl_download_context curl_ctx
= {};
948 headers
= curl_slist_append(headers
, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1");
949 headers
= curl_slist_append(headers
, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
950 headers
= curl_slist_append(headers
, "Accept-Language: en-us,en;q=0.7");
951 headers
= curl_slist_append(headers
, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7");
952 headers
= curl_slist_append(headers
, "DNT: 1");
953 headers
= curl_slist_append(headers
, "Connection: keep-alive");
954 curl_easy_setopt(h
, CURLOPT_HTTPHEADER
, headers
);
955 curl_easy_setopt(h
, CURLOPT_URL
, "http://mylittlefacewhen.com/random/");
956 curl_easy_setopt(h
, CURLOPT_WRITEFUNCTION
, write_data
);
957 curl_easy_setopt(h
, CURLOPT_WRITEDATA
, &curl_ctx
);
958 curl_easy_setopt(h
, CURLOPT_ERRORBUFFER
, error
);
959 curl_easy_setopt(h
, CURLOPT_TIMEOUT
, 8);
960 //curl_easy_setopt(h, CURLOPT_VERBOSE, 1);
962 CURLcode ret
= curl_easy_perform(h
);
963 if (ret
== CURLE_OK
&& curl_easy_getinfo(h
, CURLINFO_REDIRECT_URL
, &new_url
) == CURLE_OK
)
964 privmsg(b
, target
, "%s: %s", nick
, new_url
);
965 curl_slist_free_all(headers
);
966 curl_easy_cleanup(h
);
968 privmsg(b
, target
, "%s: You have no face", nick
);
971 static TDB_DATA
get_mail_key(const char *nick
)
975 d
.dsize
= strlen(nick
)+1;
976 d
.dptr
= malloc(d
.dsize
);
977 for (i
= 0; i
< d
.dsize
- 1; ++i
)
978 d
.dptr
[i
] = tolower(nick
[i
]);
983 static void command_mail(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
987 if (!mail_db
|| !seen_db
)
990 victim
= token(&args
, ' ');
991 if (!victim
|| !args
|| victim
[0] == '#' || strchr(victim
, '@') || strchr(victim
, '.')) {
992 privmsg(b
, target
, "%s: Usage: !mail <nick> <message>", nick
);
995 if (!strcasecmp(victim
, nick_self
)) {
996 action(b
, target
, "whirrs and clicks excitedly at the mail she received from %s", nick
);
999 if (!strcasecmp(victim
, nick
)) {
1000 action(b
, target
, "echos the words from %s back to them: %s", nick
, args
);
1004 key
= get_mail_key(victim
);
1006 val
= tdb_fetch(seen_db
, key
);
1007 if (val
.dptr
&& (x
= strchr(val
.dptr
, ','))) {
1008 int64_t now
= get_time(b
, NULL
);
1010 if (now
>= 0 && (now
- atoll(val
.dptr
)) < 300 && (!sstrncasecmp(x
, "in ") || !sstrncasecmp(x
, "joining "))) {
1011 action(b
, target
, "would rather not store mail for someone active so recently");
1016 val
= tdb_fetch(mail_db
, key
);
1022 for (cur
= val
.dptr
; cur
< val
.dptr
+ val
.dsize
; cur
+= strlen(cur
)+1)
1025 action(b
, target
, "looks sadly at %s as she cannot hold any more mail to %s", nick
, victim
);
1029 len
= strlen(nick
) + strlen(args
)+3;
1030 val
.dptr
= realloc(val
.dptr
, val
.dsize
+ len
);
1031 sprintf(val
.dptr
+ val
.dsize
, "%s: %s", nick
, args
);
1033 if (tdb_store(mail_db
, key
, val
, 0) < 0)
1034 privmsg(b
, target
, "%s: updating mail returns %s", nick
, tdb_errorstr(mail_db
));
1036 action(b
, target
, "whirrs and clicks at %s as she stores the mail for %s", nick
, victim
);
1042 static void command_deliver(struct bio
*b
, const char *nick
, const char *target
)
1048 key
= get_mail_key(nick
);
1049 val
= tdb_fetch(mail_db
, key
);
1054 if (strcasecmp(key
.dptr
, nick_self
)) {
1055 privmsg(b
, target
, "%s: You've got mail!", nick
);
1056 for (cur
= val
.dptr
; cur
< val
.dptr
+ val
.dsize
; cur
+= strlen(cur
)+1)
1057 privmsg(b
, target
, "From %s", cur
);
1060 tdb_delete(mail_db
, key
);
1064 static void update_seen(struct bio
*b
, char *doingwhat
, const char *nick
)
1068 key
= get_mail_key(nick
);
1069 TDB_DATA val
= { .dptr
= doingwhat
, .dsize
= strlen(doingwhat
)+1 };
1071 tdb_store(seen_db
, key
, val
, 0);
1075 static void command_seen(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
1077 char *arg
= token(&args
, ' '), *x
;
1078 int64_t now
= get_time(b
, target
);
1082 if (!seen_db
|| !arg
) {
1083 privmsg(b
, target
, "%s: { Error... }", nick
);
1086 if (!strcasecmp(arg
, nick_self
)) {
1087 action(b
, target
, "whirrs and clicks at %s", nick
);
1090 key
= get_mail_key(arg
);
1091 val
= tdb_fetch(seen_db
, key
);
1092 if (val
.dptr
&& (x
= strchr(val
.dptr
, ','))) {
1096 delta
= now
- atoll(val
.dptr
);
1097 str
= perty(&delta
);
1099 privmsg(b
, target
, "%s was last seen in the future %s", arg
, x
);
1101 privmsg(b
, target
, "%s was last seen %"PRIi64
" %s ago %s", arg
, delta
, str
, x
);
1103 action(b
, target
, "cannot find any evidence that %s exists", arg
);
1106 static void command_mailbag(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
1109 unsigned rem
= sizeof(buffer
)-1, first
= 2;
1114 for (TDB_DATA f
= tdb_firstkey(mail_db
); f
.dptr
;) {
1115 if (f
.dsize
+ 4 > rem
) {
1116 privmsg(b
, target
, "%s: Holding mail for: %s", nick
, &buffer
[rem
]);
1118 rem
= sizeof(buffer
)-1;
1119 assert(f
.dsize
+ 4 < rem
);
1126 memcpy(&buffer
[rem
], " and ", 5);
1130 memcpy(&buffer
[rem
], ", ", 2);
1133 memcpy(&buffer
[rem
], f
.dptr
, f
.dsize
- 1);
1135 TDB_DATA next
= tdb_nextkey(mail_db
, f
);
1140 privmsg(b
, target
, "%s: Holding mail for: %s", nick
, &buffer
[rem
]);
1143 static void command_no_deliver(struct bio
*b
, const char *nick
, const char *host
, const char *target
, char *args
)
1147 if (!mail_db
|| !(cur
= token(&args
, ' ')))
1149 key
= get_mail_key(cur
);
1150 val
= tdb_fetch(mail_db
, key
);
1152 action(b
, target
, "ponyshrugs as no mail for %s was found", cur
);
1154 action(b
, target
, "deletes all evidence of %s's mail", cur
);
1155 tdb_delete(mail_db
, key
);
1161 static void channel_msg(struct bio
*b
, const char *nick
, const char *host
,
1162 const char *chan
, const char *msg
, int64_t t
)
1164 char *cur
= NULL
, *next
;
1167 if (!msg
|| !strcmp(nick
, "`Celestia`") || !strcmp(nick
, "derpy") || !strcmp(nick
, "`Luna`") || !sstrncmp(nick
, "GitHub") || !sstrncmp(nick
, "CIA-") || !strcmp(nick
, "Terminus-Bot") || !strcmp(nick
, "r0m"))
1170 if (!sstrncasecmp(msg
, "\001ACTION ")) {
1171 msg
+= sizeof("\001ACTION ")-1;
1173 asprintf(&cur
, "%"PRIi64
",in %s: * %s %s", t
, chan
, nick
, msg
);
1175 asprintf(&cur
, "%"PRIi64
",in %s: <%s> %s", t
, chan
, nick
, msg
);
1177 update_seen(b
, cur
, nick
);
1183 while (next
&& (cur
= token(&next
, ' '))) {
1184 if (!strcasecmp(cur
, ">mfw") || !strcasecmp(cur
, "mfw")) {
1185 if (!strcasecmp(chan
, "#brony") || !ponify
)
1187 if (t
< 0 || t
> commands
[strhash("mfw") % elements(commands
)].disabled_until
)
1188 command_mfw(b
, nick
, host
, chan
, NULL
);
1190 } else if (!sstrncasecmp(cur
, "http://") || !sstrncasecmp(cur
, "https://")) {
1191 static char last_url
[512];
1193 if (!strcmp(cur
, last_url
))
1195 strncpy(last_url
, cur
, sizeof(last_url
)-1);
1197 if (t
>= 0 && t
< commands
[strhash("get") % elements(commands
)].disabled_until
)
1200 else if (strcasestr(cur
, "youtube.com/user") && (part
= strstr(cur
, "#p/"))) {
1202 part
= strrchr(part
, '/') + 1;
1203 asprintf(&foo
, "http://youtube.com/watch?v=%s", part
);
1205 internal_link(b
, nick
, host
, chan
, foo
, 0);
1208 } else if (strcasestr(cur
, "twitter.com/") || strcasestr(cur
, "mlfw.info") || strcasestr(cur
, "mylittlefacewhen.com"))
1210 internal_link(b
, nick
, host
, chan
, cur
, 0);
1216 static struct command_hash unhashed
[] = {
1217 { "1", command_coinflip
},
1218 { "get", command_get
},
1219 { "hug", command_hug
},
1220 { "hugs", command_hugs
},
1221 { "mfw", command_mfw
},
1222 { "swear", command_swear
},
1223 { "mail", command_mail
},
1224 { "seen", command_seen
},
1225 { "derpy", command_derpy
},
1227 { "rebuild", command_rebuild
, 1 },
1228 { "abort", command_abort
, 1 },
1229 { "crash", command_crash
, 1 },
1230 { "inspect", command_inspect
, 1 },
1231 { "timeout", command_timeout
, 1 },
1233 { "follow", command_follow
},
1234 { "unfollow", command_unfollow
},
1235 { "feeds", command_feeds
},
1238 { "feed_get", command_feed_get
, 1 },
1239 { "feed_set", command_feed_set
, 1 },
1240 { "feed_rem", command_feed_rem
, 1 },
1241 { "feed_xxx", command_feed_xxx
, 1 },
1242 { "feed_counter", command_feed_counter
, 1 },
1243 { "seen_xxx", command_seen_xxx
, 1 },
1244 { "mailbag", command_mailbag
, 1 },
1245 { "\"deliver\"", command_no_deliver
, 1 },
1248 static void init_hash(struct bio
*b
, const char *target
)
1251 for (i
= 0; i
< elements(unhashed
); ++i
) {
1252 unsigned h
= strhash(unhashed
[i
].string
) % elements(commands
);
1253 if (commands
[h
].string
)
1254 privmsg(b
, target
, "%s is a duplicate command with %s", commands
[h
].string
, unhashed
[i
].string
);
1256 commands
[h
] = unhashed
[i
];
1258 #ifdef local_commands
1259 for (i
= 0; i
< elements(local_commands
); ++i
) {
1260 unsigned h
= strhash(local_commands
[i
].string
) % elements(commands
);
1261 if (commands
[h
].string
)
1262 privmsg(b
, target
, "%s is a duplicate command with %s", commands
[h
].string
, local_commands
[i
].string
);
1264 commands
[h
] = local_commands
[i
];
1269 void init_hook(struct bio
*b
, const char *target
, const char *nick
, unsigned is_ponified
)
1271 char *cwd
, *path
= NULL
;
1273 static const char *messages
[] = {
1274 "feels circuits being activated that weren't before",
1275 "suddenly gets a better feel of her surroundings",
1276 "looks the same, yet there's definitely something different",
1277 "emits a beep as her lights begin to pulse slowly",
1278 "whirrrs and bleeps like never before",
1279 "bleeps a few times happily",
1280 "excitedly peeks at her surroundings"
1282 init_hash(b
, target
);
1283 ponify
= is_ponified
;
1285 cwd
= getcwd(NULL
, 0);
1286 asprintf(&path
, "%s/db/feed.tdb", cwd
);
1287 feed_db
= tdb_open(path
, 0, 0, O_RDWR
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
, 0644);
1290 privmsg(b
, target
, "Opening feed db failed: %m");
1292 asprintf(&path
, "%s/db/chan.tdb", cwd
);
1293 chan_db
= tdb_open(path
, 0, 0, O_RDWR
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
, 0644);
1296 privmsg(b
, target
, "Opening chan db failed: %m");
1298 asprintf(&path
, "%s/db/mail.tdb", cwd
);
1299 mail_db
= tdb_open(path
, 0, 0, O_RDWR
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
, 0644);
1302 privmsg(b
, target
, "Opening mail db failed: %m");
1304 asprintf(&path
, "%s/db/seen.tdb", cwd
);
1305 seen_db
= tdb_open(path
, 0, 0, O_RDWR
|O_CREAT
|O_CLOEXEC
|O_NOCTTY
, 0644);
1308 privmsg(b
, target
, "Opening seen db failed: %m");
1311 action(b
, target
, "%s", messages
[getrand() % elements(messages
)]);
1314 static void __attribute__((destructor
)) shutdown_hook(void)
1326 static char *nom_special(char *line
)
1329 if (*line
== 0x03) {
1331 if (*line
>= '0' && *line
<= '9')
1334 if (*line
>= '0' && *line
<= '9')
1336 if (line
[0] == ',' && line
[1] >= '0' && line
[1] <= '9')
1339 if (*line
>= '0' && *line
<= '9')
1341 } else if (*line
!= 0x02 && *line
!= 0x1f && *line
!= 0x16 && *line
!= 0x06 && *line
!= 0x07)
1349 static char *cleanup_special(char *line
)
1351 char *cur
, *start
= nom_special(line
);
1354 for (line
= cur
= start
; *line
; line
= nom_special(line
))
1355 *(cur
++) = *(line
++);
1357 for (cur
--; cur
>= start
; --cur
)
1358 if (*cur
!= ' ' && *cur
!= '\001')
1367 static void rss_check(struct bio
*b
, const char *channel
, int64_t t
)
1369 static unsigned rss_enter
, rss_leave
;
1370 if (t
>= 0 && rss_enter
== rss_leave
) {
1372 channel_feed_check(b
, channel
, t
);
1377 void privmsg_hook(struct bio
*b
, const char *prefix
, const char *ident
, const char *host
,
1378 const char *const *args
, unsigned nargs
)
1380 char *cmd_args
, *cmd
;
1381 const char *target
= args
[0][0] == '#' ? args
[0] : prefix
;
1382 unsigned chan
, nick_len
;
1383 struct command_hash
*c
;
1384 int64_t t
= get_time(b
, target
);
1385 int is_admin
= admin(host
);
1387 chan
= args
[0][0] == '#';
1389 rss_check(b
, args
[0], t
);
1390 command_deliver(b
, prefix
, args
[0]);
1392 cmd_args
= cleanup_special((char*)args
[1]);
1396 if (ident
&& (!strcasecmp(ident
, "Revolver") || !strcasecmp(ident
, "Rev")))
1399 if (chan
&& cmd_args
[0] == '!') {
1401 } else if (chan
&& (nick_len
= strlen(nick_self
)) &&
1402 !strncasecmp(cmd_args
, nick_self
, nick_len
) &&
1403 (cmd_args
[nick_len
] == ':' || cmd_args
[nick_len
] == ',') && cmd_args
[nick_len
+1] == ' ') {
1404 cmd_args
+= nick_len
+ 2;
1409 if (command_channel
.enter
== command_channel
.left
) {
1410 command_channel
.enter
++;
1411 snprintf(command_channel
.failed_command
,
1412 sizeof(command_channel
) - offsetof(struct command_hash
, failed_command
) - 1,
1413 "%s:%s (%s@%s) \"#\" %s", target
, prefix
, ident
, host
, cmd_args
);
1414 channel_msg(b
, prefix
, host
, args
[0], cmd_args
, t
);
1415 command_channel
.left
++;
1419 cmd
= token(&cmd_args
, ' ');
1420 if (!chan
&& cmd_args
&& cmd_args
[0] == '#') {
1422 privmsg(b
, prefix
, "%s: { Error... }", prefix
);
1425 target
= token(&cmd_args
, ' ');
1428 c
= &commands
[strhash(cmd
) % elements(commands
)];
1429 if (c
->string
&& !strcasecmp(c
->string
, cmd
)) {
1430 if (c
->left
!= c
->enter
)
1431 privmsg(b
, target
, "Command %s is disabled because of a crash", c
->string
);
1432 else if (t
> 0 && t
< c
->disabled_until
&& !is_admin
) {
1433 int64_t delta
= c
->disabled_until
- t
;
1434 const char *str
= perty(&delta
);
1435 b
->writeline(b
, "NOTICE %s :Command %s is on timeout for the next %"PRIi64
" %s", prefix
, c
->string
, delta
, str
);
1436 } else if (c
->admin
&& !is_admin
) {
1437 static const char *insults
[] = {
1438 "{ Rarity, I love you so much! }",
1439 "{ Rarity, have I ever told you that I love you? }",
1440 "{ Yes, I love my sister, Rarity. }",
1441 "{ Raaaaaaaaaaaaaarity. }",
1442 "{ You do not fool me, Rari...bot! }"
1444 privmsg(b
, target
, "%s: %s", prefix
, insults
[getrand() % elements(insults
)]);
1447 snprintf(c
->failed_command
, sizeof(*c
) - offsetof(struct command_hash
, failed_command
) - 1,
1448 "%s:%s (%s@%s) \"%s\" %s", target
, prefix
, ident
, host
, cmd
, cmd_args
);
1449 c
->cmd(b
, prefix
, host
, target
, cmd_args
);
1452 } else if (chan
== 2)
1453 privmsg(b
, target
, "%s: { I love you! }", prefix
);
1456 void command_hook(struct bio
*b
, const char *prefix
, const char *ident
, const char *host
,
1457 const char *command
, const char *const *args
, unsigned nargs
)
1461 if (!strcasecmp(command
, "NOTICE")) {
1462 if (nargs
< 2 || args
[0][0] == '#')
1465 b
->writeline(b
, "%s", args
[1]);
1467 fprintf(stderr
, "%s: %s\n", prefix
, args
[1]);
1468 } else if (!strcasecmp(command
, "JOIN")) {
1469 t
= get_time(b
, args
[0]);
1470 rss_check(b
, args
[0], t
);
1471 command_deliver(b
, prefix
, args
[0]);
1472 asprintf(&buf
, "%"PRIi64
",joining %s", t
, args
[0]);
1473 } else if (!strcasecmp(command
, "PART")) {
1474 t
= get_time(b
, args
[0]);
1475 rss_check(b
, args
[0], t
);
1476 asprintf(&buf
, "%"PRIi64
",leaving %s", t
, args
[0]);
1477 } else if (!strcasecmp(command
, "QUIT")) {
1478 t
= get_time(b
, NULL
);
1479 asprintf(&buf
, "%"PRIi64
",quitting with the message \"%s\"", t
, args
[0]);
1480 } else if (!strcasecmp(command
, "NICK")) {
1481 t
= get_time(b
, NULL
);
1483 asprintf(&buf
, "%"PRIi64
",changing nick from %s", t
, prefix
);
1485 update_seen(b
, buf
, args
[0]);
1489 asprintf(&buf
, "%"PRIi64
",changing nick to %s", t
, args
[0]);
1492 fprintf(stderr
, ":%s!%s%s %s", prefix
, ident
, host
, command
);
1493 for (i
= 0; i
< nargs
; ++i
)
1494 fprintf(stderr
, " %s", args
[i
]);
1495 fprintf(stderr
, "\n");
1498 update_seen(b
, buf
, prefix
);