From 410499e5bf54bc71e02238cfac5eb0f614fe8cea Mon Sep 17 00:00:00 2001 From: santic-zombie Date: Mon, 21 Feb 2022 21:25:47 +0300 Subject: [PATCH] first commit --- .gitlab-ci.yml | 30 +++++ README.md | 0 bot.cfg | 2 + masterkeys.txt | 2 + src/commands.c | 245 ++++++++++++++++++++++++++++++++++++++++ src/commands.h | 6 + src/misc.c | 128 +++++++++++++++++++++ src/misc.h | 37 ++++++ src/tox_bot.c | 298 +++++++++++++++++++++++++++++++++++++++++++++++++ src/tox_bot.h | 8 ++ toxvert.png | Bin 0 -> 18982 bytes 11 files changed, 756 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 README.md create mode 100644 bot.cfg create mode 100644 masterkeys.txt create mode 100644 src/commands.c create mode 100644 src/commands.h create mode 100644 src/misc.c create mode 100644 src/misc.h create mode 100644 src/tox_bot.c create mode 100644 src/tox_bot.h create mode 100644 toxvert.png diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1dd8bfa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +image: gcc + +stages: + - build + +build: + stage: build + # instead of calling g++ directly you can also use some build toolkit like make + # install the necessary build tools when needed + before_script: + - apt update && apt -y install make autoconf gcc libtoxcore-dev libconfig-dev + script: + - cd src/ + - gcc -o echo_bot misc.c tox_bot.c commands.c -std=gnu99 -lsodium -I /usr/local/include/ -ltoxcore -lconfig + artifacts: + paths: + - src/echo_bot + # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time + # cache: + # paths: + # - "*.o" + +# run tests using the binary built before +#test: +# stage: test +# script: +# - ./runmytests.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bot.cfg b/bot.cfg new file mode 100644 index 0000000..7199893 --- /dev/null +++ b/bot.cfg @@ -0,0 +1,2 @@ +Tox_Bot_Name = "MyBot"; +Tox_Bot_Status = "Nya^_^"; diff --git a/masterkeys.txt b/masterkeys.txt new file mode 100644 index 0000000..bc6cb56 --- /dev/null +++ b/masterkeys.txt @@ -0,0 +1,2 @@ +76367760E8BDF485BE5FE616A9B445A5EE4F74DB180353591AADAEC800D307315DDBEF1679B2 +3AABCAD5409C24BEEF0B789BF8357A6D7C83C41BF575D53EA87E967A16DDBD017B1C26D4D157 diff --git a/src/commands.c b/src/commands.c new file mode 100644 index 0000000..41a060a --- /dev/null +++ b/src/commands.c @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "tox_bot.h" +#include "misc.h" + +#define MAX_COMMAND_LENGTH TOX_MAX_MESSAGE_LENGTH +#define MAX_NUM_ARGS 4 + +/* Commands */ +/* */ +static void cmd_help(Tox *tox, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH], bool user) +{ + char com_res[TOX_MAX_MESSAGE_LENGTH]; + char str1[100] = "\n\n\t\t### Command list ### \n"; + char str2[100] = "ping\t\t\t\t\t - Print pong answer\n"; + char str3[100] = "echo \"\"\t\t\t - Echoing your message\n"; + char str4[100] = "changename \"\"\t\t - Set a new bot name\n"; + char str5[100] = "changestatus \"\"\t - Set a new bot status message\n"; + snprintf(com_res, sizeof com_res, "%s%s%s%s%s", str1, str2, str3, str4, str5); + + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) com_res, strlen(com_res), NULL); + log_msg(tox, com_res, friendnum, user); +} + +/* -------------------------------------------------------------------------- */ + +static void cmd_ping(Tox *tox, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH], bool user) +{ + const char *outmsg = NULL; + + outmsg = "pong"; + + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL); + log_msg(tox, outmsg, friendnum, user); +} + +/* -------------------------------------------------------------------------- */ + +static void cmd_echo(Tox *tox, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH], bool user) +{ + const char *inmsg = NULL; + + if (argc < 1) { + inmsg = "Error: Message required"; + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) inmsg, strlen(inmsg), NULL); + return; + } + + char outmsg[TOX_MAX_MESSAGE_LENGTH]; + int len = 0; + + if (argv[1][0] == '\"') { /* remove opening and closing quotes */ + snprintf(outmsg, sizeof(outmsg), "%s", &argv[1][1]); + len = strlen(outmsg) - 1; + } else { + snprintf(outmsg, sizeof(outmsg), "%s", argv[1]); + len = strlen(outmsg); + } + + outmsg[len] = '\0'; + + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL); + log_msg(tox, outmsg, friendnum, user); +} + +/* -------------------------------------------------------------------------- */ + +static void bot_changename(Tox *tox, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH], bool user) +{ + const char *outmsg = NULL; + + if (argc < 1) { + outmsg = "Error: Name required"; + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL); + return; + } + + char name[TOX_MAX_NAME_LENGTH]; + int len = 0; + + if (argv[1][0] == '\"') { /* remove opening and closing quotes */ + snprintf(name, sizeof(name), "%s", &argv[1][1]); + len = strlen(name) - 1; + } else { + snprintf(name, sizeof(name), "%s", argv[1]); + len = strlen(name); + } + + name[len] = '\0'; + tox_self_set_name(tox, (uint8_t *) name, (uint16_t) len, NULL); + + char com_res[TOX_MAX_MESSAGE_LENGTH]; + snprintf(com_res, sizeof com_res, "%s%s%s", "*** Name changed to \"", name, "\" ***"); + + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) com_res, + strlen(com_res), NULL); + write_conf(tox, name, NULL); + log_msg(tox, com_res, friendnum, user); + +} + +/* -------------------------------------------------------------------------- */ + +static void bot_changestatus(Tox *tox, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH], bool user) +{ + const char *outmsg = NULL; + + if (argc < 1) { + outmsg = "Error: Status required"; + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL); + return; + } + + char status[TOX_MAX_STATUS_MESSAGE_LENGTH]; + int len = 0; + + if (argv[1][0] == '\"') { /* remove opening and closing quotes */ + snprintf(status, sizeof(status), "%s", &argv[1][1]); + len = strlen(status) - 1; + } else { + snprintf(status, sizeof(status), "%s", argv[1]); + len = strlen(status); + } + + status[len] = '\0'; + + tox_self_set_status_message(tox, (uint8_t *) status, (uint16_t) len, NULL); + + char com_res[TOX_MAX_MESSAGE_LENGTH]; + snprintf(com_res, sizeof com_res, "%s%s%s", "*** Status changed to \"", status, "\" ***"); + + tox_friend_send_message(tox, friendnum, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) com_res, + strlen(com_res), NULL); + write_conf(tox, NULL, status); + log_msg(tox, com_res, friendnum, user); + +} + +/* */ +/* Commands end */ + + +/* Parses input command and puts args into arg array. + Returns number of arguments on success, -1 on failure. */ +static int parse_command(const char *input, char (*args)[MAX_COMMAND_LENGTH]) +{ + char *cmd = strdup(input); + + if (cmd == NULL) { + exit(EXIT_FAILURE); + } + + int num_args = 0; + int i = 0; /* index of last char in an argument */ + + /* characters wrapped in double quotes count as one arg */ + while (num_args < MAX_NUM_ARGS) { + int qt_ofst = 0; /* set to 1 to offset index for quote char at end of arg */ + + if (*cmd == '\"') { + qt_ofst = 1; + i = char_find(1, cmd, '\"'); + + if (cmd[i] == '\0') { + free(cmd); + return -1; + } + } else { + i = char_find(0, cmd, ' '); + } + + memcpy(args[num_args], cmd, i + qt_ofst); + args[num_args++][i + qt_ofst] = '\0'; + + if (cmd[i] == '\0') { /* no more args */ + break; + } + + char tmp[MAX_COMMAND_LENGTH]; + snprintf(tmp, sizeof(tmp), "%s", &cmd[i + 1]); + strcpy(cmd, tmp); /* tmp will always fit inside cmd */ + } + + free(cmd); + return num_args; +} + +/* -------------------------------------------------------------------------- */ + +static struct { + const char *name; + void (*func)(Tox *tox, uint32_t friendnum, int argc, char (*argv)[MAX_COMMAND_LENGTH], + bool user); +} commands[] = { + { "help", cmd_help }, + { "ping", cmd_ping }, + { "echo", cmd_echo }, + { "changename", bot_changename }, + { "changestatus", bot_changestatus }, + { NULL, NULL }, +}; + +/* -------------------------------------------------------------------------- */ + +static int do_command(Tox *tox, uint32_t friendnum, int num_args, char (*args)[MAX_COMMAND_LENGTH], + bool user) +{ + int i; + + for (i = 0; commands[i].name; ++i) { + if (strcmp(args[0], commands[i].name) == 0) { + (commands[i].func)(tox, friendnum, num_args - 1, args, user); + return 0; + } + } + + return -1; +} + +/* -------------------------------------------------------------------------- */ + +int execute(Tox *tox, uint32_t friendnum, const char *input, int length, bool user) +{ + if (length >= MAX_COMMAND_LENGTH) { + return -1; + } + + char args[MAX_NUM_ARGS][MAX_COMMAND_LENGTH]; + int num_args = parse_command(input, args); + + if (num_args == -1) { + return -1; + } + + return do_command(tox, friendnum, num_args, args, user); +} diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..89f03ed --- /dev/null +++ b/src/commands.h @@ -0,0 +1,6 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + +int execute(Tox *m, int friendnumber, const char *input, int length, bool user); + +#endif /* COMMANDS_H */ diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 0000000..c972b5d --- /dev/null +++ b/src/misc.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "misc.h" + +char *hex_string_to_bin(const char *hex_string) +{ + size_t len = strlen(hex_string); + char *val = malloc(len); + + if (val == NULL) { + exit(EXIT_FAILURE); + } + + size_t i; + + for (i = 0; i < len; ++i, hex_string += 2) { + sscanf(hex_string, "%2hhx", &val[i]); + } + + return val; +} + +uint16_t copy_tox_str(char *msg, size_t size, const char *data, uint16_t length) +{ + int len = MIN(length, size - 1); + memcpy(msg, data, len); + msg[len] = '\0'; + return len; +} + +int char_find(int idx, const char *s, char ch) +{ + int i = idx; + + for (i = idx; s[i]; ++i) { + if (s[i] == ch) { + break; + } + } + + return i; +} + +void log_msg(Tox *tox, const uint8_t *inmsg, uint32_t friend_number, bool user) +{ + char name[TOX_MAX_NAME_LENGTH]; + + if (user == true) { + tox_friend_get_name(tox, friend_number, (uint8_t *) name, NULL); + size_t len = tox_friend_get_name_size(tox, friend_number, NULL); + name[len] = '\0'; + } else { + tox_self_get_name(tox, (uint8_t *) name); + size_t len = tox_self_get_name_size(tox); + name[len] = '\0'; + } + + char cur_time[128]; + time_t t; + struct tm* ptm; + + t = time(NULL); + ptm = localtime(&t); + + strftime(cur_time, 128, "%d-%b-%Y %H:%M:%S", ptm); + + printf("%s (%s): %s\n", cur_time, name, inmsg); +} + +int file_contains_key(const char *public_key, const char *path) +{ + FILE *fp = NULL; + + struct stat s; + + if (stat(path, &s) != 0) { + FILE *fp = fopen(path, "w"); + + if (fp == NULL) { + fprintf(stderr, "Warning: failed to create '%s' file\n", path); + return -1; + } + + fprintf(stderr, "Warning: creating new '%s' file. Did you lose the old one?\n", path); + fclose(fp); + return 0; + } + + fp = fopen(path, "r"); + + if (fp == NULL) { + fprintf(stderr, "Warning: failed to read '%s' file\n", path); + return -1; + } + + char id[256]; + + while (fgets(id, sizeof(id), fp)) { + int len = strlen(id); + + if (--len < TOX_PUBLIC_KEY_SIZE) { + continue; + } + + char *key_bin = hex_string_to_bin(id); + + if (memcmp(key_bin, public_key, TOX_PUBLIC_KEY_SIZE) == 0) { + free(key_bin); + fclose(fp); + return 1; + } + + free(key_bin); + } + + fclose(fp); + return 0; +} diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 0000000..6c2ac07 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,37 @@ +#ifndef MISC_H +#define MISC_H + +#ifndef MIN +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +/* converts hexidecimal string to binary */ +char *hex_string_to_bin(const char *hex_string); + +/* copies data to msg buffer. + returns length of msg, which will be no larger than size-1 */ +uint16_t copy_tox_str(char *msg, size_t size, const char *data, uint16_t length); + +/* returns index of the first instance of ch in s starting at idx. + returns length of s if char not found */ +int char_find(int idx, const char *s, char ch); + +/* add msg time */ +void log_msg(Tox *tox, const uint8_t *msg, uint32_t friend_number, bool user); + +/* + * Searches plain text file pointed to by path for lines that match public_key. + * + * Returns 1 if a match is found. + * Returns 0 if a match is not found. + * Returns -1 on file operation failure. + * + * public_key must be a binary representation of a Tox public key. + */ +int file_contains_key(const char *public_key, const char *path); + +#endif /* MISC_H */ diff --git a/src/tox_bot.c b/src/tox_bot.c new file mode 100644 index 0000000..e19821e --- /dev/null +++ b/src/tox_bot.c @@ -0,0 +1,298 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "misc.h" +#include "commands.h" + +typedef struct DHT_node { + const char *ip; + uint16_t port; + const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; +} DHT_node; + +const char *SAVEDATA_FILENAME = "savedata.tox"; +const char *SAVEDATA_TMP_FILENAME = "savedata.tox.tmp"; +const char *MASTERLIST_FILE = "masterkeys.txt"; +const char *CFG_FILE = "bot.cfg"; + +/* -------------------------------------------------------------------------- */ + +Tox *create_tox() +{ + Tox *tox; + + struct Tox_Options options; + + tox_options_default(&options); + + FILE *f = fopen(SAVEDATA_FILENAME, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + uint8_t *savedata = malloc(fsize); + + fread(savedata, fsize, 1, f); + fclose(f); + + options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; + options.savedata_data = savedata; + options.savedata_length = fsize; + + tox = tox_new(&options, NULL); + + free(savedata); + } else { + tox = tox_new(&options, NULL); + } + + return tox; +} + +/* -------------------------------------------------------------------------- */ + +int config_tox(Tox *tox) +{ + config_t cfg; + config_setting_t *setting; + const char *str; + + config_init(&cfg); + + if (!config_read_file(&cfg, CFG_FILE)) { + fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + config_destroy(&cfg); + exit(EXIT_FAILURE); + } + + const char *name; + + if (config_lookup_string(&cfg, "Tox_Bot_Name", &str)) { + printf("Tox bot name: %s\n", str); + name = str; + } + else { + name = "Tox Bot test"; + fprintf(stderr, "No 'Tox_Bot_Name' setting in configuration file. "); + printf("The default name is used\n"); + } + + const char *status_message; + + if (config_lookup_string(&cfg, "Tox_Bot_Status", &str)) { + printf("Tox bot status: %s\n", str); + status_message = str; + } + else { + status_message = "Hello, World!"; + fprintf(stderr, "No 'Tox_Bot_Status' setting in configuration file. "); + printf("The default status is used\n"); + } + + tox_self_set_name(tox, name, strlen(name), NULL); + + tox_self_set_status_message(tox, status_message, strlen(status_message), NULL); + + config_destroy(&cfg); + return 1; +} + +int write_conf(Tox *tox, uint8_t *name, uint8_t *status_message) +{ + config_t cfg; + config_setting_t *setting; + const char *str; + + config_init(&cfg); + + if (!config_read_file(&cfg, CFG_FILE)) { + fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + config_destroy(&cfg); + exit(EXIT_FAILURE); + } + + if (name != NULL) { + setting = config_lookup(&cfg, "Tox_Bot_Name"); + config_setting_set_string(setting, name); + } + + if (status_message != NULL) { + setting = config_lookup(&cfg, "Tox_Bot_Status"); + config_setting_set_string(setting, status_message); + } + + if(! config_write_file(&cfg, CFG_FILE)) + { + fprintf(stderr, "Error while writing file.\n"); + config_destroy(&cfg); + exit(EXIT_FAILURE); + } + + config_destroy(&cfg); + return 1; +} + +/* -------------------------------------------------------------------------- */ + +void update_savedata_file(const Tox *tox) +{ + size_t size = tox_get_savedata_size(tox); + uint8_t *savedata = malloc(size); + tox_get_savedata(tox, savedata); + + FILE *f = fopen(SAVEDATA_TMP_FILENAME, "wb"); + fwrite(savedata, size, 1, f); + fclose(f); + + rename(SAVEDATA_TMP_FILENAME, SAVEDATA_FILENAME); + + free(savedata); +} + +/* -------------------------------------------------------------------------- */ + +void bootstrap(Tox *tox) +{ + DHT_node nodes[] = + { + {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, + {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, + {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, + {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, + {"tox.initramfs.io", 33445, "3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25"}, + {"tox2.abilinski.com", 33445, "7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D"}, + {"205.185.115.131", 53, "3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68"}, + {"tox.kurnevsky.net", 33445, "82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23"} + }; + + for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { + unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; + sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, + NULL, NULL, NULL); + tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); + } +} + +/* -------------------------------------------------------------------------- */ + +void print_tox_id(Tox *tox) +{ + uint8_t tox_id_bin[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox, tox_id_bin); + + char tox_id_hex[TOX_ADDRESS_SIZE*2 + 1]; + sodium_bin2hex(tox_id_hex, sizeof(tox_id_hex), tox_id_bin, sizeof(tox_id_bin)); + + for (size_t i = 0; i < sizeof(tox_id_hex)-1; i ++) { + tox_id_hex[i] = toupper(tox_id_hex[i]); + } + + printf("Tox ID: %s\n", tox_id_hex); +} + + +/** CallBacks **/ +/** **/ +static void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data) +{ + + char tox_id_hex[TOX_ADDRESS_SIZE*2 + 1]; + uint8_t tox_id_bin[TOX_ADDRESS_SIZE]; + + sodium_bin2hex(tox_id_hex, sizeof(tox_id_hex), public_key, sizeof(tox_id_bin)); + + for (size_t i = 0; i < sizeof(tox_id_hex)-1; i ++) { + tox_id_hex[i] = toupper(tox_id_hex[i]); + } + + if (file_contains_key(public_key, MASTERLIST_FILE) == 1) { + printf("New friend is Master: %s\n", tox_id_hex); + tox_friend_add_norequest(tox, public_key, NULL); + update_savedata_file(tox); + } + else { + printf("New friend not on the list: %s\n", tox_id_hex); + } +} + +/* -------------------------------------------------------------------------- */ + +void friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *inmsg, + size_t length, void *user_data) +{ + bool user = true; + log_msg(tox, inmsg, friend_number, user); + + const char *outmsg; + char message[TOX_MAX_MESSAGE_LENGTH]; + length = copy_tox_str(message, sizeof(message), (const char *) inmsg, length); + message[length] = '\0'; + + user = false; + + if (length && execute(tox, friend_number, message, length, user) == -1) { + outmsg = "Invalid command. Type 'help' for a list of commands"; + tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) outmsg, strlen(outmsg), NULL); + log_msg(tox, outmsg, friend_number, user); + } +} + +/* -------------------------------------------------------------------------- */ + +void self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data) +{ + switch (connection_status) { + case TOX_CONNECTION_NONE: + printf("Offline\n"); + break; + case TOX_CONNECTION_TCP: + printf("Online, using TCP\n"); + break; + case TOX_CONNECTION_UDP: + printf("Online, using UDP\n"); + break; + } +} +/** **/ +/** CallBacks END**/ + + +int main() +{ + Tox *tox = create_tox(); + + config_tox(tox); + bootstrap(tox); + print_tox_id(tox); + + tox_callback_friend_request(tox, friend_request_cb); + tox_callback_friend_message(tox, friend_message_cb); + + tox_callback_self_connection_status(tox, self_connection_status_cb); + + update_savedata_file(tox); + + printf("Connecting...\n"); + + while (1) { + tox_iterate(tox, NULL); + usleep(tox_iteration_interval(tox) * 1000); + } + + tox_kill(tox); + + return 0; +} diff --git a/src/tox_bot.h b/src/tox_bot.h new file mode 100644 index 0000000..f33d7fa --- /dev/null +++ b/src/tox_bot.h @@ -0,0 +1,8 @@ +#ifndef TOXBOT_H +#define TOXBOT_H + +#include + +int write_conf(Tox *tox, uint8_t *name, uint8_t *status_message); + +#endif /* TOXBOT_H */ diff --git a/toxvert.png b/toxvert.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b2d502016be5139eb62e0efe1b0947ddb02962 GIT binary patch literal 18982 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