PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/jbot/jbot.c

http://github.com/JAChapmanII/jbotc
C | 452 lines | 304 code | 59 blank | 89 comment | 73 complexity | 16a648f6c244c5bd8b66b917d50caf3d MD5 | raw file
  1. /* jbot is the program doing the major parsing of input and sending output
  2. * back to users. It is run by conbot, which sets up stdin and stdout to go to
  3. * pipes from which conbot can read and write to.
  4. *
  5. * Since jbotc reads from stdin and writes to stdout, it can be run in a
  6. * terminal and fed made up input for testing purposes
  7. */
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <regex.h>
  12. #include <unistd.h>
  13. #include <time.h>
  14. #include "functions.h"
  15. #include "greetings.h"
  16. #include "defines.h"
  17. #include "util.h"
  18. #include "rlist.h"
  19. #include "markov.h"
  20. // Container for all of the variables and configuration settings
  21. BMap *varsMap = NULL;
  22. BMap *confMap = NULL;
  23. // Container for regex based "is" function
  24. RList *regexList = NULL;
  25. /* Add a regex to the regexList for later matching */
  26. void addRegex(FunctionArgs *fa) { // {{{
  27. if(!fa->toUs)
  28. return;
  29. if(rlist_size(regexList) >= 256) {
  30. send(fa->target, "%s: We already have 256 \"is\" clauses!", fa->name);
  31. return;
  32. }
  33. fa->matchedOn[fa->matches[1].rm_eo] = '\0';
  34. fa->matchedOn[fa->matches[2].rm_eo] = '\0';
  35. char *msg = rlist_add(regexList,
  36. fa->matchedOn + fa->matches[1].rm_so,
  37. fa->matchedOn + fa->matches[2].rm_so);
  38. send(fa->target, "%s: %s", fa->name, (msg ? msg : "Okay!"));
  39. } // }}}
  40. /* Remove a regex from regexList */
  41. void removeRegex(FunctionArgs *fa) { // {{{
  42. fa->matchedOn[fa->matches[1].rm_eo] = '\0';
  43. if(rlist_remove(regexList, fa->matchedOn + fa->matches[1].rm_so))
  44. send(fa->target, "%s: Okay!", fa->name);
  45. else
  46. send(fa->target, "%s: I don't know anything about that", fa->name);
  47. } // }}}
  48. // Container for information necessary to generate markov chains
  49. Markov *markovGenerator = NULL;
  50. /* markov will eventually print markov chains generated from previous input. */
  51. void markov(FunctionArgs *fa) { // {{{
  52. if(fa->matchCount == 0) {
  53. // if we didn't recieve an argument, print usage
  54. send(fa->target, "%s: Usage: markov <word>", fa->name);
  55. } else {
  56. char *msg = markov_fetch(markovGenerator,
  57. fa->matchedOn + fa->matches[1].rm_so, 4096);
  58. if(!msg) {
  59. send(fa->target, "%s: Did not match! (%s)", fa->name,
  60. fa->matchedOn + fa->matches[1].rm_so);
  61. } else {
  62. send(fa->target, "%s: %s", fa->name, msg + ((msg[0] == ' ') ? 1 : 0));
  63. free(msg);
  64. }
  65. }
  66. } // }}}
  67. // FUNCREG should not be used directly
  68. #define FUNCREGX(x, y) { #x, #y, 1, 0, NULL, &x }
  69. /* There are two types of functions:
  70. * A: Has to have some sort of argument after it
  71. * B: May or may not have argumens after it
  72. * the function must make sure that the first token is what it expects
  73. * C: Either the function name is by itself, or it is type A
  74. */
  75. #define FUNCTIONA(x) FUNCREGX(x, ^x (.*)$)
  76. #define FUNCTIONB(x) FUNCREGX(x, ^x.*$)
  77. #define FUNCTIONC(x) FUNCREGX(x, ^x ## $), FUNCTIONA(x)
  78. FuncStruct functions[] = {
  79. // Type A functions
  80. FUNCTIONA(declare),
  81. FUNCTIONA(increment), { "++", "^ *\\+\\+ *(.*)$", 1, 0, NULL, &increment },
  82. FUNCTIONA(decrement), { "--", "^ *-- *(.*)$", 1, 0, NULL, &decrement },
  83. { "delete", "^ *delete *(.*)$", 1, 0, NULL, &deleteVariable },
  84. // Type B functions
  85. FUNCTIONB(set), FUNCTIONB(help),
  86. // Type C functions
  87. FUNCTIONC(wave),
  88. { "wave", "^o/$", 0, 0, NULL, &wave },
  89. { "wave", "^o/ .*$", 0, 0, NULL, &wave },
  90. { "wave", "^\\\\o$", 0, 0, NULL, &wave },
  91. { "wave", "^\\\\o .*$", 0, 0, NULL, &wave },
  92. { "<3", "^<3$", 0, 0, NULL, &lessThanThree },
  93. FUNCTIONC(fish), FUNCTIONC(fishes), FUNCTIONC(sl), FUNCTIONC(dubstep),
  94. FUNCTIONC(list), FUNCTIONC(markov),
  95. // Entirely special type functions
  96. { "or", "^(.*) or (.*)$", 2, 0, NULL, &eitherOr },
  97. { "is", "^(.*) is (.*)$", 2, 0, NULL, &addRegex },
  98. { "forget", "^forget (.*)$", 1, 0, NULL, &removeRegex },
  99. // End of functions marker
  100. { NULL, NULL, 0, 0, NULL, NULL },
  101. };
  102. /* Allocate memory for function regex and compile them */
  103. int setupFunctions() { // {{{
  104. for(int i = 0; functions[i].name && functions[i].f; ++i) {
  105. functions[i].r = malloc(sizeof(regex_t));
  106. if(!functions[i].r) {
  107. fprintf(stderr, "Could not malloc space for %s regex!\n",
  108. functions[i].name);
  109. return 0;
  110. }
  111. //fprintf(stderr, "%s: \"%s\"\n", functions[i].name, functions[i].regx);
  112. // If we fail to compile the function regex, abort
  113. int fail = regcomp(functions[i].r, functions[i].regx, REG_EXTENDED);
  114. if(fail) {
  115. fprintf(stderr, "Could not compile %s regex!\n", functions[i].name);
  116. fprintf(stderr, "erromsg: %s\n", getRegError(fail, functions[i].r));
  117. return 0;
  118. }
  119. }
  120. return 1;
  121. } // }}}
  122. /* Free memory associated with functions and their regex */
  123. int deinitFunctions() { // {{{
  124. for(int i = 0; functions[i].name && functions[i].f; ++i) {
  125. if(functions[i].r)
  126. regfree(functions[i].r);
  127. free(functions[i].r);
  128. }
  129. return 0;
  130. } // }}}
  131. /* main runs through a loop getting input and sending output. We logFile
  132. * everything to a file name *lfname. See internals for commands recognized
  133. */
  134. int main(int argc, char **argv) {
  135. int markovMode = 0, seed = 0;
  136. if(argc > 1) {
  137. if(!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) {
  138. printf("Usage: %s", argv[0]);
  139. printf("Usually, this is run by conbot. If you want to run it");
  140. printf(" manually, you must type standard IRC broadcast messages\n");
  141. printf("If the second argument is an integer, it is used as a random");
  142. printf(" seed. If it is anything else, markov mode will be entered\n");
  143. return 0;
  144. } else {
  145. seed = atoi(argv[1]);
  146. if(!seed) {
  147. markovMode = 1;
  148. fprintf(stderr, "Entering markov mode\n");
  149. }
  150. }
  151. }
  152. char str[BSIZE] = { 0 }, *tok = NULL, *tmsg = NULL, *cstart = NULL;
  153. char name[PBSIZE] = { 0 }, hmask[PBSIZE] = { 0 }, cname[PBSIZE] = { 0 }, msg[BSIZE] = { 0 };
  154. regex_t pmsgRegex, joinRegex;
  155. regmatch_t mptr[16];
  156. // TODO: abuse macros? stringification/concatenation?
  157. const char *privmsgRegexExp =
  158. "^:([A-Za-z0-9_]*)!([-@~A-Za-z0-9_\\.]*) PRIVMSG ([#A-Za-z0-9_]*) :(.*)";
  159. const char *joinRegexExp =
  160. "^:([A-Za-z0-9_]*)!([-@~A-Za-z0-9_\\.]*) JOIN :([#A-Za-z0-9_]*)";
  161. // seed random number generator with current time
  162. srand(time(NULL));
  163. if(seed)
  164. srand(atoi(argv[1]));
  165. // If we fail to compile the PRIVMSG regex, abort
  166. int fail = regcomp(&pmsgRegex, privmsgRegexExp, REG_EXTENDED);
  167. if(fail) {
  168. fprintf(stderr, "Could not compile privmsg regex!\n");
  169. fprintf(stderr, "erromsg: %s\n", getRegError(fail, &pmsgRegex));
  170. return 1;
  171. }
  172. // If we fail to compile the JOIN regex, abort
  173. fail = regcomp(&joinRegex, joinRegexExp, REG_EXTENDED);
  174. if(fail) {
  175. fprintf(stderr, "Could not compile join regex!\n");
  176. fprintf(stderr, "erromsg: %s\n", getRegError(fail, &joinRegex));
  177. return 1;
  178. }
  179. // If we can't malloc/compile the function regex's, abort
  180. if(!setupFunctions())
  181. return 1;
  182. // If we fail to create a basic map, abort
  183. if((varsMap = bmap_create()) == NULL) {
  184. fprintf(stderr, "Could not create contstant map!\n");
  185. return 1;
  186. }
  187. // If we fail to create a basic map, abort
  188. if((confMap = bmap_create()) == NULL) {
  189. fprintf(stderr, "Could not create configuration map!\n");
  190. return 1;
  191. }
  192. // if we fail to create an RList head, abort
  193. if((regexList = rlist_create()) == NULL) {
  194. fprintf(stderr, "Could not create regex list!\n");
  195. return 1;
  196. }
  197. // if we fail to create a Markov, abort
  198. if((markovGenerator = markov_create(2)) == NULL) {
  199. fprintf(stderr, "Could not create markov generator!\n");
  200. return 1;
  201. }
  202. // If we fail to open the logFile in append mode, abort
  203. if(!initLogFile())
  204. return 1;
  205. // print separator to logFile
  206. lprintf("------------------------------------\n");
  207. // Setup the command start string, abort on failure
  208. // TODO: regex-ify this thing
  209. cstart = malloc(strlen(nick) + 2);
  210. if(!cstart) {
  211. fprintf(stderr, "Could not malloc space for command start string!\n");
  212. return 1;
  213. }
  214. strcpy(cstart, nick);
  215. strcat(cstart, ":");
  216. send(owner, "%s", obtainGreeting());
  217. // try to read old variables in
  218. int count = bmap_read(varsMap, dumpFileName);
  219. if(count > 0) {
  220. send(owner, "Read in %d variables", count);
  221. }
  222. // try to read in old regex
  223. count = rlist_read(regexList, regexDumpFileName);
  224. if(count > 0) {
  225. send(owner, "Read in %d regex", count);
  226. }
  227. // try to read in old markov chain generator
  228. count = markov_read(markovGenerator, markovDumpFileName);
  229. if(count > 0)
  230. send(owner, "Read in %d markov chain entries", count);
  231. else if(count)
  232. send(owner, "Error code reading markov chain: %d", count);
  233. fflush(stdout);
  234. lflush();
  235. // main loop, go until we say we are done or parent is dead/closes us off
  236. int done = 0;
  237. while(!feof(stdin) && !done) {
  238. if(fgets(str, BSIZE - 1, stdin) == str) {
  239. // replace newline with null
  240. str[strlen(str) - 1] = '\0';
  241. // log all incoming data
  242. lprintf(" <- %s\n", str);
  243. fail = regexec(&pmsgRegex, str, pmsgRegex.re_nsub + 1, mptr, 0);
  244. // if a PRIVMSG was broadcast to us
  245. if(!fail) {
  246. // copy nick of sender into name
  247. strncpy(name, str + mptr[1].rm_so, mptr[1].rm_eo - mptr[1].rm_so);
  248. name[mptr[1].rm_eo - mptr[1].rm_so] = '\0';
  249. // copy host mask of sender into hmask
  250. strncpy(hmask, str + mptr[2].rm_so, mptr[2].rm_eo - mptr[2].rm_so);
  251. hmask[mptr[2].rm_eo - mptr[2].rm_so] = '\0';
  252. // copy target of PRIVMSG into cname
  253. strncpy(cname, str + mptr[3].rm_so, mptr[3].rm_eo - mptr[3].rm_so);
  254. cname[mptr[3].rm_eo - mptr[3].rm_so] = '\0';
  255. // copy message part into msg
  256. strcpy(msg, str + mptr[4].rm_so);
  257. // setup tmsg to point to the msg in str
  258. tmsg = str + mptr[4].rm_so;
  259. char *msgp = msg;
  260. lprintf("PRIVMSG from %s to %s: %s\n", name, cname, msg);
  261. tok = strtok(tmsg, " ");
  262. int toUs = 0;
  263. // if this is targeted at us
  264. if(!strcmp(tok, cstart)) {
  265. // ignore it
  266. tok = strtok(NULL, " ");
  267. toUs = 1;
  268. if(strlen(msgp) <= strlen(cstart) + 1) {
  269. // then there is nothing to do a command on
  270. continue;
  271. }
  272. msgp += strlen(cstart) + 1;
  273. }
  274. if(!strcmp(cname, nick))
  275. toUs = 1;
  276. FunctionArgs fargs = {
  277. name, hmask, ((!strcmp(cname, nick)) ? name : chan),
  278. toUs, msgp, 0, mptr, varsMap, confMap
  279. };
  280. int matched = 0;
  281. if(!markovMode) {
  282. //fprintf(stderr, "Matching on \"%s\"\n", msgp);
  283. for(int i = 0; !matched && functions[i].f; ++i) {
  284. fail = regexec(functions[i].r,
  285. msgp, functions[i].r->re_nsub + 1, mptr, 0);
  286. if(!fail) {
  287. matched = 1;
  288. fargs.matchCount = functions[i].r->re_nsub;
  289. functions[i].f(&fargs);
  290. }
  291. }
  292. }
  293. // none of the standard functions matched
  294. if(!matched) {
  295. // reload stops this instance, parent conbot starts new one
  296. if(!strcmp(tok, "reload") && !strcmp(name, owner) && toUs) {
  297. lprintf("Got message to restart...\n");
  298. done = 1;
  299. // we should write out the var map to a file
  300. } else if(!strcmp(tok, "wvar") && !strcmp(name, owner) && toUs) {
  301. tok = strtok(NULL, " ");
  302. if(!tok) {
  303. send(fargs.target, "%s: Must specify a file name!\n",
  304. fargs.name);
  305. } else {
  306. int res = bmap_writeDot(varsMap, tok);
  307. send(fargs.target, "%s: Wrote var map to %s (%d)\n",
  308. fargs.name, tok, res);
  309. }
  310. // token after cstart does not match any command
  311. } else {
  312. char *is = rlist_match(regexList, msg);
  313. if(is) {
  314. send(fargs.target, "%s", is);
  315. } else {
  316. markov_insert(markovGenerator, msg);
  317. //fprintf(stderr, "inserted \"%s\" into mgen", msg);
  318. //fprintf(stderr, "Could not match!\n");
  319. // msg ends with question mark, guess an answer
  320. if(toUs && (strlen(msg) > 0) &&
  321. (msg[strlen(msg) - 1] == '?')) {
  322. send(fargs.target, "%s: %s", name,
  323. ((rand() % 2) ? "Yes" : "No"));
  324. }
  325. }
  326. }
  327. }
  328. }
  329. fail = regexec(&joinRegex, str, joinRegex.re_nsub + 1, mptr, 0);
  330. // if a JOIN was broadcast to us
  331. if(!fail) {
  332. // copy nick of sender into name
  333. strncpy(name, str + mptr[1].rm_so, mptr[1].rm_eo - mptr[1].rm_so);
  334. name[mptr[1].rm_eo - mptr[1].rm_so] = '\0';
  335. // copy host mask of sender into hmask
  336. strncpy(hmask, str + mptr[2].rm_so, mptr[2].rm_eo - mptr[2].rm_so);
  337. hmask[mptr[2].rm_eo - mptr[2].rm_so] = '\0';
  338. // copy target of PRIVMSG into cname
  339. strncpy(cname, str + mptr[3].rm_so, mptr[3].rm_eo - mptr[3].rm_so);
  340. cname[mptr[3].rm_eo - mptr[3].rm_so] = '\0';
  341. /*
  342. if(strcmp(name, "ajanata"))
  343. send(chan, "%s: %s", name, obtainGreeting());
  344. */
  345. }
  346. // flush everything so output goes out immediately
  347. fflush(stdout);
  348. lflush();
  349. } else {
  350. if(!feof(stdin)) {
  351. // fgets failed, handle printing error message
  352. fprintf(stderr, "fgets failed in main jbot loop!\n");
  353. lprintf("fgets failed in main jbot loop!\n");
  354. }
  355. }
  356. }
  357. // try to dump variables for reload
  358. count = bmap_dump(varsMap, dumpFileName);
  359. if(count > 0) {
  360. send(owner, "Dumped %d variables", count);
  361. }
  362. bmap_free(varsMap);
  363. // free conf map
  364. // TODO: use this
  365. bmap_free(confMap);
  366. // try to dump out regex for reload
  367. count = rlist_dump(regexList, regexDumpFileName);
  368. if(count > 0) {
  369. send(owner, "Dumped %d regex", count);
  370. }
  371. rlist_free(regexList);
  372. // try to dump the markov chain generator to a file/dir
  373. count = markov_dump(markovGenerator, markovDumpFileName);
  374. if(count > 0) {
  375. send(owner, "Dumped %d markov chain entries", count);
  376. }
  377. markov_free(markovGenerator);
  378. // free regex objects used in main
  379. regfree(&pmsgRegex);
  380. regfree(&joinRegex);
  381. // free function regex objects
  382. deinitFunctions();
  383. // free cstart string
  384. free(cstart);
  385. fflush(stdout);
  386. lflush();
  387. // de initialize log file
  388. deinitLogFile();
  389. // close stdin/stdout just to make sure conbot knows we stopped
  390. close(0);
  391. close(1);
  392. return 0;
  393. }