: Request invite to group chat n (with password p if protected)";
235 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
236 |
237 | outmsg = "group : Creates a new groupchat with type: text | audio (optional password)";
238 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
239 |
240 | if (friend_is_master(m, friendnum)) {
241 | outmsg = "For a list of master commands see the commands.txt file";
242 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
243 | }
244 | }
245 |
246 | static void cmd_id(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
247 | {
248 | char outmsg[TOX_ADDRESS_SIZE * 2 + 1];
249 | char address[TOX_ADDRESS_SIZE];
250 | tox_self_get_address(m, (uint8_t *) address);
251 |
252 | for (size_t i = 0; i < TOX_ADDRESS_SIZE; ++i) {
253 | char d[3];
254 | sprintf(d, "%02X", address[i] & 0xff);
255 | memcpy(outmsg + i * 2, d, 2);
256 | }
257 |
258 | outmsg[TOX_ADDRESS_SIZE * 2] = '\0';
259 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
260 | }
261 |
262 | static void cmd_info(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
263 | {
264 | char outmsg[MAX_COMMAND_LENGTH];
265 | char timestr[64];
266 |
267 | time_t curtime = get_time();
268 | get_elapsed_time_str(timestr, sizeof(timestr), curtime - Tox_Bot.start_time);
269 | snprintf(outmsg, sizeof(outmsg), "Uptime: %s", timestr);
270 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
271 |
272 | uint32_t numfriends = tox_self_get_friend_list_size(m);
273 | snprintf(outmsg, sizeof(outmsg), "Friends: %d (%d online)", numfriends, Tox_Bot.num_online_friends);
274 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
275 |
276 | snprintf(outmsg, sizeof(outmsg), "Inactive friends are purged after %"PRIu64" days",
277 | Tox_Bot.inactive_limit / SECONDS_IN_DAY);
278 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
279 |
280 | /* List active group chats and number of peers in each */
281 | size_t num_chats = tox_conference_get_chatlist_size(m);
282 |
283 | if (num_chats == 0) {
284 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) "No active groupchats",
285 | strlen("No active groupchats"), NULL);
286 | return;
287 | }
288 |
289 | uint32_t groupchat_list[num_chats];
290 |
291 | tox_conference_get_chatlist(m, groupchat_list);
292 |
293 | for (size_t i = 0; i < num_chats; ++i) {
294 | TOX_ERR_CONFERENCE_PEER_QUERY err;
295 | uint32_t groupnum = groupchat_list[i];
296 | uint32_t num_peers = tox_conference_peer_count(m, groupnum, &err);
297 |
298 | if (err == TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
299 | int idx = group_index(groupnum);
300 | const char *title = Tox_Bot.g_chats[idx].title_len
301 | ? Tox_Bot.g_chats[idx].title : "None";
302 | const char *type = tox_conference_get_type(m, groupnum, NULL) == TOX_CONFERENCE_TYPE_AV ? "Audio" : "Text";
303 | snprintf(outmsg, sizeof(outmsg), "Group %d | %s | peers: %d | Title: %s", groupnum, type,
304 | num_peers, title);
305 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
306 | }
307 | }
308 | }
309 |
310 | static void cmd_invite(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
311 | {
312 | const char *outmsg = NULL;
313 | int groupnum = Tox_Bot.default_groupnum;
314 |
315 | if (argc >= 1) {
316 | groupnum = atoi(argv[1]);
317 |
318 | if (groupnum == 0 && strcmp(argv[1], "0")) {
319 | outmsg = "Error: Invalid group number";
320 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
321 | return;
322 | }
323 | }
324 |
325 | int idx = group_index(groupnum);
326 |
327 | if (idx == -1) {
328 | outmsg = "Group doesn't exist.";
329 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
330 | return;
331 | }
332 |
333 | int has_pass = Tox_Bot.g_chats[idx].has_pass;
334 |
335 | char name[TOX_MAX_NAME_LENGTH];
336 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
337 | size_t len = tox_friend_get_name_size(m, friendnum, NULL);
338 | name[len] = '\0';
339 |
340 | const char *passwd = NULL;
341 |
342 | if (argc >= 2) {
343 | passwd = argv[2];
344 | }
345 |
346 | if (has_pass && (!passwd || strcmp(argv[2], Tox_Bot.g_chats[idx].password) != 0)) {
347 | log_error_timestamp(-1, "Failed to invite %s to group %d (invalid password)", name, groupnum);
348 | outmsg = "Invalid password.";
349 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
350 | return;
351 | }
352 |
353 | TOX_ERR_CONFERENCE_INVITE err;
354 |
355 | if (!tox_conference_invite(m, friendnum, groupnum, &err)) {
356 | log_error_timestamp(err, "Failed to invite %s to group %d", name, groupnum);
357 | outmsg = "Invite failed";
358 | send_error(m, friendnum, outmsg, err);
359 | return;
360 | }
361 |
362 | log_timestamp("Invited %s to group %d", name, groupnum);
363 | }
364 |
365 | static void cmd_leave(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
366 | {
367 | const char *outmsg = NULL;
368 |
369 | if (!friend_is_master(m, friendnum)) {
370 | authent_failed(m, friendnum);
371 | return;
372 | }
373 |
374 | if (argc < 1) {
375 | outmsg = "Error: Group number required";
376 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
377 | return;
378 | }
379 |
380 | int groupnum = atoi(argv[1]);
381 |
382 | if (groupnum == 0 && strcmp(argv[1], "0")) {
383 | outmsg = "Error: Invalid group number";
384 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
385 | return;
386 | }
387 |
388 | if (!tox_conference_delete(m, groupnum, NULL)) {
389 | outmsg = "Error: Invalid group number";
390 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
391 | return;
392 | }
393 |
394 | char msg[MAX_COMMAND_LENGTH];
395 |
396 | char name[TOX_MAX_NAME_LENGTH];
397 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
398 | size_t len = tox_friend_get_name_size(m, friendnum, NULL);
399 | name[len] = '\0';
400 |
401 | group_leave(groupnum);
402 |
403 | log_timestamp("Left group %d (%s)", groupnum, name);
404 | snprintf(msg, sizeof(msg), "Left group %d", groupnum);
405 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) msg, strlen(msg), NULL);
406 | }
407 |
408 | static void cmd_master(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
409 | {
410 | const char *outmsg = NULL;
411 |
412 | if (!friend_is_master(m, friendnum)) {
413 | authent_failed(m, friendnum);
414 | return;
415 | }
416 |
417 | if (argc < 1) {
418 | outmsg = "Error: Tox ID required";
419 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
420 | return;
421 | }
422 |
423 | const char *id = argv[1];
424 |
425 | if (strlen(id) != TOX_ADDRESS_SIZE * 2) {
426 | outmsg = "Error: Invalid Tox ID";
427 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
428 | return;
429 | }
430 |
431 | FILE *fp = fopen(MASTERLIST_FILE, "a");
432 |
433 | if (fp == NULL) {
434 | outmsg = "Error: could not find masterkeys file";
435 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
436 | return;
437 | }
438 |
439 | fprintf(fp, "%s\n", id);
440 | fclose(fp);
441 |
442 | char name[TOX_MAX_NAME_LENGTH];
443 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
444 | size_t len = tox_friend_get_name_size(m, friendnum, NULL);
445 | name[len] = '\0';
446 |
447 | log_timestamp("%s added master: %s", name, id);
448 | outmsg = "ID added to masterkeys list";
449 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
450 | }
451 |
452 | static void cmd_name(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
453 | {
454 | const char *outmsg = NULL;
455 |
456 | if (!friend_is_master(m, friendnum)) {
457 | authent_failed(m, friendnum);
458 | return;
459 | }
460 |
461 | if (argc < 1) {
462 | outmsg = "Error: Name required";
463 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
464 | return;
465 | }
466 |
467 | char name[TOX_MAX_NAME_LENGTH];
468 | int len = 0;
469 |
470 | if (argv[1][0] == '\"') { /* remove opening and closing quotes */
471 | snprintf(name, sizeof(name), "%s", &argv[1][1]);
472 | len = strlen(name) - 1;
473 | } else {
474 | snprintf(name, sizeof(name), "%s", argv[1]);
475 | len = strlen(name);
476 | }
477 |
478 | name[len] = '\0';
479 | tox_self_set_name(m, (uint8_t *) name, (uint16_t) len, NULL);
480 |
481 | char m_name[TOX_MAX_NAME_LENGTH];
482 | tox_friend_get_name(m, friendnum, (uint8_t *) m_name, NULL);
483 | size_t nlen = tox_friend_get_name_size(m, friendnum, NULL);
484 | m_name[nlen] = '\0';
485 |
486 | log_timestamp("%s set name to %s", m_name, name);
487 | save_data(m, DATA_FILE);
488 | }
489 |
490 | static void cmd_passwd(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
491 | {
492 | const char *outmsg = NULL;
493 |
494 | if (!friend_is_master(m, friendnum)) {
495 | authent_failed(m, friendnum);
496 | return;
497 | }
498 |
499 | if (argc < 1) {
500 | outmsg = "Error: group number required";
501 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
502 | return;
503 | }
504 |
505 | int groupnum = atoi(argv[1]);
506 |
507 | if (groupnum == 0 && strcmp(argv[1], "0")) {
508 | outmsg = "Error: Invalid group number";
509 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
510 | return;
511 | }
512 |
513 | int idx = group_index(groupnum);
514 |
515 | if (idx == -1) {
516 | outmsg = "Error: Invalid group number";
517 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
518 | return;
519 | }
520 |
521 | char name[TOX_MAX_NAME_LENGTH];
522 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
523 | size_t nlen = tox_friend_get_name_size(m, friendnum, NULL);
524 | name[nlen] = '\0';
525 |
526 |
527 | /* no password */
528 | if (argc < 2) {
529 | Tox_Bot.g_chats[idx].has_pass = false;
530 | memset(Tox_Bot.g_chats[idx].password, 0, MAX_PASSWORD_SIZE);
531 |
532 | outmsg = "No password set";
533 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
534 | log_timestamp("No password set for group %d by %s", groupnum, name);
535 | return;
536 | }
537 |
538 | if (strlen(argv[2]) >= MAX_PASSWORD_SIZE) {
539 | outmsg = "Password too long";
540 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
541 | return;
542 | }
543 |
544 | Tox_Bot.g_chats[idx].has_pass = true;
545 | snprintf(Tox_Bot.g_chats[idx].password, sizeof(Tox_Bot.g_chats[idx].password), "%s", argv[2]);
546 |
547 | outmsg = "Password set";
548 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
549 | log_timestamp("Password for group %d set by %s", groupnum, name);
550 |
551 | }
552 |
553 | static void cmd_purge(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
554 | {
555 | const char *outmsg = NULL;
556 |
557 | if (!friend_is_master(m, friendnum)) {
558 | authent_failed(m, friendnum);
559 | return;
560 | }
561 |
562 | if (argc < 1) {
563 | outmsg = "Error: number > 0 required";
564 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
565 | return;
566 | }
567 |
568 | uint64_t days = (uint64_t) atoi(argv[1]);
569 |
570 | if (days <= 0) {
571 | outmsg = "Error: number > 0 required";
572 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
573 | return;
574 | }
575 |
576 | uint64_t seconds = days * SECONDS_IN_DAY;
577 | Tox_Bot.inactive_limit = seconds;
578 |
579 | char name[TOX_MAX_NAME_LENGTH];
580 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
581 | size_t nlen = tox_friend_get_name_size(m, friendnum, NULL);
582 | name[nlen] = '\0';
583 |
584 | char msg[MAX_COMMAND_LENGTH];
585 | snprintf(msg, sizeof(msg), "Purge time set to %"PRIu64" days", days);
586 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) msg, strlen(msg), NULL);
587 |
588 | log_timestamp("Purge time set to %"PRIu64" days by %s", days, name);
589 | }
590 |
591 | static void cmd_status(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
592 | {
593 | const char *outmsg = NULL;
594 |
595 | if (!friend_is_master(m, friendnum)) {
596 | authent_failed(m, friendnum);
597 | return;
598 | }
599 |
600 | if (argc < 1) {
601 | outmsg = "Error: status required";
602 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
603 | return;
604 | }
605 |
606 | TOX_USER_STATUS type;
607 | const char *status = argv[1];
608 |
609 | if (strcasecmp(status, "online") == 0) {
610 | type = TOX_USER_STATUS_NONE;
611 | } else if (strcasecmp(status, "away") == 0) {
612 | type = TOX_USER_STATUS_AWAY;
613 | } else if (strcasecmp(status, "busy") == 0) {
614 | type = TOX_USER_STATUS_BUSY;
615 | } else {
616 | outmsg = "Invalid status. Valid statuses are: online, busy and away.";
617 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
618 | return;
619 | }
620 |
621 | tox_self_set_status(m, type);
622 |
623 | char name[TOX_MAX_NAME_LENGTH];
624 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
625 | size_t nlen = tox_friend_get_name_size(m, friendnum, NULL);
626 | name[nlen] = '\0';
627 |
628 | log_timestamp("%s set status to %s", name, status);
629 | save_data(m, DATA_FILE);
630 | }
631 |
632 | static void cmd_statusmessage(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
633 | {
634 | const char *outmsg = NULL;
635 |
636 | if (!friend_is_master(m, friendnum)) {
637 | authent_failed(m, friendnum);
638 | return;
639 | }
640 |
641 | if (argc < 1) {
642 | outmsg = "Error: message required";
643 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
644 | return;
645 | }
646 |
647 | if (argv[1][0] != '\"') {
648 | outmsg = "Error: message must be enclosed in quotes";
649 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
650 | return;
651 | }
652 |
653 | /* remove opening and closing quotes */
654 | char msg[MAX_COMMAND_LENGTH];
655 | snprintf(msg, sizeof(msg), "%s", &argv[1][1]);
656 | int len = strlen(msg) - 1;
657 | msg[len] = '\0';
658 |
659 | tox_self_set_status_message(m, (uint8_t *) msg, len, NULL);
660 |
661 | char name[TOX_MAX_NAME_LENGTH];
662 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
663 | size_t nlen = tox_friend_get_name_size(m, friendnum, NULL);
664 | name[nlen] = '\0';
665 |
666 | log_timestamp("%s set status message to \"%s\"", name, msg);
667 | save_data(m, DATA_FILE);
668 | }
669 |
670 | static void cmd_title_set(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH])
671 | {
672 | const char *outmsg = NULL;
673 |
674 | if (!friend_is_master(m, friendnum)) {
675 | authent_failed(m, friendnum);
676 | return;
677 | }
678 |
679 | if (argc < 2) {
680 | outmsg = "Error: Two arguments are required";
681 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
682 | return;
683 | }
684 |
685 | if (argv[2][0] != '\"') {
686 | outmsg = "Error: title must be enclosed in quotes";
687 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
688 | return;
689 | }
690 |
691 | int groupnum = atoi(argv[1]);
692 |
693 | if (groupnum == 0 && strcmp(argv[1], "0")) {
694 | outmsg = "Error: Invalid group number";
695 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
696 | return;
697 | }
698 |
699 | /* remove opening and closing quotes */
700 | char title[MAX_COMMAND_LENGTH];
701 | snprintf(title, sizeof(title), "%s", &argv[2][1]);
702 | int len = strlen(title) - 1;
703 | title[len] = '\0';
704 |
705 | char name[TOX_MAX_NAME_LENGTH];
706 | tox_friend_get_name(m, friendnum, (uint8_t *) name, NULL);
707 | size_t nlen = tox_friend_get_name_size(m, friendnum, NULL);
708 | name[nlen] = '\0';
709 |
710 | TOX_ERR_CONFERENCE_TITLE err;
711 |
712 | if (!tox_conference_set_title(m, groupnum, (uint8_t *) title, len, &err)) {
713 | log_error_timestamp(err, "%s failed to set the title '%s' for group %d", name, title, groupnum);
714 | outmsg = "Failed to set title. This may be caused by an invalid group number or an empty room";
715 | send_error(m, friendnum, outmsg, err);
716 | return;
717 | }
718 |
719 | int idx = group_index(groupnum);
720 | memcpy(Tox_Bot.g_chats[idx].title, title, len + 1);
721 | Tox_Bot.g_chats[idx].title_len = len;
722 |
723 | outmsg = "Group title set";
724 | tox_friend_send_message(m, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
725 | log_timestamp("%s set group %d title to %s", name, groupnum, title);
726 | }
727 |
728 | /* Parses input command and puts args into arg array.
729 | Returns number of arguments on success, -1 on failure. */
730 | static int parse_command(const char *input, char (*args)[MAX_COMMAND_LENGTH])
731 | {
732 | char *cmd = strdup(input);
733 |
734 | if (cmd == NULL) {
735 | exit(EXIT_FAILURE);
736 | }
737 |
738 | int num_args = 0;
739 | int i = 0; /* index of last char in an argument */
740 |
741 | /* characters wrapped in double quotes count as one arg */
742 | while (num_args < MAX_NUM_ARGS) {
743 | int qt_ofst = 0; /* set to 1 to offset index for quote char at end of arg */
744 |
745 | if (*cmd == '\"') {
746 | qt_ofst = 1;
747 | i = char_find(1, cmd, '\"');
748 |
749 | if (cmd[i] == '\0') {
750 | free(cmd);
751 | return -1;
752 | }
753 | } else {
754 | i = char_find(0, cmd, ' ');
755 | }
756 |
757 | memcpy(args[num_args], cmd, i + qt_ofst);
758 | args[num_args++][i + qt_ofst] = '\0';
759 |
760 | if (cmd[i] == '\0') { /* no more args */
761 | break;
762 | }
763 |
764 | char tmp[MAX_COMMAND_LENGTH];
765 | snprintf(tmp, sizeof(tmp), "%s", &cmd[i + 1]);
766 | strcpy(cmd, tmp); /* tmp will always fit inside cmd */
767 | }
768 |
769 | free(cmd);
770 | return num_args;
771 | }
772 |
773 | static struct {
774 | const char *name;
775 | void (*func)(Tox *m, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH]);
776 | } commands[] = {
777 | { "default", cmd_default },
778 | { "group", cmd_group },
779 | { "gmessage", cmd_gmessage },
780 | { "help", cmd_help },
781 | { "id", cmd_id },
782 | { "info", cmd_info },
783 | { "invite", cmd_invite },
784 | { "leave", cmd_leave },
785 | { "master", cmd_master },
786 | { "name", cmd_name },
787 | { "passwd", cmd_passwd },
788 | { "purge", cmd_purge },
789 | { "status", cmd_status },
790 | { "statusmessage", cmd_statusmessage },
791 | { "title", cmd_title_set },
792 | { NULL, NULL },
793 | };
794 |
795 | static int do_command(Tox *m, uint32_t friendnum, int num_args, char (*args)[MAX_COMMAND_LENGTH])
796 | {
797 | for (size_t i = 0; commands[i].name; ++i) {
798 | if (strcmp(args[0], commands[i].name) == 0) {
799 | (commands[i].func)(m, friendnum, num_args - 1, args);
800 | return 0;
801 | }
802 | }
803 |
804 | return -1;
805 | }
806 |
807 | int execute(Tox *m, uint32_t friendnum, const char *input, int length)
808 | {
809 | if (length >= MAX_COMMAND_LENGTH) {
810 | return -1;
811 | }
812 |
813 | char args[MAX_NUM_ARGS][MAX_COMMAND_LENGTH];
814 | int num_args = parse_command(input, args);
815 |
816 | if (num_args == -1) {
817 | return -1;
818 | }
819 |
820 | return do_command(m, friendnum, num_args, args);
821 | }
822 |
823 |
--------------------------------------------------------------------------------
/src/commands.h:
--------------------------------------------------------------------------------
1 | /* commands.h
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #ifndef COMMANDS_H
24 | #define COMMANDS_H
25 |
26 | int execute(Tox *m, int friendnumber, const char *input, int length);
27 |
28 | #endif /* COMMANDS_H */
29 |
30 |
--------------------------------------------------------------------------------
/src/groupchats.c:
--------------------------------------------------------------------------------
1 | /* groupchats.c
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #include
24 | #include
25 | #include
26 |
27 | #include "toxbot.h"
28 | #include "groupchats.h"
29 |
30 | extern struct Tox_Bot Tox_Bot;
31 |
32 | void realloc_groupchats(int n)
33 | {
34 | if (n <= 0) {
35 | free(Tox_Bot.g_chats);
36 | Tox_Bot.g_chats = NULL;
37 | return;
38 | }
39 |
40 | struct Group_Chat *g = realloc(Tox_Bot.g_chats, n * sizeof(struct Group_Chat));
41 |
42 | if (g == NULL) {
43 | exit(EXIT_FAILURE);
44 | }
45 |
46 | Tox_Bot.g_chats = g;
47 | }
48 |
49 | int group_add(uint32_t groupnum, uint8_t type, const char *password)
50 | {
51 | realloc_groupchats(Tox_Bot.chats_idx + 1);
52 | memset(&Tox_Bot.g_chats[Tox_Bot.chats_idx], 0, sizeof(struct Group_Chat));
53 |
54 | for (int i = 0; i <= Tox_Bot.chats_idx && i < MAX_NUM_GROUPS; ++i) {
55 | if (Tox_Bot.g_chats[i].active) {
56 | continue;
57 | }
58 |
59 | memset(&Tox_Bot.g_chats[i], 0, sizeof(struct Group_Chat));
60 | Tox_Bot.g_chats[i].groupnum = groupnum;
61 | Tox_Bot.g_chats[i].active = true;
62 | Tox_Bot.g_chats[i].type = type;
63 |
64 | if (password) {
65 | Tox_Bot.g_chats[i].has_pass = true;
66 | snprintf(Tox_Bot.g_chats[i].password, sizeof(Tox_Bot.g_chats[i].password), "%s", password);
67 | }
68 |
69 | if (Tox_Bot.chats_idx == i) {
70 | ++Tox_Bot.chats_idx;
71 | }
72 |
73 | return 0;
74 | }
75 |
76 | return -1;
77 | }
78 |
79 | void group_leave(uint32_t groupnum)
80 | {
81 | int i;
82 |
83 | for (i = 0; i < Tox_Bot.chats_idx; ++i) {
84 | if (Tox_Bot.g_chats[i].active && Tox_Bot.g_chats[i].groupnum == groupnum) {
85 | memset(&Tox_Bot.g_chats[i], 0, sizeof(struct Group_Chat));
86 | break;
87 | }
88 | }
89 |
90 | for (i = Tox_Bot.chats_idx; i > 0; --i) {
91 | if (Tox_Bot.g_chats[i - 1].active) {
92 | break;
93 | }
94 | }
95 |
96 | Tox_Bot.chats_idx = i;
97 | realloc_groupchats(i);
98 | }
99 |
100 | int group_index(uint32_t groupnum)
101 | {
102 | for (int i = 0; i < Tox_Bot.chats_idx; ++i) {
103 | if (Tox_Bot.g_chats[i].active && Tox_Bot.g_chats[i].groupnum == groupnum) {
104 | return i;
105 | }
106 | }
107 |
108 | return -1;
109 | }
110 |
111 |
--------------------------------------------------------------------------------
/src/groupchats.h:
--------------------------------------------------------------------------------
1 | /* groupchats.h
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #ifndef GROUPCHATS_H
24 | #define GROUPCHATS_H
25 |
26 | #define SECONDS_IN_DAY 86400UL
27 | #define MAX_PASSWORD_SIZE 64
28 |
29 | struct Group_Chat {
30 | uint32_t groupnum;
31 | bool active;
32 | bool has_pass;
33 | uint8_t type;
34 | char title[TOX_MAX_NAME_LENGTH];
35 | int title_len;
36 | char password[MAX_PASSWORD_SIZE];
37 | };
38 |
39 | int group_add(uint32_t groupnum, uint8_t type, const char *password);
40 | void group_leave(uint32_t groupnum);
41 | int group_index(uint32_t groupnum);
42 | void realloc_groupchats(int n);
43 |
44 | #endif /* GROUPCHATS_H */
45 |
46 |
--------------------------------------------------------------------------------
/src/log.c:
--------------------------------------------------------------------------------
1 | /* log.c
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #include
24 | #include
25 |
26 | #include "misc.h"
27 |
28 | #define TIMESTAMP_SIZE 64
29 | #define MAX_MESSAGE_SIZE 512
30 |
31 | static struct tm *get_wall_time(void)
32 | {
33 | struct tm *timeinfo;
34 | time_t t = get_time();
35 | timeinfo = localtime((const time_t *) &t);
36 | return timeinfo;
37 | }
38 |
39 | void log_timestamp(const char *message, ...)
40 | {
41 | char format[MAX_MESSAGE_SIZE];
42 |
43 | va_list args;
44 | va_start(args, message);
45 | vsnprintf(format, sizeof(format), message, args);
46 | va_end(args);
47 |
48 | char ts[TIMESTAMP_SIZE];
49 | strftime(ts, TIMESTAMP_SIZE,"[%H:%M:%S]", get_wall_time());
50 |
51 | printf("%s %s\n", ts, format);
52 | }
53 |
54 | void log_error_timestamp(int err, const char *message, ...)
55 | {
56 | char format[MAX_MESSAGE_SIZE];
57 |
58 | va_list args;
59 | va_start(args, message);
60 | vsnprintf(format, sizeof(format), message, args);
61 | va_end(args);
62 |
63 | char ts[TIMESTAMP_SIZE];
64 | strftime(ts, TIMESTAMP_SIZE,"[%H:%M:%S]", get_wall_time());
65 |
66 | fprintf(stderr, "%s %s (error %d)\n", ts, format, err);
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/src/log.h:
--------------------------------------------------------------------------------
1 | /* log.h
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #ifndef LOG_H
24 | #define LOG_H
25 |
26 | /* Print `message` to stdout prefixed with a timestamp */
27 | void log_timestamp(const char *message, ...);
28 |
29 | /* Print `message` with `err` to stderr prefixed with a timestamp */
30 | void log_error_timestamp(int err, const char *message, ...);
31 |
32 | #endif // LOG_H
33 |
34 |
--------------------------------------------------------------------------------
/src/misc.c:
--------------------------------------------------------------------------------
1 | /* misc.c
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | #include
31 |
32 | #include "misc.h"
33 |
34 | bool timed_out(time_t timestamp, time_t curtime, uint64_t timeout)
35 | {
36 | return timestamp + timeout <= curtime;
37 | }
38 |
39 | time_t get_time(void)
40 | {
41 | return time(NULL);
42 | }
43 |
44 | char *hex_string_to_bin(const char *hex_string)
45 | {
46 | size_t len = strlen(hex_string);
47 | char *val = malloc(len);
48 |
49 | if (val == NULL) {
50 | exit(EXIT_FAILURE);
51 | }
52 |
53 | for (size_t i = 0; i < len; ++i, hex_string += 2) {
54 | sscanf(hex_string, "%2hhx", &val[i]);
55 | }
56 |
57 | return val;
58 | }
59 |
60 | off_t file_size(const char *path)
61 | {
62 | struct stat st;
63 |
64 | if (stat(path, &st) == -1) {
65 | return 0;
66 | }
67 |
68 | return st.st_size;
69 | }
70 |
71 | bool file_exists(const char *path)
72 | {
73 | struct stat s;
74 | return stat(path, &s) == 0;
75 | }
76 |
77 | uint16_t copy_tox_str(char *msg, size_t size, const char *data, uint16_t length)
78 | {
79 | int len = MIN(length, size - 1);
80 | memcpy(msg, data, len);
81 | msg[len] = '\0';
82 | return len;
83 | }
84 |
85 | int char_find(int idx, const char *s, char ch)
86 | {
87 | int i = idx;
88 |
89 | for (i = idx; s[i]; ++i) {
90 | if (s[i] == ch) {
91 | break;
92 | }
93 | }
94 |
95 | return i;
96 | }
97 |
98 | void get_elapsed_time_str(char *buf, int bufsize, uint64_t secs)
99 | {
100 | long unsigned int minutes = (secs % 3600) / 60;
101 | long unsigned int hours = (secs / 3600) % 24;
102 | long unsigned int days = (secs / 3600) / 24;
103 |
104 | snprintf(buf, bufsize, "%lud %luh %lum", days, hours, minutes);
105 | }
106 |
107 | /*
108 | * Searches plain text file pointed to by path for lines that match public_key.
109 | *
110 | * Returns 1 if a match is found.
111 | * Returns 0 if a match is not found.
112 | * Returns -1 on file operation failure.
113 | *
114 | * public_key must be a binary representation of a Tox public key.
115 | */
116 | int file_contains_key(const char *public_key, const char *path)
117 | {
118 | FILE *fp = NULL;
119 |
120 | struct stat s;
121 |
122 | if (stat(path, &s) != 0) {
123 | FILE *fp = fopen(path, "w");
124 |
125 | if (fp == NULL) {
126 | fprintf(stderr, "Warning: failed to create '%s' file\n", path);
127 | return -1;
128 | }
129 |
130 | fprintf(stderr, "Warning: creating new '%s' file. Did you lose the old one?\n", path);
131 | fclose(fp);
132 | return 0;
133 | }
134 |
135 | fp = fopen(path, "r");
136 |
137 | if (fp == NULL) {
138 | fprintf(stderr, "Warning: failed to read '%s' file\n", path);
139 | return -1;
140 | }
141 |
142 | char id[256];
143 |
144 | while (fgets(id, sizeof(id), fp)) {
145 | int len = strlen(id);
146 |
147 | if (--len < TOX_PUBLIC_KEY_SIZE) {
148 | continue;
149 | }
150 |
151 | char *key_bin = hex_string_to_bin(id);
152 |
153 | if (memcmp(key_bin, public_key, TOX_PUBLIC_KEY_SIZE) == 0) {
154 | free(key_bin);
155 | fclose(fp);
156 | return 1;
157 | }
158 |
159 | free(key_bin);
160 | }
161 |
162 | fclose(fp);
163 | return 0;
164 | }
165 |
166 |
--------------------------------------------------------------------------------
/src/misc.h:
--------------------------------------------------------------------------------
1 | /* misc.h
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #ifndef MISC_H
24 | #define MISC_H
25 |
26 | #ifndef MIN
27 | #define MIN(x, y) (((x) < (y)) ? (x) : (y))
28 | #endif
29 |
30 | #ifndef MAX
31 | #define MAX(x, y) (((x) > (y)) ? (x) : (y))
32 | #endif
33 |
34 | #include
35 | #include
36 | #include
37 | #include
38 |
39 | bool timed_out(time_t timestamp, time_t curtime, uint64_t timeout);
40 |
41 | /* Returns current unix timestamp */
42 | time_t get_time(void);
43 |
44 | /* converts hexidecimal string to binary */
45 | char *hex_string_to_bin(const char *hex_string);
46 |
47 | /* returns file size or 0 on error */
48 | off_t file_size(const char *path);
49 |
50 | /* Return true if a file exists at `path`. */
51 | bool file_exists(const char *path);
52 |
53 | /* copies data to msg buffer.
54 | returns length of msg, which will be no larger than size-1 */
55 | uint16_t copy_tox_str(char *msg, size_t size, const char *data, uint16_t length);
56 |
57 | /* returns index of the first instance of ch in s starting at idx.
58 | returns length of s if char not found */
59 | int char_find(int idx, const char *s, char ch);
60 |
61 | /* Converts seconds to string in format days hours minutes */
62 | void get_elapsed_time_str(char *buf, int bufsize, uint64_t secs);
63 |
64 | /*
65 | * Searches plain text file pointed to by path for lines that match public_key.
66 | *
67 | * Returns 1 if a match is found.
68 | * Returns 0 if a match is not found.
69 | * Returns -1 on file operation failure.
70 | *
71 | * public_key must be a binary representation of a Tox public key.
72 | */
73 | int file_contains_key(const char *public_key, const char *path);
74 |
75 | #endif /* MISC_H */
76 |
77 |
--------------------------------------------------------------------------------
/src/toxbot.c:
--------------------------------------------------------------------------------
1 | /* toxbot.c
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 |
34 | #include
35 | #include
36 |
37 | #include "misc.h"
38 | #include "commands.h"
39 | #include "toxbot.h"
40 | #include "groupchats.h"
41 | #include "log.h"
42 |
43 | #define VERSION "0.1.2"
44 |
45 | /* How often we attempt to purge inactive friends */
46 | #define FRIEND_PURGE_INTERVAL (60 * 60)
47 |
48 | /* How often we attempt to purge inactive groups */
49 | #define GROUP_PURGE_INTERVAL (60 * 10)
50 |
51 | /* How long we need to have had a stable connection before purging inactive groups */
52 | #define GROUP_PURGE_CONNECT_TIMEOUT (60 * 60)
53 |
54 | /* How often we attempt to bootstrap when not presently connected to the network */
55 | #define BOOTSTRAP_INTERVAL 20
56 |
57 | #define MAX_PORT_RANGE 65535
58 |
59 | /* Name of data file prior to version 0.1.1 */
60 | #define DATA_FILE_PRE_0_1_1 "toxbot_save"
61 |
62 | volatile sig_atomic_t FLAG_EXIT = false; /* set on SIGINT */
63 |
64 | struct Tox_Bot Tox_Bot;
65 |
66 | static struct Options {
67 | TOX_PROXY_TYPE proxy_type;
68 | char proxy_host[256];
69 | uint16_t proxy_port;
70 | bool disable_udp;
71 | bool disable_lan;
72 | bool force_ipv4;
73 | } Options;
74 |
75 | static void init_toxbot_state(void)
76 | {
77 | Tox_Bot.start_time = get_time();
78 | Tox_Bot.last_connected = get_time();
79 | Tox_Bot.default_groupnum = 0;
80 | Tox_Bot.chats_idx = 0;
81 | Tox_Bot.num_online_friends = 0;
82 |
83 | /* 1 year default; anything lower should be explicitly set until we have a config file */
84 | Tox_Bot.inactive_limit = 31536000;
85 | }
86 |
87 | static void catch_SIGINT(int sig)
88 | {
89 | FLAG_EXIT = true;
90 | }
91 |
92 | static void exit_toxbot(Tox *m)
93 | {
94 | save_data(m, DATA_FILE);
95 | tox_kill(m);
96 | exit(EXIT_SUCCESS);
97 | }
98 |
99 | /* Returns true if friendnumber's Tox ID is in the masterkeys list. */
100 | bool friend_is_master(Tox *m, uint32_t friendnumber)
101 | {
102 | char public_key[TOX_PUBLIC_KEY_SIZE];
103 |
104 | if (tox_friend_get_public_key(m, friendnumber, (uint8_t *) public_key, NULL) == 0) {
105 | return false;
106 | }
107 |
108 | return file_contains_key(public_key, MASTERLIST_FILE) == 1;
109 | }
110 |
111 | /* Returns true if public_key is in the blockedkeys list. */
112 | static bool public_key_is_blocked(const char *public_key)
113 | {
114 | return file_contains_key(public_key, BLOCKLIST_FILE) == 1;
115 | }
116 |
117 | /* START CALLBACKS */
118 | static void cb_self_connection_change(Tox *m, TOX_CONNECTION connection_status, void *userdata)
119 | {
120 | switch (connection_status) {
121 | case TOX_CONNECTION_NONE:
122 | log_timestamp("Connection lost");
123 | Tox_Bot.last_bootstrap = get_time(); // usually we don't need to manually bootstrap if connection lost
124 | break;
125 |
126 | case TOX_CONNECTION_TCP:
127 | Tox_Bot.last_connected = get_time();
128 | log_timestamp("Connection established (TCP)");
129 | break;
130 |
131 | case TOX_CONNECTION_UDP:
132 | Tox_Bot.last_connected = get_time();
133 | log_timestamp("Connection established (UDP)");
134 | break;
135 | }
136 | }
137 |
138 | static void cb_friend_connection_change(Tox *m, uint32_t friendnumber, TOX_CONNECTION connection_status, void *userdata)
139 | {
140 | Tox_Bot.num_online_friends = 0;
141 |
142 | size_t i, size = tox_self_get_friend_list_size(m);
143 |
144 | if (size == 0) {
145 | return;
146 | }
147 |
148 | uint32_t list[size];
149 | tox_self_get_friend_list(m, list);
150 |
151 | for (i = 0; i < size; ++i) {
152 | if (tox_friend_get_connection_status(m, list[i], NULL) != TOX_CONNECTION_NONE) {
153 | ++Tox_Bot.num_online_friends;
154 | }
155 | }
156 | }
157 |
158 | static void cb_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length,
159 | void *userdata)
160 | {
161 | if (public_key_is_blocked((char *) public_key)) {
162 | return;
163 | }
164 |
165 | TOX_ERR_FRIEND_ADD err;
166 | tox_friend_add_norequest(m, public_key, &err);
167 |
168 | if (err != TOX_ERR_FRIEND_ADD_OK) {
169 | log_error_timestamp(err, "tox_friend_add_norequest failed");
170 | } else {
171 | log_timestamp("Accepted friend request");
172 | }
173 |
174 | save_data(m, DATA_FILE);
175 | }
176 |
177 | static void cb_friend_message(Tox *m, uint32_t friendnumber, TOX_MESSAGE_TYPE type, const uint8_t *string,
178 | size_t length, void *userdata)
179 | {
180 | if (type != TOX_MESSAGE_TYPE_NORMAL) {
181 | return;
182 | }
183 |
184 | char public_key[TOX_PUBLIC_KEY_SIZE];
185 |
186 | if (tox_friend_get_public_key(m, friendnumber, (uint8_t *) public_key, NULL) == 0) {
187 | return;
188 | }
189 |
190 | if (public_key_is_blocked(public_key)) {
191 | tox_friend_delete(m, friendnumber, NULL);
192 | return;
193 | }
194 |
195 | const char *outmsg;
196 | char message[TOX_MAX_MESSAGE_LENGTH];
197 | length = copy_tox_str(message, sizeof(message), (const char *) string, length);
198 | message[length] = '\0';
199 |
200 | if (length && execute(m, friendnumber, message, length) == -1) {
201 | outmsg = "Invalid command. Type help for a list of commands";
202 | tox_friend_send_message(m, friendnumber, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL);
203 | }
204 | }
205 |
206 | static void cb_group_invite(Tox *m, uint32_t friendnumber, TOX_CONFERENCE_TYPE type,
207 | const uint8_t *cookie, size_t length, void *userdata)
208 | {
209 | if (!friend_is_master(m, friendnumber)) {
210 | return;
211 | }
212 |
213 | char name[TOX_MAX_NAME_LENGTH];
214 | tox_friend_get_name(m, friendnumber, (uint8_t *) name, NULL);
215 | size_t len = tox_friend_get_name_size(m, friendnumber, NULL);
216 | name[len] = '\0';
217 |
218 | int groupnum = -1;
219 |
220 | if (type == TOX_CONFERENCE_TYPE_TEXT) {
221 | TOX_ERR_CONFERENCE_JOIN err;
222 | groupnum = tox_conference_join(m, friendnumber, cookie, length, &err);
223 |
224 | if (err != TOX_ERR_CONFERENCE_JOIN_OK) {
225 | goto on_error;
226 | }
227 | } else if (type == TOX_CONFERENCE_TYPE_AV) {
228 | groupnum = toxav_join_av_groupchat(m, friendnumber, cookie, length, NULL, NULL);
229 |
230 | if (groupnum == -1) {
231 | goto on_error;
232 | }
233 | }
234 |
235 | if (group_add(groupnum, type, NULL) == -1) {
236 | log_error_timestamp(-1, "Invite from %s failed (group_add failed)", name);
237 | tox_conference_delete(m, groupnum, NULL);
238 | return;
239 | }
240 |
241 | log_timestamp("Accepted groupchat invite from %s [%d]", name, groupnum);
242 | return;
243 |
244 | on_error:
245 | log_error_timestamp(-1, "Invite from %s failed (core failure)", name);
246 | }
247 |
248 | static void cb_group_titlechange(Tox *m, uint32_t groupnumber, uint32_t peernumber, const uint8_t *title,
249 | size_t length, void *userdata)
250 | {
251 | char message[TOX_MAX_MESSAGE_LENGTH];
252 | length = copy_tox_str(message, sizeof(message), (const char *) title, length);
253 |
254 | int idx = group_index(groupnumber);
255 |
256 | if (idx == -1) {
257 | return;
258 | }
259 |
260 | memcpy(Tox_Bot.g_chats[idx].title, message, length + 1);
261 | Tox_Bot.g_chats[idx].title_len = length;
262 | }
263 | /* END CALLBACKS */
264 |
265 | int save_data(Tox *m, const char *path)
266 | {
267 | if (path == NULL) {
268 | goto on_error;
269 | }
270 |
271 | FILE *fp = fopen(path, "wb");
272 |
273 | if (fp == NULL) {
274 | return -1;
275 | }
276 |
277 | size_t data_len = tox_get_savedata_size(m);
278 | char *data = malloc(data_len);
279 |
280 | if (data == NULL) {
281 | goto on_error;
282 | }
283 |
284 | tox_get_savedata(m, (uint8_t *) data);
285 |
286 | if (fwrite(data, data_len, 1, fp) != 1) {
287 | free(data);
288 | fclose(fp);
289 | goto on_error;
290 | }
291 |
292 | free(data);
293 | fclose(fp);
294 | return 0;
295 |
296 | on_error:
297 | log_error_timestamp(-1, "Warning: save_data failed");
298 | return -1;
299 | }
300 |
301 | static Tox *load_tox(struct Tox_Options *options, char *path)
302 | {
303 | FILE *fp = fopen(path, "rb");
304 | Tox *m = NULL;
305 |
306 | if (fp == NULL) {
307 | TOX_ERR_NEW err;
308 | m = tox_new(options, &err);
309 |
310 | if (err != TOX_ERR_NEW_OK) {
311 | fprintf(stderr, "tox_new failed with error %d\n", err);
312 | return NULL;
313 | }
314 |
315 | save_data(m, path);
316 | return m;
317 | }
318 |
319 | off_t data_len = file_size(path);
320 |
321 | if (data_len == 0) {
322 | fprintf(stderr, "tox_new failed: toxbot save file is empty\n");
323 | fclose(fp);
324 | return NULL;
325 | }
326 |
327 | char data[data_len];
328 |
329 | if (fread(data, sizeof(data), 1, fp) != 1) {
330 | fclose(fp);
331 | return NULL;
332 | }
333 |
334 | TOX_ERR_NEW err;
335 | options->savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE;
336 | options->savedata_data = (uint8_t *) data;
337 | options->savedata_length = data_len;
338 |
339 | m = tox_new(options, &err);
340 |
341 | if (err != TOX_ERR_NEW_OK) {
342 | fprintf(stderr, "tox_new failed with error %d\n", err);
343 | return NULL;
344 | }
345 |
346 | fclose(fp);
347 | return m;
348 | }
349 |
350 | static void load_conferences(Tox *m)
351 | {
352 | size_t num_chats = tox_conference_get_chatlist_size(m);
353 |
354 | if (num_chats == 0) {
355 | return;
356 | }
357 |
358 | uint32_t *chatlist = malloc(num_chats * sizeof(uint32_t));
359 |
360 | if (chatlist == NULL) {
361 | fprintf(stderr, "malloc() failed in load_conferences()\n");
362 | return;
363 | }
364 |
365 | tox_conference_get_chatlist(m, chatlist);
366 |
367 | for (size_t i = 0; i < num_chats; ++i) {
368 | uint32_t groupnumber = chatlist[i];
369 |
370 | Tox_Err_Conference_Get_Type type_err;
371 | Tox_Conference_Type type = tox_conference_get_type(m, groupnumber, &type_err);
372 |
373 | if (type_err != TOX_ERR_CONFERENCE_GET_TYPE_OK) {
374 | tox_conference_delete(m, groupnumber, NULL);
375 | continue;
376 | }
377 |
378 | if (group_add(groupnumber, type, NULL) != 0) {
379 | fprintf(stderr, "Failed to autoload group %d\n", groupnumber);
380 | tox_conference_delete(m, groupnumber, NULL);
381 | continue;
382 | }
383 | }
384 |
385 | free(chatlist);
386 | }
387 |
388 | static void print_usage(void)
389 | {
390 | printf("usage: toxbot [OPTION] ...\n");
391 | printf(" -4, --ipv4 Force IPv4\n");
392 | printf(" -h, --help Show this message and exit\n");
393 | printf(" -L, --no-lan Disable LAN\n");
394 | printf(" -P, --HTTP-proxy Use HTTP proxy. Requires: [IP] [port]\n");
395 | printf(" -p, --SOCKS5-proxy Use SOCKS proxy. Requires: [IP] [port]\n");
396 | printf(" -t, --force-tcp Force connections through TCP relays (DHT disabled)\n");
397 | }
398 |
399 | static void set_default_options(void)
400 | {
401 | Options = (struct Options) {
402 | 0
403 | };
404 |
405 | /* set any non-zero defaults here*/
406 | Options.proxy_type = TOX_PROXY_TYPE_NONE;
407 | }
408 |
409 | static void parse_args(int argc, char *argv[])
410 | {
411 | set_default_options();
412 |
413 | static struct option long_opts[] = {
414 | {"ipv4", no_argument, 0, '4'},
415 | {"help", no_argument, 0, 'h'},
416 | {"no-lan", no_argument, 0, 'L'},
417 | {"SOCKS5-proxy", required_argument, 0, 'p'},
418 | {"HTTP-proxy", required_argument, 0, 'P'},
419 | {"force-tcp", no_argument, 0, 't'},
420 | {NULL, no_argument, NULL, 0},
421 | };
422 |
423 | const char *options_string = "4hLtp:P:";
424 | int opt = 0;
425 | int indexptr = 0;
426 |
427 | while ((opt = getopt_long(argc, argv, options_string, long_opts, &indexptr)) != -1) {
428 | switch (opt) {
429 | case '4': {
430 | Options.force_ipv4 = true;
431 | printf("Option set: Forcing IPV4\n");
432 | break;
433 | }
434 |
435 | case 'L': {
436 | Options.disable_lan = true;
437 | printf("Option set: LAN disabled\n");
438 | break;
439 | }
440 |
441 | case 'p': {
442 | Options.proxy_type = TOX_PROXY_TYPE_SOCKS5;
443 | }
444 |
445 | // Intentional fallthrough
446 |
447 | case 'P': {
448 | if (optarg == NULL) {
449 | fprintf(stderr, "Invalid argument for option: %d", opt);
450 | Options.proxy_type = TOX_PROXY_TYPE_NONE;
451 | break;
452 | }
453 |
454 | if (Options.proxy_type != TOX_PROXY_TYPE_SOCKS5) {
455 | Options.proxy_type = TOX_PROXY_TYPE_HTTP;
456 | }
457 |
458 | snprintf(Options.proxy_host, sizeof(Options.proxy_host), "%s", optarg);
459 |
460 | if (++optind > argc || argv[optind - 1][0] == '-') {
461 | fprintf(stderr, "Error setting proxy\n");
462 | exit(EXIT_FAILURE);
463 | }
464 |
465 | long int port = strtol(argv[optind - 1], NULL, 10);
466 |
467 | if (port <= 0 || port > MAX_PORT_RANGE) {
468 | fprintf(stderr, "Invalid port given for proxy\n");
469 | exit(EXIT_FAILURE);
470 | }
471 |
472 | Options.proxy_port = port;
473 |
474 | const char *proxy_str = Options.proxy_type == TOX_PROXY_TYPE_SOCKS5 ? "SOCKS5" : "HTTP";
475 |
476 | printf("Option set: %s proxy %s:%ld\n", proxy_str, optarg, port);
477 | }
478 |
479 | // Intentional fallthrough
480 | // we always want UDP disabled if proxy is set
481 | // don't change order, as -t must come after -P or -p
482 |
483 | case 't': {
484 | Options.disable_udp = true;
485 | printf("Option set: UDP/DHT disabled\n");
486 | break;
487 | }
488 |
489 | case 'h':
490 |
491 | // Intentional fallthrough
492 |
493 | default: {
494 | print_usage();
495 | exit(EXIT_SUCCESS);
496 | }
497 | }
498 | }
499 | }
500 |
501 | static void init_tox_options(struct Tox_Options *tox_opts)
502 | {
503 | tox_options_default(tox_opts);
504 |
505 | tox_options_set_ipv6_enabled(tox_opts, !Options.force_ipv4);
506 | tox_options_set_udp_enabled(tox_opts, !Options.disable_udp);
507 | tox_options_set_proxy_type(tox_opts, Options.proxy_type);
508 | tox_options_set_local_discovery_enabled(tox_opts, !Options.disable_lan);
509 |
510 | if (Options.proxy_type != TOX_PROXY_TYPE_NONE) {
511 | tox_options_set_proxy_port(tox_opts, Options.proxy_port);
512 | tox_options_set_proxy_host(tox_opts, Options.proxy_host);
513 | }
514 | }
515 |
516 | static Tox *init_tox(void)
517 | {
518 | Tox_Err_Options_New err;
519 | struct Tox_Options *tox_opts = tox_options_new(&err);
520 |
521 | if (!tox_opts || err != TOX_ERR_OPTIONS_NEW_OK) {
522 | fprintf(stderr, "Failed to initialize tox options: error %d\n", err);
523 | exit(EXIT_FAILURE);
524 | }
525 |
526 | init_tox_options(tox_opts);
527 |
528 | Tox *m = load_tox(tox_opts, DATA_FILE);
529 |
530 | tox_options_free(tox_opts);
531 |
532 | if (!m) {
533 | return NULL;
534 | }
535 |
536 | tox_callback_self_connection_status(m, cb_self_connection_change);
537 | tox_callback_friend_connection_status(m, cb_friend_connection_change);
538 | tox_callback_friend_request(m, cb_friend_request);
539 | tox_callback_friend_message(m, cb_friend_message);
540 | tox_callback_conference_invite(m, cb_group_invite);
541 | tox_callback_conference_title(m, cb_group_titlechange);
542 |
543 | size_t s_len = tox_self_get_status_message_size(m);
544 |
545 | if (s_len == 0) {
546 | const char *statusmsg = "Send me the the command 'help' for more info";
547 | tox_self_set_status_message(m, (uint8_t *) statusmsg, strlen(statusmsg), NULL);
548 | }
549 |
550 | size_t n_len = tox_self_get_name_size(m);
551 |
552 | if (n_len == 0) {
553 | tox_self_set_name(m, (uint8_t *) "Tox_Bot", strlen("Tox_Bot"), NULL);
554 | }
555 |
556 | return m;
557 | }
558 |
559 | /* TODO: hardcoding is bad stop being lazy */
560 | static struct toxNodes {
561 | const char *ip;
562 | uint16_t port;
563 | const char *key;
564 | } nodes[] = {
565 | { "95.79.50.56", 33445, "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832" },
566 | { "85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43" },
567 | { "46.229.52.198", 33445, "813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307" },
568 | { "144.217.167.73", 33445, "7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C" },
569 | { "198.199.98.108", 33445, "BEF0CFB37AF874BD17B9A8F9FE64C75521DB95A37D33C5BDB00E9CF58659C04F" },
570 | { "81.169.136.229", 33445, "E0DB78116AC6500398DDBA2AEEF3220BB116384CAB714C5D1FCD61EA2B69D75E" },
571 | { "205.185.115.131", 53, "3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68" },
572 | { "46.101.197.175", 33445, "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707" },
573 | { "195.201.7.101", 33445, "B84E865125B4EC4C368CD047C72BCE447644A2DC31EF75BD2CDA345BFD310107" },
574 | { "168.138.203.178", 33445, "6D04D8248E553F6F0BFDDB66FBFB03977E3EE54C432D416BC2444986EF02CC17" },
575 | { "5.19.249.240", 38296, "DA98A4C0CD7473A133E115FEA2EBDAEEA2EF4F79FD69325FC070DA4DE4BA3238" },
576 | { "209.59.144.175", 33445, "214B7FEA63227CAEC5BCBA87F7ABEEDB1A2FF6D18377DD86BF551B8E094D5F1E" },
577 | { "188.225.9.167", 33445, "1911341A83E02503AB1FD6561BD64AF3A9D6C3F12B5FBB656976B2E678644A67" },
578 | { "122.116.39.151", 33445, "5716530A10D362867C8E87EE1CD5362A233BAFBBA4CF47FA73B7CAD368BD5E6E" },
579 | { "195.123.208.139", 33445, "534A589BA7427C631773D13083570F529238211893640C99D1507300F055FE73" },
580 | { "104.225.141.59", 43334, "933BA20B2E258B4C0D475B6DECE90C7E827FE83EFA9655414E7841251B19A72C" },
581 | { "137.74.42.224", 33445, "A95177FA018066CF044E811178D26B844CBF7E1E76F140095B3A1807E081A204" },
582 | { "172.105.109.31", 33445, "D46E97CF995DC1820B92B7D899E152A217D36ABE22730FEA4B6BF1BFC06C617C" },
583 | { "91.146.66.26", 33445, "B5E7DAC610DBDE55F359C7F8690B294C8E4FCEC4385DE9525DBFA5523EAD9D53" },
584 | { NULL, 0, NULL },
585 | };
586 |
587 | static void bootstrap_DHT(Tox *m)
588 | {
589 | for (int i = 0; nodes[i].ip; ++i) {
590 | char *key = hex_string_to_bin(nodes[i].key);
591 |
592 | TOX_ERR_BOOTSTRAP err;
593 | tox_bootstrap(m, nodes[i].ip, nodes[i].port, (uint8_t *) key, &err);
594 |
595 | if (err != TOX_ERR_BOOTSTRAP_OK) {
596 | fprintf(stderr, "Failed to bootstrap DHT: %s %d (error %d)\n", nodes[i].ip, nodes[i].port, err);
597 | }
598 |
599 | tox_add_tcp_relay(m, nodes[i].ip, nodes[i].port, (uint8_t *) key, &err);
600 |
601 | if (err != TOX_ERR_BOOTSTRAP_OK) {
602 | fprintf(stderr, "Failed to add TCP relay: %s %d (error %d)\n", nodes[i].ip, nodes[i].port, err);
603 | }
604 |
605 | free(key);
606 | }
607 | }
608 |
609 | static void print_profile_info(Tox *m)
610 | {
611 | printf("Tox_Bot version %s\n", VERSION);
612 | printf("Toxcore version %d.%d.%d\n", tox_version_major(), tox_version_minor(), tox_version_patch());
613 | printf("Tox ID: ");
614 |
615 | char address[TOX_ADDRESS_SIZE];
616 | tox_self_get_address(m, (uint8_t *) address);
617 |
618 | for (int i = 0; i < TOX_ADDRESS_SIZE; ++i) {
619 | char d[3];
620 | snprintf(d, sizeof(d), "%02X", address[i] & 0xff);
621 | printf("%s", d);
622 | }
623 |
624 | printf("\n");
625 |
626 | char name[TOX_MAX_NAME_LENGTH];
627 | size_t len = tox_self_get_name_size(m);
628 | tox_self_get_name(m, (uint8_t *) name);
629 | name[len] = '\0';
630 |
631 | size_t numfriends = tox_self_get_friend_list_size(m);
632 | size_t num_chats = tox_conference_get_chatlist_size(m);
633 |
634 | printf("Name: %s\n", name);
635 | printf("Contacts: %lu\n", numfriends);
636 | printf("Active groups: %lu\n", num_chats);
637 | }
638 |
639 | static void purge_inactive_friends(Tox *m)
640 | {
641 | size_t numfriends = tox_self_get_friend_list_size(m);
642 |
643 | if (numfriends == 0) {
644 | return;
645 | }
646 |
647 | uint32_t friend_list[numfriends];
648 | tox_self_get_friend_list(m, friend_list);
649 |
650 | for (size_t i = 0; i < numfriends; ++i) {
651 | uint32_t friendnum = friend_list[i];
652 |
653 | if (!tox_friend_exists(m, friendnum)) {
654 | continue;
655 | }
656 |
657 | TOX_ERR_FRIEND_GET_LAST_ONLINE err;
658 | uint64_t last_online = tox_friend_get_last_online(m, friendnum, &err);
659 |
660 | if (err != TOX_ERR_FRIEND_GET_LAST_ONLINE_OK) {
661 | continue;
662 | }
663 |
664 | if (get_time() - last_online > Tox_Bot.inactive_limit) {
665 | tox_friend_delete(m, friendnum, NULL);
666 | }
667 | }
668 | }
669 |
670 | static void purge_empty_groups(Tox *m)
671 | {
672 | for (uint32_t i = 0; i < Tox_Bot.chats_idx; ++i) {
673 | if (!Tox_Bot.g_chats[i].active) {
674 | continue;
675 | }
676 |
677 | TOX_ERR_CONFERENCE_PEER_QUERY err;
678 | uint32_t num_peers = tox_conference_peer_count(m, Tox_Bot.g_chats[i].groupnum, &err);
679 |
680 | if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK || num_peers <= 1) {
681 | log_timestamp("Deleting empty group %d", Tox_Bot.g_chats[i].groupnum);
682 | tox_conference_delete(m, Tox_Bot.g_chats[i].groupnum, NULL);
683 | group_leave(i);
684 |
685 | if (i >= Tox_Bot.chats_idx) { // group_leave modifies chats_idx
686 | return;
687 | }
688 | }
689 | }
690 | }
691 |
692 | /* Return true if we should attempt to purge empty groups.
693 | *
694 | * Empty groups are purged on an interval, but only if we have a stable connection
695 | * to the Tox network.
696 | */
697 | static bool check_group_purge(time_t last_group_purge, time_t cur_time, TOX_CONNECTION connection_status)
698 | {
699 | if (!timed_out(last_group_purge, cur_time, GROUP_PURGE_INTERVAL)) {
700 | return false;
701 | }
702 |
703 | if (connection_status == TOX_CONNECTION_NONE) {
704 | return false;
705 | }
706 |
707 | if (!timed_out(Tox_Bot.last_connected, cur_time, GROUP_PURGE_CONNECT_TIMEOUT)) {
708 | return false;
709 | }
710 |
711 | return true;
712 | }
713 |
714 | /* Attempts to rename legacy toxbot save file to new name
715 | *
716 | * Return 0 on successful rename, or if legacy file does not exist.
717 | * Return -1 if both legacy file and new file exist. If this occurrs the user needs to manually sort
718 | * the situation out.
719 | * Return -2 if file rename operation is unsuccessful.
720 | */
721 | static int legacy_data_file_rename(void)
722 | {
723 | if (!file_exists(DATA_FILE_PRE_0_1_1)) {
724 | return 0;
725 | }
726 |
727 | if (file_exists(DATA_FILE)) {
728 | return -1;
729 | }
730 |
731 | if (rename(DATA_FILE_PRE_0_1_1, DATA_FILE) != 0) {
732 | return -2;
733 | }
734 |
735 | printf("Renaming legacy toxbot save file to '%s'\n", DATA_FILE);
736 |
737 | return 0;
738 | }
739 |
740 | int main(int argc, char **argv)
741 | {
742 | signal(SIGINT, catch_SIGINT);
743 | umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
744 |
745 | int ret = legacy_data_file_rename() ;
746 |
747 | if (ret != 0) {
748 | fprintf(stderr, "Failed to rename legacy data file. Error: %d\n", ret);
749 | exit(EXIT_FAILURE);
750 | }
751 |
752 | parse_args(argc, argv);
753 |
754 | Tox *m = init_tox();
755 |
756 | if (m == NULL) {
757 | exit(EXIT_FAILURE);
758 | }
759 |
760 | init_toxbot_state();
761 | load_conferences(m);
762 | print_profile_info(m);
763 |
764 | time_t cur_time = get_time();
765 |
766 | uint64_t last_friend_purge = cur_time;
767 | uint64_t last_group_purge = cur_time;
768 |
769 | while (!FLAG_EXIT) {
770 | TOX_CONNECTION connection_status = tox_self_get_connection_status(m);
771 |
772 | if (connection_status == TOX_CONNECTION_NONE
773 | && timed_out(Tox_Bot.last_bootstrap, cur_time, BOOTSTRAP_INTERVAL)) {
774 | log_timestamp("Bootstrapping to network...");
775 | bootstrap_DHT(m);
776 | Tox_Bot.last_bootstrap = cur_time;
777 | }
778 |
779 | if (connection_status != TOX_CONNECTION_NONE && timed_out(last_friend_purge, cur_time, FRIEND_PURGE_INTERVAL)) {
780 | purge_inactive_friends(m);
781 | save_data(m, DATA_FILE);
782 | last_friend_purge = cur_time;
783 | }
784 |
785 | if (check_group_purge(last_group_purge, cur_time, connection_status)) {
786 | purge_empty_groups(m);
787 | last_group_purge = cur_time;
788 | }
789 |
790 | tox_iterate(m, NULL);
791 |
792 | usleep(tox_iteration_interval(m) * 1000);
793 |
794 | cur_time = get_time();
795 | }
796 |
797 | exit_toxbot(m);
798 |
799 | return 0;
800 | }
801 |
802 |
--------------------------------------------------------------------------------
/src/toxbot.h:
--------------------------------------------------------------------------------
1 | /* toxbot.h
2 | *
3 | *
4 | * Copyright (C) 2021 toxbot All Rights Reserved.
5 | *
6 | * This file is part of toxbot.
7 | *
8 | * toxbot is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * toxbot is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with toxbot. If not, see .
20 | *
21 | */
22 |
23 | #ifndef TOXBOT_H
24 | #define TOXBOT_H
25 |
26 | #include
27 | #include
28 | #include "groupchats.h"
29 |
30 | #define MAX_NUM_GROUPS 256
31 |
32 | #define DATA_FILE "toxbot.tox"
33 | #define MASTERLIST_FILE "masterkeys"
34 | #define BLOCKLIST_FILE "blockedkeys"
35 |
36 | struct Tox_Bot {
37 | time_t start_time; // time toxbot was started
38 | time_t last_connected; // time we last connected to the network
39 | time_t last_bootstrap; // last time we tried to bootstrap
40 | uint64_t inactive_limit; // how often we purge inactive contacts
41 | int default_groupnum; // the group that invite commands with no ID default to
42 | int num_online_friends;
43 | int chats_idx;
44 |
45 | struct Group_Chat *g_chats;
46 | };
47 |
48 | int load_Masters(const char *path);
49 | int save_data(Tox *m, const char *path);
50 | bool friend_is_master(Tox *m, uint32_t friendnumber);
51 |
52 | #endif /* TOXBOT_H */
53 |
54 |
--------------------------------------------------------------------------------