remove boilerplate js callback wrapper funcs
[rofl0r-jsbot.git] / jsbot.c
blobb6c6a13e0408166a966cfb24930db958bd35d1a5
1 /* Copyright (C) 2017 rofl0r
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License along
14 with this program; if not, write to the Free Software Foundation, Inc.,
15 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 #include "rocksock.h"
18 #include "rsirc.h"
19 #include <string.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <ctype.h>
23 #include <unistd.h>
24 #include <stdarg.h>
25 #include <assert.h>
26 #include <fcntl.h>
27 #include <sys/stat.h>
28 #include <time.h>
29 #include <mujs.h>
30 //RcB: LINK "-lmujs"
32 #include "simplecfg.c"
33 #ifndef TRANSACT_TIME
34 #define TRANSACT_TIME 500 * 1000
35 #endif
37 static const char hextab[] = "0123456789abcdef";
39 static char* decode(const char *in, char* out) {
40 const unsigned char* pi = (void*)in;
41 char *po = out;
42 while(*pi) {
43 if(*pi > 20 && *pi < 128) *po++ = *pi++;
44 else {
45 *po++='\\';
46 *po++='x';
47 *po++= hextab[(*pi & 0xf0) >> 4];
48 *po++= hextab[*pi++ & 15];
51 *po = 0;
52 return out;
55 static int split(const char *in, char sep, int splitcount, ...) {
56 va_list ap;
57 va_start(ap, splitcount);
58 int r = 1;
59 int i = 0;
60 const char *start = in;
61 while(i < splitcount-1 || !splitcount) {
62 char * out = va_arg(ap, char*);
63 if(!out && !splitcount) break;
64 size_t idx = 0;
65 while(start[idx] && start[idx] != sep) idx++;
66 memcpy(out, start, idx);
67 out[idx] = 0;
68 if(!start[idx]) { r = 0; goto ret; }
69 start += idx + 1;
70 i++;
72 if(splitcount) {
73 char * out = va_arg(ap, char*);
74 strcpy(out, start);
76 ret:
77 va_end(ap);
78 return r;
81 #define chk(X, ACTION) if(X) { \
82 rocksock_error_dprintf(2, s); \
83 ACTION; \
85 #define chk2(X, ACTION) if(X) { ACTION; }
86 /* "" */
89 static rocksock rs, *s = &rs;
90 static rsirc ircb, *irc = &ircb;
91 static rs_proxy proxies[2];
92 static int done_rs_init;
94 //PING :kornbluth.freenode.net
95 static int ping_handler(char *buf) {
96 char b[512];
97 snprintf(b, sizeof(b), "PONG %s", buf+1);
98 return rsirc_sendline(irc, b);
101 static char *own_nick;
102 static char *alternate_nick;
103 static char nick1[32];
104 static char nick2[32];
105 static char nick3[32];
106 static char proxy[512];
107 static char savefile[64];
108 static char host[256];
109 static int port;
110 static int use_ssl;
113 static int want_quit;
115 static js_State *J;
117 static void jscb_strings_command(const char* cmd, int args, ...) {
118 int i;
119 va_list ap;
120 va_start(ap, args);
121 js_getglobal(J, cmd);
122 js_pushnull(J);
123 for (i = 0; i < args; ++i) {
124 const char *a = va_arg(ap, const char *);
125 js_pushstring(J, a);
127 va_end(ap);
128 if (js_pcall(J, args))
129 dprintf(2, "error calling %s: %s\n", cmd, js_tostring(J, -1));
130 js_pop(J, 1);
133 static void jscb_onconnect(void) {
134 jscb_strings_command("connect", 0);
136 static void jscb_onjoinself(const char* chan, const char* nick) {
137 jscb_strings_command("selfjoin", 2, chan, nick);
139 static void jscb_botnick(const char* nick) {
140 jscb_strings_command("botnick", 1, nick);
142 static int on_joinself(const char* chan, const char* nick) {
143 jscb_onjoinself(chan, nick);
144 return 0;
147 /* join: mask, cmd, chan
148 part: mask, cmd, chan, :"msg"
149 quit: mask, cmd, :msg
150 kick: mask, cmd, chan, whom, :msg
151 notice: mask, cmd, dest, :msg
152 privmsg: mask, cmd, dest, :msg
154 enum action {
155 a_join = 0, a_part, a_quit, a_kick,
156 a_notice, a_privmsg
158 static const char action_args[] = {
159 [a_join] = 1, [a_part] = 2,
160 [a_quit] = 1, [a_kick] = 3,
161 [a_notice] = 2, [a_privmsg] = 2,
163 static const char actionarg_msgadd[][3] = { /* this is to add 1 to the msg argument so the leading ':' is skipped */
164 [a_join] = "\0\0\0", [a_part] = "\0\1\0",
165 [a_quit] = "\1\0\0", [a_kick] = "\0\0\1",
166 [a_notice] = "\0\1\0", [a_privmsg] = "\0\1\0",
169 /* nick+mask always go together in that order, i.e. 0,1. */
170 static const char action_order[][5] = {
171 [a_join] ="\2\0\1\n\n", [a_part] = "\2\0\1\3\n",
172 [a_quit] ="\0\1\2\n\n", [a_kick] = "\0\1\3\2\4",
173 [a_notice]="\2\0\1\3\n", [a_privmsg]= "\2\0\1\3\n",
175 static const char dispatchtbl[][14]={
176 [a_join] = "joinhandler", [a_part] = "parthandler",
177 [a_quit] = "quithandler", [a_kick] = "kickhandler",
178 [a_notice] = "noticehandler", [a_privmsg] = "msghandler",
180 static const char *action_arg(enum action a, int pos, const char* args[]) {
181 int l = action_order[a][pos];
182 return l == '\n' ? 0 : args[l];
184 static void action_dispatch(enum action a, const char* args[]) {
185 const char *a0 = action_arg(a, 0, args);
186 const char *a1 = action_arg(a, 1, args);
187 const char *a2 = action_arg(a, 2, args);
188 const char *a3 = action_arg(a, 3, args);
189 const char *a4 = action_arg(a, 4, args);
190 jscb_strings_command(dispatchtbl[a], action_args[a]+2, a0, a1, a2, a3, a4);
192 static void prep_action_handler(char *buf, size_t cmdpos, enum action a) {
193 char nick[512];
194 char mask[512];
195 char cmd[16];
196 char a1[512];
197 char a2[512];
198 char a3[512];
199 size_t i;
200 a1[0] = a2[0] = a3[0] = i = 0;
201 split(buf+1, ' ', 2+action_args[a], mask, cmd, a1, a2, a3);
202 while(mask[i] != '!' && mask[i] != ' ') i++;
203 memcpy(nick, mask, i);
204 nick[i] = 0;
205 unsigned a1off = a1[0] ? actionarg_msgadd[a][0] : 0;
206 unsigned a2off = a2[0] ? actionarg_msgadd[a][1] : 0;
207 unsigned a3off = a3[0] ? actionarg_msgadd[a][2] : 0;
208 const char* args[5] = {nick, mask, a1+a1off, a2+a2off, a3+a3off};
209 action_dispatch(a, args);
212 static int motd_finished() {
213 jscb_onconnect();
214 return 0;
217 void switch_names(void) {
218 char *t = own_nick;
219 own_nick = alternate_nick;
220 alternate_nick = t;
221 rsirc_sendlinef(irc, "NICK %s", own_nick);
224 int read_cb(char* buf, size_t bufsize) {
225 if(buf[0] == ':') {
226 size_t i = 0, j, k;
227 while(!isspace(buf[i])) i++;
228 int cmd = atoi(buf+i);
229 switch(cmd) {
230 case 0: /* no number */
231 j = ++i;
232 while(!isspace(buf[j])) j++;
233 switch(j - i) {
234 case 4:
235 if(!memcmp(buf+i,"JOIN", 4))
236 prep_action_handler(buf, i, a_join);
237 else if(!memcmp(buf+i,"PART", 4))
238 prep_action_handler(buf, i, a_part);
239 else if(!memcmp(buf+i,"QUIT", 4)) prep_action_handler(buf, i, a_quit);
240 break;
241 case 7:
242 if(!memcmp(buf+i,"PRIVMSG", 7))
243 prep_action_handler(buf, i, a_privmsg);
244 break;
245 case 6:
246 if(!memcmp(buf+i,"NOTICE", 6))
247 prep_action_handler(buf, i, a_notice);
248 default:
249 break;
251 break;
252 /* status messages having the bot nickname in it, like:
253 :rajaniemi.freenode.net 255 foobot :I have 8369 clients and 1 servers */
254 case 5: case 250: case 251: case 252: case 253:
255 case 254: case 255: case 265: case 266: case 375:
256 i++;
257 while(!isspace(buf[i])) i++;
258 j = ++i;
259 while(!isspace(buf[i])) i++;
260 buf[i] = 0;
261 jscb_botnick(buf + j);
262 break;
263 case 376: motd_finished(); break;
264 //:kornbluth.freenode.net 433 * foobot :Nickname is already in use.
265 case 433:
266 if(alternate_nick) switch_names();
267 else {
268 rocksock_disconnect(s);
269 sleep(30);
271 break;
272 default: break;
273 case 366:
274 while(!isspace(buf[++i]));
275 i++;
276 k = i;
277 while(!isspace(buf[++i]));
278 buf[i] = 0;
279 j = ++i;
280 while(!isspace(buf[++i]));
281 buf[i] = 0;
282 on_joinself(buf + j, buf + k);
283 break;
285 } else {
286 size_t i = 0;
287 while(!isspace(buf[i])) i++;
288 if(i == 4 && !memcmp(buf, "PING", 4)) ping_handler(buf + 5);
290 return 0;
293 char *cfgfilename;
294 static int load_cfg(void) {
295 FILE *cfg = cfg_open(cfgfilename);
296 if(!cfg) { perror("fopen"); return 0; }
297 cfg_getstr(cfg, "nick1", nick1, sizeof(nick1));
298 *nick2 = 0;
299 cfg_getstr(cfg, "nick2", nick2, sizeof(nick2));
300 cfg_getstr(cfg, "nick3", nick3, sizeof(nick3));
301 cfg_getstr(cfg, "proxy", proxy, sizeof(proxy));
302 int hostnr = (rand()%2)+1;
303 char hb[10];
304 again:
305 snprintf(hb, sizeof hb, "host%d", hostnr);
306 if(!cfg_getstr(cfg, hb, host, sizeof(host)) && hostnr == 2) { hostnr = 1; goto again; }
307 port = cfg_getint(cfg, "port");
308 use_ssl = cfg_getint(cfg, "ssl");
309 cfg_getstr(cfg, "savefile", savefile, sizeof savefile);
310 if(!savefile[0]) {
311 dprintf(2, "error: savefile config item not set!\n");
312 exit(1);
314 cfg_close(cfg);
315 own_nick = nick1;
316 if(*nick2) alternate_nick = nick2;
317 return 1;
320 int connect_it(void) {
321 load_cfg();
322 if(done_rs_init) {
323 rocksock_disconnect(s);
324 rocksock_clear(s);
326 rocksock_init(s, proxies);
327 done_rs_init = 1;
328 rocksock_set_timeout(s, 36000);
329 if(proxy[0])
330 chk(rocksock_add_proxy_fromstring(s, proxy), exit(1));
331 chk(rocksock_connect(s, host, port, use_ssl), goto err);
332 chk(rsirc_init(irc, s), goto err);
333 chk(rsirc_handshake(irc, host, own_nick, "foo"), goto err);
335 return 1;
336 err:
337 usleep(TRANSACT_TIME);
338 return 0;
341 static void js_sendline(js_State *J) {
342 const char* msg = js_tostring(J, 1);
343 int ret = rsirc_sendline(irc, msg);
344 js_pushnumber(J, ret);
347 static void js_privmsg(js_State *J) {
348 const char* chan = js_tostring(J, 1);
349 const const char* msg = js_tostring(J, 2);
350 int ret = rsirc_privmsg(irc, chan, msg);
351 js_pushnumber(J, ret);
354 static void js_errmsg(js_State *J) {
355 js_pushstring(J, rocksock_strerror(s));
358 static int reload_script() {
359 if (js_dofile(J, "ircbot.js"))
360 return 1;
361 js_gc(J, 0);
362 return 0;
365 static void js_reload(js_State *J) {
366 int ret = reload_script();
367 js_pushboolean(J, !ret);
370 static void js_disconnect(js_State *J) {
371 rocksock_disconnect(s);
372 js_pushundefined(J);
375 static void js_debugprint(js_State *J) {
376 const char* msg = js_tostring(J, 1);
377 dprintf(2, "%s\n", msg);
378 js_pushundefined(J);
381 static void js_writesettings(js_State *J) {
382 int fd, fail = 1; size_t l;
383 const char* contents = js_tostring(J, 1);
384 if( 0 == contents) goto err;
385 if( 0 == (l = strlen(contents))) goto err;
386 if(-1 == (fd = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0660))) goto err;
387 if( l != write(fd, contents, l)) goto fderr;
388 fail = 0;
389 fderr:
390 close(fd);
391 err:
392 js_pushboolean(J, !fail);
395 static void js_readsettings(js_State *J) {
396 struct stat st;
397 char *contents;
398 int fd, failed = 1;
399 if(stat(savefile, &st)) goto err;
400 if(-1 == (fd = open(savefile, O_RDONLY))) goto err;
401 if( 0 == (contents = malloc(st.st_size + 1))) goto fderr;
402 if(st.st_size != read(fd, contents, st.st_size)) goto mallerr;
403 failed = 0;
404 contents[st.st_size] = 0;
405 js_pushstring(J, contents);
406 mallerr:
407 free(contents);
408 fderr:
409 close(fd);
410 err:
411 if(failed) js_pushundefined(J);
414 static int syntax() { dprintf(2, "need filename of cfg file\n"); return 1; }
415 int main(int argc, char** argv) {
416 if(argc <= 1) return syntax();
417 cfgfilename = argv[1];
418 srand(time(0));
419 if(!load_cfg()) return 1;
421 J = js_newstate(NULL, NULL, JS_STRICT);
423 js_newcfunction(J, js_privmsg, "privmsg", 2);
424 js_setglobal(J, "privmsg");
426 js_newcfunction(J, js_sendline, "send", 1);
427 js_setglobal(J, "send");
429 js_newcfunction(J, js_errmsg, "errmsg", 0);
430 js_setglobal(J, "errmsg");
432 js_newcfunction(J, js_reload, "reload", 0);
433 js_setglobal(J, "reload");
435 js_newcfunction(J, js_writesettings, "writesettings", 1);
436 js_setglobal(J, "writesettings");
438 js_newcfunction(J, js_readsettings, "readsettings", 0);
439 js_setglobal(J, "readsettings");
441 js_newcfunction(J, js_disconnect, "disconnect", 0);
442 js_setglobal(J, "disconnect");
444 js_newcfunction(J, js_debugprint, "debugprint", 1);
445 js_setglobal(J, "debugprint");
447 if(reload_script()) {
448 dprintf(2, "error: loading ircbot.js failed\n");
449 return 1;
452 size_t rcvd;
454 rocksock_init_ssl();
456 conn:
457 while(!connect_it());
459 while(!want_quit) {
460 char line[512];
461 char decodebuf[512*4];
462 chk(rsirc_process(irc, line, &rcvd), goto conn);
463 if(rcvd) {
464 dprintf(2, "LEN %zu - %s\n", rcvd, decode(line, decodebuf));
465 chk(read_cb(line, sizeof line), goto conn);
468 usleep(10000);
471 rocksock_disconnect(s);
472 rocksock_free_ssl();
473 return 0;